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;