# Properties under /consumables/fuel/tank[n]: # + level-gal_us - Current fuel load. Can be set by user code. # + level-lbs - OUTPUT ONLY property, do not try to set # + selected - boolean indicating tank selection. # + density-ppg - Fuel density, in lbs/gallon. # + capacity-gal_us - Tank capacity # # Properties under /engines/engine[n]: # + fuel-consumed-lbs - Output from the FDM, zeroed by this script # + out-of-fuel - boolean, set by this code. var UPDATE_PERIOD = 0.3; var enabled = nil; var serviceable = nil; var fuel_freeze = nil; var ai_enabled = nil; var engines = nil; var tanks = []; var refuelingN = nil; var reportContactNode = nil; var reportContact = 0; var contactN = nil; var aimodelsN = nil; var types = {}; var update_model_list = 0; var tankerNode = nil; var tankerContactNode = nil; setlistener("/ai/models/model-added", func(v){ update_model_list = 1; }); setlistener("/ai/models/model-removed", func(v){ update_model_list = 1; }); var tankers = []; var update_loop = func { # check for contact with tanker aircraft if (ai_enabled) { if (update_model_list) { update_model_list=0; tankers = []; var ac = aimodelsN.getChildren("tanker"); var mp = aimodelsN.getChildren("multiplayer"); foreach (var a; ac ~ mp) { if (!a.getNode("valid", 1).getValue()) continue ; if (!a.getNode("tanker", 1).getValue()) continue ; foreach (var t; a.getNode("refuel", 1).getChildren("type")) { var type = t.getValue(); if (contains(types, type) and types[type]) append(tankers, a); } } } } var foundTankerNode = nil; if (serviceable) { foreach (var t; tankers) { if (t.getNode("refuel/contact", 1).getValue()){ foundTankerNode = t; } } } if (foundTankerNode != tankerNode){ tankerNode = foundTankerNode; if (tankerNode != nil){ tankerContactNode = tankerNode.getNode("refuel/contact", 1); } else tankerContactNode = nil; } reportContact = reportContactNode.getValue(); if (tankerNode != nil and tankerContactNode != nil) { var tankerContact = tankerContactNode.getValue(); if (tankerContact != contactN.getValue()) { if (reportContactNode.getValue()) { if (tankerContact) setprop("/sim/messages/copilot", "Engage"); else setprop("/sim/messages/copilot", "Disengage"); } contactN.setBoolValue(tankerContact); } } else { if (reportContact and contactN.getValue()) setprop("/sim/messages/copilot", "Disengage"); contactN.setBoolValue(0); } if (fuel_freeze){ return; } # sum up consumed fuel var consumed = 0; foreach (var e; engines) { var fuel = e.getNode("fuel-consumed-lbs"); consumed += fuel.getValue(); fuel.setDoubleValue(0); } # calculate fuel received if (tankerNode != nil) { # Flow rate is the minimum of the tanker maxium rate # and the aircraft maximum rate. Both are expressed # in lbs/min var fuel_rate = math.min(tankerNode.getNode("refuel/max-fuel-transfer-lbs-min", 1).getValue() or 6000, refuelingN.getNode("max-fuel-transfer-lbs-min", 1).getValue() or 6000); var received = UPDATE_PERIOD * fuel_rate / 60; consumed -= received; } # make list of selected tanks var selected_tanks = []; foreach (var t; tanks) { var cap = t.getNode("capacity-gal_us", 1).getValue(); if (cap != nil and cap > 0.01 and t.getNode("selected", 1).getBoolValue()) append(selected_tanks, t); } var out_of_fuel = 0; if (size(selected_tanks) == 0) { out_of_fuel = 1; } elsif (consumed >= 0) { var fuel_per_tank = consumed / size(selected_tanks); foreach (var t; selected_tanks) { var ppg = t.getNode("density-ppg").getValue(); var lbs = t.getNode("level-gal_us").getValue() * ppg; lbs -= fuel_per_tank; if (lbs < 0) { lbs = 0; # Kill the engines if we're told to, otherwise simply # deselect the tank. if (t.getNode("kill-when-empty", 1).getBoolValue()) out_of_fuel = 1; else t.getNode("selected", 1).setBoolValue(0); } var gals = lbs / ppg; t.getNode("level-gal_us").setDoubleValue(gals); t.getNode("level-lbs").setDoubleValue(lbs); } } else { #find the number of tanks which can accept fuel var available = 0; foreach (var t; selected_tanks) { var ppg = t.getNode("density-ppg").getValue(); var capacity = t.getNode("capacity-gal_us").getValue() * ppg; var lbs = t.getNode("level-gal_us").getValue() * ppg; if (lbs < capacity) available += 1; } if (available > 0) { var fuel_per_tank = -consumed / available; # add fuel to each available tank foreach (var t; selected_tanks) { var ppg = t.getNode("density-ppg").getValue(); var capacity = t.getNode("capacity-gal_us").getValue() * ppg; var lbs = t.getNode("level-gal_us").getValue() * ppg; lbs += fuel_per_tank; if (lbs > capacity) lbs = capacity; t.getNode("level-gal_us").setDoubleValue(lbs / ppg); t.getNode("level-lbs").setDoubleValue(lbs); } # print ("available ", available , " fuel_per_tank " , fuel_per_tank); } } var gals = 0; var lbs = 0; var cap = 0; foreach (var t; tanks) { gals += t.getNode("level-gal_us", 1).getValue(); lbs += t.getNode("level-lbs", 1).getValue(); cap += t.getNode("capacity-gal_us", 1).getValue(); } setprop("/consumables/fuel/total-fuel-gals", gals); setprop("/consumables/fuel/total-fuel-lbs", lbs); if (cap == 0) setprop("/consumables/fuel/total-fuel-norm", 0); else setprop("/consumables/fuel/total-fuel-norm", gals / cap); foreach (var e; engines) e.getNode("out-of-fuel", 1).setBoolValue(out_of_fuel); } var aarTimer = maketimer(UPDATE_PERIOD, update_loop); aarTimer.simulatedTime = 1; setlistener("/sim/signals/fdm-initialized", func { if (contains(globals, "fuel") and typeof(fuel) == "hash") fuel.loop = func nil; # kill $FG_ROOT/Nasal/fuel.nas' loop contactN = props.globals.initNode("/systems/refuel/contact", 0, "BOOL"); refuelingN = props.globals.getNode("/systems/refuel", 1); aimodelsN = props.globals.getNode("ai/models", 1); engines = props.globals.getNode("engines", 1).getChildren("engine"); reportContactNode = refuelingN.getNode("report-contact", 1); foreach (var e; engines) { e.getNode("fuel-consumed-lbs", 1).setDoubleValue(0); e.getNode("out-of-fuel", 1).setBoolValue(0); } foreach (var t; props.globals.getNode("consumables/fuel", 1).getChildren("tank")) { if (!t.getAttribute("children")) continue; # skip native_fdm.cxx generated zombie tanks append(tanks, t); t.initNode("level-gal_us", 0.0); t.initNode("level-lbs", 0.0); t.initNode("capacity-gal_us", 0.01); # not zero (div/zero issue) t.initNode("density-ppg", 6.0); # gasoline t.initNode("selected", 1, "BOOL"); } foreach (var t; props.globals.getNode("systems/refuel", 1).getChildren("type")) types[t.getValue()] = 1; setlistener("sim/freeze/fuel", func(n) fuel_freeze = n.getBoolValue(), 1); setlistener("sim/ai/enabled", func(n) ai_enabled = n.getBoolValue(), 1); setlistener("systems/refuel/serviceable", func(n) serviceable = n.getBoolValue(), 1); aarTimer.restart(UPDATE_PERIOD); });