// client processing of the incoming network stream #include "cube.h" #include "bot/bot.h" VARP(networkdebug, 0, 0, 1); #define DEBUGCOND (networkdebug==1) extern bool watchingdemo; extern string clientpassword; extern int ispaused; void *downloaddemomenu = NULL; static vector demo_mlines; packetqueue pktlogger; void neterr(const char *s) { conoutf("\f3illegal network message (%s)", s); // might indicate a client/server communication bug, create error report pktlogger.flushtolog("packetlog.txt"); conoutf("\f3wrote a network error report to packetlog.txt, please post this file to the bugtracker now!"); disconnect(); } VARP(autogetmap, 0, 1, 1); // only if the client doesn't have that map VARP(autogetnewmaprevisions, 0, 1, 1); VAR(disconnectonmaperror, 0, 1, 1); extern bool incompatiblemap; extern void trydisconnect(); bool localwrongmap = false; bool changemapserv(char *name, int mode, int download, int revision) // forced map change from the server { gamemode = mode; if(m_demo) return true; if(m_coop) { if(!name[0] || load_world(name) < 0) empty_world(0, true); return true; } else if(player1->state==CS_EDITING) { /*conoutf("SANITY drop from EDITING");*/ toggleedit(true); } // fix stuck-in-editmode bug bool loaded = load_world(name) >= 0; if(disconnectonmaperror && incompatiblemap && multiplayer(NULL)) trydisconnect(); if(download > 0) { bool revmatch = hdr.maprevision == revision || revision == 0; if(watchingdemo) { if(loaded && !revmatch) conoutf("\f3demo was recorded on map revision %d, you have map revision %d", revision, hdr.maprevision); } else { if(securemapcheck(name, false)) return true; bool sizematch = maploaded == download || download < 10; if(loaded && sizematch && revmatch) return true; bool getnewrev = autogetnewmaprevisions && revision > hdr.maprevision; if(autogetmap || getnewrev) { if(!loaded || getnewrev) getmap(); // no need to ask else { defformatstring(msg)("map '%s' revision: local %d, provided by server %d", name, hdr.maprevision, revision); alias("__getmaprevisions", msg, true); showmenu("getmap"); } } else { if(!loaded || download < 10) conoutf("\"getmap\" to download the current map from the server"); else conoutf("\"getmap\" to download a %s version of the current map from the server", revision == 0 ? "different" : (revision > hdr.maprevision ? "newer" : "older")); } } } else return true; return false; } // 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 void updateplayerpos(playerent *d) { 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_DEAD) { if(fxo.y += dy<0 ? r-fy : -(r-fy); // push aside else d->o.x += dx<0 ? r-fx : -(r-fx); } } void updatelagtime(playerent *d) { 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 SV_POS: // position of another client case SV_POSC: case SV_POSC2: case SV_POSC3: case SV_POSC4: { int cn, f, yaw, pitch, z; vec o, vel; if(type != SV_POS) { bitbuf q(p); cn = q.getbits(5); int usefactor = type - SV_POSC + 7; o.x = q.getbits(usefactor + 4) / DMF; o.y = q.getbits(usefactor + 4) / DMF; yaw = q.getbits(YAWBITS); pitch = q.getbits(PITCHBITS); f = q.getbits(FLAGBITS); if(f & (1 << 9)) { vel.x = (q.getbits(4) - 8) / DVELF; vel.y = (q.getbits(4) - 8) / DVELF; vel.z = (q.getbits(4) - 8) / DVELF; } else vel.x = vel.y = vel.z = 0.0f; int full = q.getbits(1); int s = q.rembits(); if(s < 3) s += 8; if(full) s = 11; z = q.getbits(s); } else { cn = getint(p); o.x = getuint(p)/DMF; o.y = getuint(p)/DMF; z = getuint(p); uint64_t ff = getuintn(p, (YAWBITS + PITCHBITS + FLAGBITS + 7) / 8); yaw = (ff >> (FLAGBITS + PITCHBITS)) & ((1 << YAWBITS) - 1); pitch = (ff >> FLAGBITS) & ((1 << PITCHBITS) - 1); f = ff & ((1 << FLAGBITS) - 1); if(f & (1 << 9)) { vel.x = getint(p)/DVELF; vel.y = getint(p)/DVELF; vel.z = getint(p)/DVELF; } else vel.x = vel.y = vel.z = 0.0f; } if(f & (1 << 10)) z = -z; o.z = z / DMF; int seqcolor = (f>>6)&1; playerent *d = getclient(cn); if(!d || seqcolor!=(d->lifesequence&1)) continue; vec oldpos(d->o); float oldyaw = d->yaw, oldpitch = d->pitch; d->o = o; d->o.z += d->eyeheight; d->yaw = decodeyaw(yaw); d->pitch = decodepitch(pitch); if(d->weaponsel->type == GUN_SNIPER) { sniperrifle *sr = (sniperrifle *)d->weaponsel; sr->scoped = d->scoping = (f & (1 << 5)) ? true : false; } d->roll = 0; d->vel = vel; int ft = f & 0x1f; d->strafe = (ft % 3) - 1; ft /= 3; d->move = (ft % 3) - 1; ft /= 3; d->jumpd = (f & (1 << 8)) ? true : false; // FIXME d->onfloor = f & (1 << 7) ? true : false; d->onladder = ft == 1; d->last_pos = totalmillis; updatecrouch(d, ft == 2); updateplayerpos(d); updatelagtime(d); extern int smoothmove, smoothdist; if(d->state==CS_DEAD) { d->resetinterp(); d->smoothmillis = 0; } else if(smoothmove && d->smoothmillis>=0 && oldpos.dist(d->o) < smoothdist) { d->newpos = d->o; d->newpos.z -= d->eyeheight; d->newyaw = d->yaw; d->newpitch = d->pitch; d->o = oldpos; d->yaw = oldyaw; d->pitch = oldpitch; oldpos.z -= d->eyeheight; (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->smoothmillis = lastmillis; } else d->smoothmillis = 0; if(d->state==CS_LAGGED || d->state==CS_SPAWNING) d->state = CS_ALIVE; // when playing a demo spectate first player we know about if(player1->isspectating() && player1->spectatemode==SM_NONE) togglespect(); break; } default: neterr("type"); return; } } int lastspawn = 0; void onCallVote(int type, int vcn, char *text, char *a) { exechook(HOOK_SP_MP, "onCallVote", "%d %d [%s] [%s]", type, vcn, text, a); } void onChangeVote(int mod, int id) { exechook(HOOK_SP_MP, "onChangeVote", "%d %d", mod, id); } VARP(voicecomsounds, 0, 1, 2); struct session_s { enet_uint32 serverip, clientip, clientipcensored, curpeerip; int curpeerport, serverclock, clientclock, cn, clientsalt, datecodes; uchar serverpubkey[32], clientsignature[64]; string clan, publiccomment; } session; int srvgamesalt; void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { static char text[MAXTRANS]; int type, joining = 0; while(p.remaining()) { type = getint(p); #ifdef _DEBUG if(type!=SV_POS && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_PONG && type!=SV_CLIENT) { DEBUGVAR(cn); ASSERT(type>=0 && typecn = getint(p); int prot = getint(p); if(prot!=CUR_PROTOCOL_VERSION && !(watchingdemo && prot == -PROTOCOL_VERSION)) { conoutf("\f3incompatible game protocol (local protocol: %d :: server protocol: %d)", CUR_PROTOCOL_VERSION, prot); conoutf("\f3if this occurs a lot, obtain an upgrade from \f1http://assault.cubers.net"); if(watchingdemo) conoutf("breaking loop : \f3this demo is using a different protocol\f5 : end it now!"); // SVN-WiP-bug: causes endless retry loop else! else disconnect(); return; } int req_auth = getint(p); if(!sk && req_auth) { conoutf("\f3server requires a valid game account to connect"); conoutf("\f3please authenticate or create a new game account"); disconnect(); return; } if(getint(p) > 0) conoutf("INFO: this server is password protected"); sessionid = getint(p); // salt s->serverip = getip4(p); s->clientip = getip4(p); s->clientipcensored = getip4(p); s->serverclock = getint(p); char cc[3] = { (char)getint(p), (char)getint(p), 0 }; filtercountrycode(cc, cc); p.get(s->serverpubkey, 32); entropy_get((uchar *)&(s->clientsalt), sizeof(int)); s->clientclock = (int) (time(NULL) / (time_t) 60); int clockoffset = s->serverclock - s->clientclock; if(curpeer && iabs(clockoffset) > 60 * 24) conoutf("\f3warning: server and client clock are offset more than one day (%d minutes)", clockoffset); clientlogf("own IP: %s, censored own IP: %s, %s, clock offset %d hours %d minutes", iptoa(s->clientip, tmp1), iptoa(s->clientipcensored, tmp2), cc, clockoffset / 60, clockoffset % 60); if(curpeer && s->serverip) { s->curpeerip = ENET_NET_TO_HOST_32(curpeer->address.host); s->curpeerport = curpeer->address.port; } if(s->curpeerip && s->curpeerip != s->serverip) conoutf("\f3warning: server IP mismatch (true: %s, reported: %s)", iptoa(s->curpeerip, tmp1), iptoa(s->serverip, tmp2)); defformatstring(challenge)("SERVINFOCHALLENGE<(%d) cn: %d c: %s (%s) s: %s:%d", sessionid, s->cn, iptoa(s->clientipcensored, tmp1), cc, iptoa(s->serverip, tmp2), s->curpeerport); concatformatstring(challenge, " %s st: %d ct: %d (%d)>", bin2hex(tmp1, s->serverpubkey, 32), s->serverclock, s->clientclock, s->clientsalt); clientlogf("auth challenge: %s", challenge); int chlen = (int)strlen(challenge); if(chlen >= MAXSTRLEN - 65) { conoutf("\f3ERROR: oversized auth challenge (%d), can't connect", chlen); disconnect(); return; } if(sk) ed25519_sign((uchar*)challenge, NULL, (uchar*)challenge, chlen, sk); packetbuf pr(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); putint(pr, SV_SERVINFO_RESPONSE); putint(pr, s->clientsalt); putint(pr, s->clientclock); putint(pr, s->curpeerport); putip4(pr, s->curpeerip); int wantauth = sk && curpeer; putint(pr, wantauth); if(wantauth) { memcpy(s->clientsignature, challenge, 64); pr.put(sk + 32, 32); pr.put(s->clientsignature, 64); } sendpackettoserv(1, pr.finalize()); break; } case SV_SERVINFO_CONTD: // 2nd welcome message from the server { session_s *s = &session; string tmp, servauth; int auth = getint(p); if(auth) { formatstring(servauth)("SERVINFOSIGNED<%s>", bin2hex(tmp, (uchar*)s->clientsignature, 64)); memmove(servauth + 64, servauth, (auth = (int)strlen(servauth)) + 1); p.get((uchar*)servauth, 64); s->datecodes = getint(p); getstring(text, p); filtertext(s->clan, text, FTXT__VITACLAN); getstring(text, p); filtertext(s->publiccomment, text, FTXT__VITACOMMENT); #ifdef _DEBUG formatstring(text)("server-side%s", s->datecodes ? " timed flags:" : ""); loopi(VS_NUMCOUNTERS) if(s->datecodes & (1 << i)) concatformatstring(text, " %s", vskeywords[i]); if(s->clan[0]) concatformatstring(text, " clan tag: \"%s\"", s->clan); if(s->publiccomment[0]) concatformatstring(text, " comment: \"%s\"", s->publiccomment); if(strlen(text) > 12) conoutf("%s", text); #endif } if(curpeer && sk && !auth) conoutf("\f3server refuses to authenticate"); if(sk && auth) { if(!ed25519_sign_check((uchar*)servauth, auth + 64, s->serverpubkey)) conoutf("\f3server authentication failed"); else clientlogf("server authenticated as %s", bin2hex(tmp, s->serverpubkey, 32)); } player1->clientnum = s->cn; sendintro(); break; } case SV_WELCOME: joining = getint(p); player1->resetspec(); resetcamera(); break; case SV_CLIENT: { int cn = getint(p), len = getuint(p); ucharbuf q = p.subbuf(len); parsemessages(cn, getclient(cn), q, demo); break; } case SV_SOUND: audiomgr.playsound(getint(p), d); break; case SV_VOICECOMTEAM: { playerent *d = getclient(getint(p)); if(d) d->lastvoicecom = lastmillis; int t = getint(p); if(!d || !(d->muted || d->ignored)) { if ( voicecomsounds == 1 || (voicecomsounds == 2 && m_teammode) ) audiomgr.playsound(t, SP_HIGH); } break; } case SV_VOICECOM: { int t = getint(p); if(!d || !(d->muted || d->ignored)) { if ( voicecomsounds == 1 ) audiomgr.playsound(t, SP_HIGH); } if(d) d->lastvoicecom = lastmillis; break; } case SV_TEAMTEXTME: case SV_TEAMTEXT: { int cn = getint(p); getstring(text, p); filtertext(text, text, FTXT__CHAT); playerent *d = getclient(cn); if(!d) break; if(d->ignored) clientlogf("ignored: %s%s %s", colorname(d), type == SV_TEAMTEXT ? ":" : "", text); else { if(m_teammode || team_isspect(player1->team)) conoutf(type == SV_TEAMTEXTME ? "\f1%s %s" : "%s:\f1 %s", colorname(d), highlight(text)); else conoutf(type == SV_TEAMTEXTME ? "\f0%s %s" : "%s:\f0 %s", colorname(d), highlight(text)); } break; } case SV_TEXTME: case SV_TEXT: if(cn == -1) { getstring(text, p); conoutf("MOTD:"); conoutf("\f4%s", text); } else if(d) { getstring(text, p); filtertext(text, text, FTXT__CHAT); if(d->ignored && d->clientrole != CR_ADMIN) clientlogf("ignored: %s%s %s", colorname(d), type == SV_TEXT ? ":" : "", text); else conoutf(type == SV_TEXTME ? "\f0%s %s" : "%s:\f0 %s", colorname(d), highlight(text)); } else return; break; case SV_TEXTPRIVATE: { int src = getint(p), dst = getint(p); getstring(text, p); filtertext(text, text, FTXT__CHAT); playerent *s = getclient(src), *d = getclient(dst); if(!s) break; if(s == player1 && d) conoutf("to %s (PM):\f9 %s", colorname(d), highlight(text)); else if(s->ignored) clientlogf("ignored: pm %s %s", colorname(s), text); else { conoutf("%s (PM):\f9 %s", colorname(s), highlight(text)); lastpm = s->clientnum; exechook(HOOK_SP_MP, "onPM", "%d [%s]", s->clientnum, text); } break; } case SV_MAPCHANGE: { extern int spawnpermission; spawnpermission = SP_SPECT; getstring(text, p); int mode = getint(p); int downloadable = getint(p); int revision = getint(p); srvgamesalt = getint(p); localwrongmap = !changemapserv(text, mode, downloadable, revision); if(m_arena && joining > 1 && !watchingdemo) deathstate(player1); break; } case SV_ITEMLIST: { int n; resetpickups(); while((n = getint(p))!=-1) setpickupspawn(n, true); break; } case SV_MAPIDENT: { loopi(2) getint(p); break; } case SV_SWITCHNAME: getstring(text, p); filtertext(text, text, FTXT__PLAYERNAME, MAXNAMELEN); if(!text[0]) copystring(text, "unarmed"); if(d) { if(strcmp(d->name, text)) conoutf("%s is now known as %s", colorname(d), colorname(d, text)); exechook(HOOK_SP, "onNameChange", "%d \"%s\"", d->clientnum, text); copystring(d->name, text, MAXNAMELEN+1); updateclientname(d); } break; case SV_SWITCHTEAM: getint(p); break; case SV_SWITCHSKIN: loopi(2) { int skin = getint(p); if(d) d->setskin(i, skin); } break; case SV_INITCLIENT: // another client either connected or changed name/team { int cn = getint(p); playerent *d = newclient(cn); if(!d) { getstring(text, p); loopi(2) getint(p); getint(p); loopi(4) getint(p); getint(p); break; } getstring(text, p); filtertext(text, text, FTXT__PLAYERNAME, MAXNAMELEN); if(!text[0]) copystring(text, "unarmed"); 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("connected: %s", colorname(d, text)); } copystring(d->name, text, MAXNAMELEN+1); exechook(HOOK_SP_MP, "onConnect", "%d", d->clientnum); loopi(2) d->setskin(i, getint(p)); d->team = getint(p); d->maxroll = (float)clamp(getint(p), 0, ROLLMOVMAX); d->maxrolleffect = (float)clamp(getint(p), 0, ROLLEFFMAX); d->ffov = (float)clamp(getint(p), 75, 120); d->scopefov = (float)clamp(getint(p), 5, 60); d->address = getint(p); // partial IP address if(m_flags_) loopi(2) { flaginfo &f = flaginfos[i]; if(!f.actor) f.actor = getclient(f.actor_cn); } updateclientname(d); break; } case SV_CDIS: { int cn = getint(p); playerent *d = getclient(cn); if(!d) break; if(d->name[0]) conoutf("player %s disconnected", colorname(d)); zapplayer(players[cn]); exechook(HOOK_SP_MP, "onDisconnect", "%d", d->clientnum); break; } case SV_EDITMODE: { int val = getint(p); if(!d) break; if(val) d->state = CS_EDITING; else d->state = CS_ALIVE; break; } case SV_SPAWN: { playerent *s = d; if(!s) { static playerent dummy; s = &dummy; } s->respawn(); s->lifesequence = getint(p); s->health = getint(p); s->armour = getint(p); int gunselect = getint(p); s->setprimary(gunselect); s->selectweapon(gunselect); loopi(NUMGUNS) s->ammo[i] = getint(p); loopi(NUMGUNS) s->mag[i] = getint(p); s->state = CS_SPAWNING; arenaintermission = 0; if(s->lifesequence==0) s->resetstats(); //NEW break; } case SV_SPAWNSTATE: { if(editmode) toggleedit(true); showscores(false); setscope(false); player1->respawn(); player1->lifesequence = getint(p); player1->health = getint(p); player1->armour = getint(p); player1->setprimary(getint(p)); player1->selectweapon(getint(p)); int arenaspawn = getint(p); loopi(NUMGUNS) player1->ammo[i] = getint(p); loopi(NUMGUNS) player1->mag[i] = getint(p); player1->state = CS_ALIVE; lastspawn = lastmillis; findplayerstart(player1, false, arenaspawn); arenaintermission = 0; if(m_arena && !localwrongmap) { if(curpeer) closemenu(NULL); conoutf("new round starting... fight!"); hudeditf(HUDMSG_TIMER, "FIGHT!"); if(m_botmode) BotManager.RespawnBots(); } addmsg(SV_SPAWN, "rii", player1->lifesequence, player1->weaponsel->type); player1->weaponswitch(player1->primweap, false); player1->weaponchanging -= weapon::weaponchangetime/2; if(player1->lifesequence==0) player1->resetstats(); //NEW break; } case SV_SPECTCN: getint(p); break; case SV_SHOTFX: { int scn = getint(p), gun = getint(p); vec from, to; loopk(3) to[k] = getint(p)/DMF; playerent *s = getclient(scn); if(!s || !valid_weapon(gun)) break; loopk(3) from[k] = s->o.v[k]; if(gun==GUN_SHOTGUN) createrays(from, to); s->lastaction = lastmillis; s->weaponchanging = 0; s->mag[gun]--; if(s->weapons[gun]) { s->lastattackweapon = s->weapons[gun]; s->weapons[gun]->gunwait = s->weapons[gun]->info.attackdelay; s->weapons[gun]->attackfx(from, to, -1); s->weapons[gun]->reloading = 0; } s->pstatshots[gun]++; //NEW break; } case SV_THROWNADE: { vec from, vel; loopk(3) from[k] = getint(p)/DMF; loopk(3) vel[k] = getint(p)/DNF; int nademillis = getint(p); if(!d) break; d->lastaction = lastmillis; d->weaponchanging = 0; d->lastattackweapon = d->weapons[GUN_GRENADE]; if(d->weapons[GUN_GRENADE]) { d->weapons[GUN_GRENADE]->attackfx(from, vel, nademillis); d->weapons[GUN_GRENADE]->reloading = 0; } if(d!=player1) d->pstatshots[GUN_GRENADE]++; //NEW break; } case SV_RELOAD: { int cn = getint(p), gun = getint(p); if(!valid_weapon(gun)) break; playerent *p = getclient(cn); if(p && p!=player1) p->weapons[gun]->reload(false); break; } case SV_GIBDAMAGE: case SV_DAMAGE: { int tcn = getint(p), acn = getint(p), gun = getint(p), damage = getint(p), armour = getint(p), health = getint(p); playerent *target = getclient(tcn), *actor = getclient(acn); if(!target || !actor || !valid_weapon(gun)) break; target->armour = armour; target->health = health; dodamage(damage, target, actor, -1, type==SV_GIBDAMAGE, false); actor->pstatdamage[gun]+=damage; //NEW break; } case SV_HITPUSH: { int gun = getint(p), damage = getint(p); vec dir; loopk(3) dir[k] = getint(p)/DNF; player1->hitpush(damage, dir, NULL, gun); break; } case SV_GIBDIED: case SV_DIED: { int vcn = getint(p), acn = getint(p), frags = getint(p), gun = getint(p); playerent *victim = getclient(vcn), *actor = getclient(acn); if(!actor) break; if ( m_mp(gamemode) ) actor->frags = frags; if(!victim) break; dokill(victim, actor, type==SV_GIBDIED, gun); break; } case SV_RESUME: { loopi(MAXCLIENTS) { int cn = getint(p); if(p.overread() || cn<0) break; int state = getint(p), lifesequence = getint(p), primary = getint(p), gunselect = getint(p), flagscore = getint(p), frags = getint(p), deaths = getint(p), health = getint(p), armour = getint(p), points = getint(p), teamkills = getint(p); int ammo[NUMGUNS], mag[NUMGUNS]; loopi(NUMGUNS) ammo[i] = getint(p); loopi(NUMGUNS) mag[i] = getint(p); playerent *d = (cn == getclientnum() ? player1 : newclient(cn)); if(!d) continue; if(d!=player1) d->state = state; d->lifesequence = lifesequence; d->flagscore = flagscore; d->frags = frags; d->deaths = deaths; d->points = points; d->tks = teamkills; if(d!=player1) { d->setprimary(primary); d->selectweapon(gunselect); d->health = health; d->armour = armour; memcpy(d->ammo, ammo, sizeof(ammo)); memcpy(d->mag, mag, sizeof(mag)); if(d->lifesequence==0) d->resetstats(); //NEW } } break; } case SV_DISCSCORES: { discscores.shrink(0); int team; while((team = getint(p)) >= 0) { discscore &ds = discscores.add(); ds.team = team; getstring(text, p); filtertext(ds.name, text, FTXT__PLAYERNAME, MAXNAMELEN); ds.flags = getint(p); ds.frags = getint(p); ds.deaths = getint(p); ds.points = getint(p); } break; } case SV_ITEMSPAWN: { int i = getint(p); setpickupspawn(i, true); break; } case SV_ITEMACC: { int i = getint(p), cn = getint(p); playerent *d = getclient(cn); pickupeffects(i, d); break; } case SV_EDITXY: // coop editing messages, should be extended to include all possible editing ops { int cmd = getint(p); int x = getint(p); int y = getint(p); int xs = getint(p); int ys = getint(p); int a1 = getint(p); int a2 = getint(p); if(m_coop && !OUTBORD(x,y) && xs > 0 && ys > 0 && !OUTBORD(x + xs-1, y + ys - 1)) { block b = { x, y, xs, ys }; switch(cmd) { case EDITXY_HEIGHT: editheightxy(a1 != 0, a2, b); break; case EDITXY_TEX: edittexxy(a1, a2, b); break; case EDITXY_TYPE: edittypexy(a1, b); break; case EDITXY_VDELTA: setvdeltaxy(a1, b); break; case EDITXY_EQUALISE: editequalisexy(a1 != 0, b); break; case EDITXY_TAG: edittagxy(a1, a2, b); break; case EDITXY_SLOPE: slopexy(a1, a2, b); break; case EDITXY_STAIRS: stairsxy(a1, a2, b); break; case EDITXY_FLIPROT: selfliprotate(b, a1); break; } } break; } case SV_EDITARCH: { int av[50]; // MAXARCHVERT, hardcoded int x = getint(p); int y = getint(p); int xs = getint(p); int ys = getint(p); int a1 = getint(p); loopi(50) av[i] = getint(p); if(m_coop && !OUTBORD(x,y) && xs > 0 && ys > 0 && !OUTBORD(x + xs-1, y + ys - 1)) { block b = { x, y, xs, ys }; archxy(a1, av, b); } break; } case SV_EDITBLOCK: { int bx = getuint(p), by = getuint(p), bxs = getuint(p), bys = getuint(p), light = getuint(p); ucharbuf *pp = getgzbuf(p); if(m_coop) netblockpastexy(pp, bx, by, bxs, bys, light); freegzbuf(pp); break; } case SV_NEWMAP: { int size = getint(p); if(!m_coop) break; if(size < 0 && sfactor > 9) break; // don't enlarge over 10 in MP empty_world(size, true); if(d && d!=player1) conoutf(size>=0 ? "%s started a new map of size %d" : "%s enlarged the map to size %d", colorname(d), sfactor); break; } case SV_EDITENT: // coop edit of ent { if(!m_coop) { loopi(12) getint(p); break; } uint i = getint(p); while((uint)ents.length()<=i) ents.add().type = NOTUSED; int to = ents[i].type; if(ents[i].type==SOUND) { entity &e = ents[i]; entityreference entref(&e); location *loc = audiomgr.locations.find(e.attr1, &entref, mapsounds); if(loc) loc->drop(); } ents[i].type = getint(p); ents[i].x = getint(p); ents[i].y = getint(p); ents[i].z = getint(p); ents[i].attr1 = getint(p); ents[i].attr2 = getint(p); ents[i].attr3 = getint(p); ents[i].attr4 = getint(p); ents[i].attr5 = getint(p); ents[i].attr6 = getint(p); ents[i].attr7 = getint(p); ents[i].spawned = false; if(ents[i].type==LIGHT || to==LIGHT) calclight(); if(ents[i].type==SOUND) audiomgr.preloadmapsound(ents[i]); break; } case SV_PONG: { int millis = getint(p); addmsg(SV_CLIENTPING, "i", player1->ping = max(0, (player1->ping*5+totalmillis-millis)/6)); break; } case SV_CLIENTPING: if(!d) return; d->ping = getint(p); break; case SV_TIMEUP: { int curgamemillis = getint(p); int curgamelimit = getint(p); timeupdate(curgamemillis, curgamelimit); break; } case SV_WEAPCHANGE: { int gun = getint(p); if(d) d->selectweapon(gun); break; } case SV_SERVMSGVERB: // FIXME case SV_SERVMSG: getstring(text, p); conoutf("%s", text); break; case SV_FLAGINFO: { int flag = getint(p); if(flag<0 || flag>1) return; flaginfo &f = flaginfos[flag]; f.state = getint(p); switch(f.state) { case CTFF_STOLEN: flagstolen(flag, getint(p)); break; case CTFF_DROPPED: { float x = getuint(p)/DMF; float y = getuint(p)/DMF; float z = getuint(p)/DMF; flagdropped(flag, x, y, z); break; } case CTFF_INBASE: flaginbase(flag); break; case CTFF_IDLE: flagidle(flag); break; } break; } case SV_FLAGMSG: { int flag = getint(p); int message = getint(p); int actor = getint(p); int flagtime = message == FM_KTFSCORE ? getint(p) : -1; flagmsg(flag, message, actor, flagtime); break; } case SV_FLAGCNT: { int fcn = getint(p); int flags = getint(p); playerent *p = getclient(fcn); if(p) p->flagscore = flags; break; } case SV_ARENAWIN: { int acn = getint(p); playerent *alive = getclient(acn); conoutf("the round is over! next round in 5 seconds..."); if(m_botmode && acn==-2) hudoutf("the bots have won the round!"); else if(acn==-1) hudoutf("everyone died!"); else if(m_teammode) hudoutf("team %s has won the round!", team_string(alive->team)); else if(alive==player1) hudoutf("you are the survivor!"); else hudoutf("%s is the survivor!", colorname(alive)); arenaintermission = lastmillis; break; } case SV_SPAWNDENY: { extern int spawnpermission; spawnpermission = getint(p); if(spawnpermission == SP_REFILLMATCH) hudoutf("\f3you can spawn now to refill your team"); break; } case SV_FORCEDEATH: { int cn = getint(p); playerent *d = cn==getclientnum() ? player1 : newclient(cn); if(!d) break; deathstate(d); break; } case SV_SERVOPINFO: { loopv(players) { if(players[i]) players[i]->clientrole = CR_DEFAULT; } player1->clientrole = CR_DEFAULT; int cl = getint(p), r = getint(p); if(cl >= 0 && r >= 0) { playerent *pl = (cl == getclientnum() ? player1 : newclient(cl)); if(pl) { pl->clientrole = r; if(pl->name[0]) { // two messages required to allow for proper german translation - is there a better way to do it? if(pl==player1) conoutf("you claimed %s status", r == CR_ADMIN ? "admin" : "master"); else conoutf("%s claimed %s status", colorname(pl), r == CR_ADMIN ? "admin" : "master"); } } } break; } case SV_TEAMDENY: { int t = getint(p); if(m_teammode) { if(team_isvalid(t)) conoutf("you can't change to team %s", team_string(t)); } else { if(team_isspect(t)) conoutf("you can't change to spectate mode"); else if (player1->state!=CS_ALIVE) conoutf("you can't change to active mode"); else conoutf("you can't switch teams while being alive"); } break; } case SV_SETTEAM: { int fpl = getint(p), fnt = getint(p), ftr = fnt >> 4; fnt &= 0x0f; playerent *d = (fpl == getclientnum() ? player1 : newclient(fpl)); if(d) { const char *nts = team_string(fnt); bool you = fpl == player1->clientnum; if(m_teammode || team_isspect(fnt)) { if(d->team == fnt) { if(you && ftr == FTR_AUTOTEAM) hudoutf("you stay in team %s", nts); } else { if(you && !watchingdemo) { switch(ftr) { case FTR_PLAYERWISH: conoutf("you're now in team %s", nts); break; case FTR_AUTOTEAM: hudoutf("the server forced you to team %s", nts); break; } } else { const char *pls = colorname(d); bool et = team_base(player1->team) != team_base(fnt); switch(ftr) { case FTR_PLAYERWISH: conoutf("player %s switched to team %s", pls, nts); // new message break; case FTR_AUTOTEAM: if(watchingdemo || team_isspect(player1->team)) conoutf("the server forced %s to team %s", colorname(d), nts); else hudoutf("the server forced %s to %s team", colorname(d), et ? "the enemy" : "your"); break; } } if(you && !team_isspect(d->team) && team_isspect(fnt) && d->state == CS_DEAD) spectatemode(SM_FLY); } } else if(d->team != fnt && ftr == FTR_PLAYERWISH && !team_isactive(d->team)) conoutf("%s changed to active play", you ? "you" : colorname(d)); d->team = fnt; if(team_isspect(d->team)) d->state = CS_SPECTATE; } break; } case SV_SERVERMODE: { int sm = getint(p); servstate.autoteam = sm & 1; servstate.mastermode = (sm >> 2) & MM_MASK; servstate.matchteamsize = sm >> 4; //if(sm & AT_SHUFFLE) playsound(TEAMSHUFFLE); // TODO break; } case SV_PAUSEMODE: { ispaused = getint(p); if (player1->state == CS_ALIVE) player1->attacking = false; break; } case SV_CALLVOTE: { int type = getint(p); int vcn = -1, n_yes = 0, n_no = 0; if ( type == -1 ) { d = getclient(vcn = getint(p)); n_yes = getint(p); n_no = getint(p); type = getint(p); } if (type == SA_MAP && d == NULL) d = player1; // gonext uses this if( type < 0 || type >= SA_NUM || !d ) return; votedisplayinfo *v = NULL; string a1, a2; switch(type) { case SA_MAP: { getstring(text, p); int mode = getint(p); if(m_isdemo(mode)) filtertext(text, text, FTXT__DEMONAME); else filtertext(text, behindpath(text), FTXT__MAPNAME); itoa(a1, mode); defformatstring(t)("%d", getint(p)); v = newvotedisplayinfo(d, type, text, a1, t); break; } case SA_KICK: case SA_BAN: { itoa(a1, getint(p)); getstring(text, p); filtertext(text, text, FTXT__CHAT); v = newvotedisplayinfo(d, type, a1, text); break; } case SA_SERVERDESC: getstring(text, p); filtertext(text, text, FTXT__SERVDESC); v = newvotedisplayinfo(d, type, text, NULL); break; case SA_STOPDEMO: // compatibility break; case SA_REMBANS: case SA_SHUFFLETEAMS: v = newvotedisplayinfo(d, type, NULL, NULL); break; case SA_FORCETEAM: itoa(a1, getint(p)); itoa(a2, getint(p)); v = newvotedisplayinfo(d, type, a1, a2); break; case SA_PAUSE: itoa(a1, getint(p)); v = newvotedisplayinfo(d, type, a1, NULL); break; default: itoa(a1, getint(p)); v = newvotedisplayinfo(d, type, a1, NULL); break; } displayvote(v); onCallVote(type, v->owner->clientnum, text, a1); if (vcn >= 0) { loopi(n_yes) votecount(VOTE_YES); loopi(n_no) votecount(VOTE_NO); } extern int vote(int); if (d == player1) vote(VOTE_YES); break; } case SV_CALLVOTESUC: { callvotesuc(); onChangeVote( 0, -1); break; } case SV_CALLVOTEERR: { int errn = getint(p); callvoteerr(errn); onChangeVote( 1, errn); break; } case SV_VOTE: { int vote = getint(p); votecount(vote); onChangeVote( 2, vote); break; } case SV_VOTERESULT: { int vres = getint(p); voteresult(vres); onChangeVote( 3, vres); break; } case SV_IPLIST: { int cn; while((cn = getint(p)) >= 0 && !p.overread()) { playerent *pl = getclient(cn); int ip = getint(p); if(!pl) continue; else pl->address = ip; } break; } case SV_SENDDEMOLIST: { int demos = getint(p); menureset(downloaddemomenu); demo_mlines.shrink(0); if(!demos) { conoutf("no demos available"); mline &m = demo_mlines.add(); copystring(m.name, "no demos available"); menuitemmanual(downloaddemomenu,m.name); } else { demo_mlines.reserve(demos); loopi(demos) { int seq = getint(p); getstring(text, p); // info filtertext(text, text, FTXT__DEMOINFO, MAXMAPNAMELEN + 64); int len = getint(p); // len concatformatstring(text, ", %.2f%s", len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); conoutf("%d. %s", seq, text); mline &m = demo_mlines.add(); formatstring(m.name)("%d. %s", seq, text); formatstring(m.cmd)("getdemo %d", seq); menuitemmanual(downloaddemomenu, m.name, m.cmd); getstring(text, p); // mapname filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN); getint(p); // gmode getint(p); // timeplayed getint(p); // timeremain getint(p); // timestamp } } break; } case SV_DEMOPLAYBACK: { bool demoplayback = false; string demofile; extern char *curdemofile; getstring(demofile, p, MAXSTRLEN); watchingdemo = demoplayback = demofile[0] != '\0'; DELETEA(curdemofile); if(demoplayback) { curdemofile = newstring(demofile); player1->resetspec(); player1->state = CS_SPECTATE; player1->team = TEAM_SPECT; } else { // cleanups curdemofile = newstring("n/a"); loopv(players) zapplayer(players[i]); clearvote(); player1->state = CS_ALIVE; player1->resetspec(); } player1->clientnum = getint(p); break; } case SV_DEMOCHECKSUM: // raw data checksum of demo file { uchar dtiger[TIGERHASHSIZE]; if(p.get(dtiger, TIGERHASHSIZE) == TIGERHASHSIZE) { bin2hex(text, dtiger, TIGERHASHSIZE); defformatstring(msg)("CLIENTDEMOEND<%s %d>", iptoa(session.curpeerip, text + 100), session.curpeerport); int msglen = (int)strlen(msg); ASSERT(msglen + 64 < MAXSTRLEN); if(sk) ed25519_sign((uchar*)msg, NULL, (uchar*)msg, msglen, sk); else memset(msg, 0, 64); packetbuf pr(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); putint(pr, SV_DEMOSIGNATURE); putint(pr, session.curpeerport); putip4(pr, session.curpeerip); pr.put((uchar*)msg, 64); sendpackettoserv(1, pr.finalize()); } break; } case SV_DEMOSIGNATURE: // client acknowledging a demo checksum getint(p); getip4(p); p.get((uchar*)text, 64); break; default: neterr("type"); return; } } #ifdef _DEBUG protocoldebug(false); #endif } SVARP(demonameformat, "%w_%h_%n_%Mmin_%G"); SVARP(demotimestampformat, "%Y%m%d_%H%M"); void receivefile(uchar *data, int len) { static char text[MAXTRANS]; ucharbuf p(data, len); int type = getint(p); switch(type) { case SV_SENDDEMO: { /* int sequence = */ getint(p); getstring(text, p); filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN); int gmode = getint(p); int timeplayed = getint(p); int timeremain = getint(p); int timestamp = getint(p); int demosize = getint(p); if(demosize <= MAXDEMOSENDSIZE && demosize <= p.remaining()) { extern string demosubpath; string buf; enet_uint32 ip = curpeer ? ntohl(curpeer->address.host) : 0; defformatstring(fname)("demos/%s%s.dmo", demosubpath, formatdemofilename(demonameformat, demotimestampformat, text, gmode, timestamp, timeplayed, timeremain, ip, buf)); copystring(demosubpath, ""); path(fname); stream *demo = openfile(fname, "wb"); if(demo) { conoutf("received demo \"%s\"", fname); demo->write(&p.buf[p.len], demosize); delete demo; } else conoutf("failed writing to \"%s\"", fname); } break; } case SV_RECVMAP: { getstring(text, p); filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN); conoutf("received map \"%s\" from server, reloading..", text); int mapsize = getint(p); int cfgsize = getint(p); int cfgsizegz = getint(p); int size = mapsize + cfgsizegz; if(mapsize <= MAXMAPSENDSIZE && cfgsizegz <= MAXMAPSENDSIZE && size <= MAXMAPSENDSIZE && cfgsize <= MAXCFGFILESIZE && size <= p.remaining() && !securemapcheck(behindpath(text))) { writemap(behindpath(text), mapsize, &p.buf[p.len]); p.len += mapsize; writecfggz(behindpath(text), cfgsize, cfgsizegz, &p.buf[p.len]); p.len += cfgsizegz; } break; } default: p.len = 0; parsemessages(-1, NULL, p); break; } } void servertoclient(int chan, uchar *buf, int len, bool demo) // processes any updates from the server { ucharbuf p(buf, len); switch(chan) { case 0: parsepositions(p); break; case 1: parsemessages(-1, NULL, p, demo); break; case 2: receivefile(p.buf, p.maxlen); break; } } void localservertoclient(int chan, uchar *buf, int len, bool demo) // processes any updates from the server { // pktlogger.queue(enet_packet_create (buf, len, 0)); // log local & demo packets servertoclient(chan, buf, len, demo); }