1
0
Fork 0

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
This commit is contained in:
Richard Harrison 2021-06-22 23:39:45 +02:00
parent a23313176c
commit d9ffc50011
11 changed files with 1169 additions and 0 deletions

View file

@ -28,6 +28,7 @@
551-600 Military large aircraft (tankers, transports) 551-600 Military large aircraft (tankers, transports)
601-700 Helicopters 601-700 Helicopters
701-800 Other 701-800 Other
801-900 Maritime
--> -->
<PropertyList> <PropertyList>
@ -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. --> then be invisible to other MP players who are not in a UFO themselves. -->
<model n="712" type="string">Aircraft/ufo/Models/ufo.xml</model> <model n="712" type="string">Aircraft/ufo/Models/ufo.xml</model>
<model n="801" type="string">AI/Maritime/MP-Carriers/mp-nimitz-0.xml</model>
<model n="802" type="string">AI/Maritime/MP-Carriers/mp-vinson-0.xml</model>
<model n="803" type="string">AI/Maritime/MP-Carriers/mp-eisenhower-0.xml</model>
<model n="804" type="string">AI/Maritime/MP-Carriers/mp-truman-0.xml</model>
<model n="805" type="string">AI/Maritime/MP-Carriers/mp-clemenceau-0.xml</model>
<model n="806" type="string">AI/Maritime/MP-Carriers/mp-foch-0.xml</model>
<model n="807" type="string">AI/Maritime/MP-Carriers/mp-kuznetsov-0.xml</model>
<model n="808" type="string">AI/Maritime/MP-Carriers/mp-liaoning-0.xml</model>
<model n="809" type="string">AI/Maritime/MP-Carriers/mp-sanantonio-0.xml</model>
</PropertyList> </PropertyList>

View file

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!--
Multiplayer carrier Clemenceau fallback model. ID# 806
This will connect the MP carrier to the AI carrier when the low detail model is loaded.
Later on if the high detail model is also loaded it will be still be good
because the logic is resilient enough not to trample over itself.
Copyright (C) 2021 : Richard Harrison rjh@zaretto.com
Copyright (C) 2007 - 2016 Anders Gidenstam (anders(at)gidenstam.org)
This file is released under the GPL license version 2 or later.
-->
<PropertyList>
<nasal>
<load>
<![CDATA[
##############################################################################
# This logic connects the carrier to the AI carrier
var rplayer = cmdarg();
print("MP Clemenceau(Fallback): LOAD " ~ rplayer.getPath());
# Load the main MPCarriers Nasal module if needed.
if (!contains(globals, "MPCarriers")) {
var base = "Aircraft/Generic/MPCarriers.nas";
io.load_nasal(resolvepath(base), "MPCarriers");
}
# Create manager class.
var manager = nil;
var accept_callsign = "/sim/mp-carriers/clemenceau-callsign";
var init = func {
# Initialize the callsign property if not done already.
props.globals.initNode(accept_callsign, "", "STRING");
manager = MPCarriers.Manager.new(rplayer,
"Clemenceau",
props.globals.getNode(accept_callsign));
print("MP Clemenceau(Fallback): " ~ rplayer.getPath() ~ " done.");
}
settimer(init, 3); # 3 second delay should be sufficient
print("MP Clemenceau(Fallback): " ~ rplayer.getPath() ~ " waiting.");
##############################################################################
]]>
</load>
<unload>
<![CDATA[
##############################################################################
print("MP Clemenceau(Fallback): unload", cmdarg().getPath());
if (manager != nil) manager.die();
##############################################################################
]]>
</unload>
</nasal>
</PropertyList>

View file

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!--
Multiplayer carrier Eisenhower fallback model. ID# 803
This will connect the MP carrier to the AI carrier when the low detail model is loaded.
Later on if the high detail model is also loaded it will be still be good
because the logic is resilient enough not to trample over itself.
Copyright (C) 2021 : Richard Harrison rjh@zaretto.com
Copyright (C) 2007 - 2016 Anders Gidenstam (anders(at)gidenstam.org)
This file is released under the GPL license version 2 or later.
-->
<PropertyList>
<nasal>
<load>
<![CDATA[
##############################################################################
# This logic connects the carrier to the AI carrier
var rplayer = cmdarg();
print("MP Eisenhower(Fallback): LOAD " ~ rplayer.getPath());
# Load the main MPCarriers Nasal module if needed.
if (!contains(globals, "MPCarriers")) {
var base = "Aircraft/Generic/MPCarriers.nas";
io.load_nasal(resolvepath(base), "MPCarriers");
}
# Create manager class.
var manager = nil;
var accept_callsign = "/sim/mp-carriers/eisenhower-callsign";
var init = func {
# Initialize the callsign property if not done already.
props.globals.initNode(accept_callsign, "", "STRING");
manager = MPCarriers.Manager.new(rplayer,
"Eisenhower",
props.globals.getNode(accept_callsign));
print("MP Eisenhower(Fallback): " ~ rplayer.getPath() ~ " done.");
}
settimer(init, 3); # 3 second delay should be sufficient
print("MP Eisenhower(Fallback): " ~ rplayer.getPath() ~ " waiting.");
##############################################################################
]]>
</load>
<unload>
<![CDATA[
##############################################################################
print("MP Eisenhower(Fallback): unload", cmdarg().getPath());
if (manager != nil) manager.die();
##############################################################################
]]>
</unload>
</nasal>
</PropertyList>

