// client processing of the incoming network stream #include "cube.h" #include "bot/bot.h" extern int democlientnum; extern bool c2sinit, senditemstoserver; extern string clientpassword; void neterr(char *s) { conoutf("\f3illegal network message (%s)", s); disconnect(); } void changemapserv(char *name, int mode) // forced map change from the server { gamemode = mode; load_world(name); } void changemap(char *name) // request map change, server may ignore { addmsg(SV_MAPCHANGE, "rsi", name, nextmode); } // 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 updatepos(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 = lastmillis-d->lastupdate; if(lagtime) { d->plag = (d->plag*5+lagtime)/6; d->lastupdate = lastmillis; } } extern void trydisconnect(); void parsepositions(ucharbuf &p) { int type; while(p.remaining()) switch(type = getint(p)) { case SV_POS: // position of another client { int cn = getint(p); vec o, vel; float yaw, pitch, roll; o.x = getuint(p)/DMF; o.y = getuint(p)/DMF; o.z = getuint(p)/DMF; yaw = getuint(p)/DAF; pitch = getint(p)/DAF; roll = getint(p)/DAF; vel.x = getint(p)/DVF; vel.y = getint(p)/DVF; vel.z = getint(p)/DVF; int f = getint(p); playerent *d = getclient(cn); if(!d) continue; d->o = o; d->yaw = yaw; d->pitch = pitch; d->roll = roll; d->vel = vel; d->strafe = (f&3)==3 ? -1 : f&3; f >>= 2; d->move = (f&3)==3 ? -1 : f&3; f >>= 2; d->onfloor = f&1; f >>= 1; int oldstate = d->state, state = f&7; if(state==CS_DEAD && oldstate!=CS_DEAD) d->lastaction = lastmillis; d->state = state; f >>= 3; d->onladder = f&1; if(!demoplayback) updatepos(d); if(oldstate!=CS_DEAD && d->state!=CS_DEAD) updatelagtime(d); break; } default: neterr("type"); return; } } void parsemessages(int cn, playerent *d, ucharbuf &p) { static char text[MAXTRANS]; int type, joining = 0; bool mapchanged = false, c2si = false, gib = false; while(p.remaining()) switch(type = getint(p)) { case SV_INITS2C: // welcome messsage from the server { int mycn = getint(p), prot = getint(p); if(prot!=PROTOCOL_VERSION) { conoutf("you are using a different game protocol (you: %d, server: %d)", PROTOCOL_VERSION, prot); disconnect(); return; } player1->clientnum = mycn; // we are now fully connected joining = getint(p); if(getint(p) > 0) conoutf("INFO: this server is password protected"); if(joining<0 && getclientmap()[0]) changemap(getclientmap()); // we are the first client on this server, set map break; } case SV_CLIENT: { int cn = getint(p), len = getuint(p); ucharbuf q = p.subbuf(len); parsemessages(cn, getclient(cn), q); break; } case SV_SOUND: playsound(getint(p), !d || localdemoplayer1st() ? NULL : &d->o); break; case SV_TEAMTEXT: { int cn = getint(p); getstring(text, p); filtertext(text, text); playerent *d = getclient(cn); if(!d) break; conoutf("%s:\f1 %s", colorname(d), text); break; } case SV_TEXT: if(cn == -1) { getstring(text, p); conoutf("MOTD:"); conoutf("\f4%s", text); } else if(d) { getstring(text, p); filtertext(text, text); conoutf("%s:\f0 %s", colorname(d), text); } else return; break; case SV_MAPCHANGE: getstring(text, p); changemapserv(text, getint(p)); if(m_arena && joining>2) { deathstate(player1); showscores(true); } mapchanged = true; break; case SV_ITEMLIST: { int n; if(mapchanged) { senditemstoserver = false; resetspawns(); } while((n = getint(p))!=-1) if(mapchanged) setspawn(n, true); break; } case SV_MAPRELOAD: // server requests next map { getint(p); s_sprintfd(nextmapalias)("nextmap_%s", getclientmap()); char *map = getalias(nextmapalias); // look up map in the cycle changemap(map ? map : getclientmap()); break; } case SV_INITC2S: // another client either connected or changed name/team { d = newclient(cn); getstring(text, p); filtertext(text, text, false, MAXNAMELEN); if(!text[0]) s_strcpy(text, "unarmed"); if(d->name[0]) // already connected { if(strcmp(d->name, text)) conoutf("%s is now known as %s", colorname(d, 0), colorname(d, 1, text)); } else // new client { c2sinit = false; // send new players my info again conoutf("connected: %s", colorname(d, 0, text)); gun_changed = true; } s_strncpy(d->name, text, MAXNAMELEN+1); getstring(text, p); filtertext(d->team, text, false, MAXTEAMLEN); setskin(d, getint(p)); d->lifesequence = getint(p); c2si = true; if(m_ctf) loopi(2) { flaginfo &f = flaginfos[i]; if(!f.actor) f.actor = getclient(f.actor_cn); } 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]); break; } case SV_SHOT: { if(!d) return; int gun = getint(p); vec s, e; s.x = getint(p)/DMF; s.y = getint(p)/DMF; s.z = getint(p)/DMF; e.x = getint(p)/DMF; e.y = getint(p)/DMF; e.z = getint(p)/DMF; if(gun==GUN_SHOTGUN) createrays(s, e); d->lastaction = lastmillis; d->lastattackgun = gun; shootv(gun, s, e, d, false, getint(p)); break; } case SV_GIBDAMAGE: gib = true; case SV_DAMAGE: { if(!d) return; int target = getint(p); int damage = getint(p); int ls = getint(p); if(target==getclientnum()) { if(ls==player1->lifesequence) dodamage(damage, cn, d, gib); } else { playerent *victim = getclient(target); if(victim) { playsound(S_PAIN1+rnd(5), &victim->o); victim->lastpain = lastmillis; } } gib = false; break; } case SV_GIBDIED: gib = true; case SV_DIED: { if(!d) return; int actor = getint(p); playerent *act = NULL; s_sprintfd(death)("%s", gib ? "gibbed" : "fragged"); if(actor==cn) { conoutf("\f2%s suicided", colorname(d)); act = d; } else if(actor==getclientnum() /*|| (demoplayback && actor==democlientnum)*/) // player-neutral gameplay messages? { act = player1; int frags; if(isteam(player1->team, d->team)) { frags = -1; conoutf("\f2you %s a teammate (%s)", death, colorname(d)); extern void showteamkill(); showteamkill(); } else { frags = gib ? 2 : 1; conoutf("\f2you %s %s", death, colorname(d)); } addmsg(SV_FRAGS, "ri", player1->frags += frags); if(gib) addgib(d); } else { playerent *a = getclient(actor); if(a) { act = a; if(isteam(a->team, d->team)) { conoutf("\f2%s %s his teammate (%s)", colorname(a, 0), death, colorname(d, 1)); } else { conoutf("\f2%s %s %s", colorname(a, 0), death, colorname(d, 1)); } if(gib) addgib(d); } } playsound(S_DIE1+rnd(2), &d->o); if(act && act->gunselect == GUN_SNIPER && gib) playsound(S_HEADSHOT); if(!c2si) d->lifesequence++; gib = false; break; } case SV_FRAGS: if(!d) return; d->frags = getint(p); break; case SV_RESUME: { int cn = getint(p), frags = getint(p), flags = getint(p), lseq = getint(p); playerent *d = cn==getclientnum() ? player1 : newclient(cn); if(d) { d->frags = frags; d->flagscore = flags; d->lifesequence = lseq; } break; } case SV_ITEMPICKUP: setspawn(getint(p), false); getint(p); break; case SV_ITEMSPAWN: { uint i = getint(p); setspawn(i, true); break; } case SV_ITEMACC: // server acknowledges that I picked up this item realpickup(getint(p), player1); break; case SV_EDITH: // coop editing messages, should be extended to include all possible editing ops case SV_EDITT: case SV_EDITS: case SV_EDITD: case SV_EDITE: { int x = getint(p); int y = getint(p); int xs = getint(p); int ys = getint(p); int v = getint(p); block b = { x, y, xs, ys }; switch(type) { case SV_EDITH: editheightxy(v!=0, getint(p), b); break; case SV_EDITT: edittexxy(v, getint(p), b); break; case SV_EDITS: edittypexy(v, b); break; case SV_EDITD: setvdeltaxy(v, b); break; case SV_EDITE: editequalisexy(v!=0, b); break; } break; } case SV_EDITENT: // coop edit of ent { uint i = getint(p); while((uint)ents.length()<=i) ents.add().type = NOTUSED; int to = ents[i].type; 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].spawned = false; if(ents[i].type==LIGHT || to==LIGHT) calclight(); break; } case SV_PONG: addmsg(SV_CLIENTPING, "i", player1->ping = (player1->ping*5+lastmillis-getint(p))/6); break; case SV_CLIENTPING: if(!d) return; d->ping = getint(p); break; case SV_GAMEMODE: nextmode = getint(p); break; case SV_TIMEUP: timeupdate(getint(p)); break; case SV_RECVMAP: { getstring(text, p); conoutf("received map \"%s\" from server, reloading..", &text); int mapsize = getint(p); if(p.remaining() < mapsize) { p.forceoverread(); break; } writemap(text, mapsize, &p.buf[p.len]); p.len += mapsize; changemapserv(text, gamemode); break; } case SV_WEAPCHANGE: { if(!d) return; d->gunselect = getint(p); break; } 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); int action = getint(p); switch(f.state) { case CTFF_STOLEN: { flagstolen(flag, action, getint(p)); break; } case CTFF_DROPPED: { short x = (ushort) (getint(p)/DMF); short y = (ushort) (getint(p)/DMF); short z = (ushort) (getint(p)/DMF); flagdropped(flag, action, x, y, z); break; } case CTFF_INBASE: { int actor = -1; if(action == SV_FLAGRETURN) actor = getint(p); flaginbase(flag, action, actor); break; } } break; } case SV_FLAGS: { if(!d) return; d->flagscore = getint(p); break; } case SV_ARENAWIN: { int alive = getint(p); getstring(text, p); conoutf("arena round is over! next round in 5 seconds..."); if(!alive) conoutf("everyone died!"); else if(m_botmode && player1->state==CS_DEAD) conoutf("the bots have won the round!"); else if(m_teammode) conoutf("team %s has won the round!", text); else conoutf("%s is the survivor!", text); break; } case SV_ARENASPAWN: conoutf("new round starting... fight!"); respawnself(); if(m_botmode) BotManager.RespawnBots(); clearbounceents(); 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]) conoutf("%s claimed %s status", pl == player1 ? "you" : colorname(pl), r == CR_ADMIN ? "admin" : "master"); } } break; } case SV_SERVOPCMD: { int cmd = getint(p), arg = getint(p); playerent *pl = (arg == getclientnum() ? NULL : getclient(arg)); switch(cmd) { case SOPCMD_KICK: case SOPCMD_BAN: { if(pl) conoutf("%s has been %s", colorname(pl), cmd == SOPCMD_KICK ? "kicked" : "banned"); break; } case SOPCMD_REMBANS: conoutf("bans removed"); break; case SOPCMD_MASTERMODE: conoutf("mastermode set to \"%s\"", arg ? "private" : "open"); break; case SOPCMD_AUTOTEAM: autoteambalance = arg == 1; if(!joining) conoutf("autoteam is %s", autoteambalance ? "enabled" : "disabled"); break; case SOPCMD_GIVEMASTER: if(pl) conoutf("the admin gave master state to %s", colorname(pl)); break; case SOPCMD_FORCETEAM: if(pl) conoutf("player %s was forced to change the team", colorname(pl)); break; } break; } case SV_SERVOPCMDDENIED: { conoutf("\f3denied. you have to be at least %s to perform this action", getint(p) == CR_ADMIN ? "admin" : "master"); break; } case SV_FORCETEAM: { changeteam(getint(p)); if(!m_arena || joining<=2) spawnplayer(player1); break; } /* demo recording compat */ case SV_PING: getint(p); break; default: if(demoplayback) // filter demo messages { int size = msgsizelookup(type); if(size) { loopi(size) getint(p); break; } } neterr("type"); return; } } void localservertoclient(int chan, uchar *buf, int len) // processes any updates from the server { incomingdemodata(chan, buf, len); ucharbuf p(buf, len); switch(chan) { case 0: parsepositions(p); break; case 1: case 2: parsemessages(-1, NULL, p); break; case 42: // player1 demo data only extern int democlientnum; parsemessages(democlientnum, getclient(democlientnum), p); break; } }