diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fc50e6d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# CHANGELOG + +## v.1.0.1 + +- Fix AI aircraft going underground during take-off due to uneven runway elevations. +- Possibility to add thermal with configuration of its parameters. + +## v.1.0.0 + +- Possibility to call a towing aircraft at any airport +- One defined route for the towing aircraft +- 3 types of tow airplanes diff --git a/README.md b/README.md index c9cdadf..f437ceb 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,28 @@ You can disconnect from the aircraft at any time, most often by pressing the `o` ## Menu of add-on -This add-on add a new item to main menu named "Aerotow Everywhere" with following items: +This add-on add a new item to main menu named `Aerotow Everywhere` with following items: 1. `Call for Piper J3 Cub aircraft` - load AI tow sceneraio with Piper J3 Cub. Possible altitude to reach ~3,600 ft. 2. `Call for Robin DR400 aircraft` - load AI tow sceneraio with Robin DR400. This aircraft has better performance and can take you to over 4,500 ft. 3. `Call for Cessna 182 aircraft` - load AI tow sceneraio with Cessna 182. This aircraft has little bit better performance than Robin. 4. `Disable tow aircraft` - unload AI tow sceneraio. -5. `Help` - display help dialog. -6. `About` - display about dialog with add-on information. +5. `Add thermal` - display the dialog for configuring and adding thermal. +6. `Help` - display help dialog. +7. `About` - display about dialog with add-on information. + +## Adding thermals + +An additional feature of this add-on is the possibility of placing the thermals just in front of the glider. To do this, go to menu `Aerotow Everywhere` -> `Add thermal` where you can configure the following parameters: + +1. `Distance` - the distance in metres at which the thermals will be placed in front of the gliders. +2. `Strength` - thermal strength in feet per second. +3. `Diameter` - diameter of thermal in feet. +4. `Height` - height of thermal in feet above mean sea level. + +Click `Add thermal` button for add the thermal. + +Many thanks to the forum user "wlbragg" for proposing and presenting a solution to this feature. ## Limitations diff --git a/addon-config.xml b/addon-config.xml index 3f516ee..1fa3ad9 100644 --- a/addon-config.xml +++ b/addon-config.xml @@ -27,6 +27,16 @@ Cub + + 300 + 0.16 + 16.0 + 4.88 + 4000 + 1219 + 9000 + 2742 + === How to start? === From the top menu, select "Aerotow Everywhere" -> "Call for Piper J3 Cub aircraft", "Robin DR400" or "Cessna 182" (yes, you can choose from many aircrafts). diff --git a/addon-main.nas b/addon-main.nas index d4200ea..b1c8f8d 100644 --- a/addon-main.nas +++ b/addon-main.nas @@ -21,11 +21,15 @@ var unload = func(addon) { # # Other resources should be freed by adding the corresponding code here, # e.g. myCanvas.del(); + + aerotow.uninit(); + thermal.uninit(); } 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/ @@ -36,17 +40,19 @@ var main = func(addon) { loadExtraNasalFiles(addon); - setlistener(addon.node.getPath() ~ "/addon-devel/ai-model", func(n) { - aerotow.startAerotow(); - }); + aerotow.init(); + thermal.init(); } +# +# Load extra Nasal files in main add-on directory +# var loadExtraNasalFiles = func (addon) { - foreach (var scriptName; ["aerotow", "messages"]) { + foreach (var scriptName; ["aerotow", "messages", "thermal"]) { var fileName = addon.basePath ~ "/" ~ scriptName ~ ".nas"; - print("Loading Aerotown Add-on module ", fileName); - - io.load_nasal(fileName, scriptName); + if (io.load_nasal(fileName, scriptName)) { + print("Aerotown Add-on module \"", scriptName, "\" loaded OK"); + } } } diff --git a/addon-menubar-items.xml b/addon-menubar-items.xml index dd7c744..149420d 100644 --- a/addon-menubar-items.xml +++ b/addon-menubar-items.xml @@ -53,10 +53,6 @@ c182 - - - false - @@ -65,7 +61,18 @@ - + + false + + + + + dialog-show + add-thermal + + + + false diff --git a/aerotow.nas b/aerotow.nas index aa67a71..8548f05 100644 --- a/aerotow.nas +++ b/aerotow.nas @@ -30,6 +30,26 @@ var g_fpFileHandler = nil; # Handler for wrire flight plan to file var g_coord = nil; # Coordinates for flight plan var g_heading = nil; # AI plane heading var g_altitude = nil; # AI plane altitude +var g_listeners = []; + +# +# Initialize thermal module +# +var init = func () { + # Listener for ai-model property triggered when the user select a tow aircraft from add-on menu + append(g_listeners, setlistener(addon.node.getPath() ~ "/addon-devel/ai-model", func () { + startAerotow(); + })); +} + +# +# Uninitialize thermal module +# +var uninit = func () { + foreach (var listener; g_listeners) { + removelistener(listener); + } +} # # Main function to prepare AI scenario and run it. @@ -235,7 +255,7 @@ var generateFlightPlanXml = func () { addWptGround({"hdgChange": 0, "dist": 10 * perf.rolling}, {"altChange": 0, "ktas": perf.speed}); # Takeof - addWptAir({"hdgChange": 0, "dist": 100 * perf.rolling}, {"altChange": 3, "ktas": perf.speed * 1.05}); + addWptAir({"hdgChange": 0, "dist": 100 * perf.rolling}, {"elevationPlus": 3, "ktas": perf.speed * 1.05}); addWptAir({"hdgChange": 0, "dist": 100}, {"altChange": perf.vs / 10, "ktas": perf.speed * 1.025}); # Circle around airport @@ -304,7 +324,7 @@ var getAircraftPerformance = func () { "rolling": 2, }; } - + if (aiModel == "c182") { return { "vs": 295, # ft per 1000 m @@ -332,7 +352,7 @@ var initAircraftVariable = func (airport, runway, isGliderPos = 1) { var gliderCoord = geo.aircraft_position(); # Set coordinates as glider position or runway threshold - g_coord = isGliderPos + g_coord = isGliderPos ? gliderCoord : geo.Coord.new().set_latlon(runway.lat, runway.lon); @@ -365,7 +385,7 @@ var getMinRunwayLength = func () { if (aiModel == "DR400") { return 470; } - + if (aiModel == "c182") { return 508; } @@ -404,14 +424,14 @@ var addWptAir = func (coordOffset, performance) { # sec - Number of seconds for wait # var addWptWait = func (sec) { - wrireWpt("WAIT", {"hdgChange": nil, "dist": nil}, {"altChange": nil, "ktas": nil}, nil, sec); + wrireWpt("WAIT", {}, {}, nil, sec); } # # Add "END" waypoint # var addWptEnd = func () { - wrireWpt("END", {"hdgChange": nil, "dist": nil}, {"altChange": nil, "ktas": nil}); + wrireWpt("END", {}, {}); } # @@ -421,6 +441,9 @@ var addWptEnd = func () { # coordOffset.hdgChange - How the aircraft's heading supposed to change? # coordOffset.dist - Distance in meters to calculate next waypoint coordinates # performance.altChange - How the aircraft's altitude is supposed to change? +# performance.elevationPlus - Set aircraft altitude as current terrain elevation + given value in feets. +# It's best to use for the first point in the air to avoid the plane collapsing into +# the ground in a bumpy airport # performance.ktas - True air speed of AI plane at the waypoint # groundAir - Allowed value: "ground or "air". The "ground" means that AI plane is on the ground, "air" - in air # sec - Number of seconds for "WAIT" waypoint @@ -432,8 +455,8 @@ var wrireWpt = func ( groundAir = nil, sec = nil ) { - var localCoord = nil; - if (coordOffset.hdgChange != nil and coordOffset.dist != nil) { + var coord = nil; + if (contains(coordOffset, "hdgChange") and contains(coordOffset, "dist")) { g_heading = g_heading + coordOffset.hdgChange; if (g_heading < 0) { g_heading = 360 + g_heading; @@ -444,24 +467,29 @@ var wrireWpt = func ( } g_coord.apply_course_distance(g_heading, coordOffset.dist); - localCoord = g_coord; + coord = g_coord; } - var localAlt = nil; - if (performance.altChange != nil) { - g_altitude = g_altitude + performance.altChange; - localAlt = g_altitude; + var alt = nil; + if (coord != nil and contains(performance, "elevationPlus")) { + var elevation = geo.elevation(coord.lat(), coord.lon()); + if (elevation == nil) { + g_altitude = g_altitude + performance.elevationPlus; + } + else { + g_altitude = elevation * globals.M2FT + performance.elevationPlus; + } + alt = g_altitude; } + else if (contains(performance, "altChange")) { + g_altitude = g_altitude + performance.altChange; + alt = g_altitude; + } + + var ktas = contains(performance, "ktas") ? performance.ktas : nil; name = name == nil ? g_wptCount : name; - var data = getWptString( - name, - localCoord, - localAlt, - performance.ktas, - groundAir, - sec - ); + var data = getWptString(name, coord, alt, ktas, groundAir, sec); io.write(g_fpFileHandler, data); diff --git a/gui/dialogs/add-thermal.xml b/gui/dialogs/add-thermal.xml new file mode 100644 index 0000000..44ed112 --- /dev/null +++ b/gui/dialogs/add-thermal.xml @@ -0,0 +1,214 @@ + + + + + + + add-thermal + false + vbox + 3 + + + hbox + + + 1 + + + + + + + + 1 + + + + + + + + + left + + + + left + + + + + + + table + + + 0 + 0 + right + + + + 0 + 1 + + left + /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/distance-m + true + + dialog-apply + + + + 0 + 2 + + left + (%.2f nm) + true + /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/distance-nm + + + + 1 + 0 + right + + + + 1 + 1 + + left + /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/strength-fps + true + + dialog-apply + + + + 1 + 2 + + left + (%.2f m/s) + true + /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/strength-mps + + + + 2 + 0 + right + + + + 2 + 1 + + left + /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/diameter-ft + true + + dialog-apply + + + + 2 + 2 + + left + (%.f m) + true + /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/diameter-m + + + + 3 + 0 + right + + + + 3 + 1 + + left + /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/height-msl + true + + dialog-apply + + + + 3 + 2 + + left + (%.f m) + true + /addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/height-msl-m + + + + + + + + true + + + hbox + + + + + + + true + + + + diff --git a/thermal.nas b/thermal.nas new file mode 100644 index 0000000..3b18a25 --- /dev/null +++ b/thermal.nas @@ -0,0 +1,97 @@ +# +# 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) +# + +# +# Constants +# +var addon = addons.getAddon("org.flightgear.addons.Aerotow"); + +# +# Variables +# +var g_thermalListeners = []; + +# +# Initialize thermal module +# +var init = func () { + append(g_thermalListeners, setlistener(addon.node.getPath() ~ "/addon-devel/add-thermal/distance-m", func () { + setprop( + addon.node.getPath() ~ "/addon-devel/add-thermal/distance-nm", + getprop(addon.node.getPath() ~ "/addon-devel/add-thermal/distance-m") * globals.M2NM + ); + })); + + append(g_thermalListeners, setlistener(addon.node.getPath() ~ "/addon-devel/add-thermal/strength-fps", func () { + setprop( + addon.node.getPath() ~ "/addon-devel/add-thermal/strength-mps", + getprop(addon.node.getPath() ~ "/addon-devel/add-thermal/strength-fps") * globals.FPS2KT * globals.KT2MPS + ); + })); + + append(g_thermalListeners, setlistener(addon.node.getPath() ~ "/addon-devel/add-thermal/diameter-ft", func () { + setprop( + addon.node.getPath() ~ "/addon-devel/add-thermal/diameter-m", + getprop(addon.node.getPath() ~ "/addon-devel/add-thermal/diameter-ft") * globals.FT2M + ); + })); + + append(g_thermalListeners, setlistener(addon.node.getPath() ~ "/addon-devel/add-thermal/height-msl", func () { + setprop( + addon.node.getPath() ~ "/addon-devel/add-thermal/height-msl-m", + getprop(addon.node.getPath() ~ "/addon-devel/add-thermal/height-msl") * globals.FT2M + ); + })); +} + +# +# Uninitialize thermal module +# +var uninit = func () { + foreach (var listener; g_thermalListeners) { + removelistener(listener); + } +} + +# +# Add thermal 300 m before glider position. +# +# Return 1 on successful, otherwise 0. +# +var add = func () { + var heading = getprop("/orientation/heading-deg") or 0; + var distance = getprop(addon.node.getPath() ~ "/addon-devel/add-thermal/distance-m") or 300; + + var position = geo.aircraft_position(); + position.apply_course_distance(heading, distance); + + # Get random layer from 1 do 4 + var layer = int(rand() * 4) + 1; + + var args = props.Node.new({ + "type": "thermal", + "model": "Models/Weather/altocumulus_layer" ~ layer ~ ".xml", + "latitude": position.lat(), + "longitude": position.lon(), + "strength-fps": getprop(addon.node.getPath() ~ "/addon-devel/add-thermal/strength-fps") or 16.0, + "diameter-ft": getprop(addon.node.getPath() ~ "/addon-devel/add-thermal/diameter-ft") or 4000, + "height-msl": getprop(addon.node.getPath() ~ "/addon-devel/add-thermal/height-msl") or 9000, + "search-order": "DATA_ONLY" + }); + + if (fgcommand("add-aiobject", args)) { + messages.displayOk("The thermal has been added"); + return 1; + } + + messages.displayError("Adding thermal failed"); + return 0; +}