View file

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!--
Multiplayer carrier Foch fallback model. ID# 807
This will connect the MP carrier to the AI carrier when the low detail model is loaded.
Later on if the high detail model is also loaded it will be still be good
because the logic is resilient enough not to trample over itself.
Copyright (C) 2021 : Richard Harrison rjh@zaretto.com
Copyright (C) 2007 - 2016 Anders Gidenstam (anders(at)gidenstam.org)
This file is released under the GPL license version 2 or later.
-->
<PropertyList>
<nasal>
<load>
<![CDATA[
##############################################################################
# This logic connects the carrier to the AI carrier
var rplayer = cmdarg();
print("MP Foch(Fallback): LOAD " ~ rplayer.getPath());
# Load the main MPCarriers Nasal module if needed.
if (!contains(globals, "MPCarriers")) {
var base = "Aircraft/Generic/MPCarriers.nas";
io.load_nasal(resolvepath(base), "MPCarriers");
}
# Create manager class.
var manager = nil;
var accept_callsign = "/sim/mp-carriers/foch-callsign";
var init = func {
# Initialize the callsign property if not done already.
props.globals.initNode(accept_callsign, "", "STRING");
manager = MPCarriers.Manager.new(rplayer,
"Foch",
props.globals.getNode(accept_callsign));
print("MP Foch(Fallback): " ~ rplayer.getPath() ~ " done.");
}
settimer(init, 3); # 3 second delay should be sufficient
print("MP Foch(Fallback): " ~ rplayer.getPath() ~ " waiting.");
##############################################################################
]]>
</load>
<unload>
<![CDATA[
##############################################################################
print("MP Foch(Fallback): unload", cmdarg().getPath());
if (manager != nil) manager.die();
##############################################################################
]]>
</unload>
</nasal>
</PropertyList>

View file

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!--
Multiplayer carrier Kuznetsov fallback model. ID# 808
This will connect the MP carrier to the AI carrier when the low detail model is loaded.
Later on if the high detail model is also loaded it will be still be good
because the logic is resilient enough not to trample over itself.
Copyright (C) 2021 : Richard Harrison rjh@zaretto.com
Copyright (C) 2007 - 2016 Anders Gidenstam (anders(at)gidenstam.org)
This file is released under the GPL license version 2 or later.
-->
<PropertyList>
<nasal>
<load>
<![CDATA[
##############################################################################
# This logic connects the carrier to the AI carrier
var rplayer = cmdarg();
print("MP Kuznetsov(Fallback): LOAD " ~ rplayer.getPath());
# Load the main MPCarriers Nasal module if needed.
if (!contains(globals, "MPCarriers")) {
var base = "Aircraft/Generic/MPCarriers.nas";
io.load_nasal(resolvepath(base), "MPCarriers");
}
# Create manager class.
var manager = nil;
var accept_callsign = "/sim/mp-carriers/kuznetsov-callsign";
var init = func {
# Initialize the callsign property if not done already.
props.globals.initNode(accept_callsign, "", "STRING");
manager = MPCarriers.Manager.new(rplayer,
"Kuznetsov",
props.globals.getNode(accept_callsign));
print("MP Kuznetsov(Fallback): " ~ rplayer.getPath() ~ " done.");
}
settimer(init, 3); # 3 second delay should be sufficient
print("MP Kuznetsov(Fallback): " ~ rplayer.getPath() ~ " waiting.");
##############################################################################
]]>
</load>
<unload>
<![CDATA[
##############################################################################
print("MP Kuznetsov(Fallback): unload", cmdarg().getPath());
if (manager != nil) manager.die();
##############################################################################
]]>
</unload>
</nasal>
</PropertyList>

