5a4bc3fc53
We tie MP and AI carrier together if /sim/mp-carriers/latch-always is true. This is done by setting /ai/models/carrier[]/ai-latch to the /ai/models/multiplayer path containing the corresponding Multiplayer information. C++ code will detect this and copy across the position and orientation each frame.
637 lines
24 KiB
Text
637 lines
24 KiB
Text
###############################################################################
|
|
##
|
|
## 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);
|
|
var g_latch_always = props.globals.getNode("/sim/mp-carriers/latch-always", 1);
|
|
printf("fgdata/Aircraft/Generic/MPCarriers.nas: g_auto_attach.getValue()=%s g_latch_always=%s",
|
|
g_auto_attach.getValue(),
|
|
g_latch_always.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;
|
|
}
|
|
|
|
if (!g_latch_always.getValue())
|
|
{
|
|
# 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 (g_latch_always.getValue()) {
|
|
# Tell C++ to copy MP position/orientation directly on to AI
|
|
# position/orientation, every frame.
|
|
#
|
|
# Set /ai/models/multiplayer[]/ai-latch to "/ai/models/carrier[]".
|
|
#
|
|
# Set /ai/models/carrier[]/ai-latch to "/ai/models/multiplayer[]".
|
|
me.rplayer.getNode("ai-latch", 1).setValue(me.carrier.getPath());
|
|
me.carrier.getNode("ai-latch", 1).setValue(me.rplayer.getPath());
|
|
}
|
|
elsif (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);
|
|
me.rplayer.getNode("ai-latch").setValue("");
|
|
me.carrier.getNode("ai-latch").setValue("");
|
|
}
|
|
##################################################
|
|
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);
|
|
|
|
}
|