Air-to-air refueling enhancements
[fg:toms-fgdata.git] / Aircraft / Generic / aar.nas
1 # Properties under /consumables/fuel/tank[n]:
2 # + level-gal_us    - Current fuel load.  Can be set by user code.
3 # + level-lbs       - OUTPUT ONLY property, do not try to set
4 # + selected        - boolean indicating tank selection.
5 # + density-ppg     - Fuel density, in lbs/gallon.
6 # + capacity-gal_us - Tank capacity
7 #
8 # Properties under /engines/engine[n]:
9 # + fuel-consumed-lbs - Output from the FDM, zeroed by this script
10 # + out-of-fuel       - boolean, set by this code.
11
12
13 var UPDATE_PERIOD = 0.3;
14
15 var enabled = nil;
16 var serviceable = nil;
17 var fuel_freeze = nil;
18 var ai_enabled = nil;
19 var engines = nil;
20 var tanks = [];
21 var refuelingN = nil;
22 var contactN = nil;
23 var aimodelsN = nil;
24 var types = {};
25
26
27
28 var update_loop = func {
29         # check for contact with tanker aircraft
30         var tankers = [];
31         if (ai_enabled) {
32                 var ac = aimodelsN.getChildren("tanker");
33                 var mp = aimodelsN.getChildren("multiplayer");
34
35                 foreach (var a; ac ~ mp) {
36                         if (!a.getNode("valid", 1).getValue())
37                                 continue;
38                         if (!a.getNode("tanker", 1).getValue())
39                                 continue;
40                         if (!a.getNode("refuel/contact", 1).getValue())
41                                 continue;
42                         foreach (var t; a.getNode("refuel", 1).getChildren("type")) {
43                                 var type = t.getValue();
44                                 if (contains(types, type) and types[type])
45                                         append(tankers, a);
46                         }
47                 }
48         }
49
50         var refueling = serviceable and size(tankers) > 0;
51         
52         if (refuelingN.getNode("report-contact", 1).getValue()) {
53           if (refueling and !contactN.getValue()) {
54                         setprop("/sim/messages/copilot", "Engage");
55           }
56           
57           if (!refueling and contactN.getValue()) {
58                         setprop("/sim/messages/copilot", "Disengage");
59           }
60         }
61         
62         contactN.setBoolValue(refueling);
63
64         if (fuel_freeze)
65                 return settimer(update_loop, UPDATE_PERIOD);
66
67
68         # sum up consumed fuel
69         var consumed = 0;
70         foreach (var e; engines) {
71                 var fuel = e.getNode("fuel-consumed-lbs");
72                 consumed += fuel.getValue();
73                 fuel.setDoubleValue(0);
74         }
75
76
77
78         # calculate fuel received
79         if (refueling) {
80                 # Flow rate is the minimum of the tanker maxium rate
81                 # and the aircraft maximum rate.  Both are expressed
82                 # in lbs/min
83                 var fuel_rate = math.min(tankers[0].getNode("refuel/max-fuel-transfer-lbs-min", 1).getValue() or 6000, 
84                                          refuelingN.getNode("max-fuel-transfer-lbs-min", 1).getValue());
85                 var received =  UPDATE_PERIOD * fuel_rate / 60;
86                 consumed -= received;
87         }
88
89
90         # make list of selected tanks
91         var selected_tanks = [];
92         foreach (var t; tanks) {
93                 var cap = t.getNode("capacity-gal_us", 1).getValue();
94                 if (cap != nil and cap > 0.01 and t.getNode("selected", 1).getBoolValue())
95                         append(selected_tanks, t);
96         }
97
98
99         var out_of_fuel = 0;
100         if (size(selected_tanks) == 0) {
101                 out_of_fuel = 1;
102
103         } elsif (consumed >= 0) {
104                 var fuel_per_tank = consumed / size(selected_tanks);
105                 foreach (var t; selected_tanks) {
106                         var ppg = t.getNode("density-ppg").getValue();
107                         var lbs = t.getNode("level-gal_us").getValue() * ppg;
108                         lbs -= fuel_per_tank;
109
110                         if (lbs < 0) {
111                                 lbs = 0;
112                                 # Kill the engines if we're told to, otherwise simply
113                                 # deselect the tank.
114                                 if (t.getNode("kill-when-empty", 1).getBoolValue())
115                                         out_of_fuel = 1;
116                                 else
117                                         t.getNode("selected", 1).setBoolValue(0);
118                         }
119
120                         var gals = lbs / ppg;
121                         t.getNode("level-gal_us").setDoubleValue(gals);
122                         t.getNode("level-lbs").setDoubleValue(lbs);
123                 }
124
125         } else {
126                 #find the number of tanks which can accept fuel
127                 var available = 0;
128
129                 foreach (var t; selected_tanks) {
130                         var ppg = t.getNode("density-ppg").getValue();
131                         var capacity = t.getNode("capacity-gal_us").getValue() * ppg;
132                         var lbs = t.getNode("level-gal_us").getValue() * ppg;
133
134                         if (lbs < capacity)
135                                 available += 1;
136                 }
137
138                 if (available > 0) {
139                         var fuel_per_tank = -consumed / available;
140
141                         # add fuel to each available tank
142                         foreach (var t; selected_tanks) {
143                                 var ppg = t.getNode("density-ppg").getValue();
144                                 var capacity = t.getNode("capacity-gal_us").getValue() * ppg;
145                                 var lbs = t.getNode("level-gal_us").getValue() * ppg;
146
147                                 lbs += fuel_per_tank;
148                                 if (lbs > capacity)
149                                         lbs = capacity;
150
151                                 t.getNode("level-gal_us").setDoubleValue(lbs / ppg);
152                                 t.getNode("level-lbs").setDoubleValue(lbs);
153                         }
154
155                         # print ("available ", available , " fuel_per_tank " , fuel_per_tank);
156                 }
157         }
158
159
160         var gals = 0;
161         var lbs = 0;
162         var cap = 0;
163         foreach (var t; tanks) {
164                 gals += t.getNode("level-gal_us", 1).getValue();
165                 lbs += t.getNode("level-lbs", 1).getValue();
166                 cap += t.getNode("capacity-gal_us", 1).getValue();
167         }
168
169         setprop("/consumables/fuel/total-fuel-gals", gals);
170         setprop("/consumables/fuel/total-fuel-lbs", lbs);
171         if (cap == 0)
172                 setprop("/consumables/fuel/total-fuel-norm", 0);
173         else
174                 setprop("/consumables/fuel/total-fuel-norm", gals / cap);
175
176         foreach (var e; engines)
177                 e.getNode("out-of-fuel", 1).setBoolValue(out_of_fuel);
178
179         settimer(update_loop, UPDATE_PERIOD);
180 }
181
182
183
184 setlistener("/sim/signals/fdm-initialized", func {
185         if (contains(globals, "fuel") and typeof(fuel) == "hash")
186                 fuel.loop = func nil;       # kill $FG_ROOT/Nasal/fuel.nas' loop
187
188         contactN = props.globals.initNode("/systems/refuel/contact", 0, "BOOL");
189         refuelingN = props.globals.getNode("/systems/refuel", 1);
190         aimodelsN = props.globals.getNode("ai/models", 1);
191         engines = props.globals.getNode("engines", 1).getChildren("engine");
192
193         foreach (var e; engines) {
194                 e.getNode("fuel-consumed-lbs", 1).setDoubleValue(0);
195                 e.getNode("out-of-fuel", 1).setBoolValue(0);
196         }
197
198         foreach (var t; props.globals.getNode("consumables/fuel", 1).getChildren("tank")) {
199                 if (!t.getAttribute("children"))
200                         continue;           # skip native_fdm.cxx generated zombie tanks
201
202                 append(tanks, t);
203                 t.initNode("level-gal_us", 0.0);
204                 t.initNode("level-lbs", 0.0);
205                 t.initNode("capacity-gal_us", 0.01); # not zero (div/zero issue)
206                 t.initNode("density-ppg", 6.0);      # gasoline
207                 t.initNode("selected", 1, "BOOL");
208         }
209
210         foreach (var t; props.globals.getNode("systems/refuel", 1).getChildren("type"))
211                 types[t.getValue()] = 1;
212
213         setlistener("sim/freeze/fuel", func(n) fuel_freeze = n.getBoolValue(), 1);
214         setlistener("sim/ai/enabled", func(n) ai_enabled = n.getBoolValue(), 1);
215         setlistener("systems/refuel/serviceable", func(n) serviceable = n.getBoolValue(), 1);
216         update_loop();
217 });
218
219