View file

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!--
Multiplayer carrier Liaoning fallback model. ID# 809
This will connect the MP carrier to the AI carrier when the low detail model is loaded.
Later on if the high detail model is also loaded it will be still be good
because the logic is resilient enough not to trample over itself.
Copyright (C) 2021 : Richard Harrison rjh@zaretto.com
Copyright (C) 2007 - 2016 Anders Gidenstam (anders(at)gidenstam.org)
This file is released under the GPL license version 2 or later.
-->
<PropertyList>
<nasal>
<load>
<![CDATA[
##############################################################################
# This logic connects the carrier to the AI carrier
var rplayer = cmdarg();
print("MP Liaoning(Fallback): LOAD " ~ rplayer.getPath());
# Load the main MPCarriers Nasal module if needed.
if (!contains(globals, "MPCarriers")) {
var base = "Aircraft/Generic/MPCarriers.nas";
io.load_nasal(resolvepath(base), "MPCarriers");
}
# Create manager class.
var manager = nil;
var accept_callsign = "/sim/mp-carriers/liaoning-callsign";
var init = func {
# Initialize the callsign property if not done already.
props.globals.initNode(accept_callsign, "", "STRING");
manager = MPCarriers.Manager.new(rplayer,
"Liaoning",
props.globals.getNode(accept_callsign));
print("MP Liaoning(Fallback): " ~ rplayer.getPath() ~ " done.");
}
settimer(init, 3); # 3 second delay should be sufficient
print("MP Liaoning(Fallback): " ~ rplayer.getPath() ~ " waiting.");
##############################################################################
]]>
</load>
<unload>
<![CDATA[
##############################################################################
print("MP Liaoning(Fallback): unload", cmdarg().getPath());
if (manager != nil) manager.die();
##############################################################################
]]>
</unload>
</nasal>
</PropertyList>

View file

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!--
Multiplayer carrier Nimitz fallback model. ID# 801
This will connect the MP carrier to the AI carrier when the low detail model is loaded.
Later on if the high detail model is also loaded it will be still be good
because the logic is resilient enough not to trample over itself.
Copyright (C) 2021 : Richard Harrison rjh@zaretto.com
Copyright (C) 2007 - 2016 Anders Gidenstam (anders(at)gidenstam.org)
This file is released under the GPL license version 2 or later.
-->
<PropertyList>
<nasal>
<load>
<![CDATA[
##############################################################################
# This logic connects the carrier to the AI carrier
var rplayer = cmdarg();
print("MP Nimitz(Fallback): LOAD " ~ rplayer.getPath());
# Load the main MPCarriers Nasal module if needed.
if (!contains(globals, "MPCarriers")) {
var base = "Aircraft/Generic/MPCarriers.nas";
io.load_nasal(resolvepath(base), "MPCarriers");
}
# Create manager class.
var manager = nil;
var accept_callsign = "/sim/mp-carriers/nimitz-callsign";
var init = func {
# Initialize the callsign property if not done already.
props.globals.initNode(accept_callsign, "", "STRING");
manager = MPCarriers.Manager.new(rplayer,
"Nimitz",
props.globals.getNode(accept_callsign));
print("MP Nimitz(Fallback): " ~ rplayer.getPath() ~ " done.");
}
settimer(init, 15); # delay to prevent recursion.
print("MP Nimitz(Fallback): " ~ rplayer.getPath() ~ " waiting.");
##############################################################################
]]>
</load>
<unload>
<![CDATA[
##############################################################################
print("MP Nimitz(Fallback): unload", cmdarg().getPath());
if (manager != nil) manager.die();
##############################################################################
]]>
</unload>
</nasal>
</PropertyList>

View file

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!--
Multiplayer carrier Sanantonio fallback model. ID# 810
This will connect the MP carrier to the AI carrier when the low detail model is loaded.
Later on if the high detail model is also loaded it will be still be good
because the logic is resilient enough not to trample over itself.
Copyright (C) 2021 : Richard Harrison rjh@zaretto.com
Copyright (C) 2007 - 2016 Anders Gidenstam (anders(at)gidenstam.org)
This file is released under the GPL license version 2 or later.
-->
<PropertyList>
<nasal>
<load>
<![CDATA[
##############################################################################
# This logic connects the carrier to the AI carrier
var rplayer = cmdarg();
print("MP Sanantonio(Fallback): LOAD " ~ rplayer.getPath());
# Load the main MPCarriers Nasal module if needed.
if (!contains(globals, "MPCarriers")) {
var base = "Aircraft/Generic/MPCarriers.nas";
io.load_nasal(resolvepath(base), "MPCarriers");
}
# Create manager class.
var manager = nil;
var accept_callsign = "/sim/mp-carriers/sanantonio-callsign";
var init = func {
# Initialize the callsign property if not done already.
props.globals.initNode(accept_callsign, "", "STRING");
manager = MPCarriers.Manager.new(rplayer,
"Sanantonio",
props.globals.getNode(accept_callsign));
print("MP Sanantonio(Fallback): " ~ rplayer.getPath() ~ " done.");
}
settimer(init, 3); # 3 second delay should be sufficient
print("MP Sanantonio(Fallback): " ~ rplayer.getPath() ~ " waiting.");
##############################################################################
]]>
</load>
<unload>
<![CDATA[
##############################################################################
print("MP Sanantonio(Fallback): unload", cmdarg().getPath());
if (manager != nil) manager.die();
##############################################################################
]]>
</unload>
</nasal>
</PropertyList>

