#include "game.hh" namespace game { VARP(minradarscale, 0, 384, 10000); VARP(maxradarscale, 1, 1024, 10000); VARP(radarteammates, 0, 1, 1); FVARP(minimapalpha, 0, 1, 1); float calcradarscale() { return clamp(max(minimapradius.x, minimapradius.y)/3, float(minradarscale), float(maxradarscale)); } void drawminimap(gameent *d, float x, float y, float s) { vec pos = vec(d->o).sub(minimapcenter).mul(minimapscale).add(0.5f), dir; vecfromyawpitch(camera1->yaw, 0, 1, 0, dir); float scale = calcradarscale(); gle::defvertex(2); gle::deftexcoord0(); gle::begin(GL_TRIANGLE_FAN); loopi(16) { vec v = vec(0, -1, 0).rotate_around_z(i/16.0f*2*M_PI); gle::attribf(x + 0.5f*s*(1.0f + v.x), y + 0.5f*s*(1.0f + v.y)); vec tc = vec(dir).rotate_around_z(i/16.0f*2*M_PI); gle::attribf(1.0f - (pos.x + tc.x*scale*minimapscale.x), pos.y + tc.y*scale*minimapscale.y); } gle::end(); } void setradartex() { settexture("media/interface/radar/radar.png", 3); } void drawradar(float x, float y, float s) { gle::defvertex(2); gle::deftexcoord0(); gle::begin(GL_TRIANGLE_STRIP); gle::attribf(x, y); gle::attribf(0, 0); gle::attribf(x+s, y); gle::attribf(1, 0); gle::attribf(x, y+s); gle::attribf(0, 1); gle::attribf(x+s, y+s); gle::attribf(1, 1); gle::end(); } void drawteammate(gameent *d, float x, float y, float s, gameent *o, float scale, float blipsize = 1) { vec dir = d->o; dir.sub(o->o).div(scale); float dist = dir.magnitude2(), maxdist = 1 - 0.05f - 0.05f; if(dist >= maxdist) dir.mul(maxdist/dist); dir.rotate_around_z(-camera1->yaw*RAD); float bs = 0.06f*blipsize*s, bx = x + s*0.5f*(1.0f + dir.x), by = y + s*0.5f*(1.0f + dir.y); vec v(-0.5f, -0.5f, 0); v.rotate_around_z((90+o->yaw-camera1->yaw)*RAD); gle::attribf(bx + bs*v.x, by + bs*v.y); gle::attribf(0, 0); gle::attribf(bx + bs*v.y, by - bs*v.x); gle::attribf(1, 0); gle::attribf(bx - bs*v.x, by - bs*v.y); gle::attribf(1, 1); gle::attribf(bx - bs*v.y, by + bs*v.x); gle::attribf(0, 1); } void setbliptex(int team, const char *type = "") { defformatstring(blipname, "media/interface/radar/blip%s%s.png", teamblipcolor[validteam(team) ? team : 0], type); settexture(blipname, 3); } void drawplayerblip(gameent *d, float x, float y, float s, float blipsize = 1) { if(d->state != CS_ALIVE && d->state != CS_DEAD) return; float scale = calcradarscale(); setbliptex(d->team, d->state == CS_DEAD ? "_dead" : "_alive"); gle::defvertex(2); gle::deftexcoord0(); gle::begin(GL_QUADS); drawteammate(d, x, y, s, d, scale, blipsize); gle::end(); } void drawteammates(gameent *d, float x, float y, float s) { if(!radarteammates) return; float scale = calcradarscale(); int alive = 0, dead = 0; loopv(players) { gameent *o = players[i]; if(o != d && o->state == CS_ALIVE && o->team == d->team) { if(!alive++) { setbliptex(d->team, "_alive"); gle::defvertex(2); gle::deftexcoord0(); gle::begin(GL_QUADS); } drawteammate(d, x, y, s, o, scale); } } if(alive) gle::end(); loopv(players) { gameent *o = players[i]; if(o != d && o->state == CS_DEAD && o->team == d->team) { if(!dead++) { setbliptex(d->team, "_dead"); gle::defvertex(2); gle::deftexcoord0(); gle::begin(GL_QUADS); } drawteammate(d, x, y, s, o, scale); } } if(dead) gle::end(); } clientmode *cmode = NULL; void setclientmode() { cmode = NULL; } bool senditemstoserver = false, sendcrc = false; // after a map change, since server doesn't have map data int lastping = 0; bool connected = false, remote = false, demoplayback = false, gamepaused = false; int sessionid = 0, gamespeed = 100; string servdesc = "", servauth = ""; VARP(deadpush, 1, 2, 20); void sendmapinfo() { if(!connected) return; sendcrc = true; senditemstoserver = true; } void writeclientinfo(stream *f) { f->printf("name %s\n", escapestring(player1->name)); } bool allowedittoggle() { if(editmode) return true; if(isconnected() && multiplayer(false) && !m_edit) { conoutf(CON_ERROR, "editing in multiplayer requires edit mode"); return false; } return execidentbool("allowedittoggle", true); } void edittoggled(bool on) { addmsg(N_EDITMODE, "ri", on ? 1 : 0); if(player1->state==CS_DEAD) deathstate(player1, true); disablezoom(); player1->suicided = player1->respawned = -2; checkfollow(); } string clientmap = ""; void changemapserv(const char *name, int mode) // forced map change from the server { if(editmode) toggleedit(); if((m_edit && !name[0]) || !load_world(name)) { emptymap(0, true, name); senditemstoserver = false; } startgame(); } void changemap(const char *name, int mode) // request map change, server may ignore { server::forcemap(name, mode); if(!isconnected()) localconnect(); } void changemap(const char *name) { changemap(name, 0); } ICOMMAND(map, "s", (char *name), changemap(name)); void forceedit(const char *name) { changemap(name, 0); } void newmap(int size) { addmsg(N_NEWMAP, "ri", size); } int needclipboard = -1; void sendclipboard() { uchar *outbuf = NULL; int inlen = 0, outlen = 0; if(!packeditinfo(localedit, inlen, outbuf, outlen)) { outbuf = NULL; inlen = outlen = 0; } packetbuf p(16 + outlen, ENET_PACKET_FLAG_RELIABLE); putint(p, N_CLIPBOARD); putint(p, inlen); putint(p, outlen); if(outlen > 0) p.put(outbuf, outlen); sendclientpacket(p.finalize(), 1); needclipboard = -1; } void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3, const VSlot *vs) { if(m_edit) switch(op) { case EDIT_FLIP: case EDIT_COPY: case EDIT_PASTE: case EDIT_DELCUBE: { switch(op) { case EDIT_COPY: needclipboard = 0; break; case EDIT_PASTE: if(needclipboard > 0) { c2sinfo(true); sendclipboard(); } break; } addmsg(N_EDITF + op, "ri9i4", sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner); break; } case EDIT_ROTATE: { addmsg(N_EDITF + op, "ri9i5", sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, arg1); break; } case EDIT_MAT: case EDIT_FACE: { addmsg(N_EDITF + op, "ri9i6", sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, arg1, arg2); break; } case EDIT_TEX: { int tex1 = shouldpacktex(arg1); if(addmsg(N_EDITF + op, "ri9i6", sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, tex1 ? tex1 : arg1, arg2)) { messages.pad(2); int offset = messages.length(); if(tex1) packvslot(messages, arg1); *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); } break; } case EDIT_REPLACE: { int tex1 = shouldpacktex(arg1), tex2 = shouldpacktex(arg2); if(addmsg(N_EDITF + op, "ri9i7", sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, tex1 ? tex1 : arg1, tex2 ? tex2 : arg2, arg3)) { messages.pad(2); int offset = messages.length(); if(tex1) packvslot(messages, arg1); if(tex2) packvslot(messages, arg2); *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); } break; } case EDIT_CALCLIGHT: case EDIT_REMIP: { addmsg(N_EDITF + op, "r"); break; } case EDIT_VSLOT: { if(addmsg(N_EDITF + op, "ri9i6", sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, arg1, arg2)) { messages.pad(2); int offset = messages.length(); packvslot(messages, vs); *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); } break; } case EDIT_UNDO: case EDIT_REDO: { uchar *outbuf = NULL; int inlen = 0, outlen = 0; if(packundo(op, inlen, outbuf, outlen)) { if(addmsg(N_EDITF + op, "ri2", inlen, outlen)) messages.put(outbuf, outlen); delete[] outbuf; } break; } } } void printvar(gameent *d, ident *id) { if(id) switch(id->type) { case ID_VAR: { int val = *id->storage.i; string str; if(val < 0) formatstring(str, "%d", val); else if(id->flags&IDF_HEX && id->maxval==0xFFFFFF) formatstring(str, "0x%.6X (%d, %d, %d)", val, (val>>16)&0xFF, (val>>8)&0xFF, val&0xFF); else formatstring(str, id->flags&IDF_HEX ? "0x%X" : "%d", val); conoutf("%s set map var \"%s\" to %s", colorname(d), id->name, str); break; } case ID_FVAR: conoutf("%s set map var \"%s\" to %s", colorname(d), id->name, floatstr(*id->storage.f)); break; case ID_SVAR: conoutf("%s set map var \"%s\" to \"%s\"", colorname(d), id->name, *id->storage.s); break; } } void vartrigger(ident *id) { if(!m_edit) return; switch(id->type) { case ID_VAR: addmsg(N_EDITVAR, "risi", ID_VAR, id->name, *id->storage.i); break; case ID_FVAR: addmsg(N_EDITVAR, "risf", ID_FVAR, id->name, *id->storage.f); break; case ID_SVAR: addmsg(N_EDITVAR, "riss", ID_SVAR, id->name, *id->storage.s); break; default: return; } printvar(player1, id); } void pausegame(bool val) { if(!connected) return; if(!remote) server::forcepaused(val); else addmsg(N_PAUSEGAME, "ri", val ? 1 : 0); } ICOMMAND(pausegame, "i", (int *val), pausegame(*val > 0)); ICOMMAND(paused, "iN$", (int *val, int *numargs, ident *id), { if(*numargs > 0) pausegame(clampvar(id, *val, 0, 1) > 0); else if(*numargs < 0) intret(gamepaused ? 1 : 0); else printvar(id, gamepaused ? 1 : 0); }); bool ispaused() { return gamepaused; } bool allowmouselook() { return !gamepaused || !remote || m_edit; } void changegamespeed(int val) { if(!connected) return; if(!remote) server::forcegamespeed(val); else addmsg(N_GAMESPEED, "ri", val); } ICOMMAND(gamespeed, "iN$", (int *val, int *numargs, ident *id), { if(*numargs > 0) changegamespeed(clampvar(id, *val, 10, 1000)); else if(*numargs < 0) intret(gamespeed); else printvar(id, gamespeed); }); ICOMMAND(prettygamespeed, "i", (), result(tempformatstring("%d.%02dx", gamespeed/100, gamespeed%100))); int scaletime(int t) { return t*gamespeed; } // collect c2s messages conveniently vector messages; int messagecn = -1, messagereliable = false; bool addmsg(int type, const char *fmt, ...) { if(!connected) return false; static uchar buf[MAXTRANS]; ucharbuf p(buf, sizeof(buf)); putint(p, type); int numi = 1, numf = 0, nums = 0; bool reliable = false; if(fmt) { va_list args; va_start(args, fmt); while(*fmt) switch(*fmt++) { case 'r': reliable = true; break; case 'c': { break; } case 'v': { int n = va_arg(args, int); int *v = va_arg(args, int *); loopi(n) putint(p, v[i]); numi += n; break; } case 'i': { int n = isdigit(*fmt) ? *fmt++-'0' : 1; loopi(n) putint(p, va_arg(args, int)); numi += n; break; } case 'f': { int n = isdigit(*fmt) ? *fmt++-'0' : 1; loopi(n) putfloat(p, (float)va_arg(args, double)); numf += n; break; } case 's': sendstring(va_arg(args, const char *), p); nums++; break; } va_end(args); } int num = nums || numf ? 0 : numi, msgsize = server::msgsizelookup(type); if(msgsize && num!=msgsize) { fatal("inconsistent msg size for %d (%d != %d)", type, num, msgsize); } if(reliable) messagereliable = true; messages.put(buf, p.length()); return true; } void connectattempt(const char *name, const char *password, const ENetAddress &address) { } void connectfail() { } void gameconnect(bool _remote) { remote = _remote; } void gamedisconnect(bool cleanup) { if(remote) stopfollowing(); connected = remote = false; player1->clientnum = -1; if(editmode) toggleedit(); sessionid = 0; messages.setsize(0); messagereliable = false; messagecn = -1; player1->respawn(); player1->lifesequence = 0; player1->state = CS_ALIVE; sendcrc = senditemstoserver = false; demoplayback = false; gamepaused = false; gamespeed = 100; clearclients(false); if(cleanup) { clientmap[0] = '\0'; } } VARP(teamcolorchat, 0, 1, 1); const char *chatcolorname(gameent *d) { return teamcolorchat ? teamcolorname(d, NULL) : colorname(d); } void toserver(char *text) { conoutf(CON_CHAT, "%s:%s %s", chatcolorname(player1), teamtextcode[0], text); addmsg(N_TEXT, "rcs", player1, text); } COMMANDN(say, toserver, "C"); void sayteam(char *text) { if(!m_teammode || !validteam(player1->team)) return; conoutf(CON_TEAMCHAT, "%s:%s %s", chatcolorname(player1), teamtextcode[player1->team], text); addmsg(N_SAYTEAM, "rcs", player1, text); } COMMAND(sayteam, "C"); ICOMMAND(servcmd, "C", (char *cmd), addmsg(N_SERVCMD, "rs", cmd)); static void sendposition(gameent *d, packetbuf &q) { putint(q, N_POS); putuint(q, d->clientnum); // 3 bits phys state, 1 bit life sequence, 2 bits move, 2 bits strafe uchar physstate = d->physstate | ((d->lifesequence&1)<<3) | ((d->move&3)<<4) | ((d->strafe&3)<<6); q.put(physstate); ivec o = ivec(vec(d->o.x, d->o.y, d->o.z-d->eyeheight).mul(DMF)); uint vel = min(int(d->vel.magnitude()*DVELF), 0xFFFF), fall = min(int(d->falling.magnitude()*DVELF), 0xFFFF); // 3 bits position, 1 bit velocity, 3 bits falling, 1 bit material, 1 bit crouching uint flags = 0; if(o.x < 0 || o.x > 0xFFFF) flags |= 1<<0; if(o.y < 0 || o.y > 0xFFFF) flags |= 1<<1; if(o.z < 0 || o.z > 0xFFFF) flags |= 1<<2; if(vel > 0xFF) flags |= 1<<3; if(fall > 0) { flags |= 1<<4; if(fall > 0xFF) flags |= 1<<5; if(d->falling.x || d->falling.y || d->falling.z > 0) flags |= 1<<6; } if((lookupmaterial(d->feetpos())&MATF_CLIP) == MAT_GAMECLIP) flags |= 1<<7; if(d->crouching < 0) flags |= 1<<8; putuint(q, flags); loopk(3) { q.put(o[k]&0xFF); q.put((o[k]>>8)&0xFF); if(o[k] < 0 || o[k] > 0xFFFF) q.put((o[k]>>16)&0xFF); } uint dir = (d->yaw < 0 ? 360 + int(d->yaw)%360 : int(d->yaw)%360) + clamp(int(d->pitch+90), 0, 180)*360; q.put(dir&0xFF); q.put((dir>>8)&0xFF); q.put(clamp(int(d->roll+90), 0, 180)); q.put(vel&0xFF); if(vel > 0xFF) q.put((vel>>8)&0xFF); float velyaw, velpitch; vectoyawpitch(d->vel, velyaw, velpitch); uint veldir = (velyaw < 0 ? 360 + int(velyaw)%360 : int(velyaw)%360) + clamp(int(velpitch+90), 0, 180)*360; q.put(veldir&0xFF); q.put((veldir>>8)&0xFF); if(fall > 0) { q.put(fall&0xFF); if(fall > 0xFF) q.put((fall>>8)&0xFF); if(d->falling.x || d->falling.y || d->falling.z > 0) { float fallyaw, fallpitch; vectoyawpitch(d->falling, fallyaw, fallpitch); uint falldir = (fallyaw < 0 ? 360 + int(fallyaw)%360 : int(fallyaw)%360) + clamp(int(fallpitch+90), 0, 180)*360; q.put(falldir&0xFF); q.put((falldir>>8)&0xFF); } } } void sendposition(gameent *d, bool reliable) { if(d->state != CS_ALIVE && d->state != CS_EDITING) return; packetbuf q(100, reliable ? ENET_PACKET_FLAG_RELIABLE : 0); sendposition(d, q); sendclientpacket(q.finalize(), 0); } void sendpositions() { loopv(players) { gameent *d = players[i]; if((d == player1) && (d->state == CS_ALIVE || d->state == CS_EDITING)) { packetbuf q(100); sendposition(d, q); for(int j = i+1; j < players.length(); j++) { gameent *d = players[j]; if((d == player1) && (d->state == CS_ALIVE || d->state == CS_EDITING)) sendposition(d, q); } sendclientpacket(q.finalize(), 0); break; } } } void sendmessages() { packetbuf p(MAXTRANS); if(sendcrc) { p.reliable(); sendcrc = false; const char *mname = getclientmap(); putint(p, N_MAPCRC); sendstring(mname, p); putint(p, mname[0] ? getmapcrc() : 0); } if(senditemstoserver) { p.reliable(); entities::putitems(p); if(cmode) cmode->senditems(p); senditemstoserver = false; } if(messages.length()) { p.put(messages.getbuf(), messages.length()); messages.setsize(0); if(messagereliable) p.reliable(); messagereliable = false; messagecn = -1; } if(totalmillis-lastping>250) { putint(p, N_PING); putint(p, totalmillis); lastping = totalmillis; } sendclientpacket(p.finalize(), 1); } void c2sinfo(bool force) // send update to the server { static int lastupdate = -1000; if(totalmillis - lastupdate < 40 && !force) return; // don't update faster than 30fps lastupdate = totalmillis; sendpositions(); sendmessages(); flushclient(); } void sendintro() { packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); putint(p, N_CONNECT); sendstring(player1->name, p); putint(p, player1->playermodel); putint(p, player1->playercolor); sendclientpacket(p.finalize(), 1); } void updatepos(gameent *d) { // update the position of other clients in the game in our world // don't care if he's in the scenery or other players, // just don't overlap with our client const float r = player1->radius+d->radius; const float dx = player1->o.x-d->o.x; const float dy = player1->o.y-d->o.y; const float dz = player1->o.z-d->o.z; const float rz = player1->aboveeye+d->eyeheight; const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz); if(fxstate!=CS_SPECTATOR && d->state!=CS_DEAD) { if(fxo.y += dy<0 ? r-fy : -(r-fy); // push aside else d->o.x += dx<0 ? r-fx : -(r-fx); } int lagtime = totalmillis-d->lastupdate; if(lagtime) { if(d->state!=CS_SPAWNING && d->lastupdate) d->plag = (d->plag*5+lagtime)/6; d->lastupdate = totalmillis; } } void parsepositions(ucharbuf &p) { int type; while(p.remaining()) switch(type = getint(p)) { case N_POS: // position of another client { int cn = getuint(p), physstate = p.get(), flags = getuint(p); vec o, vel, falling; float yaw, pitch, roll; loopk(3) { int n = p.get(); n |= p.get()<<8; if(flags&(1<>3)&1; gameent *d = getclient(cn); if(!d || d->lifesequence < 0 || seqcolor!=(d->lifesequence&1) || d->state==CS_DEAD) continue; float oldyaw = d->yaw, oldpitch = d->pitch, oldroll = d->roll; d->yaw = yaw; d->pitch = pitch; d->roll = roll; d->move = (physstate>>4)&2 ? -1 : (physstate>>4)&1; d->strafe = (physstate>>6)&2 ? -1 : (physstate>>6)&1; d->crouching = (flags&(1<<8))!=0 ? -1 : abs(d->crouching); vec oldpos(d->o); d->o = o; d->o.z += d->eyeheight; d->vel = vel; d->falling = falling; d->physstate = physstate&7; updatephysstate(d); updatepos(d); if(smoothmove && d->smoothmillis>=0 && oldpos.dist(d->o) < smoothdist) { d->newpos = d->o; d->newyaw = d->yaw; d->newpitch = d->pitch; d->newroll = d->roll; d->o = oldpos; d->yaw = oldyaw; d->pitch = oldpitch; d->roll = oldroll; (d->deltapos = oldpos).sub(d->newpos); d->deltayaw = oldyaw - d->newyaw; if(d->deltayaw > 180) d->deltayaw -= 360; else if(d->deltayaw < -180) d->deltayaw += 360; d->deltapitch = oldpitch - d->newpitch; d->deltaroll = oldroll - d->newroll; d->smoothmillis = lastmillis; } else d->smoothmillis = 0; if(d->state==CS_LAGGED || d->state==CS_SPAWNING) d->state = CS_ALIVE; break; } case N_TELEPORT: { int cn = getint(p), tp = getint(p), td = getint(p); gameent *d = getclient(cn); if(!d || d->lifesequence < 0 || d->state==CS_DEAD) continue; entities::teleporteffects(d, tp, td, false); break; } case N_JUMPPAD: { int cn = getint(p), jp = getint(p); gameent *d = getclient(cn); if(!d || d->lifesequence < 0 || d->state==CS_DEAD) continue; entities::jumppadeffects(d, jp, false); break; } default: neterr("type"); return; } } void parsestate(gameent *d, ucharbuf &p, bool resume = false) { if(!d) { static gameent dummy; d = &dummy; } if(resume) { if(d==player1) getint(p); else d->state = getint(p); d->frags = getint(p); d->flags = getint(p); d->deaths = getint(p); } d->lifesequence = getint(p); d->health = getint(p); d->maxhealth = getint(p); if(resume && d==player1) { getint(p); loopi(NUMGUNS) getint(p); } else { int gun = getint(p); d->gunselect = clamp(gun, 0, NUMGUNS-1); loopi(NUMGUNS) d->ammo[i] = getint(p); } } void parsemessages(int cn, gameent *d, ucharbuf &p) { static char text[MAXTRANS]; int type; while(p.remaining()) switch(type = getint(p)) { case N_SERVINFO: // welcome messsage from the server { int mycn = getint(p), prot = getint(p); if(prot!=PROTOCOL_VERSION) { conoutf(CON_ERROR, "you are using a different game protocol (you: %d, server: %d)", PROTOCOL_VERSION, prot); disconnect(); return; } sessionid = getint(p); player1->clientnum = mycn; // we are now connected sendintro(); break; } case N_WELCOME: { connected = true; notifywelcome(); break; } case N_PAUSEGAME: { bool val = getint(p) > 0; int cn = getint(p); gameent *a = cn >= 0 ? getclient(cn) : NULL; gamepaused = val; player1->attacking = ACT_IDLE; if(a) conoutf("%s %s the game", colorname(a), val ? "paused" : "resumed"); else conoutf("game is %s", val ? "paused" : "resumed"); break; } case N_GAMESPEED: { int val = clamp(getint(p), 10, 1000), cn = getint(p); gameent *a = cn >= 0 ? getclient(cn) : NULL; gamespeed = val; if(a) conoutf("%s set gamespeed to %d", colorname(a), val); else conoutf("gamespeed is %d", val); break; } case N_CLIENT: { int cn = getint(p), len = getuint(p); ucharbuf q = p.subbuf(len); parsemessages(cn, getclient(cn), q); break; } case N_SOUND: if(!d) return; playsound(getint(p), &d->o); break; case N_TEXT: { if(!d) return; getstring(text, p); filtertext(text, text, true, true); if(d->state!=CS_DEAD && d->state!=CS_SPECTATOR) particle_textcopy(d->abovehead(), text, PART_TEXT, 2000, 0x32FF64, 4.0f, -8); conoutf(CON_CHAT, "%s:%s %s", chatcolorname(d), teamtextcode[0], text); break; } case N_SAYTEAM: { int tcn = getint(p); gameent *t = getclient(tcn); getstring(text, p); filtertext(text, text, true, true); if(!t) break; int team = validteam(t->team) ? t->team : 0; if(t->state!=CS_DEAD && t->state!=CS_SPECTATOR) particle_textcopy(t->abovehead(), text, PART_TEXT, 2000, teamtextcolor[team], 4.0f, -8); conoutf(CON_TEAMCHAT, "%s:%s %s", chatcolorname(t), teamtextcode[team], text); break; } case N_MAPCHANGE: getstring(text, p); changemapserv(text, getint(p)); if(getint(p)) entities::spawnitems(); else senditemstoserver = false; break; case N_INITCLIENT: // another client either connected or changed name/team { int cn = getint(p); gameent *d = newclient(cn); if(!d) { getstring(text, p); getstring(text, p); getint(p); getint(p); break; } getstring(text, p); filtertext(text, text, false, false, MAXNAMELEN); if(!text[0]) copystring(text, "unnamed"); if(d->name[0]) // already connected { if(strcmp(d->name, text)) conoutf("%s is now known as %s", colorname(d), colorname(d, text)); } else // new client { conoutf("\f0join:\f7 %s", colorname(d, text)); if(needclipboard >= 0) needclipboard++; } copystring(d->name, text, MAXNAMELEN+1); d->team = getint(p); if(!validteam(d->team)) d->team = 0; d->playermodel = getint(p); d->playercolor = getint(p); break; } case N_SWITCHNAME: getstring(text, p); if(d) { filtertext(text, text, false, false, MAXNAMELEN); if(!text[0]) copystring(text, "unnamed"); if(strcmp(text, d->name)) { conoutf("%s is now known as %s", colorname(d), colorname(d, text)); copystring(d->name, text, MAXNAMELEN+1); } } break; case N_CDIS: clientdisconnected(getint(p)); break; case N_SPAWN: { if(d) { if(d->state==CS_DEAD && d->lastpain) saveragdoll(d); d->respawn(); } parsestate(d, p); if(!d) break; d->state = CS_SPAWNING; if(d == followingplayer()) lasthit = 0; checkfollow(); break; } case N_SPAWNSTATE: { int scn = getint(p); gameent *s = getclient(scn); if(!s) { parsestate(NULL, p); break; } if(s->state==CS_DEAD && s->lastpain) saveragdoll(s); if(s==player1) { if(editmode) toggleedit(); } s->respawn(); parsestate(s, p); s->state = CS_ALIVE; if(cmode) cmode->pickspawn(s); else findplayerspawn(s, -1, m_teammode ? s->team : 0); if(s == player1) { lasthit = 0; } if(cmode) cmode->respawned(s); checkfollow(); addmsg(N_SPAWN, "rcii", s, s->lifesequence, s->gunselect); break; } case N_HITPUSH: { int tcn = getint(p), atk = getint(p), damage = getint(p); gameent *target = getclient(tcn); vec dir; loopk(3) dir[k] = getint(p)/DNF; if(!target || !validatk(atk)) break; target->hitpush(damage * (target->health<=0 ? deadpush : 1), dir, NULL, atk); break; } case N_DIED: { int vcn = getint(p), acn = getint(p), frags = getint(p), tfrags = getint(p); gameent *victim = getclient(vcn), *actor = getclient(acn); if(!actor) break; actor->frags = frags; if(!victim) break; killed(victim, actor); break; } case N_RESUME: { for(;;) { int cn = getint(p); if(p.overread() || cn<0) break; gameent *d = (cn == player1->clientnum ? player1 : newclient(cn)); parsestate(d, p, true); } break; } case N_CLIPBOARD: { int cn = getint(p), unpacklen = getint(p), packlen = getint(p); gameent *d = getclient(cn); ucharbuf q = p.subbuf(max(packlen, 0)); if(d) unpackeditinfo(d->edit, q.buf, q.maxlen, unpacklen); break; } case N_UNDO: case N_REDO: { int cn = getint(p), unpacklen = getint(p), packlen = getint(p); gameent *d = getclient(cn); ucharbuf q = p.subbuf(max(packlen, 0)); if(d) unpackundo(q.buf, q.maxlen, unpacklen); break; } case N_EDITF: // coop editing messages case N_EDITT: case N_EDITM: case N_FLIP: case N_COPY: case N_PASTE: case N_ROTATE: case N_REPLACE: case N_DELCUBE: case N_EDITVSLOT: { if(!d) return; selinfo sel; sel.o.x = getint(p); sel.o.y = getint(p); sel.o.z = getint(p); sel.s.x = getint(p); sel.s.y = getint(p); sel.s.z = getint(p); sel.grid = getint(p); sel.orient = getint(p); sel.cx = getint(p); sel.cxs = getint(p); sel.cy = getint(p), sel.cys = getint(p); sel.corner = getint(p); switch(type) { case N_EDITF: { int dir = getint(p), mode = getint(p); if(sel.validate()) mpeditface(dir, mode, sel, false); break; } case N_EDITT: { int tex = getint(p), allfaces = getint(p); if(p.remaining() < 2) return; int extra = lilswap(*(const ushort *)p.pad(2)); if(p.remaining() < extra) return; ucharbuf ebuf = p.subbuf(extra); if(sel.validate()) mpedittex(tex, allfaces, sel, ebuf); break; } case N_EDITM: { int mat = getint(p), filter = getint(p); if(sel.validate()) mpeditmat(mat, filter, sel, false); break; } case N_FLIP: if(sel.validate()) mpflip(sel, false); break; case N_COPY: if(d && sel.validate()) mpcopy(d->edit, sel, false); break; case N_PASTE: if(d && sel.validate()) mppaste(d->edit, sel, false); break; case N_ROTATE: { int dir = getint(p); if(sel.validate()) mprotate(dir, sel, false); break; } case N_REPLACE: { int oldtex = getint(p), newtex = getint(p), insel = getint(p); if(p.remaining() < 2) return; int extra = lilswap(*(const ushort *)p.pad(2)); if(p.remaining() < extra) return; ucharbuf ebuf = p.subbuf(extra); if(sel.validate()) mpreplacetex(oldtex, newtex, insel>0, sel, ebuf); break; } case N_DELCUBE: if(sel.validate()) mpdelcube(sel, false); break; case N_EDITVSLOT: { int delta = getint(p), allfaces = getint(p); if(p.remaining() < 2) return; int extra = lilswap(*(const ushort *)p.pad(2)); if(p.remaining() < extra) return; ucharbuf ebuf = p.subbuf(extra); if(sel.validate()) mpeditvslot(delta, allfaces, sel, ebuf); break; } } break; } case N_REMIP: if(!d) return; conoutf("%s remipped", colorname(d)); mpremip(false); break; case N_CALCLIGHT: if(!d) return; conoutf("%s calced lights", colorname(d)); mpcalclight(false); break; case N_EDITENT: // coop edit of ent { if(!d) return; int i = getint(p); float x = getint(p)/DMF, y = getint(p)/DMF, z = getint(p)/DMF; int type = getint(p); int attr1 = getint(p), attr2 = getint(p), attr3 = getint(p), attr4 = getint(p), attr5 = getint(p); mpeditent(i, vec(x, y, z), type, attr1, attr2, attr3, attr4, attr5, false); break; } case N_EDITVAR: { if(!d) return; int type = getint(p); getstring(text, p); string name; filtertext(name, text, false); ident *id = getident(name); switch(type) { case ID_VAR: { int val = getint(p); if(id && id->flags&IDF_OVERRIDE && !(id->flags&IDF_READONLY)) setvar(name, val); break; } case ID_FVAR: { float val = getfloat(p); if(id && id->flags&IDF_OVERRIDE && !(id->flags&IDF_READONLY)) setfvar(name, val); break; } case ID_SVAR: { getstring(text, p); if(id && id->flags&IDF_OVERRIDE && !(id->flags&IDF_READONLY)) setsvar(name, text); break; } } printvar(d, id); break; } case N_PONG: addmsg(N_CLIENTPING, "i", player1->ping = (player1->ping*5+totalmillis-getint(p))/6); break; case N_CLIENTPING: if(!d) return; d->ping = getint(p); break; case N_TIMEUP: timeupdate(getint(p)); break; case N_SERVMSG: getstring(text, p); conoutf("%s", text); break; case N_EDITMODE: { int val = getint(p); if(!d) break; if(val) { d->editstate = d->state; d->state = CS_EDITING; } else { d->state = d->editstate; if(d->state==CS_DEAD) deathstate(d, true); } checkfollow(); break; } case N_NEWMAP: { int size = getint(p); if(size>=0) emptymap(size, true, NULL); else enlargemap(true); if(d && d!=player1) { int newsize = 0; while(1<=0 ? "%s started a new map of size %d" : "%s enlarged the map to size %d", colorname(d), newsize); } break; } case N_SERVCMD: getstring(text, p); break; default: neterr("type", cn < 0); return; } } void receivefile(packetbuf &p) { } void parsepacketclient(int chan, packetbuf &p) // processes any updates from the server { if(p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED) return; switch(chan) { case 0: parsepositions(p); break; case 1: parsemessages(-1, NULL, p); break; case 2: receivefile(p); break; } } }