diff --git a/addon-main.nas b/addon-main.nas
index d0f1c85..151b318 100644
--- a/addon-main.nas
+++ b/addon-main.nas
@@ -28,17 +28,10 @@ var unload = func(addon) {
var main = func(addon) {
print("Aerotow Everywhere add-on initialized from path ", addon.basePath);
- # Create $FG_HOME/Export/Addons/org.flightgear.addons.Aerotow directory
- addon.createStorageDir();
-
- # Create /AI/FlightPlans/ directory in $FG_HOME/Export/Addons/org.flightgear.addons.Aerotow/
- # User has to add the path as --data=$FG_HOME/Export/Addons/org.flightgear.addons.Aerotow
- # Then the FG will be able to read flight plan file
- var path = os.path.new(addon.storagePath ~ "/AI/FlightPlans/dummy-file.txt");
- path.create_dir();
-
loadExtraNasalFiles(addon);
+ createDirectories();
+
aerotow.init(addon);
}
@@ -56,6 +49,7 @@ var loadExtraNasalFiles = func (addon) {
"nasal/flight-plan",
"nasal/scenario",
"nasal/io/flight-plan-writer",
+ "nasal/aerotow",
"aerotow",
];
@@ -67,3 +61,21 @@ var loadExtraNasalFiles = func (addon) {
}
}
}
+
+#
+# Create all needed directories.
+#
+var createDirectories = func (addon) {
+ # Create $FG_HOME/Export/Addons/org.flightgear.addons.Aerotow directory
+ addon.createStorageDir();
+
+ # Create /AI/FlightPlans/ directory in $FG_HOME/Export/Addons/org.flightgear.addons.Aerotow/
+ # User has to add the path as --data=$FG_HOME/Export/Addons/org.flightgear.addons.Aerotow
+ # Then the FG will be able to read flight plan file
+ var path = os.path.new(addon.storagePath ~ "/AI/FlightPlans/dummy-file.txt");
+ path.create_dir();
+
+ # Create /route-saves directory in $FG_HOME/Export/Addons/org.flightgear.addons.Aerotow/
+ path = os.path.new(addon.storagePath ~ "/" ~ aerotow.RouteDialog.ROUTE_SAVES_DIR ~ "/dummy-file.txt");
+ path.create_dir();
+}
diff --git a/aerotow.nas b/aerotow.nas
index 3d9d182..42456a9 100644
--- a/aerotow.nas
+++ b/aerotow.nas
@@ -9,108 +9,6 @@
# under the GNU Public License v3 (GPLv3)
#
-#
-# Aerotow object
-#
-var Aerotow = {
- #
- # Constructor
- #
- # addon - Addon object
- #
- new: func (addon) {
- var obj = { parents: [Aerotow] };
-
- obj.addon = addon;
- obj.addonNodePath = addon.node.getPath();
- obj.listeners = [];
-
- obj.message = Message.new();
- obj.thermal = Thermal.new(addon, obj.message);
- obj.scenario = Scenario.new(addon, obj.message);
-
- # Listener for ai-model property triggered when the user select a tow aircraft from add-on menu
- append(obj.listeners, setlistener(obj.addonNodePath ~ "/addon-devel/ai-model", func () {
- obj.restartAerotow();
- }));
-
- return obj;
- },
-
- #
- # Uninitialize aerotow module
- #
- del: func () {
- me.thermal.del();
-
- foreach (var listener; me.listeners) {
- removelistener(listener);
- }
- },
-
- #
- # Function for restart AI scenario with delay when the sound has to stop.
- #
- # Return 1 on successful, otherwise 0.
- #
- restartAerotow: func () {
- me.message.success("Aerotow on the way");
-
- # Stop playing engine sound
- setprop(me.addonNodePath ~ "/addon-devel/sound/enable", 0);
-
- # Wait a second for the engine sound to turn off
- var timer = maketimer(1, func () {
- me.unloadScenario();
- });
- timer.singleShot = 1;
- timer.start();
- },
-
- #
- # Unload scenario and start a new one
- #
- unloadScenario: func () {
- if (!me.scenario.unload()) {
- return;
- }
-
- # Start aerotow with delay to avoid duplicate engine sound playing
- var timer = maketimer(1, func () {
- me.startAerotow();
- });
- timer.singleShot = 1;
- timer.start();
- },
-
- #
- # Main function to prepare AI scenario and run it.
- #
- # Return 1 on successful, otherwise 0.
- #
- startAerotow: func () {
- if (!me.scenario.unload()) {
- return 0;
- }
-
- if (!me.scenario.generateXml()) {
- return 0;
- }
-
- return me.scenario.load();
- },
-
- #
- # Function for unload our AI scenario.
- #
- # Return 1 on successful, otherwise 0.
- #
- stopAerotow: func () {
- var withMessages = 1;
- return me.scenario.unload(withMessages);
- },
-};
-
var g_Aerotow = nil;
#
diff --git a/gui/dialogs/route-aerotow.xml b/gui/dialogs/route-aerotow.xml
index 4b10f40..9c89c9f 100644
--- a/gui/dialogs/route-aerotow.xml
+++ b/gui/dialogs/route-aerotow.xml
@@ -116,7 +116,7 @@
0
2
left
-
+
@@ -133,7 +133,7 @@
1
left
- %.f
+ %.f m
true
/addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/init-wpt/distance-m
@@ -142,7 +142,7 @@
2
left
- %.f
+ %.f ft
true
/addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/init-wpt/alt-change-agl-ft
@@ -163,14 +163,14 @@
2
2
left
-
+
3
0
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[0]/heading-change
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[0]/heading-change
true
dialog-apply
@@ -180,7 +180,7 @@
3
1
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[0]/distance-m
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[0]/distance-m
true
dialog-apply
@@ -191,16 +191,16 @@
2
left
- %.f
+ %.f ft
true
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[0]/alt-change-agl-ft
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[0]/alt-change-agl-ft
4
0
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[1]/heading-change
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[1]/heading-change
true
dialog-apply
@@ -210,7 +210,7 @@
4
1
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[1]/distance-m
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[1]/distance-m
true
dialog-apply
@@ -221,16 +221,16 @@
2
left
- %.f
+ %.f ft
true
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[1]/alt-change-agl-ft
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[1]/alt-change-agl-ft
5
0
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[2]/heading-change
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[2]/heading-change
true
dialog-apply
@@ -240,7 +240,7 @@
5
1
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[2]/distance-m
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[2]/distance-m
true
dialog-apply
@@ -251,16 +251,16 @@
2
left
- %.f
+ %.f ft
true
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[2]/alt-change-agl-ft
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[2]/alt-change-agl-ft
6
0
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[3]/heading-change
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[3]/heading-change
true
dialog-apply
@@ -270,7 +270,7 @@
6
1
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[3]/distance-m
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[3]/distance-m
true
dialog-apply
@@ -281,16 +281,16 @@
2
left
- %.f
+ %.f ft
true
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[3]/alt-change-agl-ft
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[3]/alt-change-agl-ft
7
0
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[4]/heading-change
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[4]/heading-change
true
dialog-apply
@@ -300,7 +300,7 @@
7
1
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[4]/distance-m
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[4]/distance-m
true
dialog-apply
@@ -311,16 +311,16 @@
2
left
- %.f
+ %.f ft
true
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[4]/alt-change-agl-ft
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[4]/alt-change-agl-ft
8
0
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[5]/heading-change
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[5]/heading-change
true
dialog-apply
@@ -330,7 +330,7 @@
8
1
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[5]/distance-m
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[5]/distance-m
true
dialog-apply
@@ -341,16 +341,16 @@
2
left
- %.f
+ %.f ft
true
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[5]/alt-change-agl-ft
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[5]/alt-change-agl-ft
9
0
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[6]/heading-change
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[6]/heading-change
true
dialog-apply
@@ -360,7 +360,7 @@
9
1
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[6]/distance-m
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[6]/distance-m
true
dialog-apply
@@ -371,16 +371,16 @@
2
left
- %.f
+ %.f ft
true
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[6]/alt-change-agl-ft
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[6]/alt-change-agl-ft
10
0
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[7]/heading-change
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[7]/heading-change
true
dialog-apply
@@ -390,7 +390,7 @@
10
1
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[7]/distance-m
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[7]/distance-m
true
dialog-apply
@@ -401,16 +401,16 @@
2
left
- %.f
+ %.f ft
true
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[7]/alt-change-agl-ft
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[7]/alt-change-agl-ft
11
0
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[8]/heading-change
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[8]/heading-change
true
dialog-apply
@@ -420,7 +420,7 @@
11
1
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[8]/distance-m
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[8]/distance-m
true
dialog-apply
@@ -431,16 +431,16 @@
2
left
- %.f
+ %.f ft
true
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[8]/alt-change-agl-ft
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[8]/alt-change-agl-ft
12
0
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[9]/heading-change
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[9]/heading-change
true
dialog-apply
@@ -450,7 +450,7 @@
12
1
left
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[9]/distance-m
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[9]/distance-m
true
dialog-apply
@@ -461,9 +461,9 @@
2
left
- %.f
+ %.f ft
true
- /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpt[9]/alt-change-agl-ft
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/wpt[9]/alt-change-agl-ft
@@ -494,6 +494,23 @@
+
+ left
+
+
+
+
+ fill
+ left
+ /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/route/wpts/description
+ true
+
+ dialog-apply
+
+
+
+
+
true
@@ -523,6 +540,28 @@
+
+
+
+
true
diff --git a/nasal/aerotow.nas b/nasal/aerotow.nas
new file mode 100644
index 0000000..28a9ade
--- /dev/null
+++ b/nasal/aerotow.nas
@@ -0,0 +1,112 @@
+#
+# Aerotow Everywhere - Add-on for FlightGear
+#
+# Written and developer by Roman Ludwicki (PlayeRom, SP-ROM)
+#
+# Copyright (C) 2022 Roman Ludwicki
+#
+# Aerotow Everywhere is an Open Source project and it is licensed
+# under the GNU Public License v3 (GPLv3)
+#
+
+#
+# Aerotow object
+#
+var Aerotow = {
+ #
+ # Constructor
+ #
+ # addon - Addon object
+ #
+ new: func (addon) {
+ var obj = { parents: [Aerotow] };
+
+ obj.addon = addon;
+ obj.addonNodePath = addon.node.getPath();
+ obj.listeners = [];
+
+ obj.message = Message.new();
+ obj.thermal = Thermal.new(addon, obj.message);
+ obj.scenario = Scenario.new(addon, obj.message);
+
+ # Listener for ai-model property triggered when the user select a tow aircraft from add-on menu
+ append(obj.listeners, setlistener(obj.addonNodePath ~ "/addon-devel/ai-model", func () {
+ obj.restartAerotow();
+ }));
+
+ return obj;
+ },
+
+ #
+ # Uninitialize aerotow module
+ #
+ del: func () {
+ me.thermal.del();
+
+ foreach (var listener; me.listeners) {
+ removelistener(listener);
+ }
+ },
+
+ #
+ # Function for restart AI scenario with delay when the sound has to stop.
+ #
+ # Return 1 on successful, otherwise 0.
+ #
+ restartAerotow: func () {
+ me.message.success("Aerotow on the way");
+
+ # Stop playing engine sound
+ setprop(me.addonNodePath ~ "/addon-devel/sound/enable", 0);
+
+ # Wait a second for the engine sound to turn off
+ var timer = maketimer(1, func () {
+ me.unloadScenario();
+ });
+ timer.singleShot = 1;
+ timer.start();
+ },
+
+ #
+ # Unload scenario and start a new one
+ #
+ unloadScenario: func () {
+ if (!me.scenario.unload()) {
+ return;
+ }
+
+ # Start aerotow with delay to avoid duplicate engine sound playing
+ var timer = maketimer(1, func () {
+ me.startAerotow();
+ });
+ timer.singleShot = 1;
+ timer.start();
+ },
+
+ #
+ # Main function to prepare AI scenario and run it.
+ #
+ # Return 1 on successful, otherwise 0.
+ #
+ startAerotow: func () {
+ if (!me.scenario.unload()) {
+ return 0;
+ }
+
+ if (!me.scenario.generateXml()) {
+ return 0;
+ }
+
+ return me.scenario.load();
+ },
+
+ #
+ # Function for unload our AI scenario.
+ #
+ # Return 1 on successful, otherwise 0.
+ #
+ stopAerotow: func () {
+ var withMessages = 1;
+ return me.scenario.unload(withMessages);
+ },
+};
diff --git a/nasal/dialogs/route.nas b/nasal/dialogs/route.nas
index 99766c0..dcc5e42 100644
--- a/nasal/dialogs/route.nas
+++ b/nasal/dialogs/route.nas
@@ -13,17 +13,24 @@
# Object for hande Route Dialog
#
var RouteDialog = {
+ #
+ # Constants
+ #
+ ROUTE_SAVES_DIR: "route-saves",
+
#
# Constructor
#
# addon - Addon object
#
- new: func (addon) {
+ new: func (addon, message) {
var obj = { parents: [RouteDialog] };
obj.addon = addon;
+ obj.message = message;
obj.addonNodePath = addon.node.getPath();
+ obj.savePath = addon.storagePath ~ "/" ~ RouteDialog.ROUTE_SAVES_DIR;
obj.maxRouteWaypoints = 10;
obj.listeners = [];
@@ -34,7 +41,7 @@ var RouteDialog = {
# Set listeners for distance fields for calculate altitude change
for (var i = 0; i < obj.maxRouteWaypoints; i += 1) {
- append(obj.listeners, setlistener(obj.addonNodePath ~ "/addon-devel/route/wpt[" ~ i ~ "]/distance-m", func () {
+ append(obj.listeners, setlistener(obj.addonNodePath ~ "/addon-devel/route/wpts/wpt[" ~ i ~ "]/distance-m", func () {
obj.calculateAltChangeAndTotals();
}));
}
@@ -63,13 +70,13 @@ var RouteDialog = {
var aircraft = Aircraft.getSelected(me.addon, isRouteMode);
for (var i = 0; i < me.maxRouteWaypoints; i += 1) {
- var distance = getprop(me.addonNodePath ~ "/addon-devel/route/wpt[" ~ i ~ "]/distance-m");
+ var distance = getprop(me.addonNodePath ~ "/addon-devel/route/wpts/wpt[" ~ i ~ "]/distance-m");
if (distance == nil) {
break;
}
var altChange = aircraft.getAltChange(distance);
- setprop(me.addonNodePath ~ "/addon-devel/route/wpt[" ~ i ~ "]/alt-change-agl-ft", altChange);
+ setprop(me.addonNodePath ~ "/addon-devel/route/wpts/wpt[" ~ i ~ "]/alt-change-agl-ft", altChange);
if (!isEnd) {
if (distance > 0.0) {
@@ -85,4 +92,44 @@ var RouteDialog = {
setprop(me.addonNodePath ~ "/addon-devel/route/total/distance", totalDistance);
setprop(me.addonNodePath ~ "/addon-devel/route/total/alt", totalAlt);
},
+
+ #
+ # Save route with description to the XML file.
+ #
+ save: func () {
+ me.openFileSelector(
+ func (node) {
+ var nodeSave = props.globals.getNode(me.addonNodePath ~ "/addon-devel/route/wpts");
+ if (io.write_properties(node.getValue(), nodeSave)) {
+ me.message.success("The route has been saved");
+ }
+ },
+ "Save route",
+ "Save"
+ );
+ },
+
+ #
+ # Load route with description from the XML file.
+ #
+ load: func () {
+ me.openFileSelector(
+ func (node) {
+ var nodeLoad = props.globals.getNode(me.addonNodePath ~ "/addon-devel/route/wpts");
+ if (io.read_properties(node.getValue(), nodeLoad)) {
+ me.message.success("The route has been loaded");
+ }
+ },
+ "Load route",
+ "Load"
+ );
+ },
+
+ #
+ # Open file selector dialog for save/load XML file with route.
+ #
+ openFileSelector: func (callback, title, button) {
+ var fileSelector = gui.FileSelector.new(callback, title, button, ["*.xml"], me.savePath, "route.xml");
+ fileSelector.open();
+ },
};
diff --git a/nasal/flight-plan.nas b/nasal/flight-plan.nas
index 85b4d5e..1d4d352 100644
--- a/nasal/flight-plan.nas
+++ b/nasal/flight-plan.nas
@@ -178,15 +178,17 @@ var FlightPlan = {
var index = 0;
foreach (var wpt; wptData) {
- setprop(me.addonNodePath ~ "/addon-devel/route/wpt[" ~ index ~ "]/heading-change", wpt.hdgChange);
- setprop(me.addonNodePath ~ "/addon-devel/route/wpt[" ~ index ~ "]/distance-m", wpt.dist);
- setprop(me.addonNodePath ~ "/addon-devel/route/wpt[" ~ index ~ "]/alt-change-agl-ft", wpt.altChange);
+ setprop(me.addonNodePath ~ "/addon-devel/route/wpts/wpt[" ~ index ~ "]/heading-change", wpt.hdgChange);
+ setprop(me.addonNodePath ~ "/addon-devel/route/wpts/wpt[" ~ index ~ "]/distance-m", wpt.dist);
+ setprop(me.addonNodePath ~ "/addon-devel/route/wpts/wpt[" ~ index ~ "]/alt-change-agl-ft", wpt.altChange);
index += 1;
}
me.routeDialog.calculateAltChangeAndTotals();
+ setprop(me.addonNodePath ~ "/addon-devel/route/wpts/description", "Default route around the start location");
+
return 1;
},
@@ -244,7 +246,7 @@ var FlightPlan = {
me.addWptAir({"hdgChange": 0, "dist": 100}, {"altChange": aircraft.vs / 10, "ktas": aircraft.speed * 1.025});
var speedInc = 1.0;
- foreach (var wptNode; props.globals.getNode(me.addonNodePath ~ "/addon-devel/route").getChildren("wpt")) {
+ foreach (var wptNode; props.globals.getNode(me.addonNodePath ~ "/addon-devel/route/wpts").getChildren("wpt")) {
var dist = wptNode.getChild("distance-m").getValue();
if (dist <= 0.0) {
break;
diff --git a/nasal/scenario.nas b/nasal/scenario.nas
index 8ef8636..23b0146 100644
--- a/nasal/scenario.nas
+++ b/nasal/scenario.nas
@@ -36,7 +36,7 @@ var Scenario = {
obj.addonNodePath = addon.node.getPath();
obj.listeners = [];
- obj.routeDialog = RouteDialog.new(addon);
+ obj.routeDialog = RouteDialog.new(addon, message);
obj.flightPlan = FlightPlan.new(addon, message, obj.routeDialog);
obj.isScenarioLoaded = 0;
obj.scenarioPath = addon.storagePath ~ "/" ~ Scenario.FILENAME_SCENARIO;