/*
Copyright (C) 2012 Andrew P. Sillers (apsillers@gmail.com)
The structure of this file borrows heavily from the Gytha client,
which is Copyright (C) 2007-2011 James Cameron (quozl@us.netrek.org)
This file is part of the HTML5 Netrek Client.
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
This file lists all supported server and client messages (sometimes called
"packets", but a TCP/UDP packet can have many messages in it).
See http://james.tooraweenah.com/darcs/netrek-server/Vanilla/include/packets.h
for an explanation of how the netrek protocol works
*/
net_logging = false;
/*
CLIENT MESSAGES - these objects all contain a data() member function that
returns an array of bytes, which can be sent to the server using
net.sendArray()
*/
CP_SOCKET = {
code: 27,
format: '!bbbxI',
data: function(tcpVersion) {
if(net_logging) console.log("CP_SOCKET");
return packer.pack(this.format,[this.code,4,tcpVersion,0]);
}
}
CP_PING_RESPONSE = {
code: 42,
format: "!bBbxll",
data: function(number, pingme, cp_sent, cp_recv) {
if(net_logging || true) console.log("CP_PING_RESPONSE pingme=", pingme);
return packer.pack(this.format, [this.code, number, pingme, cp_sent, cp_recv]);
// From James Cameron's pygame file (this may or may not apply to the JS version):
// FIXME: bug #1215317195 reported by Zach, pinging the player
// using "!" results in O0 PING stats: Avg: 364 ms, Stdv: 19
// ms, Loss: ^@100.0%/nan% s->c/c->s
}
}
CP_LOGIN = {
code: 8,
format: '!bbxx16s16s16s',
data: function(query, name, password, login) {
if(net_logging) console.log("CP_LOGIN query=",query,"name=",name);
return packer.pack(this.format, [this.code, query, name, password, login])
}
}
CP_UPDATES = {
code: 31,
format: '!bxxxI',
data: function(usecs) {
if(net_logging) console.log("CP_UPDATES usecs=",usecs);
return packer.pack(this.format, [this.code, usecs]);
}
}
CP_OUTFIT = {
code: 9,
format: '!bbbx',
data: function(race, ship) {
if(net_logging) console.log("CP_OUTFIT team=",teamLib.raceDecode(race),"ship=",ship);
return packer.pack(this.format, [this.code, race, ship]);
}
}
CP_SPEED = {
code: 2,
format: '!bbxx',
data: function(speed) {
if(net_logging) console.log("CP_SPEED speed=",speed);
if(tutorial.active) {
if(speed==0) { tutorial.handleKeyword("speed0"); }
if(speed==2) { tutorial.handleKeyword("speed2"); }
if(speed==5) { tutorial.handleKeyword("speed5"); }
}
return packer.pack(this.format, [this.code, speed]);
}
}
CP_DIRECTION = {
code: 3,
format: '!bBxx',
data: function(direction) {
if(net_logging) console.log("CP_DIRECTION direction=",direction);
if(tutorial.active) tutorial.handleKeyword("direct");
return packer.pack(this.format, [this.code, direction & 255]);
}
}
CP_MESSAGE = {
code: 1,
format: "!bBBx80s",
data: function(group, indiv, mesg) {
if(net_logging) console.log("CP_MESSAGE group=",group,"indiv=",indiv,"mesg=",mesg);
if ((group == MGOD)) {
$("#inbox").append(mesg + "
");
$("#inbox").scrollTop($("#inbox").height())
}
return packer.pack(this.format, [this.code, group, indiv, mesg]);
}
}
CP_PHASER = {
code: 4,
format: '!bBxx',
data: function(direction) {
if(net_logging) console.log("CP_PHASER direction=",direction);
if(tutorial.active) { tutorial.handleKeyword("phaser"); }
return packer.pack(this.format, [this.code, direction & 255]);
}
}
CP_PLASMA = {
code: 5,
format: '!bBxx',
data: function(direction) {
if(net_logging) console.log("CP_PLASMA direction=",direction);
return packer.pack(this.format, [this.code, direction & 255]);
}
}
CP_TORP = {
code: 6,
format: '!bBxx',
data: function(direction) {
if(net_logging) console.log("CP_TORP direction=",direction);
if(tutorial.active) { tutorial.handleKeyword("torp"); }
return packer.pack(this.format, [this.code, direction & 255]);
}
}
CP_DET_TORPS = {
code: 20,
format: '!bxxx',
data: function() {
if(net_logging) console.log("CP_DET_TORPS");
return packer.pack(this.format, [this.code])
}
}
CP_DET_MYTORP = {
code: 21,
format: '!bxh',
data: function(tnum) {
if(net_logging) console.log("CP_DET_MYTORP");
if(tutorial.active) { tutorial.handleKeyword("detmytorp"); }
return packer.pack(this.format, [this.code, tnum])
}
}
CP_TRACTOR = {
code: 24,
format: '!bbbx',
data: function(state, pnum) {
if(net_logging) console.log("CP_TRACTOR state=",state,"pnum=",pnum);
return packer.pack(this.format, [this.code, state, pnum]);
}
}
CP_REPRESS = {
format: '!bbbx',
data: function(state, pnum) {
if(net_logging) console.log("CP_REPRESS state=",state,"pnum=",pnum);
return packer.pack(this.format, [this.code, state, pnum]);
}
}
CP_CLOAK = {
code: 19,
format: '!bbxx',
data: function(state) {
if(net_logging) console.log("CP_CLOAK state=",state);
if(tutorial.active) {
if(state==1) { tutorial.handleKeyword("cloakon"); }
if(state==0) { tutorial.handleKeyword("cloakoff"); }
}
return packer.pack(this.format, [this.code, state]);
}
}
CP_REPAIR = {
code: 13,
format: '!bbxx',
data: function(state) {
if(net_logging) console.log("CP_REPAIR state=",state);
if(tutorial.active) { tutorial.handleKeyword("repair"); }
return packer.pack(this.format, [this.code, state]);
}
}
CP_SHIELD = {
code: 12,
format: '!bbxx',
data: function(state) {
if(net_logging) console.log("CP_SHIELD state=",state);
if(tutorial.active) { tutorial.handleKeyword("shields"); }
return packer.pack(this.format, [this.code, state]);
}
}
CP_ORBIT = {
code: 14,
format: '!bbxx',
data: function(state) {
if(net_logging) console.log("CP_ORBIT =",state);
return packer.pack(this.format, [this.code, state]);
}
}
CP_BOMB = {
code: 17,
format: '!bbxx',
data: function(state) {
if(net_logging) console.log("CP_BOMB state=",state);
return packer.pack(this.format, [this.code, state]);
}
}
CP_BEAM = {
code: 18,
format: '!bbxx',
data: function(state) {
if(net_logging) console.log("CP_BEAM state=",state);
if(tutorial.active && state == 1) { tutorial.handleKeyword("pickup"); }
return packer.pack(this.format, [this.code, state]);
}
}
/*************************************************************************
All server packet types listed below
Each packet object contains:
- the packet's identifying code byte
- pack format string (for unpacking off the network; see Python struct docs)
- handler function (what we do when this kind of packet arrives)
*************************************************************************/
serverPackets = [
{ // SP_MOTD
code: 11,
format: '!bxxx80s',
handler: function(data) {
var unpacked = packer.unpack(this.format, data);
if(unpacked) message = unpacked[1].replace(/\x00/g," ");
if(net_logging) console.log(message);
outfitting.motdLine(message);
}
},
{ // SP_YOU
code: 12,
format: '!bbbbbbxxIlllhhhh',
handler: function(data) {
// unpack all the data into variables
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), hostile = uvars.shift(),
swar = uvars.shift(), armies = uvars.shift(), tractor = uvars.shift(),
flags = uvars.shift(), damage = uvars.shift(), shield = uvars.shift(),
fuel = uvars.shift(), etemp = uvars.shift(), wtemp = uvars.shift(),
whydead = uvars.shift(), whodead = uvars.shift();
if(net_logging) console.log("SP_YOU pnum=",pnum,"hostile=",team_decode(hostile),"swar=",team_decode(swar),
"armies=",armies,"tractor=",tractor,"flags=",flags.toString(2),"damage=",
damage,"shield=",shield,"fuel=",fuel,"etemp=",etemp,"wtemp=",
wtemp,"whydead=",whydead,"whodead=",whodead);
if(world.playerNum == null) { world.playerNum = pnum; }
else {
world.ships[world.playerNum].handleFlags(flags);
hud.showEngineTemp(etemp/10);
if(tutorial.active && (flags & PFORBIT)) { tutorial.handleKeyword("orbit"); }
}
if(world.player != null) {
hud.showFuelLevel(100 * fuel / shipStats[world.player.shipType].fuel);
hud.showHullLevel(100 * (shipStats[world.player.shipType].hull - damage) / shipStats[world.player.shipType].hull);
hud.showShieldLevel(100 * shield / shipStats[world.player.shipType].shields);
hud.showArmies(armies, world.player.kills || 0);
hud.showMaxSpeed(shipStats[world.player.shipType].speed);
if(tractor & 0x40) {
if(world.tractors[pnum] == undefined) {
world.addTractor(pnum, new Tractor(pnum, flags));
}
world.tractors[pnum].sp_tractor(flags, tractor)
} else {
if(world.tractors[pnum] != undefined) {
world.removeTractor(pnum);
}
}
}
}
},
{ // SP_PL_LOGIN
code: 24,
format: "!bbbx16s16s16s",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), rank = uvars.shift(),
name = uvars.shift(), monitor = uvars.shift(), login = uvars.shift();
if(net_logging) console.log("SP_PL_LOGIN pnum=",pnum,"rank=",rank,"name=",name,"monitor=",monitor,"login=",login)
playerList.addPlayer(pnum, name, rank);
}
},
{ // SP_PING - only received if client sends CP_PING_RESPONSE after SP_LOGIN
// ...doesn't seem to work
code: 46,
format: "!bBHBBBB",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), number = uvars.shift(), lag = uvars.shift(),
tloss_sc = uvars.shift(), tloss_cs = uvars.shift(), iloss_sc = uvars.shift(),
iloss_cs = uvars.shift();
if(net_logging || true) { console.log("SP_PING"); }
net.send(CP_PING_RESPONSE.data(0, 1, 0, 0));
}
},
{ // SP_HOSTILE
code: 22,
format: "!bbbb",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), war = uvars.shift(),
hostile = uvars.shift();
if(net_logging) console.log("SP_HOSTILE pnum=",pnum,"war=",team_decode(war),"hostile=",team_decode(hostile));
}
},
{ // SP_PLAYER_INFO
code: 2,
format:"!bbbb",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), shiptype = uvars.shift(), team = team_decode(uvars.shift());
if(net_logging) console.log("SP_PLAYER_INFO pnum=",pnum,"shiptype=",shiptype,"team=",team);
var img = imageLib.images[team.length?team[0]:FED][shiptype];
if(world.ships[pnum] == undefined) {
world.addShip(pnum, new Ship({
img: img,
galImg: imageLib.images[1][shiptype],
team:team[0],
number:pnum.toString(),
shipType: shiptype
}));
}
world.ships[pnum].setImage(img);
world.ships[pnum].setTeam(team[0]);
world.ships[pnum].shipType = shiptype;
playerList.updatePlayer(pnum, team);
}
},
{ // SP_KILLS
code: 3,
format:"!bbxxI",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), kills = uvars.shift();
if(net_logging) console.log("SP_KILLS pnum=",pnum,"kills=",kills);
if(world.ships[pnum]) world.ships[pnum].kills = kills;
playerList.updatePlayer(pnum, null, kills);
}
},
{ // SP_PSTATUS
code: 20,
format:"!bbbx",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), status = uvars.shift();
if(net_logging) console.log("SP_PSTATUS pnum=",pnum,"status=",status);
if(connected_yet && world.player && pnum == world.player.number && status == POUTFIT) {
world.undraw();
outfitting.draw(leftCanvas, rightCanvas);
}
if(status == PFREE && world.ships[pnum] != undefined) {
playerList.removePlayer(pnum);
world.removeShip(pnum);
}
else if(status == PALIVE) {
world.addShip(pnum, world.ships[pnum]);
}
else if(status == PEXPLODE) {
world.ships[pnum].explode();
world.removeShip(pnum);
}
}
},
{ // SP_PLAYER
code: 4,
format:"!bbBbll",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), dir = uvars.shift(), speed = uvars.shift(), x = uvars.shift(), y = uvars.shift();
if(net_logging) console.log("SP_PLAYER pnum=",pnum,"dir=",dir,"speed=",speed,"x=",x,"y=",y);
world.ships[pnum].setRotation(dir);
world.ships[pnum].setPosition(x, y);
world.ships[pnum].speed = speed;
if(world.player && pnum == world.player.number) {
hud.showSpeed(speed);
if(tutorial.active && speed <= 2) {
// TODO: if near a planet
for(var i=0; i < world.planets.length; ++i) {
var planet = world.planets[i];
if((planet.x - x)*(planet.x - x) + (planet.y - y)*(planet.y - y) <= 1600000) {
tutorial.handleKeyword("approach")
}
}
}
}
}
},
{ // SP_FLAGS
code: 18,
format:"!bbbxI",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), tractor = uvars.shift(), flags = uvars.shift();
if(net_logging) console.log("SP_FLAGS pnum=",pnum,"tractor=",tractor,"flags=",flags);
world.ships[pnum].handleFlags(flags);
if(tractor & 0x40) {
if(world.tractors[pnum] == undefined) {
world.addTractor(pnum, new Tractor(pnum, flags));
}
world.tractors[pnum].sp_tractor(flags, tractor);
} else {
if(world.tractors[pnum] != undefined) {
world.removeTractor(pnum);
}
}
}
},
{ // SP_PLANET_LOC
code: 26,
format:"!bbxxll16s",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), x = uvars.shift(), y = uvars.shift(), name = uvars.shift();
if(net_logging) console.log("SP_PLANET_LOC pnum=",pnum,"x=",x,"y=",y,"name=",name);
if(world.planets[pnum] == undefined) {
world.addPlanet(pnum, new world.Planet(x, y, name, [], world));
} else {
world.planets[pnum].setXY(x,y);
}
}
},
{ // SP_LOGIN
code: 17,
format:"!bbxxl96s",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), accept = uvars.shift(), flags = uvars.shift(), keymap = uvars.shift();
if(net_logging) console.log("SP_LOGIN accept=",accept,"flags=",flags.toString(2));
//net.sendArray(CP_PING_RESPONSE.data(0, 1, 0, 0));
}
},
{ // SP_MASK
code: 19,
format:"!bbxx",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), mask = uvars.shift();
if(net_logging) console.log("SP_MASK mask=",team_decode(mask));
outfitting.applyMask(team_decode(mask))
// when first connecting, delay showing outfitting until the first SP_MASK is seen
if(!connected_yet) {
connected_yet = true;
$("#overlay").hide();
$("#login-box").hide();
outfitting.draw(leftCanvas, rightCanvas);
}
}
},
{ // SP_PICKOK
code: 16,
format:"!bbxx",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), state = uvars.shift();
if(net_logging) console.log("SP_PICKOK state=", state);
if(state == 1) {
outfitting.undraw();
world.ships[world.playerNum];
world.draw();
if(tutorial.active) { tutorial.handleKeyword("join"); }
}
}
},
{ // SP_RESERVED
code: 25,
format:"!bxxx16s",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), data = uvars.shift();
var text = packer.unpack('16b', data)
if(net_logging) console.log("SP_RESERVED data=",text);
}
},
{ // SP_TORP_INFO
code: 5,
format:"!bbbxhxx",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), war = uvars.shift(), status = uvars.shift(), tnum = uvars.shift();
if(net_logging) console.log("SP_TORP_INFO war=",team_decode(war)," status=",status," tnum=",tnum);
if(world.torps[tnum] == undefined && status != TFREE) {
world.addTorp(tnum, new Torp(-10000, -10000, 0, team_decode(war), world));
}
else if(world.torps[tnum] != undefined && status == PTFREE || status == PTEXPLODE || status == PTDET) {
if(status == PTEXPLODE || status == PTDET) {
world.torps[tnum].explode();
world.removeTorp(tnum);
}
world.removeTorp(tnum);
}
}
},
{ // SP_TORP
code: 6,
format:"!bBhll",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), dir = uvars.shift(), tnum = uvars.shift(), x = uvars.shift(), y = uvars.shift();
if(net_logging) console.log("SP_TORP dir=",dir," tnum=",tnum," x=",x," y=",y);
if(world.torps[tnum] != undefined) {
world.torps[tnum].setXYDir(x,y, dir);
}
}
},
{ // SP_PLASMA_INFO
code: 8,
format:"!bbbxhxx",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), war = uvars.shift(), status = uvars.shift(), pnum = uvars.shift();
if(net_logging) console.log("SP_PLASMA_INFO war=",team_decode(war),"status=",status,"pnum=",pnum);
}
},
{ // SP_PLASMA
code: 9,
format:"!bxhll",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), x = uvars.shift(), y = uvars.shift();
if(net_logging) console.log("SP_PLASMA pnum=",pnum,"x=",x,"y=",y);
}
},
{ // SP_STATUS
code: 14,
format:"!bbxxIIIIIL",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), tourn = uvars.shift(),
armsbomb = uvars.shift(), planets = uvars.shift(),
kills = uvars.shift(), losses = uvars.shift(), time = uvars.shift(),
timeprod = uvars.shift();
if(net_logging) console.log("SP_STATUS tourn=",tourn,"armsbomb=",armsbomb,"planets=",planets,"kills=",kills,"losses=",losses,"time=",time,"timepro=",timeprod);
}
},
{ // SP_PHASER
code: 7,
format:"!bbbBlll",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), status = uvars.shift(), dir = uvars.shift(), x = uvars.shift(), y = uvars.shift(), target = uvars.shift();
if(net_logging) console.log("SP_PHASER pnum=",pnum,"status=",status,"dir=",dir,"x=",x,"y=",y,"target=",target);
if(status != PHFREE) { world.addPhaser(pnum, new Phaser(world.ships[pnum].x, world.ships[pnum].y, dir, status, target, world)); }
else { if(world.phasers[pnum] != undefined) world.removePhaser(pnum); }
}
},
{ // SP_PLANET
code: 15,
format:"!bbbbhxxl",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), owner = uvars.shift(), info = uvars.shift(), flags = uvars.shift(), armies = uvars.shift();
if(net_logging) console.log("SP_PLANET pnum=",pnum,"owner=",owner,"info=",info,"flags=",flags.toString(2),"armies=",armies);
world.planets[pnum].applyFlags(flags);
world.planets[pnum].showArmies(armies);
}
},
{ // SP_MESSAGE
code: 1,
format:"!bBBB80s",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), m_flags = uvars.shift(),
m_recpt = uvars.shift(), m_from = uvars.shift(), mesg = uvars.shift();
if(net_logging) console.log("SP_MESSAGE m_flags=",m_flags.toString(2),"m_recpt=",m_recpt,"m_from=",m_from,"mesg=",mesg);
// bold the sender/receiver pair of a message
mesg = mesg.replace(/^(El Nath|Beta Crucis|[^\-]+)->(\S+)/,"$1->$2 ")
$("#inbox").append(mesg + "
");
$("#inbox").scrollTop($("#inbox")[0].scrollHeight);
}
},
{ // SP_STATS
code: 23,
format:"!bbxx13l",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pnum = uvars.shift(), tkills = uvars.shift(),
tlosses = uvars.shift(), kills = uvars.shift(),
losses = uvars.shift(), tticks = uvars.shift(),
tplanets = uvars.shift(), tarmies = uvars.shift(),
sbkills = uvars.shift(), sblosses = uvars.shift(),
armies = uvars.shift(), planets = uvars.shift(),
maxkills = uvars.shift(), sbmaxkills = uvars.shift();
if(net_logging) console.log("SP_STATS pnum=",pnum, "tkills=",tkills,
"tlosses=",tlosses, "kills=",kills, "losses=",losses,
"tticks=",tticks, "tplanets=",tplanets, "tarmies=",tarmies,
"sbkills=",sbkills, "sblosses=",sblosses, "armies=",armies,
"planets=",planets, "maxkills=",maxkills, "sbmaxkills=",
sbmaxkills);
if(world.player != null && pnum == world.player.number) {
hud.showArmies(armies, kills);
}
playerList.updatePlayer(pnum, null, kills);
}
},
{ // SP_WARNING
code: 10,
format: '!bxxx80s',
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), message = uvars.shift().replace(/\x00/g," ");
console.log("SP_WARNING message=", message);
hud.showWarning(message);
}
},
{ // SP_FEATURE
code: 60,
format: "!bcbbi80s",
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), type = uvars.shift(), arg1 = uvars.shift(),
arg2 = uvars.shift(), value = uvars.shift(), name = uvars.shift();
if(net_logging) console.log("SP_FEATURE type=%s arg1=%d arg2=%d value=%d name=%s", type, arg1, arg2, value, name);
/* if (type, arg1, arg2, value, name) == ('S', 0, 0, 1, 'FEATURE_PACKETS'):
# server says features packets are okay to send,
# so send this client's features
if rcd.cp_feature: # we want binary RCDs in SP_MESSAGE packets
nt.send(cp_feature.data('S', 0, 0, 1, 'RC_DISTRESS'))
nt.send(cp_feature.data('S', 0, 0, 1, 'SHIP_CAP'))
nt.send(cp_feature.data('S', 2, 0, 1, 'SP_GENERIC_32'))
nt.send(cp_feature.data('S', 0, 0, 1, 'TIPS'))
nt.send(cp_feature.data('S', 0, 0, 1, 'SHOW_ALL_TRACTORS'))
if name == 'UPS':
galaxy.ups = value
# FIXME: process the other feature packets received
*/
net.sendArray(CP_FEATURE.data('S', 0, 0, 1, 'SHOW_ALL_TRACTORS'));
//net.sendArray(CP_FEATURE.data('S', 0, 0, 1, 'TIPS'));
}
},
{ // SP_QUEUE
code: 13,
format: '!bxh',
handler: function(data) {
var uvars = packer.unpack(this.format, data);
var ignored = uvars.shift(), pos = uvars.shift();
if(net_logging) console.log("SP_QUEUE pos=",pos);
}
}
]
// index server packets by code number
var sp = [];
for(var i=0;i