From d9ffc500110382c3f6afc8e85ae0f02899d9a3f2 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Tue, 22 Jun 2021 23:39:45 +0200 Subject: [PATCH] MP Carrier fixes for loading Generally this change proves that we probably don't need the MPCarrier model on the target system - because by using the fallback model to load the Nasal this will happen without needing the high detail models at all. To support this : - Added fallback models for all carriers (defined as AI scenarios) - Added logic to fallback models so that MP carriers load correctly - The model will still be the AI model. - MPCarriers.nas from MPCarrier/systems now in Aircraft/Generic --- AI/Aircraft/fallback_models.xml | 11 + AI/Maritime/MP-Carriers/mp-clemenceau-0.xml | 60 ++ AI/Maritime/MP-Carriers/mp-eisenhower-0.xml | 60 ++ AI/Maritime/MP-Carriers/mp-foch-0.xml | 60 ++ AI/Maritime/MP-Carriers/mp-kuznetsov-0.xml | 60 ++ AI/Maritime/MP-Carriers/mp-liaoning-0.xml | 60 ++ AI/Maritime/MP-Carriers/mp-nimitz-0.xml | 60 ++ AI/Maritime/MP-Carriers/mp-sanantonio-0.xml | 60 ++ AI/Maritime/MP-Carriers/mp-truman-0.xml | 60 ++ AI/Maritime/MP-Carriers/mp-vinson-0.xml | 60 ++ Aircraft/Generic/MPCarriers.nas | 618 ++++++++++++++++++++ 11 files changed, 1169 insertions(+) create mode 100644 AI/Maritime/MP-Carriers/mp-clemenceau-0.xml create mode 100644 AI/Maritime/MP-Carriers/mp-eisenhower-0.xml create mode 100644 AI/Maritime/MP-Carriers/mp-foch-0.xml create mode 100644 AI/Maritime/MP-Carriers/mp-kuznetsov-0.xml create mode 100644 AI/Maritime/MP-Carriers/mp-liaoning-0.xml create mode 100644 AI/Maritime/MP-Carriers/mp-nimitz-0.xml create mode 100644 AI/Maritime/MP-Carriers/mp-sanantonio-0.xml create mode 100644 AI/Maritime/MP-Carriers/mp-truman-0.xml create mode 100644 AI/Maritime/MP-Carriers/mp-vinson-0.xml create mode 100644 Aircraft/Generic/MPCarriers.nas diff --git a/AI/Aircraft/fallback_models.xml b/AI/Aircraft/fallback_models.xml index 12edd5a77..7f682f4d7 100644 --- a/AI/Aircraft/fallback_models.xml +++ b/AI/Aircraft/fallback_models.xml @@ -28,6 +28,7 @@ 551-600 Military large aircraft (tankers, transports) 601-700 Helicopters 701-800 Other + 801-900 Maritime --> @@ -181,4 +182,14 @@ AI model. We also want to load it in preference to the default glider because i then be invisible to other MP players who are not in a UFO themselves. --> Aircraft/ufo/Models/ufo.xml +AI/Maritime/MP-Carriers/mp-nimitz-0.xml +AI/Maritime/MP-Carriers/mp-vinson-0.xml +AI/Maritime/MP-Carriers/mp-eisenhower-0.xml +AI/Maritime/MP-Carriers/mp-truman-0.xml +AI/Maritime/MP-Carriers/mp-clemenceau-0.xml +AI/Maritime/MP-Carriers/mp-foch-0.xml +AI/Maritime/MP-Carriers/mp-kuznetsov-0.xml +AI/Maritime/MP-Carriers/mp-liaoning-0.xml +AI/Maritime/MP-Carriers/mp-sanantonio-0.xml + diff --git a/AI/Maritime/MP-Carriers/mp-clemenceau-0.xml b/AI/Maritime/MP-Carriers/mp-clemenceau-0.xml new file mode 100644 index 000000000..c75696bad --- /dev/null +++ b/AI/Maritime/MP-Carriers/mp-clemenceau-0.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/AI/Maritime/MP-Carriers/mp-eisenhower-0.xml b/AI/Maritime/MP-Carriers/mp-eisenhower-0.xml new file mode 100644 index 000000000..3a0fcd404 --- /dev/null +++ b/AI/Maritime/MP-Carriers/mp-eisenhower-0.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/AI/Maritime/MP-Carriers/mp-foch-0.xml b/AI/Maritime/MP-Carriers/mp-foch-0.xml new file mode 100644 index 000000000..8d7a5f533 --- /dev/null +++ b/AI/Maritime/MP-Carriers/mp-foch-0.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/AI/Maritime/MP-Carriers/mp-kuznetsov-0.xml b/AI/Maritime/MP-Carriers/mp-kuznetsov-0.xml new file mode 100644 index 000000000..48efd3ebe --- /dev/null +++ b/AI/Maritime/MP-Carriers/mp-kuznetsov-0.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/AI/Maritime/MP-Carriers/mp-liaoning-0.xml b/AI/Maritime/MP-Carriers/mp-liaoning-0.xml new file mode 100644 index 000000000..9b4b1bca7 --- /dev/null +++ b/AI/Maritime/MP-Carriers/mp-liaoning-0.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/AI/Maritime/MP-Carriers/mp-nimitz-0.xml b/AI/Maritime/MP-Carriers/mp-nimitz-0.xml new file mode 100644 index 000000000..4a2c5a9d3 --- /dev/null +++ b/AI/Maritime/MP-Carriers/mp-nimitz-0.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/AI/Maritime/MP-Carriers/mp-sanantonio-0.xml b/AI/Maritime/MP-Carriers/mp-sanantonio-0.xml new file mode 100644 index 000000000..3cd4aeeb9 --- /dev/null +++ b/AI/Maritime/MP-Carriers/mp-sanantonio-0.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/AI/Maritime/MP-Carriers/mp-truman-0.xml b/AI/Maritime/MP-Carriers/mp-truman-0.xml new file mode 100644 index 000000000..a745bc0f9 --- /dev/null +++ b/AI/Maritime/MP-Carriers/mp-truman-0.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/AI/Maritime/MP-Carriers/mp-vinson-0.xml b/AI/Maritime/MP-Carriers/mp-vinson-0.xml new file mode 100644 index 000000000..b4c650c52 --- /dev/null +++ b/AI/Maritime/MP-Carriers/mp-vinson-0.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/Aircraft/Generic/MPCarriers.nas b/Aircraft/Generic/MPCarriers.nas new file mode 100644 index 000000000..21387fb53 --- /dev/null +++ b/Aircraft/Generic/MPCarriers.nas @@ -0,0 +1,618 @@ +############################################################################### +## +## Nasal module for connecting a local AI carrier to a MP player. +## +## Copyright (C) 2007 - 2012 Anders Gidenstam (anders(at)gidenstam.org) +## Copyright (C) 2009 Vivian Meazza +## This file is licensed under the GPL license version 2 or later. +## +############################################################################### + +# NOTE: +# This module is intended to be loaded under the name MPCarriers. + +# To see what is happening with multiplayer/ai carriers, look in +# /ai/models/carrier[]/mp-control/. +# + +# If this is true, we auto-enable ai scenarios and auto-attach ai carriers to +# mp carriers. +# +var g_auto_attach = props.globals.getNode("/sim/mp-carriers/auto-attach", 1); +printf("g_auto_attach.getValue()=%s", g_auto_attach.getValue()); + +# Constants +var lat = "position/latitude-deg"; +var lon = "position/longitude-deg"; +var alt = "position/altitude-ft"; +var c_heading = "orientation/true-heading-deg"; +var c_pitch = "orientation/pitch-deg"; +var c_roll = "orientation/roll-deg"; +var c_speed = "velocities/speed-kts"; +var x = "position/global-x"; +var y = "position/global-y"; +var z = "position/global-z"; + +#var c_control_speed = "controls/base-speed-kts"; +#var c_control_course = "controls/base-course-deg"; +var c_control_speed = "controls/tgt-speed-kts"; +var c_control_course = "controls/tgt-heading-degs"; +var c_wave_off_lights = "controls/flols/wave-off-lights"; + + +var c_control_mp_ctrl = "controls/mp-control"; + +var c_deck_elev = "controls/elevators"; +var c_deck_lights = "controls/lighting/deck-lights"; +var c_flood_lights = "controls/lighting/flood-lights-red-norm"; +var c_turn_to_launch_hdg = "controls/turn-to-launch-hdg"; +var c_turn_to_recvry_hdg = "controls/turn-to-recovery-hdg"; +var c_turn_to_base_co = "controls/turn-to-base-course"; + + +var mp_heading = "orientation/true-heading-deg"; +var mp_pitch = "orientation/pitch-deg"; +var mp_roll = "orientation/roll-deg"; + +# MP transmitted controls +var mp_speed = "surface-positions/flap-pos-norm"; +var mp_rudder = "surface-positions/rudder-pos-norm"; +var mp_network = "sim/multiplay/generic/string[0]"; +var mp_message = "sim/multiplay/generic/string[2]"; +var mp_tgt_hdg = "sim/multiplay/generic/float[0]"; +var mp_tgt_spd = "sim/multiplay/generic/float[1]"; +var mp_turn_radius = "sim/multiplay/generic/float[2]"; + +# Controller parameters. +var cross_course_gain = 0.2; +var cross_course_fadeout = 100.0; +var cross_course_limit = 20.0; + +############################################################################### +# Manager class for one model instance. +var Manager = {}; +################################################## +Manager.new = func (player = nil, carrier_name = nil, callsign = nil) { + # e.g. carrier_name = 'Clemenceau'. + printf("Manager.new(): player=%s carrier_name=%s callsign=%s", + player, carrier_name, callsign); + + if (g_auto_attach.getValue()) { + # Look for matching ai scenario and enable it. We don't care if it is + # already enabled - FGAIManager::loadScenarioCommand() will know to do + # nothing. + printf("Checking ai scenarios..."); + foreach (var n; props.globals.getNode("sim/ai/scenarios", 1).getChildren("scenario")) { + var n_carrier_name = n.getValue("carrier/name"); + var n_scenario_id = n.getValue("id"); # e.g. clemenceau_demo. + if (n_carrier_name != nil) { + if (0) { + printf(" %s: id=%s n_carrier_name=%s", + n.getPath(), + n_scenario_id, + n_carrier_name + ); + } + if (n_carrier_name == carrier_name) { + printf("calling load-scenario with id=%s", n_scenario_id); + var args = props.Node.new( { name: n_scenario_id}); + var e = fgcommand("load-scenario", args); + printf("fgcommand('load-scenario') returned e=%s", e); + if (e) { + # We have loaded scenario. + gui.popupTip(sprintf("Have loaded AI senario: %s", n_scenario_id)); + } + else { + # Error, or scenario was already loaded. + } + } + } + } + printf("Enabled scenarios are:"); + foreach (var n; props.globals.getNode("sim/ai", 1).getChildren("scenario")) { + printf(" %s", n.getValue()); + } + } + + var obj = { parents : [Manager], + rplayer : player, + carrier_name : carrier_name, + carrier : nil, + accept_callsign : callsign, + callsign_listener : nil, + FREEZE_DIST : 400.0, + comms : nil, + message : nil, + loopid : 0 }; + + var carriers = props.globals.getNode("/ai/models").getChildren("carrier"); + + foreach(var c; carriers) { + if (c.getNode("name").getValue() == carrier_name) { + + obj.carrier = c; + printf("Initializing carrier_name=%s player.getPath()=%s", + carrier_name, player.getPath()); + obj.callsign_listener = + setlistener(callsign, + func { print("Callsign update"); + obj.start(); }); + MPCarriersNW.Manager_instances[player.getIndex()] = obj; + obj.start(); + return obj; + } + } + printf("Failed to find carrier_name=%s. The relevant carrier AI scenario must be active", carrier_name); + + return nil; +} +################################################## +Manager.is_valid = func { + return ((me.rplayer.getNode("valid") != nil) and + me.rplayer.getNode("valid").getValue() and + (me.rplayer.getNode("callsign") != nil)); +} +################################################## +Manager.is_active = func { + var a = me.is_valid(); + var b = (me.rplayer.getNode("callsign") != nil); + # FIXME: Sometimes the cmp() call here gets an invalid argument. + var c = (cmp(me.rplayer.getNode("callsign").getValue(), me.accept_callsign.getValue()) == 0); + if (g_auto_attach.getValue()) { + # Always attach, regardless of callsign. + c = 1; + } + return (a and b and c); +} +################################################## +Manager.set_property = func (path, value) { + if (!me.is_valid() or !me.is_active()) return; + me.carrier.getNode(path).setValue(value); +} +################################################## +Manager.update = func { + var aircraft_pos = geo.aircraft_position(); + var carrier_pos = geo.Coord.new(aircraft_pos); + + if (!me.is_valid()) { + # This carrier player is not valid anymore. + if (0) printf("calling me.die() because !me.is_valid()"); + me.die(); + return; + } + if (!me.is_active()) { + # This carrier player is not the chosen one. Go idle. + if (0) printf("calling me.stop(). me.is_valid()=%s me.rplayer.getNode('callsign').getValue()=%s me.accept_callsign.getValue()=%s", + me.is_valid(), + me.rplayer.getNode('callsign').getValue(), + me.accept_callsign.getValue() + ); + me.stop(); + return; + } + + # LSO comms + var message = me.rplayer.getNode(mp_message).getValue(); + if (message != ""){ + + if (message != me.message){ + setprop("/sim/messages/approach", message); + } + + me.message = message; + } + + # carrier_pos.set_latlon(me.carrier.getNode(lat).getValue(), + # me.carrier.getNode(lon).getValue(), + # me.carrier.getNode(alt).getValue()); + + carrier_pos.set_xyz(me.carrier.getNode(x).getValue(), + me.carrier.getNode(y).getValue(), + me.carrier.getNode(z).getValue()); + + # Compute the position and orientation error. + var rplayer_pos = geo.Coord.new(carrier_pos); + # rplayer_pos.set_latlon(me.rplayer.getNode(lat).getValue(), + # me.rplayer.getNode(lon).getValue(), + # me.rplayer.getNode(alt).getValue()); + + rplayer_pos.set_xyz(me.rplayer.getNode(x).getValue(), + me.rplayer.getNode(y).getValue(), + me.rplayer.getNode(z).getValue()); + + var master_course = normalize_course(me.rplayer.getNode(mp_heading).getValue()); + var master_speed = me.rplayer.getNode(mp_speed).getValue(); + var bearing_to_master = normalize_course(carrier_pos.course_to(rplayer_pos)); + var distance_to_master = carrier_pos.direct_distance_to(rplayer_pos); + var v = D2R * normalize_course(bearing_to_master - master_course); + var cross_track_error = distance_to_master * math.sin(v); + var along_track_error = distance_to_master * math.cos(v); + var master_tgt_hdg = as_num(me.rplayer.getNode(mp_tgt_hdg).getValue(), + me.rplayer.getNode(mp_heading).getValue()); + var master_tgt_spd = as_num(me.rplayer.getNode(mp_tgt_spd).getValue(), + me.rplayer.getNode(mp_speed).getValue()); + var master_turn_radius = as_num(me.rplayer.getNode(mp_turn_radius).getValue()); + + var diff = master_course - master_tgt_hdg; + + if (diff > 180) + diff -= 360; + elsif (diff < -180) + diff += 360; + + me.carrier.getNode("mp-control/ai-mp-course-delta", 1).setValue(diff); + + if ( diff < -1.0 or diff > 1.0){ + me.carrier.getNode("mp-control/ai-mp-course-delta-type", 1).setValue("major"); + # major course alteration - we'll just use target heading from + # master until it's nearly complete + # print("major turn" , diff); + var set_course = master_tgt_hdg ; + var correction = 0; + + if (diff < 0){ + correction = -cross_track_error * M2FT; + # print("stbd turn ", correction); + } elsif (diff > 0){ + correction = cross_track_error * M2FT; + # print("port turn ", correction); + } else { + correction = 0; + # print("no turn ", correction); + } + + me.carrier.getNode("controls/turn-radius-ft", 1).setValue(master_turn_radius + correction); + + } else { + # Use Controller. + me.carrier.getNode("mp-control/ai-mp-course-delta-type", 1).setValue("minor"); + me.carrier.getNode("controls/turn-radius-ft", 1).setValue(master_turn_radius); + var set_course = + normalize_course(180.0/math.pi * + (math.abs(cross_track_error) < cross_course_fadeout ? + math.pow + (math.abs(cross_track_error/cross_course_fadeout),2) : + 1.0) * + math.atan2(cross_course_gain * cross_track_error, + along_track_error) + + master_course); + # Limit the course to +/-cross_course_limit degrees off the master's course. + if (set_course - master_course > cross_course_limit) { + set_course = normalize_course(master_course + cross_course_limit); + } else if (set_course - master_course < -cross_course_limit) { + set_course = normalize_course(master_course - cross_course_limit); + } + + } + var spd_diff = master_speed - master_tgt_spd; + + me.carrier.getNode("mp-control/ai-mp-speed-delta", 1).setValue(spd_diff); + if ( spd_diff < -1 or spd_diff > 1){ + # major speed alteration - we'll just use target speed from + # master until it's nearly complete + me.carrier.getNode("mp-control/ai-mp-speed-delta-type", 1).setValue("major"); + var set_speed = master_tgt_spd + + 0.01 * along_track_error; + if (set_speed > master_tgt_spd + 5.0) set_speed = master_tgt_spd + 5.0; + if (set_speed < master_tgt_spd - 5.0) set_speed = master_tgt_spd - 5.0; + } else { + me.carrier.getNode("mp-control/ai-mp-speed-delta-type", 1).setValue("minor"); + var set_speed = master_speed + + 0.01 * along_track_error; + if (set_speed > master_speed + 5.0) set_speed = master_speed + 5.0; + if (set_speed < master_speed - 5.0) set_speed = master_speed - 5.0; + } + + # publish controller settings to /ai/models/carrier[]/mp-control/. + # + me.carrier.getNode("mp-control/bearing-to-master-rel-deg", 1).setValue(v); + me.carrier.getNode("mp-control/bearing-to-master-deg", 1).setValue(bearing_to_master); + me.carrier.getNode("mp-control/distance-to-master-m", 1).setValue(distance_to_master); + me.carrier.getNode("mp-control/cross-track-error-m", 1).setValue(cross_track_error); + me.carrier.getNode("mp-control/along-track-error-m", 1).setValue(along_track_error); + me.carrier.getNode("mp-control/freeze-distance", 1).setValue(me.FREEZE_DIST); + + me.carrier.getNode("mp-control/master-speed-kts", 1).setValue(master_speed); + me.carrier.getNode("mp-control/master-course-deg", 1).setValue(master_course); + + me.carrier.getNode("mp-control/set-speed-kts", 1).setValue(set_speed); + me.carrier.getNode("mp-control/set-course-deg", 1).setValue(set_course); + me.carrier.getNode("mp-control/tgt-hdg-deg", 1).setValue(sprintf ( "%03.1d", master_tgt_hdg)); + me.carrier.getNode("mp-control/tgt-spd-kts", 1).setValue(master_tgt_spd); + me.carrier.getNode("mp-control/turn-radius-ft", 1).setValue(master_turn_radius); + + # We latch ai and mp carrier positions if user aircraft is more than + # me.FREEZE_DIST away from ai carrier, or if we are in replay mode. + # + var distance_aircraft_carrier = aircraft_pos.direct_distance_to(carrier_pos); + var in_replay = props.globals.getValue("/sim/replay/replay-state"); + var do_latch = (distance_aircraft_carrier > me.FREEZE_DIST or in_replay); + var distance_ai_to_mpcarrier = rplayer_pos.direct_distance_to(carrier_pos); + + me.carrier.getNode("mp-control/aircraft-ai-distance", 1).setValue(distance_aircraft_carrier); + me.carrier.getNode("mp-control/ai-mp-latched", do_latch); + + if (do_latch) { + # Latch the local AI carrier to the remote player's + # location only when the local player is far enough away not + # to suffer side effects. + me.carrier.getNode(lat).setValue(me.rplayer.getNode(lat).getValue()); + me.carrier.getNode(lon).setValue(me.rplayer.getNode(lon).getValue()); + me.carrier.getNode(alt).setValue(me.rplayer.getNode(alt).getValue()); + + me.carrier.getNode(c_heading). + setValue(master_course); + me.carrier.getNode(c_pitch). + setValue(me.rplayer.getNode(mp_pitch).getValue()); + me.carrier.getNode(c_roll). + setValue(me.rplayer.getNode(mp_roll).getValue()); + + # Accelerate the AI carrier to the right speed. + me.carrier.getNode(c_control_speed). + setValue(master_speed); + } else { + # Player on deck. Use the speed controller. + me.carrier.getNode(c_control_speed). + setValue(set_speed); + } + + # Always set these commands. + me.carrier.getNode(c_control_course). + setValue(set_course); + + # Switch off AI control for the carrier if available. + if (me.carrier.getNode(c_control_mp_ctrl) != nil) + me.carrier.getNode(c_control_mp_ctrl).setBoolValue(1); +} +################################################## +Manager.stop = func { + me.loopid += 1; + printf("Manager.stop() me.carrier_name=%s me.rplayer.getPath()=%s", + me.carrier_name, me.rplayer.getPath()); + # Reenable AI control. + if (me.carrier.getNode(c_control_mp_ctrl) != nil) + me.carrier.getNode(c_control_mp_ctrl).setBoolValue(0); +} +################################################## +Manager.die = func { + if (me.callsign_listener == nil) return; + + delete(MPCarriersNW.Manager_instances, me.rplayer.getIndex()); + me.loopid += 1; + + # Delay the reactivation of AI control. + settimer(func { + if (me.carrier.getNode(c_control_mp_ctrl) != nil) + me.carrier.getNode(c_control_mp_ctrl).setBoolValue(0); + }, 5.0); + + removelistener(me.callsign_listener); + me.callsign_listener = nil; + printf("Manager.die(): me.carrier_name=%s me.rplayer.getPath()=%s", + me.carrier_name, me.rplayer.getPath()); +} +################################################## +Manager.start = func { + me.loopid += 1; + printf("Manager.start(): me.carrier_name=%s me.rplayer.getPath()=%s", + me.carrier_name, me.rplayer.getPath()); + me._loop_(me.loopid); +} +################################################## +Manager._loop_ = func(id) { + id == me.loopid or return; + me.update(); + settimer(func { me._loop_(id); }, 0); +} +############################################################################### + +############################################################################### + +################################################################# +var normalize_course = func(c) { + while (c < 0) c += 360; + while (c >= 360) c -= 360; + return c; +} + +################################################################# +# Return a hash containing all nearby carrier players +# indexed on MP-carrier type +var find_carrier_players = func { + var res = {}; + foreach (var c; keys(MPCarriersNW.Manager_instances)) { + if (MPCarriersNW.Manager_instances[c].is_valid()) { + var type = MPCarriersNW.Manager_instances[c].carrier_name; + var pilot = MPCarriersNW.Manager_instances[c].rplayer; + + if (!contains(res, type)) { + res[type] = [pilot.getNode("callsign").getValue()]; + } else { + append(res[type], pilot.getNode("callsign").getValue()); + } + } + } + # debug.dump(res); + return res; +} + +############################################################################### +# MPCarrier selection dialog. +var CARRIER_DLG = 0; +var carrier_dialog = {}; +############################################################ +carrier_dialog.init = func (x = nil, y = nil) { + me.x = x; + me.y = y; + me.bg = [0, 0, 0, 0.3]; # background color + me.fg = [[1.0, 1.0, 1.0, 1.0]]; + # + # "private" + me.title = "MPCarrier"; + me.basenode = props.globals.getNode("/sim/mp-carriers", 1); + me.dialog = nil; + me.namenode = props.Node.new({"dialog-name" : me.title }); + me.listeners = []; + me.players = {}; + me.carriers = { "Nimitz" : "nimitz-callsign", + "Eisenhower" : "eisenhower-callsign", + "Truman" : "truman-callsign", + "Foch" : "foch-callsign", + "Clemenceau" : "clemenceau-callsign", + "Vinson" : "vinson-callsign" + "Kuznetsov" : "kuznetsov-callsign", + "Liaoning" : "liaoning-callsign", + "Sanantonio" : "sanantonio-callsign"}; +} +############################################################ +carrier_dialog.create = func { + if (me.dialog != nil) + me.close(); + + me.dialog = gui.Widget.new(); + gui.dialog[me.title] = me.dialog; + me.dialog.set("name", me.title); + if (me.x != nil) + me.dialog.set("x", me.x); + if (me.y != nil) + me.dialog.set("y", me.y); + + me.dialog.set("layout", "vbox"); + me.dialog.set("default-padding", 0); + var titlebar = me.dialog.addChild("group"); + titlebar.set("layout", "hbox"); + titlebar.addChild("empty").set("stretch", 1); + titlebar.addChild("text").set("label", "MPCarriers online"); + var w = titlebar.addChild("button"); + w.set("pref-width", 16); + w.set("pref-height", 16); + w.set("legend", ""); + w.set("default", 0); + w.set("key", "esc"); + w.setBinding("nasal", "MPCarriers.carrier_dialog.destroy(); "); + w.setBinding("dialog-close"); + me.dialog.addChild("hrule"); + + var content = me.dialog.addChild("group"); + content.set("layout", "vbox"); + content.set("halign", "center"); + content.set("default-padding", 5); + + # Generate the dialog contents. + var players_old = me.players; + me.players = find_carrier_players(); + foreach (var type; keys(me.carriers)) { + var selected = me.basenode.getNode(me.carriers[type], 1).getValue(); + var tmpbase = me.basenode.getNode(type, 1); + + if (contains(me.players, type)) { + var i = 0; + foreach (var p; me.players[type]) { + var tmp = tmpbase.getNode("b[" ~ i ~ "]", 1); + tmp.setBoolValue(streq(selected, p)); + var w = content.addChild("checkbox"); + w.node.setValues({"label" : p ~ " (" ~ type ~ ")", + "halign" : "left", + "property" : tmp.getPath()}); + w.setBinding + ("nasal", + "MPCarriers.carrier_dialog.select_action(" ~ + "\"" ~ type ~ "\", " ~ i ~ ");"); + i = i + 1; + } + if (size(me.players[type]) == 1 + and ( + !contains(players_old, type) + or size(players_old[type]) == 0 + or g_auto_attach.getValue() + )) { + # Carrier has just appeared, and there is no other carrier of + # the same type, so attach our AI carrier to it. + printf("carrier_dialog.create(): auto-selecting type=%s", type); + me.select_action(type, 0); + } + } + } + me.dialog.addChild("hrule"); + + # Display the dialog. + fgcommand("dialog-new", me.dialog.prop()); + fgcommand("dialog-show", me.namenode); +} +############################################################ +carrier_dialog.close = func { + if (me.dialog != nil) { + me.x = me.dialog.prop().getNode("x", 1).getValue(); + me.y = me.dialog.prop().getNode("y", 1).getValue(); + } + fgcommand("dialog-close", me.namenode); +} +############################################################ +carrier_dialog.destroy = func { + CARRIER_DLG = 0; + me.close(); + foreach(var l; me.listeners) + removelistener(l); + delete(gui.dialog, "\"" ~ me.title ~ "\""); +} +############################################################ +carrier_dialog.show = func { + # print("Showing MPCarriers dialog!"); + if (!CARRIER_DLG) { + CARRIER_DLG = int(getprop("/sim/time/elapsed-sec")); + me.init(); + me.create(); + me._update_(CARRIER_DLG); + } +} +############################################################ +carrier_dialog._redraw_ = func { + if (me.dialog != nil) { + me.close(); + me.create(); + } +} +############################################################ +carrier_dialog._update_ = func (id) { + if (CARRIER_DLG != id) return; + me._redraw_(); + settimer(func { me._update_(id); }, 4.1); +} +############################################################ +carrier_dialog.select_action = func (type, n) { + var base = me.basenode.getNode(type); + var selected = me.basenode.getNode(me.carriers[type]).getValue(); + var bs = base.getChildren(); + # Assumption: There are two true b:s or none. The one not matching selected + # is the new selection. + var i = 0; + me.basenode.getNode(me.carriers[type], 1).setValue(""); + foreach (var b; bs) { + if (!b.getValue() and (i == n)) { + b.setValue(1); + me.basenode.getNode(me.carriers[type]).setValue + (me.players[type][i]); + } else { + b.setValue(0); + } + i = i + 1; + } + me._redraw_(); +} +############################################################################### + +var as_num = func (val, default=0.0) { + return (typeof(val) == "scalar") ? val : default; +} + +############################################################################### +# Overall initialization. Should only take place when the MPCarrier module is +# being loaded. + +# Load the MPCarrier MP network. +if (!contains(globals, "MPCarriersNW")) { + var base = "Aircraft/MPCarrier/Systems/mp-network.nas"; + io.load_nasal(resolvepath(base), "MPCarriersNW"); + MPCarriersNW.mp_network_init(0); + +}