Multiplayer: Added passinger

This commit is contained in:
Sidi Liang 2019-08-04 09:50:58 +08:00
parent f884138a1a
commit f8cbebf797
10 changed files with 1475 additions and 1 deletions

30
Models/followme-PAX.xml Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--####################################################################
Lake of Constance Hangar
Boeing 707 for Flightgear
Copyright (C) 2013 M.Kraus
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Every software has a developer, also free software.
As a gesture of courtesy and respect, I would be delighted
if you contacted me before making any changes to this software.
<info (at) marc-kraus.de> April, 2017
########################################################################-->
<PropertyList>
<path>707-pax.ac</path>
</PropertyList>

3
Models/followme-pax.ac Normal file
View File

@ -0,0 +1,3 @@
AC3Db
OBJECT poly
kids 0

View File

@ -0,0 +1,258 @@
###############################################################################
## $Id$
##
## Nasal for copilot for dual control over the multiplayer network.
##
## Copyright (C) 2007 - 2010 Anders Gidenstam (anders(at)gidenstam.org)
## This file is licensed under the GPL license version 2 or later.
##
###############################################################################
# Renaming (almost :)
var DCT = dual_control_tools;
var ADC = aircraft_dual_control;
# NOTE: By loading the aircraft specific dual control module
# as <aircraft_dual_control> this file is generic.
# The aircraft specific modul must set the variables
# pilot_type and copilot_type to the name (with full path) of
# main 3d model XML for the pilot and copilot aircraft.
# This module should be loades under the name dual_control.
# Allow aircraft to override the copilot view name. Deprecated.
if (!contains(ADC, "copilot_view")) {
ADC.copilot_view = "Copilot View";
}
# Properties for position and orientation of local aircraft.
var l_lat = "position/latitude-deg";
var l_lon = "position/longitude-deg";
var l_alt = "position/altitude-ft";
var l_heading = "orientation/heading-deg";
var l_pitch = "orientation/pitch-deg";
var l_roll = "orientation/roll-deg";
# Replicate remote state.
var r_airspeed = "velocities/true-airspeed-kt";
var l_airspeed = "velocities/airspeed-kt";
var vertspeed = "velocities/vertical-speed-fps";
# Default external views to slave to the MP pilot.
var views = {};
views["Helicopter View"] = 2;
views["Chase View"] = 3;
views["Tower View"] = 0;
views["Fly-By View"] = 1;
views["Chase View Without Yaw"] = 1;
######################################################################
# Connect to new pilot
var process_data = 0;
var connect = func (pilot) {
# Set external view eye and target paths.
foreach (var vn; keys(views)) {
var view_cfg = "sim/view[" ~ view.indexof(vn) ~ "]/config/";
setprop(view_cfg ~ "at-model", 0);
if (views[vn] > 0) {
setprop(view_cfg ~ "eye-lat-deg-path",
pilot.getNode(DCT.lat_mpp).getPath());
setprop(view_cfg ~ "eye-lon-deg-path",
pilot.getNode(DCT.lon_mpp).getPath());
setprop(view_cfg ~ "eye-alt-ft-path",
pilot.getNode(DCT.alt_mpp).getPath());
}
if (views[vn] > 1) {
setprop(view_cfg ~ "eye-heading-deg-path",
pilot.getNode(DCT.heading_mpp).getPath());
}
if (views[vn] > 2) {
setprop(view_cfg ~ "eye-pitch-deg-path",
pilot.getNode(DCT.pitch_mpp).getPath());
setprop(view_cfg ~ "eye-roll-deg-path",
pilot.getNode(DCT.roll_mpp).getPath());
}
setprop(view_cfg ~ "target-lat-deg-path",
pilot.getNode(DCT.lat_mpp).getPath());
setprop(view_cfg ~ "target-lon-deg-path",
pilot.getNode(DCT.lon_mpp).getPath());
setprop(view_cfg ~ "target-alt-ft-path",
pilot.getNode(DCT.alt_mpp).getPath());
setprop(view_cfg ~ "target-heading-deg-path",
pilot.getNode(DCT.heading_mpp).getPath());
setprop(view_cfg ~ "target-pitch-deg-path",
pilot.getNode(DCT.pitch_mpp).getPath());
setprop(view_cfg ~ "target-roll-deg-path",
pilot.getNode(DCT.roll_mpp).getPath());
}
# Tweak MP/AI filters
pilot.getNode("controls/allow-extrapolation").setBoolValue(1);
pilot.getNode("controls/lag-adjust-system-speed").setValue(5.0);
# Set up property aliases
# Set up property mappings.
process_data =
[
# Map /postition/*
#*/
DCT.Translator.new
(pilot.getNode(DCT.lat_mpp), props.globals.getNode(l_lat)),
DCT.Translator.new
(pilot.getNode(DCT.lon_mpp), props.globals.getNode(l_lon)),
DCT.Translator.new
(pilot.getNode(DCT.alt_mpp), props.globals.getNode(l_alt)),
# Map /orientation/*
#*/
DCT.Translator.new
(pilot.getNode(DCT.heading_mpp),
props.globals.getNode(l_heading)),
DCT.Translator.new
(pilot.getNode(DCT.pitch_mpp),
props.globals.getNode(l_pitch)),
DCT.Translator.new
(pilot.getNode(DCT.roll_mpp),
props.globals.getNode(l_roll)),
# Map /velocities/*
#*/
DCT.Translator.new
(pilot.getNode(r_airspeed),
props.globals.getNode(l_airspeed)),
DCT.Translator.new
(pilot.getNode(vertspeed),
props.globals.getNode(vertspeed)),
] ~ ADC.copilot_connect_pilot(pilot);
print("Dual control ... connected to pilot.");
setprop("sim/messages/copilot", "Welcome aboard.");
}
var disconnect = func {
# Reset external view eye and target paths.
foreach (var vn; keys(views)) {
var view_cfg = "sim/view[" ~ view.indexof(vn) ~ "]/config";
if (views[vn] > 0) {
setprop(view_cfg ~ "eye-lat-deg-path",
"position/latitude-deg");
setprop(view_cfg ~ "eye-lon-deg-path",
"position/longitude-deg");
setprop(view_cfg ~ "eye-alt-ft-path",
"position/altitude-ft");
}
if (views[vn] > 1) {
setprop(view_cfg ~ "eye-heading-deg-path",
"orientation/heading-deg");
}
if (views[vn] > 2) {
setprop(view_cfg ~ "eye-pitch-deg-path",
"orientation/pitch-deg");
setprop(view_cfg ~ "eye-roll-deg-path",
"orientation/roll-deg");
}
setprop(view_cfg ~ "target-lat-deg-path",
"sim/viewer/target/latitude-deg");
setprop(view_cfg ~ "target-lon-deg-path",
"sim/viewer/target/longitude-deg");
setprop(view_cfg ~ "target-alt-ft-path",
"sim/viewer/target/altitude-ft");
setprop(view_cfg ~ "target-heading-deg-path",
"sim/viewer/target/heading-deg");
setprop(view_cfg ~ "target-pitch-deg-path",
"sim/viewer/target/pitch-deg");
setprop(view_cfg ~ "target-roll-deg-path",
"sim/viewer/target/roll-deg");
}
}
######################################################################
# Main loop singleton class.
var main = {
init : func {
me.loopid = 0;
me.active = 0;
setlistener("ai/models/model-added", func {
settimer(func { me.activate(); }, 2);
});
print("Copilot dual control ... initialized");
settimer(func { me.activate(); }, 5);
},
reset : func {
if (me.active) {
print("Dual control ... disconnected from pilot.");
disconnect();
ADC.copilot_disconnect_pilot();
}
me.active = 0;
me.loopid += 1;
me._loop_(me.loopid);
},
activate : func {
if (!me.active) {
me.reset();
}
},
update : func {
var mpplayers =
props.globals.getNode("ai/models").getChildren("multiplayer");
var r_callsign = getprop("sim/remote/pilot-callsign");
foreach (var pilot; mpplayers) {
if ((pilot.getChild("valid").getValue()) and
(pilot.getChild("callsign") != nil) and
(pilot.getChild("callsign").getValue() == r_callsign)) {
if (me.active == 0) {
# Note: sim/model/path contains the model XML file.
if ((pilot.getNode("sim/model/path") != nil) and
(pilot.getNode("sim/model/path").getValue() ==
ADC.pilot_type)) {
me.active = 1;
connect(pilot);
} else {
print("Dual control ... pilot rejected - wrong aircraft type.");
me.loopid += 1;
return;
}
}
# Mess with the MP filters. Highly experimental.
if (pilot.getNode("controls/lag-time-offset") != nil) {
var v = pilot.getNode("controls/lag-time-offset").getValue();
#pilot.getNode("controls/lag-time-offset").setValue(0.99 * v);
}
foreach (var w; process_data) {
w.update();
}
return;
}
}
# The pilot player is not around. Idle loop.
if (me.active) {
print("Dual control ... disconnected from pilot.");
disconnect();
ADC.copilot_disconnect_pilot();
}
me.active = 0;
me.loopid += 1;
},
_loop_ : func(id) {
id == me.loopid or return;
me.update();
settimer(func { me._loop_(id); }, 0);
}
};
###############################################################################
# Initialization.
var last_view = 0;
setlistener("sim/signals/fdm-initialized", func {
main.init();
});

