Fix AI aircraft going underground during take-off due to uneven runway elevations.
Add possibility to set thermal with configuration of its parameters.
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -27,6 +27,16 @@
|
||||
<org.flightgear.addons.Aerotow>
|
||||
<addon-devel>
|
||||
<ai-model type="string">Cub</ai-model>
|
||||
<add-thermal>
|
||||
<distance-m type="int">300</distance-m>
|
||||
<distance-nm type="double">0.16</distance-nm>
|
||||
<strength-fps type="double">16.0</strength-fps>
|
||||
<strength-mps type="double">4.88</strength-mps>
|
||||
<diameter-ft type="int">4000</diameter-ft>
|
||||
<diameter-m type="int">1219</diameter-m>
|
||||
<height-msl type="int">9000</height-msl>
|
||||
<height-msl-m type="int">2742</height-msl-m>
|
||||
</add-thermal>
|
||||
<help-text type="string">=== 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).
|
||||
|
||||
+13
-7
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-5
@@ -53,10 +53,6 @@
|
||||
<value type="string">c182</value>
|
||||
</binding>
|
||||
</item>
|
||||
<item>
|
||||
<label>------------------</label>
|
||||
<enabled>false</enabled>
|
||||
</item>
|
||||
<item>
|
||||
<label>Disable tow aircraft</label>
|
||||
<binding>
|
||||
@@ -65,7 +61,18 @@
|
||||
</binding>
|
||||
</item>
|
||||
<item>
|
||||
<label>------------------</label>
|
||||
<label>---------------------</label>
|
||||
<enabled>false</enabled>
|
||||
</item>
|
||||
<item>
|
||||
<label>Add thermal</label>
|
||||
<binding>
|
||||
<command>dialog-show</command>
|
||||
<dialog-name>add-thermal</dialog-name>
|
||||
</binding>
|
||||
</item>
|
||||
<item>
|
||||
<label>---------------------</label>
|
||||
<enabled>false</enabled>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
+49
-21
@@ -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);
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
#
|
||||
# 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)
|
||||
#
|
||||
-->
|
||||
|
||||
<!-- This file requires FlightGear version 2018.2 or newer -->
|
||||
|
||||
<PropertyList>
|
||||
<name>add-thermal</name>
|
||||
<resizable>false</resizable>
|
||||
<layout>vbox</layout>
|
||||
<default-padding>3</default-padding>
|
||||
|
||||
<group>
|
||||
<layout>hbox</layout>
|
||||
|
||||
<empty>
|
||||
<stretch>1</stretch>
|
||||
</empty>
|
||||
|
||||
<text>
|
||||
<label>Add thermal</label>
|
||||
</text>
|
||||
|
||||
<empty>
|
||||
<stretch>1</stretch>
|
||||
</empty>
|
||||
|
||||
<button>
|
||||
<pref-width>16</pref-width>
|
||||
<pref-height>16</pref-height>
|
||||
<legend></legend>
|
||||
<keynum>27</keynum>
|
||||
<border>2</border>
|
||||
<binding>
|
||||
<command>dialog-close</command>
|
||||
</binding>
|
||||
</button>
|
||||
</group>
|
||||
|
||||
<hrule/>
|
||||
|
||||
<text>
|
||||
<halign>left</halign>
|
||||
<label> This option allows the thermal to be placed at the distance </label>
|
||||
</text>
|
||||
<text>
|
||||
<halign>left</halign>
|
||||
<label> designated below in front of the glider along with other parameters. </label>
|
||||
</text>
|
||||
|
||||
<hrule/>
|
||||
|
||||
<group>
|
||||
<layout>table</layout>
|
||||
|
||||
<text>
|
||||
<row>0</row>
|
||||
<col>0</col>
|
||||
<halign>right</halign>
|
||||
<label> Distance in front of the glider</label>
|
||||
</text>
|
||||
<input>
|
||||
<row>0</row>
|
||||
<col>1</col>
|
||||
<label>m</label>
|
||||
<halign>left</halign>
|
||||
<property>/addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/distance-m</property>
|
||||
<live>true</live>
|
||||
<binding>
|
||||
<command>dialog-apply</command>
|
||||
</binding>
|
||||
</input>
|
||||
<text>
|
||||
<row>0</row>
|
||||
<col>2</col>
|
||||
<label>Dummy label</label>
|
||||
<halign>left</halign>
|
||||
<format>(%.2f nm)</format>
|
||||
<live>true</live>
|
||||
<property>/addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/distance-nm</property>
|
||||
</text>
|
||||
|
||||
<text>
|
||||
<row>1</row>
|
||||
<col>0</col>
|
||||
<halign>right</halign>
|
||||
<label>Strength</label>
|
||||
</text>
|
||||
<input>
|
||||
<row>1</row>
|
||||
<col>1</col>
|
||||
<label>ft/s</label>
|
||||
<halign>left</halign>
|
||||
<property>/addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/strength-fps</property>
|
||||
<live>true</live>
|
||||
<binding>
|
||||
<command>dialog-apply</command>
|
||||
</binding>
|
||||
</input>
|
||||
<text>
|
||||
<row>1</row>
|
||||
<col>2</col>
|
||||
<label>Dummy label</label>
|
||||
<halign>left</halign>
|
||||
<format> (%.2f m/s)</format>
|
||||
<live>true</live>
|
||||
<property>/addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/strength-mps</property>
|
||||
</text>
|
||||
|
||||
<text>
|
||||
<row>2</row>
|
||||
<col>0</col>
|
||||
<halign>right</halign>
|
||||
<label>Diameter</label>
|
||||
</text>
|
||||
<input>
|
||||
<row>2</row>
|
||||
<col>1</col>
|
||||
<label>ft</label>
|
||||
<halign>left</halign>
|
||||
<property>/addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/diameter-ft</property>
|
||||
<live>true</live>
|
||||
<binding>
|
||||
<command>dialog-apply</command>
|
||||
</binding>
|
||||
</input>
|
||||
<text>
|
||||
<row>2</row>
|
||||
<col>2</col>
|
||||
<label>Dummy label</label>
|
||||
<halign>left</halign>
|
||||
<format>(%.f m)</format>
|
||||
<live>true</live>
|
||||
<property>/addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/diameter-m</property>
|
||||
</text>
|
||||
|
||||
<text>
|
||||
<row>3</row>
|
||||
<col>0</col>
|
||||
<halign>right</halign>
|
||||
<label>Height MSL</label>
|
||||
</text>
|
||||
<input>
|
||||
<row>3</row>
|
||||
<col>1</col>
|
||||
<label>ft</label>
|
||||
<halign>left</halign>
|
||||
<property>/addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/height-msl</property>
|
||||
<live>true</live>
|
||||
<binding>
|
||||
<command>dialog-apply</command>
|
||||
</binding>
|
||||
</input>
|
||||
<text>
|
||||
<row>3</row>
|
||||
<col>2</col>
|
||||
<label>Dummy label</label>
|
||||
<halign>left</halign>
|
||||
<format>(%.f m)</format>
|
||||
<live>true</live>
|
||||
<property>/addons/by-id/org.flightgear.addons.Aerotow/addon-devel/add-thermal/height-msl-m</property>
|
||||
</text>
|
||||
</group>
|
||||
|
||||
<hrule/>
|
||||
|
||||
<group>
|
||||
<empty>
|
||||
<stretch>true</stretch>
|
||||
</empty>
|
||||
|
||||
<layout>hbox</layout>
|
||||
|
||||
<button>
|
||||
<legend>Add thermal</legend>
|
||||
<equal>true</equal>
|
||||
<binding>
|
||||
<command>dialog-apply</command>
|
||||
</binding>
|
||||
<binding>
|
||||
<command>nasal</command>
|
||||
<script><![CDATA[thermal.add();]]></script>
|
||||
</binding>
|
||||
<binding>
|
||||
<command>dialog-close</command>
|
||||
</binding>
|
||||
</button>
|
||||
|
||||
<button>
|
||||
<legend>Cancel</legend>
|
||||
<equal>true</equal>
|
||||
<default>true</default>
|
||||
<key>Esc</key>
|
||||
<binding>
|
||||
<command>dialog-close</command>
|
||||
</binding>
|
||||
</button>
|
||||
|
||||
<empty>
|
||||
<stretch>true</stretch>
|
||||
</empty>
|
||||
</group>
|
||||
|
||||
</PropertyList>
|
||||
+97
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user