1
0
Fork 0
fgdata/Nasal/multiplayer.nas

381 lines
12 KiB
Text

# Multiplayer
# ===========
#
# 1) Display chat messages from other aircraft to
# the screen using screen.nas
#
# 2) Display a complete history of chat via dialog.
#
# 3) Allow chat messages to be written by the user.
var messages = {};
var check_messages = func
{
var mp = props.globals.getNode("/ai/models").getChildren("multiplayer");
var lseen = {};
foreach (i; mp)
{
var lmsg = getprop(i.getPath() ~ "/sim/multiplay/chat");
var lcallsign = getprop(i.getPath() ~ "/callsign");
var lvalid = getprop(i.getPath() ~ "/valid");
if ((lvalid) and
(lmsg != nil) and
(lmsg != "") and
(lcallsign != nil) and
(lcallsign != "") )
{
if (! contains(lseen, lcallsign))
{
# Indicate that we've seen this callsign. This handles the case
# where we have two aircraft with the same callsign in the MP
# session.
lseen[lcallsign] = 1;
if ((! contains(messages, lcallsign)) or
(! streq(lmsg, messages[lcallsign])))
{
# Save the message so we don't repeat it.
messages[lcallsign] = lmsg;
# Display the message.
echo_message(lmsg, lcallsign);
}
}
}
}
# Check for new messages every couple of seconds.
settimer(check_messages, 3);
}
var echo_message = func(msg, callsign)
{
if ((callsign != nil) and
(! string.match(msg, "*" ~ callsign ~ "*")))
{
# Only prefix with the callsign if the message doesn't already include it.
msg = callsign ~ ": " ~ msg;
}
var ldisplay = getprop("/sim/multiplay/chat-display");
if ((ldisplay != nil) and (ldisplay == "1"))
{
# Only display the message to screen if configured.
setprop("/sim/messages/ai-plane", msg);
}
# Add the chat to the chat history.
var lchat = getprop("/sim/multiplay/chat-history");
if (lchat == nil)
{
setprop("/sim/multiplay/chat-history", msg);
}
else
{
if (substr(lchat, size(lchat) -1, 1) != "\n")
{
lchat = lchat ~ "\n";
}
setprop("/sim/multiplay/chat-history", lchat ~ msg);
}
}
settimer(func {
# Call-back to ensure we see our own messages.
setlistener("/sim/multiplay/chat", func(n) {
echo_message(n.getValue(), getprop("/sim/multiplay/callsign"));
});
# check for new messages
check_messages();
}, 1);
# Message composition function, activated using the - key.
var prefix = "Chat Message:";
var input = "";
var kbdlistener = nil;
var compose_message = func(msg = "")
{
input = prefix ~ msg;
gui.popupTip(input, 1000000);
kbdlistener = setlistener("/devices/status/keyboard/event", func (event) {
var key = event.getNode("key");
# Only check the key when pressed.
if (!event.getNode("pressed").getValue())
return;
if (handle_key(key.getValue()))
key.setValue(-1); # drop key event
});
}
var handle_key = func(key)
{
if (key == `\n` or key == `\r`)
{
# CR/LF -> send the message
# Trim off the prefix
input = substr(input, size(prefix));
# Send the message and switch off the listener.
setprop("/sim/multiplay/chat", input);
removelistener(kbdlistener);
gui.popdown();
return 1;
}
elsif (key == 8)
{
# backspace -> remove a character
if (size(input) > size(prefix))
{
input = substr(input, 0, size(input) - 1);
gui.popupTip(input, 1000000);
return 1;
}
}
elsif (key == 27)
{
# escape -> cancel
removelistener(kbdlistener);
gui.popdown();
return 1;
}
elsif ((key > 31) and (key < 128))
{
# Normal character - add it to the input
input ~= chr(key);
gui.popupTip(input, 1000000);
return 1;
}
else
{
# Unknown character - pass through
return 0;
}
}
# (l) 2008 by till busch <buti (at) bux (dot) at>
#
# multiplayer.dialog.show() -- displays the dialog and starts the update loop
var PILOTSDLG_RUNNING=0;
var METER_TO_NM = 0.0005399568034557235;
var dialog = {
init : func(x = nil, y = nil) {
me.x = x;
me.y = y;
me.bg = [0, 0, 0, 0.3]; # background color
me.fg = [[0.9, 0.9, 0.2, 1], [1.0, 1.0, 1.0, 1.0]]; # alternating foreground colors
me.unit=1;
me.toggle_unit(); # set to imperial
#
# "private"
me.header = [" callsign", "model", "hdg", func { dialog.dist_hdr }, func { dialog.alt_hdr ~ " " }];
me.columns = [
{property:"callsign", format:" %s", label: "-----------", halign:"fill" },
{property:"model-short", format:"%s", label: "--------------",halign:"fill" },
{property:"bearing-to", format:" %3.0f",label: "----", halign: "right",font:{name:"FIXED_8x13"}},
{property:func {dialog.dist_node}, format:" %8.2f",label: "---------",halign: "right",font:{name:"FIXED_8x13"}},
{property:func {dialog.alt_node}, format:" %7.0f",label: "---------",halign: "right",font:{name:"FIXED_8x13"}}
];
me.name = "who-is-online";
me.namenode = props.Node.new({"dialog-name" : me.name });
me.dialog = nil;
me.mp = props.globals.getNode("/ai/models");
me.listeners=[];
append(me.listeners, setlistener("/sim/startup/xsize", func { me._redraw_() }));
append(me.listeners, setlistener("/sim/startup/ysize", func { me._redraw_() }));
append(me.listeners, setlistener("/ai/models/model-added", func(n) { me._multiplayer_update_(n) }));
append(me.listeners, setlistener("/ai/models/model-removed", func(n) { me._multiplayer_update_(n) }));
},
getloc : func (p) {
var ploc = geo.Coord.new();
ploc.set_xyz(p.getNode("global-x").getValue(), p.getNode("global-y").getValue(), p.getNode("global-z").getValue());
return ploc;
},
create : func {
if (me.dialog != nil)
me.close();
me.dialog = gui.Widget.new();
me.dialog.set("name", me.name);
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);
me.dialog.setColor(me.bg[0], me.bg[1], me.bg[2], me.bg[3]);
var titlebar=me.dialog.addChild("group");
titlebar.set("layout", "hbox");
var w = titlebar.addChild("button");
w.node.setValues({"pref-width": 16, "pref-height": 16, legend: me.unit_button, default: 0});
w.setBinding("nasal", "multiplayer.dialog.toggle_unit(); multiplayer.dialog._redraw_()");
titlebar.addChild("empty").set("stretch", 1);
titlebar.addChild("text").set("label", "Pilots: ");
var p=titlebar.addChild("text");
p.node.setValues({label: "---", live: 1, format: "%d", property: me.mp.getPath() ~ "/players"});
titlebar.addChild("empty").set("stretch", 1);
var w = titlebar.addChild("button");
w.node.setValues({"pref-width": 16, "pref-height": 16, legend: "", default: 0, key: "Esc"});
w.setBinding("nasal", "multiplayer.dialog.del()");
me.dialog.addChild("hrule");
var content=me.dialog.addChild("group");
content.set("layout", "table");
content.set("default-padding", 0);
var row=0;
var col=0;
foreach (var h; me.header) {
var w = content.addChild("text");
var l = typeof(h) == "func" ? h() : h;
w.node.setValues({"label":l, "row":row, "col":col, halign:me.columns[col].halign});
w=content.addChild("hrule");
w.node.setValues({"row":row+1,"col":col});
col+=1;
}
row+=2;
var children = me.mp.getChildren("multiplayer");
var mp_vec=[];
foreach(var c; children) {
if(c.getNode("callsign") != nil and c.getNode("valid").getValue()) {
append(mp_vec, { callsign:string.lc(c.getNode("callsign").getValue()), index:c.getIndex()});
}
}
var sorted=sort(mp_vec, func(a,b) { return cmp(a.callsign,b.callsign) });
foreach(var s; sorted) {
var c = children[s.index];
var col=0;
foreach (var column; me.columns) {
var w = content.addChild("text");
w.node.setValues(column);
var p=typeof(column.property) == "func" ? column.property() : column.property;
w.node.setValues({row: row, col: col, live: 1, property: c.getPath() ~ "/" ~ p});
var odd = math.mod(row, 2);
w.setColor(me.fg[odd][0], me.fg[odd][1], me.fg[odd][2], me.fg[odd][3]);
col +=1;
}
row +=1;
}
fgcommand("dialog-new", me.dialog.prop());
fgcommand("dialog-show", me.namenode);
},
update : func {
var children = me.mp.getChildren("multiplayer");
var loc = geo.aircraft_position();
var players = 0;
foreach(var c; children) {
if ( c.getNode("valid") == nil or !c.getNode("valid").getValue())
continue;
players+=1;
var ploc = me.getloc(c.getNode("position"));
var ploc_course = loc.course_to(ploc);
var ploc_dist = loc.distance_to(ploc);
var altitude_m = c.getNode("position/altitude-ft").getValue()*FT2M;
var pathN = c.getNode("sim/model/path");
if (pathN != nil)
var path = pathN.getValue();
else
var path="";
var name = split("/", path)[-1];
var pos = find("-model", name);
if (pos >= 0)
name = substr(name, 0, pos);
if (substr(name, -4) == ".xml")
name = substr(name, 0, size(name) - 4);
if (substr(name, -3) == ".ac")
name = substr(name, 0, size(name) - 3);
c.setValues({"model-short":name, "bearing-to": ploc_course,
"distance-to-km":ploc_dist/1000.0, "distance-to-nm":ploc_dist*METER_TO_NM,
"position/altitude-m":altitude_m });
}
me.mp.getNode("players", 1).setIntValue(players);
if(PILOTSDLG_RUNNING)
settimer( func { me.update() }, 0.4, 1);
},
_redraw_ : func {
if (me.dialog != nil) {
me.close();
me.create();
}
},
_multiplayer_update_ : func(node) {
var name = split("/", node.getValue())[-1];
if(substr(name, 0, 11) == "multiplayer")
me._redraw_()
},
toggle_unit : func {
me.unit = !me.unit;
if(me.unit)
{
me.alt_node = "position/altitude-m";
me.alt_hdr = "alt-m";
me.dist_hdr = "dist-km";
me.dist_node = "distance-to-km";
me.unit_button = "IM";
}
else
{
me.alt_node = "position/altitude-ft";
me.dist_node = "distance-to-nm";
me.alt_hdr = "alt-ft";
me.dist_hdr = "dist-nm";
me.unit_button = "SI";
}
},
close : func {
fgcommand("dialog-close", me.namenode);
},
del : func {
PILOTSDLG_RUNNING=0;
me.close();
foreach(var l; me.listeners)
removelistener(l);
delete(gui.dialog, "\"" ~ me.name ~ "\"");
},
show : func {
if(!PILOTSDLG_RUNNING)
{
PILOTSDLG_RUNNING=1;
me.init(-2, -2);
me.create();
me.update();
}
},
toggle : func {
if(!PILOTSDLG_RUNNING)
me.show();
else
me.del();
}
};