View File

@ -0,0 +1,643 @@
###############################################################################
##
## Nasal module for dual control over the multiplayer network.
##
## Copyright (C) 2007 - 2010 Anders Gidenstam (anders(at)gidenstam.org)
## This file is licensed under the GPL license version 2 or later.
##
###############################################################################
## MP properties
var lat_mpp = "position/latitude-deg";
var lon_mpp = "position/longitude-deg";
var alt_mpp = "position/altitude-ft";
var heading_mpp = "orientation/true-heading-deg";
var pitch_mpp = "orientation/pitch-deg";
var roll_mpp = "orientation/roll-deg";
# Import components from the mp_broadcast module.
var Binary = mp_broadcast.Binary;
var MessageChannel = mp_broadcast.MessageChannel;
###############################################################################
# Utility classes
############################################################
# Translate a property into another.
# Factor and offsets are only used for numeric values.
# src - source : property node
# dest - destination : property node
# factor - : double
# offset - : double
var Translator = {};
Translator.new = func (src = nil, dest = nil, factor = 1, offset = 0) {
var obj = { parents : [Translator],
src : src,
dest : dest,
factor : factor,
offset : offset };
if (obj.src == nil or obj.dest == nil) {
print("Translator[");
print(" ", debug.string(obj.src));
print(" ", debug.string(obj.dest));
print("]");
fail();
}
return obj;
}
Translator.update = func () {
var v = me.src.getValue();
if (is_num(v)) {
me.dest.setValue(me.factor * v + me.offset);
} else {
if (typeof(v) == "scalar")
me.dest.setValue(v);
}
}
############################################################
# Detects flanks on two insignals encoded in a property.
# - positive signal up/down flank
# - negative signal up/down flank
# n - source : property node
# on_positive_flank - action : func (v)
# on_negative_flank - action : func (v)
var EdgeTrigger = {};
EdgeTrigger.new = func (n, on_positive_flank, on_negative_flank) {
var obj = { parents : [EdgeTrigger],
old : 0,
node : n,
pos_flank : on_positive_flank,
neg_flank : on_negative_flank };
if (obj.node == nil) {
print("EdgeTrigger[");
print(" ", debug.string(obj.node));
print("]");
fail();
}
return obj;
}
EdgeTrigger.update = func {
# NOTE: float MP properties get interpolated.
# This detector relies on that steady state is reached between
# flanks.
var val = me.node.getValue();
if (!is_num(val)) return;
if (me.old == 1) {
if (val < me.old) {
me.pos_flank(0);
}
} elsif (me.old == 0) {
if (val > me.old) {
me.pos_flank(1);
} elsif (val < me.old) {
me.neg_flank(1);
}
} elsif (me.old == -1) {
if (val > me.old) {
me.neg_flank(0);
}
}
me.old = val;
}
############################################################
# StableTrigger: Triggers an action when a MPP property
# becomes stable (i.e. doesn't change for
# MIN_STABLE seconds).
# src - MP prop : property node
# action - action to take when the value becomes stable : [func(v)]
# An action is triggered when value has stabilized.
var StableTrigger = {};
StableTrigger.new = func (src, action) {
var obj = { parents : [StableTrigger],
src : src,
action : action,
old : 0,
stable_since : 0,
wait : 0,
MIN_STABLE : 0.01 };
# Error checking.
var bad = (obj.src == nil) or (action = nil);
if (bad) {
print("StableTrigger[");
print(" ", debug.string(obj.src));
print(" ", debug.string(obj.action));
print("]");
fail();
}
return obj;
}
StableTrigger.update = func () {
var v = me.src.getValue();
if (!is_num(v)) return;
var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
if ((me.old == v) and
((t - me.stable_since) > me.MIN_STABLE) and (me.wait == 1)) {
# Trigger action.
me.action(v);
me.wait = 0;
} elsif (me.old == v) {
# Wait. This is either before the signal is stable or after the action.
} else {
me.stable_since = t;
me.wait = 1;
me.old = me.src.getValue();
}
}
############################################################
# Selects the most recent value of two properties.
# src1 - : property node
# src2 - : property node
# dest - : property node
# threshold - : double
var MostRecentSelector = {};
MostRecentSelector.new = func (src1, src2, dest, threshold) {
var obj = { parents : [MostRecentSelector],
old1 : 0,
old2 : 0,
src1 : src1,
src2 : src2,
dest : dest,
thres : threshold };
if (obj.src1 == nil or obj.src2 == nil or obj.dest == nil) {
print("MostRecentSelector[");
print(" ", debug.string(obj.src1));
print(" ", debug.string(obj.src2));
print(" ", debug.string(obj.dest));
print("]");
}
return obj;
}
MostRecentSelector.update = func {
var v1 = me.src1.getValue();
var v2 = me.src2.getValue();
if (!is_num(v1) and !is_num(v2)) return;
elsif (!is_num(v1)) me.dest.setValue(v2);
elsif (!is_num(v2)) me.dest.setValue(v1);
else {
if (abs (v2 - me.old2) > me.thres) {
me.old2 = v2;
me.dest.setValue(me.old2);
}
if (abs (v1 - me.old1) > me.thres) {
me.old1 = v1;
me.dest.setValue(me.old1);
}
}
}
############################################################
# Adds two input properties.
# src1 - : property node
# src2 - : property node
# dest - : property node
var Adder = {};
Adder.new = func (src1, src2, dest) {
var obj = { parents : [DeltaAccumulator],
src1 : src1,
src2 : src2,
dest : dest };
if (obj.src1 == nil or obj.src2 == nil or obj.dest == nil) {
print("Adder[");
print(" ", debug.string(obj.src1));
print(" ", debug.string(obj.src2));
print(" ", debug.string(obj.dest));
print("]");
fail();
}
return obj;
}
Adder.update = func () {
var v1 = me.src1.getValue();
var v2 = me.src2.getValue();
if (!is_num(v1) or !is_num(v2)) return;
me.dest.setValue(v1 + v2);
}
############################################################
# Adds the delta of src to dest.
# src - : property node
# dest - : property node
var DeltaAdder = {};
DeltaAdder.new = func (src, dest) {
var obj = { parents : [DeltaAdder],
old : 0,
src : src,
dest : dest };
if (obj.src == nil or obj.dest == nil) {
print("DeltaAdder[", debug.string(obj.src), ", ",
debug.string(obj.dest), "]");
fail();
}
return obj;
}
DeltaAdder.update = func () {
var v = me.src.getValue();
if (!is_num(v)) return;
me.dest.setValue((v - me.old) + me.dest.getValue());
me.old = v;
}
############################################################
# Switch encoder: Encodes upto 32 boolean properties in one
# int property.
# inputs - list of property nodes
# dest - where the bitmask is stored : property node
var SwitchEncoder = {};
SwitchEncoder.new = func (inputs, dest) {
var obj = { parents : [SwitchEncoder],
inputs : inputs,
dest : dest };
# Error checking.
var bad = (obj.dest == nil);
foreach (var i; inputs) {
if (i == nil) { bad = 1; }
}
if (bad) {
print("SwitchEncoder[");
foreach (var i; inputs) {
print(" ", debug.string(i));
}
print(" ", debug.string(obj.dest));
print("]");
fail();
}
return obj;
}
SwitchEncoder.update = func () {
var v = 0;
var b = 1;
forindex (var i; me.inputs) {
if (me.inputs[i].getBoolValue()) {
v = v + b;
}
b *= 2;
}
me.dest.setIntValue(v);
}
############################################################
# Switch decoder: Decodes a bitmask in an int property.
# src - : property node
# actions - list of actions : [func(b)]
# Actions are triggered when their input bit change.
# Due to interpolation the decoder needs to wait for a
# stable input value.
var SwitchDecoder = {};
SwitchDecoder.new = func (src, actions) {
var obj = { parents : [SwitchDecoder],
wait : 0,
old : 0,
old_stable : 0,
stable_since : 0,
reset : 1,
src : src,
actions : actions,
MIN_STABLE : 0.1 };
# Error checking.
var bad = (obj.src == nil);
foreach (var a; obj.actions) {
if (a == nil) { bad = 1; }
}
if (bad) {
print("SwitchDecoder[");
print(" ", debug.string(obj.src));
foreach (var a; obj.actions) {
print(" ", debug.string(a));
}
print("]");
fail();
}
return obj;
}
SwitchDecoder.update = func () {
var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
var v = me.src.getValue();
if (!is_num(v)) return;
if ((me.old == v) and ((t - me.stable_since) > me.MIN_STABLE) and
(me.wait == 1)) {
var ov = me.old_stable;
# Use this to improve.
#<cptf> here's the boring version: var bittest = func(u, b) { while (b) { u = int(u / 2); b -= 1; } u != int(u / 2) * 2; }
forindex (var i; me.actions) {
var m = math.mod(v, 2);
var om = math.mod(ov, 2);
if ((m != om or me.reset)) { me.actions[i](m?1:0); }
v = (v - m)/2;
ov = (ov - om)/2;
}
me.old_stable = me.src.getValue();
me.wait = 0;
me.reset = 0;
} elsif (me.old == v) {
# Wait. This is either before the bitmask is stable or after
# it has been processed.
} else {
me.stable_since = t;
me.wait = 1;
me.old = me.src.getValue();
}
}
############################################################
# Time division multiplexing encoder: Transmits a list of
# properties over a MP enabled string property.
# inputs - input properties : [property node]
# dest - MP string prop : property node
# Note: TDM can have high latency so it is best used for
# non-time critical properties.
var TDMEncoder = {};
TDMEncoder.new = func (inputs, dest) {
var obj = { parents : [TDMEncoder],
inputs : inputs,
channel : MessageChannel.new(dest,
func (msg) {
print("This should not happen!");
}),
MIN_INT : 0.25,
last_time : 0,
next_item : 0,
old : [] };
# Error checking.
var bad = (dest == nil) or (obj.channel == nil);
foreach (var i; inputs) {
if (i == nil) { bad = 1; }
}
if (bad) {
print("TDMEncoder[");
foreach (var i; inputs) {
print(" ", debug.string(i));
}
print(" ", debug.string(dest));
print("]");
}
setsize(obj.old, size(obj.inputs));
return obj;
}
TDMEncoder.update = func () {
var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
if (t > me.last_time + me.MIN_INT) {
var n = size(me.inputs);
while (1) {
var v = me.inputs[me.next_item].getValue();
if ((n <= 0) or (me.old[me.next_item] != v)) {
# Set the MP properties to send the next item.
me.channel.send(Binary.encodeByte(me.next_item) ~
Binary.encodeDouble(v));
me.old[me.next_item] = v;
me.last_time = t;
me.next_item += 1;
if (me.next_item >= size(me.inputs)) { me.next_item = 0; }
return;
} else {
# Search for changed property.
n -= 1;
me.next_item += 1;
if (me.next_item >= size(me.inputs)) { me.next_item = 0; }
}
}
}
}
############################################################
# Time division multiplexing decoder: Receives a list of
# properties over a MP enabled string property.
# src - MP string prop : property node
# actions - list of actions : [func(v)]
# An action is triggered when its value is received.
# Note: TDM can have high latency so it is best used for
# non-time critical properties.
var TDMDecoder = {};
TDMDecoder.new = func (src, actions) {
var obj = { parents : [TDMDecoder],
actions : actions };
obj.channel = MessageChannel.new(src,
func (msg) {
obj.process(msg);
});
# Error checking.
var bad = (src == nil) or (obj.channel == nil);
foreach (var a; actions) {
if (a == nil) { bad = 1; }
}
if (bad) {
print("TDMDecoder[");
print(" ", debug.string(src));
foreach (var a; actions) {
print(" ", debug.string(a));
}
print("]");
fail();
}
return obj;
}
TDMDecoder.process = func (msg) {
var v1 = Binary.decodeByte(msg);
var v2 = Binary.decodeDouble(substr(msg, 1));
# Trigger action.
me.actions[v1](v2);
}
TDMDecoder.update = func {
me.channel.update();
}
###############################################################################
# Internal utility functions
var is_num = func (v) {
return num(v) != nil;
}
# fail causes a Nasal runtime error so we get a backtrace.
var fail = func {
error_detected_in_calling_context();
}
###############################################################################
###############################################################################
# Copilot selection dialog.
#
# Usage: dual_control_tools.copilot_dialog.show(<copilot type string>);
#
var COPILOT_DLG = 0;
var copilot_dialog = {};
############################################################
copilot_dialog.init = func (copilot_type, 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"
if (contains(aircraft_dual_control, "copilot_view")) {
me.title = "Pilot selection";
} else {
me.title = "Copilot selection";
}
me.basenode = props.globals.getNode("sim/remote", 1);
me.dialog = nil;
me.namenode = props.Node.new({"dialog-name" : me.title });
me.listeners = [];
me.copilot_type = copilot_type;
}
############################################################
copilot_dialog.create = func {
if (me.dialog != nil)
me.close();
me.dialog = gui.Widget.new();
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);
if (contains(aircraft_dual_control, "copilot_view")) {
titlebar.addChild("text").set("label", "Book your flight");
} else {
titlebar.addChild("text").set("label", "Passengers 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", "dual_control_tools.copilot_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.
me.players = me.find_copilot_players();
var i = 0;
var tmpbase = me.basenode.getNode("dialog", 1);
var selected = me.basenode.getNode("pilot-callsign").getValue();
foreach (var p; me.players) {
var tmp = tmpbase.getNode("b[" ~ i ~ "]", 1);
tmp.setBoolValue(streq(selected, p));
var w = content.addChild("checkbox");
w.node.setValues({"label" : p,
"halign" : "left",
"property" : tmp.getPath()});
w.setBinding
("nasal",
"dual_control_tools.copilot_dialog.select_action(" ~ i ~ ");");
i = i + 1;
}
me.dialog.addChild("hrule");
# Display the dialog.
fgcommand("dialog-new", me.dialog.prop());
fgcommand("dialog-show", me.namenode);
}
############################################################
copilot_dialog.close = func {
fgcommand("dialog-close", me.namenode);
}
############################################################
copilot_dialog.destroy = func {
COPILOT_DLG = 0;
me.close();
foreach(var l; me.listeners)
removelistener(l);
delete(gui.dialog, "\"" ~ me.title ~ "\"");
}
############################################################
copilot_dialog.show = func (copilot_type) {
# print("Showing MPCopilots dialog!");
if (!COPILOT_DLG) {
COPILOT_DLG = int(getprop("/sim/time/elapsed-sec"));
me.init(copilot_type);
me.create();
me._update_(COPILOT_DLG);
}
}
############################################################
copilot_dialog._redraw_ = func {
if (me.dialog != nil) {
me.close();
me.create();
}
}
############################################################
copilot_dialog._update_ = func (id) {
if (COPILOT_DLG != id) return;
me._redraw_();
settimer(func { me._update_(id); }, 4.1);
}
############################################################
copilot_dialog.select_action = func (n) {
var selected = me.basenode.getNode("pilot-callsign").getValue();
var bs = me.basenode.getNode("dialog").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("pilot-callsign").setValue("");
foreach (var b; bs) {
if (!b.getValue() and (i == n)) {
b.setValue(1);
me.basenode.getNode("pilot-callsign").setValue(me.players[i]);
} else {
b.setValue(0);
}
i = i + 1;
}
dual_control.main.reset();
me._redraw_();
}
############################################################
# Return a list containing all nearby copilot players of the right type.
copilot_dialog.find_copilot_players = func {
var mpplayers =
props.globals.getNode("ai/models").getChildren("multiplayer");
var res = [];
foreach (var pilot; mpplayers) {
if ((pilot.getNode("valid") != nil) and
(pilot.getNode("valid").getValue()) and
(pilot.getNode("sim/model/path") != nil)) {
var type = pilot.getNode("sim/model/path").getValue();
if (type == me.copilot_type) {
append(res, pilot.getNode("callsign").getValue());
}
}
}
# debug.dump(res);
return res;
}
###############################################################################

View File

@ -0,0 +1,29 @@
#################################################################################
# Lake of Constance Hangar #
# Boeing 707 for Flightgear #
# Copyright (C) 2013 M.Kraus #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
# #
# Every software has a developer, also free software. #
# As a gesture of courtesy and respect, I would be delighted #
# if you contacted me before making any changes to this software. #
# <info (at) marc-kraus.de> April, 2017 #
#################################################################################
setlistener("sim/current-view/view-number", func (n){
var n = n.getValue() or 0;
if (n == 0){
setprop("sim/current-view/view-number",8);
}
},0,1);

View File

@ -0,0 +1,93 @@
###############################################################################
## Nasal for dual control of the Common-Spruce CS 1 over the multiplayer network.
##
## Copyright (C) 2007 - 2008 Anders Gidenstam (anders(at)gidenstam.org)
## This file is licensed under the GPL license version 2 or later.
##
## For the CS 1, written in January 2012 by Marc Kraus
###############################################################################
## Renaming (almost :)
var DCT = dual_control_tools;
## Pilot/copilot aircraft identifiers. Used by dual_control.
var pilot_type = "Aircraft/followme_e-tron/Models/followme.xml";
var copilot_type = "Aircraft/followme_e-tron/Models/followme-PAX.xml";
############################ PROPERTIES MP ###########################
var compressionW = "sim/multiplay/generic/float[12]";
var rollspeedW = "sim/multiplay/generic/float[13]";
var l_dual_control = "dual-control/active";
######################################################################
###### Used by dual_control to set up the mappings for the pilot #####
######################## PILOT TO COPILOT ############################
######################################################################
var pilot_connect_copilot = func (copilot) {
# Make sure dual-control is activated in the FDM FCS.
print("Pilot section");
setprop(l_dual_control, 1);
return [
##################################################
# Map copilot properties to buffer properties
# copilot to pilot
];
}
##############
var pilot_disconnect_copilot = func {
setprop(l_dual_control, 0);
}
######################################################################
##### Used by dual_control to set up the mappings for the copilot ####
######################## COPILOT TO PILOT ############################
######################################################################
var copilot_connect_pilot = func (pilot) {
# Make sure dual-control is activated in the FDM FCS.
print("Copilot section");
setprop(l_dual_control, 1);
setprop("sim/current-view/view-number",8);
#setprop("b707/shake-effect/effect",1);
return [
##################################################
# Map pilot properties to buffer properties
# float[1] and float[2] for the rumble effect on ground
DCT.Translator.new(pilot.getNode("sim/multiplay/generic/float[1]"),
props.globals.getNode("sim/multiplay/generic/float[1]", 1)),
DCT.Translator.new(pilot.getNode("sim/multiplay/generic/float[2]"),
props.globals.getNode("sim/multiplay/generic/float[2]", 1)),
#DCT.Translator.new(pilot.getNode("engines/engine[0]/n1"),
# props.globals.getNode("engines/engine[0]/n1", 1)),
#DCT.Translator.new(pilot.getNode("engines/engine[0]/n2"),
# props.globals.getNode("engines/engine[0]/n2", 1)),
#DCT.Translator.new(pilot.getNode("engines/engine[1]/n1"),
# props.globals.getNode("engines/engine[1]/n1", 1)),
#DCT.Translator.new(pilot.getNode("engines/engine[1]/n2"),
# props.globals.getNode("engines/engine[1]/n2", 1)),
#DCT.Translator.new(pilot.getNode("engines/engine[2]/n1"),
# props.globals.getNode("engines/engine[2]/n1", 1)),
#DCT.Translator.new(pilot.getNode("engines/engine[2]/n2"),
# props.globals.getNode("engines/engine[2]/n2", 1)),
#DCT.Translator.new(pilot.getNode("engines/engine[3]/n1"),
# props.globals.getNode("engines/engine[3]/n1", 1)),
#DCT.Translator.new(pilot.getNode("engines/engine[3]/n2"),
# props.globals.getNode("engines/engine[3]/n2", 1))
];
}
var copilot_disconnect_pilot = func {
setprop(l_dual_control, 0);
}

View File

@ -0,0 +1,115 @@
###############################################################################
## $Id$
##
## Nasal for main pilot for dual control over the multiplayer network.
##
## Copyright (C) 2007 - 2010 Anders Gidenstam (anders(at)gidenstam.org)
## This file is licensed under the GPL license version 2 or later.
##
###############################################################################
# Renaming (almost :)
var DCT = dual_control_tools;
var ADC = aircraft_dual_control;
# NOTE: By loading the aircraft specific dual control module
# as <aircraft_dual_control> this file is generic.
# The aircraft specific modul must set the variables
# pilot_type and copilot_type to the name (with full path) of
# main 3d model XML for the pilot and copilot aircraft.
# This module should be loades under the name dual_control.
######################################################################
# Connect new copilot
var process_data = 0;
var connect = func (copilot) {
# Tweak MP/AI filters
copilot.getNode("controls/allow-extrapolation").setBoolValue(0);
copilot.getNode("controls/lag-adjust-system-speed").setValue(5);
process_data = ADC.pilot_connect_copilot(copilot);
print("Dual control ... copilot connected.");
setprop("sim/messages/copilot", "Hi.");
}
######################################################################
# Main loop singleton class.
var main = {
init : func {
me.loopid = 0;
me.active = 0;
setlistener("ai/models/model-added", func {
settimer(func { me.activate(); }, 2);
});
settimer(func { me.activate(); }, 5);
print("Pilot dual control ... initialized");
},
reset : func {
if (me.active) {
print("Dual control ... copilot disconnected.");
ADC.pilot_disconnect_copilot();
}
me.active = 0;
me.loopid += 1;
me._loop_(me.loopid);
},
activate : func {
if (!me.active) {
me.reset();
}
},
update : func {
var mpplayers =
props.globals.getNode("ai/models").getChildren("multiplayer");
var r_callsign = getprop("sim/remote/pilot-callsign");
foreach (var copilot; mpplayers) {
if ((copilot.getChild("valid").getValue()) and
(copilot.getChild("callsign") != nil) and
(copilot.getChild("callsign").getValue() == r_callsign)) {
if (me.active == 0) {
# Note: sim/model/path tells the 3d XML file of the model.
if ((copilot.getNode("sim/model/path") != nil) and
(copilot.getNode("sim/model/path").getValue() ==
ADC.copilot_type)) {
connect(copilot);
me.active = 1;
} else {
print("Dual control ... copilot rejected - wrong aircraft type.");
me.loopid += 1;
return;
}
}
# Mess with the MP filters. Highly experimental.
if (copilot.getNode("controls/lag-time-offset") != nil) {
var v = copilot.getNode("controls/lag-time-offset").getValue();
copilot.getNode("controls/lag-time-offset").setValue(0.97 * v);
}
foreach (var w; process_data) {
w.update();
}
return;
}
}
if (me.active) {
print("Dual control ... copilot disconnected.");
ADC.pilot_disconnect_copilot();
}
me.loopid += 1;
me.active = 0;
},
_loop_ : func(id) {
id == me.loopid or return;
me.update();
settimer(func { me._loop_(id); }, 0);
}
};
######################################################################
# Initialization.
setlistener("sim/signals/fdm-initialized", func {
main.init();
});

View File

@ -50,7 +50,7 @@ props.getNode("systems/display-speed", 1).setValue(0);
props.getNode("systems/speedometer/type", 1).setValue("Type_A");
props.getNode("systems/battery-gauge/type", 1).setValue("Type_A");
props.getNode("controls/lighting/headlight-als", 1).setValue(0);
props.getNode("controls/lighting/highBeam", 1).setValue(0);
props.getNode("sim/remote/pilot-callsign", 1).setValue("");
#var Led = {
#

270
followme-PAX-set.xml Normal file
View File

@ -0,0 +1,270 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--####################################################################
Lake of Constance Hangar
Boeing 707 for Flightgear
Copyright (C) 2013 M.Kraus
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Every software has a developer, also free software.
As a gesture of courtesy and respect, I would be delighted
if you contacted me before making any changes to this software.
<info (at) marc-kraus.de> April, 2017
########################################################################-->
<PropertyList>
<sim n="0">
<description>Follow Me e-tron Passenger</description>
<author>Sidi Liang</author>
<flight-model>null</flight-model>
<variant-of>followme_e-tron</variant-of>
<model>
<path archive="y">Aircraft/followme_e-tron/Models/followme-PAX.xml</path>
</model>
<remote>
<pilot-callsign type="string"/>
</remote>
<sound>
<path>Aircraft/followme_e-tron/followme-sound.xml</path>
</sound>
<previews>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/01.png</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/02.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/03.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/04.png</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/05.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/06.jpg</path>
</preview>
<preview>
<type>interior</type>
<splash type="bool">true</splash>
<path>Splash/07.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/08.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/09.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/10.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/11.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/12.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/13.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/14.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/15.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/16.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/17.jpg</path>
</preview>
<preview>
<type>interior</type>
<splash type="bool">true</splash>
<path>Splash/18.jpg</path>
</preview>
<preview>
<type>interior</type>
<splash type="bool">true</splash>
<path>Splash/19.jpg</path>
</preview>
<preview>
<type>exterior</type>
<splash type="bool">true</splash>
<path>Splash/20.jpg</path>
</preview>
</previews>
<menubar>
<default>
<menu n="100">
<label>FM e-tron PAX</label>
<enabled type="bool">true</enabled>
<item>
<label>Select driver in range</label>
<binding>
<command>nasal</command>
<script>
dual_control_tools.copilot_dialog.show(aircraft_dual_control.pilot_type);
</script>
</binding>
</item>
</menu>
</default>
</menubar>
<view>
<internal archive="y">true</internal>
<enabled type="bool">false</enabled>
<config>
<limits>
<enabled archive="y" type="bool">false</enabled>
</limits>
<!-- x/y/z == right/up/back -->
<x-offset-m archive="y">-0.46</x-offset-m>
<y-offset-m archive="y">1.19</y-offset-m>
<z-offset-m archive="y">-18.2</z-offset-m>
<pitch-offset-deg archive="y">-16.0</pitch-offset-deg>
<default-field-of-view-deg type="double">72</default-field-of-view-deg>
</config>
</view>
<view n="1">
<enabled type="bool">true</enabled>
<config>
<!-- big plane, so extend chase view offset a bit -->
<z-offset-m type="double" archive="y">-50.0</z-offset-m>
</config>
</view>
<view n="2">
<enabled type="bool">false</enabled>
</view>
<view n="3">
<enabled type="bool">false</enabled>
</view>
<view n="4">
<enabled type="bool">false</enabled>
</view>
<view n="5">
<enabled type="bool">true</enabled>
</view>
<view n="6">
<enabled type="bool">false</enabled>
</view>
<view n="7">
<enabled type="bool">true</enabled>
</view>
<view n="100">
<name>Copilot View</name>
<internal archive="y">true</internal>
<type>lookfrom</type>
<config>
<from-model type="bool">true</from-model>
<from-model-idx type="int">0</from-model-idx>
<dynamic-view type="bool">true</dynamic-view>
<x-offset-m archive="y" type="double">0.35</x-offset-m>
<y-offset-m archive="y" type="double">1.35</y-offset-m>
<z-offset-m archive="y" type="double">1.88</z-offset-m>
<pitch-offset-deg>-10.0</pitch-offset-deg>
<field-of-view>65</field-of-view>
</config>
</view>
<chase-distance-m>-45</chase-distance-m>
</sim>
<nasal>
<followme>
<file>Aircraft/followme_e-tron/Nasal/DualControl/followme_e-tron-PAX.nas</file>
</followme>
<!-- Dual control. -->
<dual_control_tools>
<file>Aircraft/followme_e-tron/Nasal/DualControl/dual-control-tools.nas</file>
</dual_control_tools>
<aircraft_dual_control>
<file>Aircraft/followme_e-tron/Nasal/DualControl/followme-dual-control.nas</file>
</aircraft_dual_control>
<dual_control>
<file>Aircraft/followme_e-tron/Nasal/DualControl/copilot-dual-control.nas</file>
</dual_control>
</nasal>
<!-- Dual control depend on this value -->
<dual-control>
<active type="bool">0</active>
</dual-control>
</PropertyList>

View File

@ -220,6 +220,21 @@
<target-y-offset-m archive="y" type="double">1.6</target-y-offset-m>
</config>
</view>
<view n="100">
<name>Copilot View</name>
<internal archive="y">true</internal>
<type>lookfrom</type>
<config>
<from-model type="bool">true</from-model>
<from-model-idx type="int">0</from-model-idx>
<dynamic-view type="bool">true</dynamic-view>
<x-offset-m archive="y" type="double">0.35</x-offset-m>
<y-offset-m archive="y" type="double">1.35</y-offset-m>
<z-offset-m archive="y" type="double">1.88</z-offset-m>
<pitch-offset-deg>-10.0</pitch-offset-deg>
<field-of-view>65</field-of-view>
</config>
</view>
<hud>
<enable3d>false</enable3d>
@ -317,6 +332,15 @@
<script>engine.stopEngine()</script>
</binding>
</item>
<item>
<label>Select Passenger</label>
<binding>
<command>nasal</command>
<script>
dual_control_tools.copilot_dialog.show(aircraft_dual_control.copilot_type);
</script>
</binding>
</item>
</menu>
</default>
</menubar>
@ -579,6 +603,15 @@
<light>
<file>Aircraft/followme_e-tron/Nasal/light-manager.nas</file>
</light>
<dual_control_tools>
<file>Aircraft/followme_e-tron/Nasal/DualControl/dual-control-tools.nas</file>
</dual_control_tools>
<aircraft_dual_control>
<file>Aircraft/followme_e-tron/Nasal/DualControl/followme-dual-control.nas</file>
</aircraft_dual_control>
<dual_control>
<file>Aircraft/followme_e-tron/Nasal/DualControl/pilot-dual-control.nas</file>
</dual_control>
</nasal>
</PropertyList>