View file

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!--
Multiplayer carrier Truman fallback model. ID# 804
This will connect the MP carrier to the AI carrier when the low detail model is loaded.
Later on if the high detail model is also loaded it will be still be good
because the logic is resilient enough not to trample over itself.
Copyright (C) 2021 : Richard Harrison rjh@zaretto.com
Copyright (C) 2007 - 2016 Anders Gidenstam (anders(at)gidenstam.org)
This file is released under the GPL license version 2 or later.
-->
<PropertyList>
<nasal>
<load>
<![CDATA[
##############################################################################
# This logic connects the carrier to the AI carrier
var rplayer = cmdarg();
print("MP Truman(Fallback): LOAD " ~ rplayer.getPath());
# Load the main MPCarriers Nasal module if needed.
if (!contains(globals, "MPCarriers")) {
var base = "Aircraft/Generic/MPCarriers.nas";
io.load_nasal(resolvepath(base), "MPCarriers");
}
# Create manager class.
var manager = nil;
var accept_callsign = "/sim/mp-carriers/truman-callsign";
var init = func {
# Initialize the callsign property if not done already.
props.globals.initNode(accept_callsign, "", "STRING");
manager = MPCarriers.Manager.new(rplayer,
"Truman",
props.globals.getNode(accept_callsign));
print("MP Truman(Fallback): " ~ rplayer.getPath() ~ " done.");
}
settimer(init, 3); # 3 second delay should be sufficient
print("MP Truman(Fallback): " ~ rplayer.getPath() ~ " waiting.");
##############################################################################
]]>
</load>
<unload>
<![CDATA[
##############################################################################
print("MP Truman(Fallback): unload", cmdarg().getPath());
if (manager != nil) manager.die();
##############################################################################
]]>
</unload>
</nasal>
</PropertyList>

View file

@ -0,0 +1,60 @@
<?xml version="1.0"?>
<!--
Multiplayer carrier Vinson fallback model. ID# 805
This will connect the MP carrier to the AI carrier when the low detail model is loaded.
Later on if the high detail model is also loaded it will be still be good
because the logic is resilient enough not to trample over itself.
Copyright (C) 2021 : Richard Harrison rjh@zaretto.com
Copyright (C) 2007 - 2016 Anders Gidenstam (anders(at)gidenstam.org)
This file is released under the GPL license version 2 or later.
-->
<PropertyList>
<nasal>
<load>
<![CDATA[
##############################################################################
# This logic connects the carrier to the AI carrier
var rplayer = cmdarg();
print("MP Vinson(Fallback): LOAD " ~ rplayer.getPath());
# Load the main MPCarriers Nasal module if needed.
if (!contains(globals, "MPCarriers")) {
var base = "Aircraft/Generic/MPCarriers.nas";
io.load_nasal(resolvepath(base), "MPCarriers");
}
# Create manager class.
var manager = nil;
var accept_callsign = "/sim/mp-carriers/vinson-callsign";
var init = func {
# Initialize the callsign property if not done already.
props.globals.initNode(accept_callsign, "", "STRING");
manager = MPCarriers.Manager.new(rplayer,
"Vinson",
props.globals.getNode(accept_callsign));
print("MP Vinson(Fallback): " ~ rplayer.getPath() ~ " done.");
}
settimer(init, 3); # 3 second delay should be sufficient
print("MP Vinson(Fallback): " ~ rplayer.getPath() ~ " waiting.");
##############################################################################
]]>
</load>
<unload>
<![CDATA[
##############################################################################
print("MP Vinson(Fallback): unload", cmdarg().getPath());
if (manager != nil) manager.die();
##############################################################################
]]>
</unload>
</nasal>
</PropertyList>

View file

@ -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);
}