1 // client processing of the incoming network stream
2 
3 #include "cube.h"
4 #include "bot/bot.h"
5 
6 VARP(networkdebug, 0, 0, 1);
7 #define DEBUGCOND (networkdebug==1)
8 
9 extern bool watchingdemo;
10 extern string clientpassword;
11 
12 packetqueue pktlogger;
13 
neterr(const char * s)14 void neterr(const char *s)
15 {
16     conoutf("\f3illegal network message (%s)", s);
17 
18     // might indicate a client/server communication bug, create error report
19     pktlogger.flushtolog("packetlog.txt");
20     conoutf("\f3wrote a network error report to packetlog.txt, please post this file to the bugtracker now!");
21 
22     disconnect();
23 }
24 
25 VARP(autogetmap, 0, 1, 1); // only if the client doesn't have that map
26 VARP(autogetnewmaprevisions, 0, 1, 1);
27 
28 bool localwrongmap = false;
29 int MA = 0, Hhits = 0; // flowtron: moved here
changemapserv(char * name,int mode,int download,int revision)30 bool changemapserv(char *name, int mode, int download, int revision)        // forced map change from the server
31 {
32     MA = Hhits = 0; // reset for checkarea()
33     gamemode = mode;
34     if(m_demo) return true;
35     if(m_coop)
36     {
37         if(!name[0] || !load_world(name)) empty_world(0, true);
38         return true;
39     }
40     else if(player1->state==CS_EDITING) { /*conoutf("SANITY drop from EDITING");*/ toggleedit(true); } // fix stuck-in-editmode bug
41     bool loaded = load_world(name);
42     if(download > 0)
43     {
44         bool revmatch = hdr.maprevision == revision || revision == 0;
45         if(watchingdemo)
46         {
47             if(!revmatch) conoutf(_("%c3demo was recorded on map revision %d, you have map revision %d"), CC, revision, hdr.maprevision);
48         }
49         else
50         {
51             if(securemapcheck(name, false)) return true;
52             bool sizematch = maploaded == download || download < 10;
53             if(loaded && sizematch && revmatch) return true;
54             bool getnewrev = autogetnewmaprevisions && revision > hdr.maprevision;
55             if(autogetmap || getnewrev)
56             {
57                 if(!loaded || getnewrev) getmap(); // no need to ask
58                 else
59                 {
60                     defformatstring(msg)("map '%s' revision: local %d, provided by server %d", name, hdr.maprevision, revision);
61                     alias("__getmaprevisions", msg);
62                     showmenu("getmap");
63                 }
64             }
65             else
66             {
67                 if(!loaded || download < 10) conoutf(_("\"getmap\" to download the current map from the server"));
68                 else conoutf(_("\"getmap\" to download a %s version of the current map from the server"),
69                          revision == 0 ? _("different") : (revision > hdr.maprevision ? _("newer") : _("older")));
70             }
71         }
72     }
73     else return true;
74     return false;
75 }
76 
77 // update the position of other clients in the game in our world
78 // don't care if he's in the scenery or other players,
79 // just don't overlap with our client
80 
updatepos(playerent * d)81 void updatepos(playerent *d)
82 {
83     const float r = player1->radius+d->radius;
84     const float dx = player1->o.x-d->o.x;
85     const float dy = player1->o.y-d->o.y;
86     const float dz = player1->o.z-d->o.z;
87     const float rz = player1->aboveeye+d->eyeheight;
88     const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz);
89     if(fx<r && fy<r && fz<rz && d->state!=CS_DEAD)
90     {
91         if(fx<fy) d->o.y += dy<0 ? r-fy : -(r-fy);  // push aside
92         else      d->o.x += dx<0 ? r-fx : -(r-fx);
93     }
94 }
95 
updatelagtime(playerent * d)96 void updatelagtime(playerent *d)
97 {
98     int lagtime = totalmillis-d->lastupdate;
99     if(lagtime)
100     {
101         if(d->state!=CS_SPAWNING && d->lastupdate) d->plag = (d->plag*5+lagtime)/6;
102         d->lastupdate = totalmillis;
103     }
104 }
105 
106 extern void trydisconnect();
107 
108 VARP(maxrollremote, 0, 0, 20); // bound remote "roll" values by our maxroll?!
109 
parsepositions(ucharbuf & p)110 void parsepositions(ucharbuf &p)
111 {
112     int type;
113     while(p.remaining()) switch(type = getint(p))
114     {
115         case SV_POS:                        // position of another client
116         case SV_POSC:
117         {
118             int cn, f, g;
119             vec o, vel;
120             float yaw, pitch, roll = 0;
121             bool scoping;//, shoot;
122             if(type == SV_POSC)
123             {
124                 bitbuf<ucharbuf> q(p);
125                 cn = q.getbits(5);
126                 int usefactor = q.getbits(2) + 7;
127                 o.x = q.getbits(usefactor + 4) / DMF;
128                 o.y = q.getbits(usefactor + 4) / DMF;
129                 yaw = q.getbits(9) * 360.0f / 512;
130                 pitch = (q.getbits(8) - 128) * 90.0f / 127;
131                 roll = !q.getbits(1) ? (q.getbits(6) - 32) * 20.0f / 31 : 0.0f;
132                 if(!q.getbits(1))
133                 {
134                     vel.x = (q.getbits(4) - 8) / DVELF;
135                     vel.y = (q.getbits(4) - 8) / DVELF;
136                     vel.z = (q.getbits(4) - 8) / DVELF;
137                 }
138                 else vel.x = vel.y = vel.z = 0.0f;
139                 f = q.getbits(8);
140                 int negz = q.getbits(1);
141                 int full = q.getbits(1);
142                 int s = q.rembits();
143                 if(s < 3) s += 8;
144                 if(full) s = 11;
145                 int z = q.getbits(s);
146                 if(negz) z = -z;
147                 o.z = z / DMF;
148                 scoping = ( q.getbits(1) ? true : false );
149                 q.getbits(1);//shoot = ( q.getbits(1) ? true : false );
150             }
151             else
152             {
153                 cn = getint(p);
154                 o.x   = getuint(p)/DMF;
155                 o.y   = getuint(p)/DMF;
156                 o.z   = getuint(p)/DMF;
157                 yaw   = (float)getuint(p);
158                 pitch = (float)getint(p);
159                 g = getuint(p);
160                 if ((g>>3) & 1) roll  = (float)(getint(p)*20.0f/125.0f);
161                 if (g & 1) vel.x = getint(p)/DVELF; else vel.x = 0;
162                 if ((g>>1) & 1) vel.y = getint(p)/DVELF; else vel.y = 0;
163                 if ((g>>2) & 1) vel.z = getint(p)/DVELF; else vel.z = 0;
164                 scoping = ( (g>>4) & 1 ? true : false );
165                 //shoot = ( (g>>5) & 1 ? true : false ); // we are not using this yet
166                 f = getuint(p);
167             }
168             int seqcolor = (f>>6)&1;
169             playerent *d = getclient(cn);
170             if(!d || seqcolor!=(d->lifesequence&1)) continue;
171             vec oldpos(d->o);
172             float oldyaw = d->yaw, oldpitch = d->pitch;
173             loopi(3)
174             {
175                 float dr = o.v[i] - d->o.v[i] + ( i == 2 ? d->eyeheight : 0);
176                 if ( !dr ) d->vel.v[i] = 0.0f;
177                 else if ( d->vel.v[i] ) d->vel.v[i] = dr * 0.05f + d->vel.v[i] * 0.95f;
178                 d->vel.v[i] += vel.v[i];
179                 if ( i==2 && d->onfloor && d->vel.v[i] < 0.0f ) d->vel.v[i] = 0.0f;
180             }
181             d->o = o;
182             d->o.z += d->eyeheight;
183             d->yaw = yaw;
184             d->pitch = pitch;
185             if(d->weaponsel->type == GUN_SNIPER)
186             {
187                 sniperrifle *sr = (sniperrifle *)d->weaponsel;
188                 sr->scoped = d->scoping = scoping;
189             }
190             d->roll = roll;
191             d->strafe = (f&3)==3 ? -1 : f&3;
192             f >>= 2;
193             d->move = (f&3)==3 ? -1 : f&3;
194             f >>= 2;
195             d->onfloor = f&1;
196             f >>= 1;
197             d->onladder = f&1;
198             f >>= 2;
199             d->last_pos = totalmillis;
200             updatecrouch(d, f&1);
201             updatepos(d);
202             updatelagtime(d);
203             extern int smoothmove, smoothdist;
204             if(d->state==CS_DEAD)
205             {
206                 d->resetinterp();
207                 d->smoothmillis = 0;
208             }
209             else if(smoothmove && d->smoothmillis>=0 && oldpos.dist(d->o) < smoothdist)
210             {
211                 d->newpos = d->o;
212                 d->newpos.z -= d->eyeheight;
213                 d->newyaw = d->yaw;
214                 d->newpitch = d->pitch;
215                 d->o = oldpos;
216                 d->yaw = oldyaw;
217                 d->pitch = oldpitch;
218                 oldpos.z -= d->eyeheight;
219                 (d->deltapos = oldpos).sub(d->newpos);
220                 d->deltayaw = oldyaw - d->newyaw;
221                 if(d->deltayaw > 180) d->deltayaw -= 360;
222                 else if(d->deltayaw < -180) d->deltayaw += 360;
223                 d->deltapitch = oldpitch - d->newpitch;
224                 d->smoothmillis = lastmillis;
225             }
226             else d->smoothmillis = 0;
227             if(d->state==CS_LAGGED || d->state==CS_SPAWNING) d->state = CS_ALIVE;
228             // when playing a demo spectate first player we know about
229             if(player1->isspectating() && player1->spectatemode==SM_NONE) togglespect();
230             extern void clamproll(physent *pl);
231             if(maxrollremote) clamproll((physent *) d);
232             break;
233         }
234 
235         default:
236             neterr("type");
237             return;
238     }
239 }
240 
241 extern int checkarea(int maplayout_factor, char *maplayout);
242 char *mlayout = NULL;
243 int Mv = 0, Ma = 0, F2F = 1000 * MINFF; // moved up:, MA = 0;
244 float Mh = 0;
245 extern int connected;
246 extern int lastpm;
247 extern bool noflags;
248 bool item_fail = false;
249 int map_quality = MAP_IS_EDITABLE;
250 
251 /// TODO: many functions and variables are redundant between client and server... someone should redo the entire server code and unify client and server.
good_map()252 bool good_map() // call this function only at startmap
253 {
254     if (mlayout) MA = checkarea(sfactor, mlayout);
255 
256     F2F = 1000 * MINFF;
257     if(m_flags)
258     {
259         flaginfo &f0 = flaginfos[0];
260         flaginfo &f1 = flaginfos[1];
261 #define DIST(x) (f0.pos.x - f1.pos.x)
262         F2F = (!numflagspawn[0] || !numflagspawn[1]) ? 1000 * MINFF : DIST(x)*DIST(x)+DIST(y)*DIST(y);
263 #undef DIST
264     }
265 
266     item_fail = false;
267     loopv(ents)
268     {
269         entity &e1 = ents[i];
270         if (e1.type < I_CLIPS || e1.type > I_AKIMBO) continue;
271         float density = 0, hdensity = 0;
272         loopvj(ents)
273         {
274             entity &e2 = ents[j];
275             if (e2.type < I_CLIPS || e2.type > I_AKIMBO || i == j) continue;
276 #define DIST(x) (e1.x - e2.x)
277 #define DIST_ATT ((e1.z + e1.attr1) - (e2.z + e2.attr1))
278             float r2 = DIST(x)*DIST(x) + DIST(y)*DIST(y) + DIST_ATT*DIST_ATT;
279 #undef DIST_ATT
280 #undef DIST
281             if ( r2 == 0.0f ) { conoutf("\f3MAP CHECK FAIL: Items too close %s %s (%hd,%hd)", entnames[e1.type], entnames[e2.type],e1.x,e1.y); item_fail = true; break; }
282             r2 = 1/r2;
283             if (r2 < 0.0025f) continue;
284             if (e1.type != e2.type)
285             {
286                 hdensity += r2;
287                 continue;
288             }
289             density += r2;
290         }
291         if ( hdensity > 0.5f ) { conoutf("\f3MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[e1.type],hdensity,e1.x,e1.y); item_fail = true; break; }
292         switch(e1.type)
293         {
294 #define LOGTHISSWITCH(X) if( density > X ) { conoutf("\f3MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[e1.type],density,e1.x,e1.y); item_fail = true; break; }
295             case I_CLIPS:
296             case I_HEALTH: LOGTHISSWITCH(0.24f); break;
297             case I_AMMO: LOGTHISSWITCH(0.04f); break;
298             case I_HELMET: LOGTHISSWITCH(0.02f); break;
299             case I_ARMOUR:
300             case I_GRENADE:
301             case I_AKIMBO: LOGTHISSWITCH(0.005f); break;
302             default: break;
303 #undef LOGTHISSWITCH
304         }
305     }
306 
307     map_quality = (!item_fail && F2F > MINFF && MA < MAXMAREA && Mh < MAXMHEIGHT && Hhits < MAXHHITS) ? MAP_IS_GOOD : MAP_IS_BAD;
308     if ( (!connected || gamemode == GMODE_COOPEDIT) && map_quality == MAP_IS_BAD ) map_quality = MAP_IS_EDITABLE;
309     return map_quality > 0;
310 }
311 
312 VARP(hudextras, 0, 0, 3);
313 
314 int teamworkid = -1;
315 
showhudextras(char hudextras,char value)316 void showhudextras(char hudextras, char value){
317     void (*outf)(const char *s, ...) = (hudextras > 1 ? hudoutf : conoutf);
318     bool caps = hudextras < 3 ? false : true;
319     switch(value)
320     {
321         case HE_COMBO:
322         case HE_COMBO2:
323         case HE_COMBO3:
324         case HE_COMBO4:
325         case HE_COMBO5:
326         {
327             int n = value - HE_COMBO;
328             if (n > 3) outf("\f3%s",strcaps("monster combo!!!",caps)); // I expect to never see this one
329             else if (!n) outf("\f5%s",strcaps("combo", caps));
330             else outf("\f5%s x%d",strcaps("multi combo", caps),n+1);
331             break;
332         }
333         case HE_TEAMWORK:
334             outf("\f5%s",strcaps("teamwork done", caps)); break;
335         case HE_FLAGDEFENDED:
336             outf("\f5%s",strcaps("you defended the flag", caps)); break;
337         case HE_FLAGCOVERED:
338             outf("\f5%s",strcaps("you covered the flag", caps)); break;
339         case HE_COVER:
340             if (teamworkid >= 0)
341             {
342                 playerent *p = getclient(teamworkid);
343                 if (!p || p == player1) teamworkid = -1;
344                 else outf("\f5you covered %s",p->name); break;
345             }
346         default:
347         {
348             if (value >= HE_NUM)
349             {
350                 teamworkid = value - HE_NUM;
351                 playerent *p = getclient(teamworkid);
352                 if (!p || p == player1) teamworkid = -1;
353                 else outf("\f4you replied to %s",p->name);
354             }
355             else outf("\f3Update your client!");
356             break;
357         }
358     }
359 #undef SSPAM
360 }
361 
362 int lastspawn = 0;
363 
onCallVote(int type,int vcn,char * text,char * a)364 void onCallVote(int type, int vcn, char *text, char *a)
365 {
366     if(identexists("onCallVote"))
367     {
368         defformatstring(runas)("%s %d %d [%s] [%s]", "onCallVote", type, vcn, text, a);
369         execute(runas);
370     }
371 }
372 
onChangeVote(int mod,int id)373 void onChangeVote(int mod, int id)
374 {
375     if(identexists("onChangeVote"))
376     {
377         defformatstring(runas)("%s %d %d", "onChangeVote", mod, id);
378         execute(runas);
379     }
380 }
381 
382 VARP(voicecomsounds, 0, 1, 2);
383 bool medals_arrived=0;
384 medalsst a_medals[END_MDS];
parsemessages(int cn,playerent * d,ucharbuf & p,bool demo=false)385 void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false)
386 {
387     static char text[MAXTRANS];
388     int type, joining = 0;
389     bool demoplayback = false;
390 
391     while(p.remaining())
392     {
393         type = getint(p);
394 
395         if(demo && watchingdemo && demoprotocol == 1132)
396         {
397             if(type > SV_IPLIST) --type;            // SV_WHOIS removed
398             if(type >= SV_TEXTPRIVATE) ++type;      // SV_TEXTPRIVATE added
399             if(type == SV_SWITCHNAME)               // SV_SPECTCN removed
400             {
401                 getint(p);
402                 continue;
403             }
404             else if(type > SV_SWITCHNAME) --type;
405         }
406 
407         #ifdef _DEBUG
408         if(type!=SV_POS && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_PONG && type!=SV_CLIENT)
409         {
410             DEBUGVAR(d);
411             ASSERT(type>=0 && type<SV_NUM);
412             DEBUGVAR(messagenames[type]);
413             protocoldebug(true);
414         }
415         else protocoldebug(false);
416         #endif
417 
418         switch(type)
419         {
420             case SV_SERVINFO:  // welcome message from the server
421             {
422                 int mycn = getint(p), prot = getint(p);
423                 if(prot!=CUR_PROTOCOL_VERSION && !(watchingdemo && prot == -PROTOCOL_VERSION))
424                 {
425                     conoutf(_("%c3incompatible game protocol (local protocol: %d :: server protocol: %d)"), CC, CUR_PROTOCOL_VERSION, prot);
426                     conoutf("\f3if this occurs a lot, obtain an upgrade from \f1http://assault.cubers.net");
427                     if(watchingdemo) conoutf("breaking loop : \f3this demo is using a different protocol\f5 : end it now!"); // SVN-WiP-bug: causes endless retry loop else!
428                     else disconnect();
429                     return;
430                 }
431                 sessionid = getint(p);
432                 player1->clientnum = mycn;
433                 if(getint(p) > 0) conoutf(_("INFO: this server is password protected"));
434                 sendintro();
435                 break;
436             }
437 
438             case SV_WELCOME:
439                 joining = getint(p);
440                 player1->resetspec();
441                 resetcamera();
442                 break;
443 
444             case SV_CLIENT:
445             {
446                 int cn = getint(p), len = getuint(p);
447                 ucharbuf q = p.subbuf(len);
448                 parsemessages(cn, getclient(cn), q, demo);
449                 break;
450             }
451 
452             case SV_SOUND:
453                 audiomgr.playsound(getint(p), d);
454                 break;
455 
456             case SV_VOICECOMTEAM:
457             {
458                 playerent *d = getclient(getint(p));
459                 if(d) d->lastvoicecom = lastmillis;
460                 int t = getint(p);
461                 if(!d || !(d->muted || d->ignored))
462                 {
463                     if ( voicecomsounds == 1 || (voicecomsounds == 2 && m_teammode) ) audiomgr.playsound(t, SP_HIGH);
464                 }
465                 break;
466             }
467             case SV_VOICECOM:
468             {
469                 int t = getint(p);
470                 if(!d || !(d->muted || d->ignored))
471                 {
472                     if ( voicecomsounds == 1 ) audiomgr.playsound(t, SP_HIGH);
473                 }
474                 if(d) d->lastvoicecom = lastmillis;
475                 break;
476             }
477 
478             case SV_TEAMTEXTME:
479             case SV_TEAMTEXT:
480             {
481                 int cn = getint(p);
482                 getstring(text, p);
483                 filtertext(text, text);
484                 playerent *d = getclient(cn);
485                 if(!d) break;
486                 if(d->ignored) clientlogf("ignored: %s%s %s", colorname(d), type == SV_TEAMTEXT ? ":" : "", text);
487                 else
488                 {
489                     if(m_teammode) conoutf(type == SV_TEAMTEXTME ? "\f1%s %s" : "%s:\f1 %s", colorname(d), highlight(text));
490                     else conoutf(type == SV_TEAMTEXTME ? "\f0%s %s" : "%s:\f0 %s", colorname(d), highlight(text));
491                 }
492                 break;
493             }
494 
495             case SV_TEXTME:
496             case SV_TEXT:
497                 if(cn == -1)
498                 {
499                     getstring(text, p);
500                     conoutf("MOTD:");
501                     conoutf("\f4%s", text);
502                 }
503                 else if(d)
504                 {
505                     getstring(text, p);
506                     filtertext(text, text);
507                     if(d->ignored && d->clientrole != CR_ADMIN) clientlogf("ignored: %s%s %s", colorname(d), type == SV_TEXT ? ":" : "", text);
508                     else conoutf(type == SV_TEXTME ? "\f0%s %s" : "%s:\f0 %s", colorname(d), highlight(text));
509                 }
510                 else return;
511                 break;
512 
513             case SV_TEXTPRIVATE:
514             {
515                 int cn = getint(p);
516                 getstring(text, p);
517                 filtertext(text, text);
518                 playerent *d = getclient(cn);
519                 if(!d) break;
520                 if(d->ignored) clientlogf("ignored: pm %s %s", colorname(d), text);
521                 else
522                 {
523                     conoutf("%s (PM):\f9 %s", colorname(d), highlight(text));
524                     lastpm = d->clientnum;
525                     if(identexists("onPM"))
526                     {
527                         defformatstring(onpm)("onPM %d [%s]", d->clientnum, text);
528                         execute(onpm);
529                     }
530                 }
531                 break;
532             }
533 
534             case SV_MAPCHANGE:
535             {
536                 extern int spawnpermission;
537                 spawnpermission = SP_SPECT;
538                 getstring(text, p);
539                 int mode = getint(p);
540                 int downloadable = getint(p);
541                 int revision = getint(p);
542                 localwrongmap = !changemapserv(text, mode, downloadable, revision);
543                 if(m_arena && joining>2) deathstate(player1);
544                 break;
545             }
546 
547             case SV_ITEMLIST:
548             {
549                 int n;
550                 resetspawns();
551                 while((n = getint(p))!=-1) setspawn(n, true);
552                 break;
553             }
554 
555             case SV_MAPIDENT:
556             {
557                 loopi(2) getint(p);
558                 break;
559             }
560 
561             case SV_SWITCHNAME:
562                 getstring(text, p);
563                 filtertext(text, text, 0, MAXNAMELEN);
564                 if(!text[0]) copystring(text, "unarmed");
565                 if(d)
566                 {
567                     if(strcmp(d->name, text))
568                         conoutf(_("%s is now known as %s"), colorname(d), colorname(d, text));
569                     if(identexists("onNameChange"))
570                     {
571                         defformatstring(onnamechange)("onNameChange %d \"%s\"", d->clientnum, text);
572                         execute(onnamechange);
573                     }
574                     copystring(d->name, text, MAXNAMELEN+1);
575                     updateclientname(d);
576                 }
577                 break;
578 
579             case SV_SWITCHTEAM:
580                 getint(p);
581                 break;
582 
583             case SV_SWITCHSKIN:
584                 loopi(2)
585                 {
586                     int skin = getint(p);
587                     if(d) d->setskin(i, skin);
588                 }
589                 break;
590 
591             case SV_INITCLIENT:            // another client either connected or changed name/team
592             {
593                 int cn = getint(p);
594                 playerent *d = newclient(cn);
595                 if(!d)
596                 {
597                     getstring(text, p);
598                     loopi(2) getint(p);
599                     getint(p);
600                     if(!demo || !watchingdemo || demoprotocol > 1132) getint(p);
601                     break;
602                 }
603                 getstring(text, p);
604                 filtertext(text, text, 0, MAXNAMELEN);
605                 if(!text[0]) copystring(text, "unarmed");
606                 if(d->name[0])          // already connected
607                 {
608                     if(strcmp(d->name, text))
609                         conoutf(_("%s is now known as %s"), colorname(d), colorname(d, text));
610                 }
611                 else                    // new client
612                 {
613                     conoutf(_("connected: %s"), colorname(d, text));
614                 }
615                 copystring(d->name, text, MAXNAMELEN+1);
616                 if(identexists("onConnect"))
617                 {
618                     defformatstring(onconnect)("onConnect %d", d->clientnum);
619                     execute(onconnect);
620                 }
621                 loopi(2) d->setskin(i, getint(p));
622                 d->team = getint(p);
623 
624                 if(!demo || !watchingdemo || demoprotocol > 1132) d->address = getint(p); // partial IP address
625 
626                 if(m_flags) loopi(2)
627                 {
628                     flaginfo &f = flaginfos[i];
629                     if(!f.actor) f.actor = getclient(f.actor_cn);
630                 }
631                 updateclientname(d);
632                 break;
633             }
634 
635             case SV_CDIS:
636             {
637                 int cn = getint(p);
638                 playerent *d = getclient(cn);
639                 if(!d) break;
640                 if(d->name[0]) conoutf(_("player %s disconnected"), colorname(d));
641                 zapplayer(players[cn]);
642                 if(identexists("onDisconnect"))
643                 {
644                     defformatstring(ondisconnect)("onDisconnect %d", d->clientnum);
645                     execute(ondisconnect);
646                 }
647                 break;
648             }
649 
650             case SV_EDITMODE:
651             {
652                 int val = getint(p);
653                 if(!d) break;
654                 if(val) d->state = CS_EDITING;
655                 else
656                 {
657                     //2011oct16:flowtron:keep spectator state
658                     //specators shouldn't be allowed to toggle editmode for themselves. they're ghosts!
659                     d->state = d->state==CS_SPECTATE?CS_SPECTATE:CS_ALIVE;
660                 }
661                 break;
662             }
663 
664             case SV_SPAWN:
665             {
666                 playerent *s = d;
667                 if(!s) { static playerent dummy; s = &dummy; }
668                 s->respawn();
669                 s->lifesequence = getint(p);
670                 s->health = getint(p);
671                 s->armour = getint(p);
672                 int gunselect = getint(p);
673                 s->setprimary(gunselect);
674                 s->selectweapon(gunselect);
675                 loopi(NUMGUNS) s->ammo[i] = getint(p);
676                 loopi(NUMGUNS) s->mag[i] = getint(p);
677                 s->state = CS_SPAWNING;
678                 if(s->lifesequence==0) s->resetstats(); //NEW
679                 break;
680             }
681 
682             case SV_SPAWNSTATE:
683             {
684                 if ( map_quality == MAP_IS_BAD )
685                 {
686                     loopi(6+2*NUMGUNS) getint(p);
687                     conoutf(_("map deemed unplayable - fix it before you can spawn"));
688                     break;
689                 }
690 
691                 if(editmode) toggleedit(true);
692                 showscores(false);
693                 setscope(false);
694                 setburst(false);
695                 player1->respawn();
696                 player1->lifesequence = getint(p);
697                 player1->health = getint(p);
698                 player1->armour = getint(p);
699                 player1->setprimary(getint(p));
700                 player1->selectweapon(getint(p));
701                 int arenaspawn = getint(p);
702                 loopi(NUMGUNS) player1->ammo[i] = getint(p);
703                 loopi(NUMGUNS) player1->mag[i] = getint(p);
704                 player1->state = CS_ALIVE;
705                 lastspawn = lastmillis;
706                 findplayerstart(player1, false, arenaspawn);
707                 arenaintermission = 0;
708                 if(m_arena && !localwrongmap)
709                 {
710                     closemenu(NULL);
711                     conoutf(_("new round starting... fight!"));
712                     hudeditf(HUDMSG_TIMER, "FIGHT!");
713                     if(m_botmode) BotManager.RespawnBots();
714                 }
715                 addmsg(SV_SPAWN, "rii", player1->lifesequence, player1->weaponsel->type);
716                 player1->weaponswitch(player1->primweap);
717                 player1->weaponchanging -= weapon::weaponchangetime/2;
718                 if(player1->lifesequence==0) player1->resetstats(); //NEW
719                 break;
720             }
721 
722             case SV_SHOTFX:
723             {
724                 int scn = getint(p), gun = getint(p);
725                 vec from, to;
726                 loopk(3) to[k] = getint(p)/DMF;
727                 playerent *s = getclient(scn);
728                 if(!s || !weapon::valid(gun)) break;
729                 loopk(3) from[k] = s->o.v[k];
730                 if(gun==GUN_SHOTGUN) createrays(from, to);
731                 s->lastaction = lastmillis;
732                 s->weaponchanging = 0;
733                 s->mag[gun]--;
734                 if(s->weapons[gun])
735                 {
736                     s->lastattackweapon = s->weapons[gun];
737                     s->weapons[gun]->gunwait = s->weapons[gun]->info.attackdelay;
738                     s->weapons[gun]->attackfx(from, to, -1);
739                     s->weapons[gun]->reloading = 0;
740                 }
741                 s->pstatshots[gun]++; //NEW
742                 break;
743             }
744 
745             case SV_THROWNADE:
746             {
747                 vec from, to;
748                 loopk(3) from[k] = getint(p)/DMF;
749                 loopk(3) to[k] = getint(p)/DMF;
750                 int nademillis = getint(p);
751                 if(!d) break;
752                 d->lastaction = lastmillis;
753                 d->weaponchanging = 0;
754                 d->lastattackweapon = d->weapons[GUN_GRENADE];
755                 if(d->weapons[GUN_GRENADE])
756                 {
757                     d->weapons[GUN_GRENADE]->attackfx(from, to, nademillis);
758                     d->weapons[GUN_GRENADE]->reloading = 0;
759                 }
760                 if(d!=player1) d->pstatshots[GUN_GRENADE]++; //NEW
761                 break;
762             }
763 
764             case SV_RELOAD:
765             {
766                 int cn = getint(p), gun = getint(p);
767                 playerent *p = getclient(cn);
768                 if(p && p!=player1) p->weapons[gun]->reload(false);
769                 break;
770             }
771 
772             // for AUTH: WIP
773 
774             case SV_AUTHREQ:
775             {
776                 extern int autoauth;
777                 getstring(text, p);
778                 if(autoauth && text[0] && tryauth(text)) conoutf("server requested authkey \"%s\"", text);
779                 break;
780             }
781 
782             case SV_AUTHCHAL:
783             {
784                 getstring(text, p);
785                 authkey *a = findauthkey(text);
786                 uint id = (uint)getint(p);
787                 getstring(text, p);
788                 if(a && a->lastauth && lastmillis - a->lastauth < 60*1000)
789                 {
790                     vector<char> buf;
791                     answerchallenge(a->key, text, buf);
792                     //conoutf("answering %u, challenge %s with %s", id, text, buf.getbuf());
793                     addmsg(SV_AUTHANS, "rsis", a->desc, id, buf.getbuf());
794                 }
795                 break;
796             }
797 
798             // :for AUTH
799 
800             case SV_GIBDAMAGE:
801             case SV_DAMAGE:
802             {
803                 int tcn = getint(p),
804                     acn = getint(p),
805                     gun = getint(p),
806                     damage = getint(p),
807                     armour = getint(p),
808                     health = getint(p);
809                 playerent *target = getclient(tcn), *actor = getclient(acn);
810                 if(!target || !actor) break;
811                 target->armour = armour;
812                 target->health = health;
813                 dodamage(damage, target, actor, -1, type==SV_GIBDAMAGE, false);
814                 actor->pstatdamage[gun]+=damage; //NEW
815                 break;
816             }
817 
818             case SV_POINTS:
819             {
820                 int count = getint(p);
821                 if ( count > 0 ) {
822                     loopi(count){
823                         int pcn = getint(p); int score = getint(p);
824                         playerent *ppl = getclient(pcn);
825                         if (!ppl) break;
826                         ppl->points += score;
827                     }
828                 } else {
829                     int medals = getint(p);
830                     if(medals > 0) {
831 //                         medals_arrived=1;
832                         loopi(medals) {
833                             int mcn=getint(p); int mtype=getint(p); int mitem=getint(p);
834                             a_medals[mtype].assigned=1;
835                             a_medals[mtype].cn=mcn;
836                             a_medals[mtype].item=mitem;
837                         }
838                     }
839                 }
840                 break;
841             }
842 
843             case SV_HUDEXTRAS:
844             {
845                 char value = getint(p);
846                 if (hudextras) showhudextras(hudextras, value);
847                 break;
848             }
849 
850             case SV_HITPUSH:
851             {
852                 int gun = getint(p), damage = getint(p);
853                 vec dir;
854                 loopk(3) dir[k] = getint(p)/DNF;
855                 player1->hitpush(damage, dir, NULL, gun);
856                 break;
857             }
858 
859             case SV_GIBDIED:
860             case SV_DIED:
861             {
862                 int vcn = getint(p), acn = getint(p), frags = getint(p), gun = getint(p);
863                 playerent *victim = getclient(vcn), *actor = getclient(acn);
864                 if(!actor) break;
865                 if ( m_mp(gamemode) ) actor->frags = frags;
866                 if(!victim) break;
867                 dokill(victim, actor, type==SV_GIBDIED, gun);
868                 break;
869             }
870 
871             case SV_RESUME:
872             {
873                 loopi(MAXCLIENTS)
874                 {
875                     int cn = getint(p);
876                     if(p.overread() || cn<0) break;
877                     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);
878                     int teamkills = 0;
879                     if(!demo || !watchingdemo || demoprotocol > 1132) teamkills = getint(p);
880                     int ammo[NUMGUNS], mag[NUMGUNS];
881                     loopi(NUMGUNS) ammo[i] = getint(p);
882                     loopi(NUMGUNS) mag[i] = getint(p);
883                     playerent *d = (cn == getclientnum() ? player1 : newclient(cn));
884                     if(!d) continue;
885                     if(d!=player1) d->state = state;
886                     d->lifesequence = lifesequence;
887                     d->flagscore = flagscore;
888                     d->frags = frags;
889                     d->deaths = deaths;
890                     d->points = points;
891                     d->tks = teamkills;
892                     if(d!=player1)
893                     {
894                         d->setprimary(primary);
895                         d->selectweapon(gunselect);
896                         d->health = health;
897                         d->armour = armour;
898                         memcpy(d->ammo, ammo, sizeof(ammo));
899                         memcpy(d->mag, mag, sizeof(mag));
900                         if(d->lifesequence==0) d->resetstats(); //NEW
901                     }
902                 }
903                 break;
904             }
905 
906             case SV_DISCSCORES:
907             {
908                 discscores.shrink(0);
909                 int team;
910                 while((team = getint(p)) >= 0)
911                 {
912                     discscore &ds = discscores.add();
913                     ds.team = team;
914                     getstring(text, p);
915                     filtertext(ds.name, text, 0, MAXNAMELEN);
916                     ds.flags = getint(p);
917                     ds.frags = getint(p);
918                     ds.deaths = getint(p);
919                     ds.points = getint(p);
920                 }
921                 break;
922             }
923             case SV_ITEMSPAWN:
924             {
925                 int i = getint(p);
926                 setspawn(i, true);
927                 break;
928             }
929 
930             case SV_ITEMACC:
931             {
932                 int i = getint(p), cn = getint(p);
933                 playerent *d = getclient(cn);
934                 pickupeffects(i, d);
935                 break;
936             }
937 
938             case SV_EDITH:              // coop editing messages, should be extended to include all possible editing ops
939             case SV_EDITT:
940             case SV_EDITS:
941             case SV_EDITD:
942             case SV_EDITE:
943             {
944                 int x  = getint(p);
945                 int y  = getint(p);
946                 int xs = getint(p);
947                 int ys = getint(p);
948                 int v  = getint(p);
949                 block b = { x, y, xs, ys };
950                 switch(type)
951                 {
952                     case SV_EDITH: editheightxy(v!=0, getint(p), b); break;
953                     case SV_EDITT: edittexxy(v, getint(p), b); break;
954                     case SV_EDITS: edittypexy(v, b); break;
955                     case SV_EDITD: setvdeltaxy(v, b); break;
956                     case SV_EDITE: editequalisexy(v!=0, b); break;
957                 }
958                 break;
959             }
960 
961             case SV_NEWMAP:
962             {
963                 int size = getint(p);
964                 if(size>=0) empty_world(size, true);
965                 else empty_world(-1, true);
966                 if(d && d!=player1)
967                     conoutf(size>=0 ? _("%s started a new map of size %d") : _("%s enlarged the map to size %d"), colorname(d), sfactor);
968                 break;
969             }
970 
971             case SV_EDITENT:            // coop edit of ent
972             {
973                 uint i = getint(p);
974                 while((uint)ents.length()<=i) ents.add().type = NOTUSED;
975                 int to = ents[i].type;
976                 if(ents[i].type==SOUND)
977                 {
978                     entity &e = ents[i];
979 
980                     entityreference entref(&e);
981                     location *loc = audiomgr.locations.find(e.attr1, &entref, mapsounds);
982 
983                     if(loc)
984                         loc->drop();
985                 }
986 
987                 ents[i].type = getint(p);
988                 ents[i].x = getint(p);
989                 ents[i].y = getint(p);
990                 ents[i].z = getint(p);
991                 ents[i].attr1 = getint(p);
992                 ents[i].attr2 = getint(p);
993                 ents[i].attr3 = getint(p);
994                 ents[i].attr4 = getint(p);
995                 ents[i].spawned = false;
996                 if(ents[i].type==LIGHT || to==LIGHT) calclight();
997                 if(ents[i].type==SOUND) audiomgr.preloadmapsound(ents[i]);
998                 break;
999             }
1000 
1001             case SV_PONG:
1002             {
1003                 int millis = getint(p);
1004                 addmsg(SV_CLIENTPING, "i", player1->ping = max(0, (player1->ping*5+totalmillis-millis)/6));
1005                 break;
1006             }
1007 
1008             case SV_CLIENTPING:
1009                 if(!d) return;
1010                 d->ping = getint(p);
1011                 break;
1012 
1013             case SV_GAMEMODE:
1014                 nextmode = getint(p);
1015                 if (nextmode >= GMODE_NUM) nextmode -= GMODE_NUM;
1016                 break;
1017 
1018             case SV_TIMEUP:
1019             {
1020                 int curgamemillis = getint(p);
1021                 int curgamelimit = getint(p);
1022                 timeupdate(curgamemillis, curgamelimit);
1023                 break;
1024             }
1025 
1026             case SV_WEAPCHANGE:
1027             {
1028                 int gun = getint(p);
1029                 if(d) d->selectweapon(gun);
1030                 break;
1031             }
1032 
1033             case SV_SERVMSG:
1034                 getstring(text, p);
1035                 conoutf("%s", text);
1036                 break;
1037 
1038             case SV_FLAGINFO:
1039             {
1040                 int flag = getint(p);
1041                 if(flag<0 || flag>1) return;
1042                 flaginfo &f = flaginfos[flag];
1043                 f.state = getint(p);
1044                 switch(f.state)
1045                 {
1046                     case CTFF_STOLEN:
1047                         flagstolen(flag, getint(p));
1048                         break;
1049                     case CTFF_DROPPED:
1050                     {
1051                         float x = getuint(p)/DMF;
1052                         float y = getuint(p)/DMF;
1053                         float z = getuint(p)/DMF;
1054                         flagdropped(flag, x, y, z);
1055                         break;
1056                     }
1057                     case CTFF_INBASE:
1058                         flaginbase(flag);
1059                         break;
1060                     case CTFF_IDLE:
1061                         flagidle(flag);
1062                         break;
1063                 }
1064                 break;
1065             }
1066 
1067             case SV_FLAGMSG:
1068             {
1069                 int flag = getint(p);
1070                 int message = getint(p);
1071                 int actor = getint(p);
1072                 int flagtime = message == FM_KTFSCORE ? getint(p) : -1;
1073                 flagmsg(flag, message, actor, flagtime);
1074                 break;
1075             }
1076 
1077             case SV_FLAGCNT:
1078             {
1079                 int fcn = getint(p);
1080                 int flags = getint(p);
1081                 playerent *p = getclient(fcn);
1082                 if(p) p->flagscore = flags;
1083                 break;
1084             }
1085 
1086             case SV_ARENAWIN:
1087             {
1088                 int acn = getint(p);
1089                 playerent *alive = getclient(acn);
1090                 conoutf(_("the round is over! next round in 5 seconds..."));
1091                 if(m_botmode && acn==-2) hudoutf(_("the bots have won the round!"));
1092                 else if(!alive) hudoutf(_("everyone died!"));
1093                 else if(m_teammode) hudoutf(_("team %s has won the round!"), team_string(alive->team));
1094                 else if(alive==player1) hudoutf(_("you are the survivor!"));
1095                 else hudoutf(_("%s is the survivor!"), colorname(alive));
1096                 arenaintermission = lastmillis;
1097                 break;
1098             }
1099 
1100             case SV_SPAWNDENY:
1101             {
1102                 extern int spawnpermission;
1103                 spawnpermission = getint(p);
1104                 if(spawnpermission == SP_REFILLMATCH) hudoutf("\f3You can now spawn to refill your team.");
1105                 break;
1106             }
1107             case SV_FORCEDEATH:
1108             {
1109                 int cn = getint(p);
1110                 playerent *d = cn==getclientnum() ? player1 : newclient(cn);
1111                 if(!d) break;
1112                 deathstate(d);
1113                 break;
1114             }
1115 
1116             case SV_SERVOPINFO:
1117             {
1118                 loopv(players) { if(players[i]) players[i]->clientrole = CR_DEFAULT; }
1119                 player1->clientrole = CR_DEFAULT;
1120 
1121                 int cl = getint(p), r = getint(p);
1122                 if(cl >= 0 && r >= 0)
1123                 {
1124                     playerent *pl = (cl == getclientnum() ? player1 : newclient(cl));
1125                     if(pl)
1126                     {
1127                         pl->clientrole = r;
1128                         if(pl->name[0])
1129                         {
1130                             // two messages required to allow for proper german translation - is there a better way to do it?
1131                             if(pl==player1) conoutf(_("you claimed %s status"), r == CR_ADMIN ? "admin" : "master");
1132                             else conoutf(_("%s claimed %s status"), colorname(pl), r == CR_ADMIN ? "admin" : "master");
1133                         }
1134                     }
1135                 }
1136                 break;
1137             }
1138 
1139             case SV_TEAMDENY:
1140             {
1141                 int t = getint(p);
1142                 if(m_teammode)
1143                 {
1144                     if(team_isvalid(t)) conoutf(_("you can't change to team %s"), team_string(t));
1145                 }
1146                 else
1147                 {
1148                     conoutf(_("you can't change to %s mode"), team_isspect(t) ? _("spectate") : _("active"));
1149                 }
1150                 break;
1151             }
1152 
1153             case SV_SETTEAM:
1154             {
1155                 int fpl = getint(p), fnt = getint(p), ftr = fnt >> 4;
1156                 fnt &= 0x0f;
1157                 playerent *d = (fpl == getclientnum() ? player1 : newclient(fpl));
1158                 if(d)
1159                 {
1160                     const char *nts = team_string(fnt);
1161                     bool you = fpl == player1->clientnum;
1162                     if(m_teammode || team_isspect(fnt))
1163                     {
1164                         if(d->team == fnt)
1165                         {
1166                             if(you && ftr == FTR_AUTOTEAM) hudoutf("you stay in team %s", nts);
1167                         }
1168                         else
1169                         {
1170                             if(you && !watchingdemo)
1171                             {
1172                                 switch(ftr)
1173                                 {
1174                                     case FTR_PLAYERWISH:
1175                                         conoutf(_("you're now in team %s"), nts);
1176                                         break;
1177                                     case FTR_AUTOTEAM:
1178                                         hudoutf(_("the server forced you to team %s"), nts);
1179                                         break;
1180                                 }
1181                             }
1182                             else
1183                             {
1184                                 const char *pls = colorname(d);
1185                                 bool et = team_base(player1->team) != team_base(fnt);
1186                                 switch(ftr)
1187                                 {
1188                                     case FTR_PLAYERWISH:
1189                                         conoutf(_("player %s switched to team %s"), pls, nts); // new message
1190                                         break;
1191                                     case FTR_AUTOTEAM:
1192                                         if(watchingdemo) conoutf(_("the server forced %s to team %s"), colorname(d), nts);
1193                                         else hudoutf(_("the server forced %s to %s team"), colorname(d), et ? _("the enemy") : _("your"));
1194                                         break;
1195                                 }
1196                             }
1197                             if(you && !team_isspect(d->team) && team_isspect(fnt) && d->state == CS_DEAD) spectatemode(SM_FLY);
1198                         }
1199                     }
1200                     else if(d->team != fnt && ftr == FTR_PLAYERWISH) conoutf(_("%s changed to active play"), you ? _("you") : colorname(d));
1201                     d->team = fnt;
1202                     if(team_isspect(d->team)) d->state = CS_SPECTATE;
1203                 }
1204                 break;
1205             }
1206 
1207             case SV_SERVERMODE:
1208             {
1209                 int sm = getint(p);
1210                 servstate.autoteam = sm & 1;
1211                 servstate.mastermode = (sm >> 2) & MM_MASK;
1212                 servstate.matchteamsize = sm >> 4;
1213                 //if(sm & AT_SHUFFLE) playsound(TEAMSHUFFLE);    // TODO
1214                 break;
1215             }
1216 
1217             case SV_CALLVOTE:
1218             {
1219                 int type = getint(p);
1220                 int vcn = -1, n_yes = 0, n_no = 0;
1221                 if ( type == -1 )
1222                 {
1223                     d = getclient(vcn = getint(p));
1224                     n_yes = getint(p);
1225                     n_no = getint(p);
1226                     type = getint(p);
1227                 }
1228                 if (type == SA_MAP && d == NULL) d = player1;      // gonext uses this
1229                 if( type < 0 || type >= SA_NUM || !d ) return;
1230                 votedisplayinfo *v = NULL;
1231                 string a1, a2;
1232                 switch(type)
1233                 {
1234                     case SA_MAP:
1235                         getstring(text, p);
1236                         filtertext(text, text);
1237                         itoa(a1, getint(p));
1238                         defformatstring(t)("%d", getint(p));
1239                         v = newvotedisplayinfo(d, type, text, a1, t);
1240                         break;
1241                     case SA_KICK:
1242                     case SA_BAN:
1243                     {
1244                         itoa(a1, getint(p));
1245                         getstring(text, p);
1246                         filtertext(text, text);
1247                         v = newvotedisplayinfo(d, type, a1, text);
1248                         break;
1249                     }
1250                     case SA_SERVERDESC:
1251                         getstring(text, p);
1252                         filtertext(text, text);
1253                         v = newvotedisplayinfo(d, type, text, NULL);
1254                         break;
1255                     case SA_STOPDEMO:
1256                         // compatibility
1257                         break;
1258                     case SA_REMBANS:
1259                     case SA_SHUFFLETEAMS:
1260                         v = newvotedisplayinfo(d, type, NULL, NULL);
1261                         break;
1262                     case SA_FORCETEAM:
1263                         itoa(a1, getint(p));
1264                         itoa(a2, getint(p));
1265                         v = newvotedisplayinfo(d, type, a1, a2);
1266                         break;
1267                     default:
1268                         itoa(a1, getint(p));
1269                         v = newvotedisplayinfo(d, type, a1, NULL);
1270                         break;
1271                 }
1272                 displayvote(v);
1273                 onCallVote(type, v->owner->clientnum, text, a1);
1274                 if (vcn >= 0)
1275                 {
1276                     loopi(n_yes) votecount(VOTE_YES);
1277                     loopi(n_no) votecount(VOTE_NO);
1278                 }
1279                 extern int vote(int);
1280                 if (d == player1) vote(VOTE_YES);
1281                 break;
1282             }
1283 
1284             case SV_CALLVOTESUC:
1285             {
1286                 callvotesuc();
1287                 onChangeVote( 0, -1);
1288                 break;
1289             }
1290 
1291             case SV_CALLVOTEERR:
1292             {
1293                 int errn = getint(p);
1294                 callvoteerr(errn);
1295                 onChangeVote( 1, errn);
1296                 break;
1297             }
1298 
1299             case SV_VOTE:
1300             {
1301                 int vote = getint(p);
1302                 votecount(vote);
1303                 onChangeVote( 2, vote);
1304                 break;
1305             }
1306 
1307             case SV_VOTERESULT:
1308             {
1309                 int vres = getint(p);
1310                 voteresult(vres);
1311                 onChangeVote( 3, vres);
1312                 break;
1313             }
1314 
1315             case SV_IPLIST:
1316             {
1317                 int cn;
1318                 while((cn = getint(p)) >= 0 && !p.overread())
1319                 {
1320                     playerent *pl = getclient(cn);
1321                     int ip = getint(p);
1322                     if(!pl) continue;
1323                     else pl->address = ip;
1324                 }
1325                 break;
1326             }
1327 
1328             case SV_SENDDEMOLIST:
1329             {
1330                 int demos = getint(p);
1331                 if(!demos) conoutf(_("no demos available"));
1332                 else loopi(demos)
1333                 {
1334                     getstring(text, p);
1335                     conoutf("%d. %s", i+1, text);
1336                 }
1337                 break;
1338             }
1339 
1340             case SV_DEMOPLAYBACK:
1341             {
1342                 string demofile;
1343                 extern char *curdemofile;
1344                 if(demo && watchingdemo && demoprotocol == 1132)
1345                 {
1346                     watchingdemo = demoplayback = getint(p)!=0;
1347                     copystring(demofile, "n/a");
1348                 }
1349                 else
1350                 {
1351                     getstring(demofile, p, MAXSTRLEN);
1352                     watchingdemo = demoplayback = demofile[0] != '\0';
1353                 }
1354                 DELETEA(curdemofile);
1355                 if(demoplayback)
1356                 {
1357                     curdemofile = newstring(demofile);
1358                     player1->resetspec();
1359                     player1->state = CS_SPECTATE;
1360                     player1->team = TEAM_SPECT;
1361                 }
1362                 else
1363                 {
1364                     // cleanups
1365                     curdemofile = newstring("n/a");
1366                     loopv(players) zapplayer(players[i]);
1367                     clearvote();
1368                     player1->state = CS_ALIVE;
1369                     player1->resetspec();
1370                 }
1371                 player1->clientnum = getint(p);
1372                 break;
1373             }
1374 
1375             default:
1376                 neterr("type");
1377                 return;
1378         }
1379     }
1380 
1381     #ifdef _DEBUG
1382     protocoldebug(false);
1383     #endif
1384 }
1385 
setDemoFilenameFormat(char * fmt)1386 void setDemoFilenameFormat(char *fmt)
1387 {
1388     extern string demofilenameformat;
1389     if(fmt && fmt[0]!='\0')
1390     {
1391         copystring(demofilenameformat, fmt);
1392     } else copystring(demofilenameformat, DEFDEMOFILEFMT); // reset to default if passed empty string - or should we output the current value in this case?
1393 }
1394 COMMANDN(demonameformat, setDemoFilenameFormat, "s");
setDemoTimestampFormat(char * fmt)1395 void setDemoTimestampFormat(char *fmt)
1396 {
1397     extern string demotimestampformat;
1398     if(fmt && fmt[0]!='\0')
1399     {
1400         copystring(demotimestampformat, fmt);
1401     } else copystring(demotimestampformat, DEFDEMOTIMEFMT); // reset to default if passed empty string - or should we output the current value in this case?
1402 }
1403 COMMANDN(demotimeformat, setDemoTimestampFormat, "s");
setDemoTimeLocal(int * truth)1404 void setDemoTimeLocal(int *truth)
1405 {
1406     extern int demotimelocal;
1407     demotimelocal = *truth == 0 ? 0 : 1;
1408 }
1409 COMMANDN(demotimelocal, setDemoTimeLocal, "i");
getdemonameformat()1410 void getdemonameformat() { extern string demofilenameformat; result(demofilenameformat); } COMMAND(getdemonameformat, "");
getdemotimeformat()1411 void getdemotimeformat() { extern string demotimestampformat; result(demotimestampformat); } COMMAND(getdemotimeformat, "");
getdemotimelocal()1412 void getdemotimelocal() { extern int demotimelocal; intret(demotimelocal); } COMMAND(getdemotimelocal, "");
1413 
1414 
parseDemoFilename(char * srvfinfo)1415 const char *parseDemoFilename(char *srvfinfo)
1416 {
1417     int gmode = 0; //-314;
1418     int mplay = 0;
1419     int mdrop = 0;
1420     int stamp = 0;
1421     string srvmap;
1422     if(srvfinfo && srvfinfo[0])
1423     {
1424         int fip = 0;
1425         char sep[] = ":";
1426         char *pch;
1427         pch = strtok (srvfinfo,sep);
1428         while (pch != NULL && fip < 4)
1429         {
1430             fip++;
1431             switch(fip)
1432             {
1433                 case 1: gmode = atoi(pch); break;
1434                 case 2: mplay = atoi(pch); break;
1435                 case 3: mdrop = atoi(pch); break;
1436                 case 4: stamp = atoi(pch); break;
1437                 default: break;
1438             }
1439             pch = strtok (NULL, sep);
1440         }
1441         copystring(srvmap, pch);
1442     }
1443     extern const char *getDemoFilename(int gmode, int mplay, int mdrop, int tstamp, char *srvmap);
1444     return getDemoFilename(gmode, mplay, mdrop, stamp, srvmap);
1445 }
1446 
receivefile(uchar * data,int len)1447 void receivefile(uchar *data, int len)
1448 {
1449     static char text[MAXTRANS];
1450     ucharbuf p(data, len);
1451     int type = getint(p);
1452     data += p.length();
1453     len -= p.length();
1454     switch(type)
1455     {
1456         case SV_SENDDEMO:
1457         {
1458             getstring(text, p);
1459             extern string demosubpath;
1460             defformatstring(demofn)("%s", parseDemoFilename(text));
1461             defformatstring(fname)("demos/%s%s.dmo", demosubpath, demofn);
1462             copystring(demosubpath, "");
1463             data += strlen(text);
1464             int demosize = getint(p);
1465             if(p.remaining() < demosize)
1466             {
1467                 p.forceoverread();
1468                 break;
1469             }
1470             path(fname);
1471             stream *demo = openrawfile(fname, "wb");
1472             if(!demo)
1473             {
1474                 conoutf(_("failed writing to \"%s\""), fname);
1475                 return;
1476             }
1477             conoutf(_("received demo \"%s\""), fname);
1478             demo->write(&p.buf[p.len], demosize);
1479             delete demo;
1480             break;
1481         }
1482 
1483         case SV_RECVMAP:
1484         {
1485             getstring(text, p);
1486             conoutf(_("received map \"%s\" from server, reloading.."), text);
1487             int mapsize = getint(p);
1488             int cfgsize = getint(p);
1489             int cfgsizegz = getint(p);
1490             /* int revision = */ getint(p);
1491             int size = mapsize + cfgsizegz;
1492             if(MAXMAPSENDSIZE < mapsize + cfgsizegz || cfgsize > MAXCFGFILESIZE) { // sam's suggestion
1493                 conoutf(_("map %s is too large to receive"), text);
1494             } else {
1495                 if(p.remaining() < size)
1496                 {
1497                     p.forceoverread();
1498                     break;
1499                 }
1500                 if(securemapcheck(text))
1501                 {
1502                     p.len += size;
1503                     break;
1504                 }
1505                 writemap(path(text), mapsize, &p.buf[p.len]);
1506                 p.len += mapsize;
1507                 writecfggz(path(text), cfgsize, cfgsizegz, &p.buf[p.len]);
1508                 p.len += cfgsizegz;
1509             }
1510             break;
1511         }
1512 
1513         default:
1514             p.len = 0;
1515             parsemessages(-1, NULL, p);
1516             break;
1517     }
1518 }
1519 
servertoclient(int chan,uchar * buf,int len,bool demo)1520 void servertoclient(int chan, uchar *buf, int len, bool demo)   // processes any updates from the server
1521 {
1522     ucharbuf p(buf, len);
1523     switch(chan)
1524     {
1525         case 0: parsepositions(p); break;
1526         case 1: parsemessages(-1, NULL, p, demo); break;
1527         case 2: receivefile(p.buf, p.maxlen); break;
1528     }
1529 }
1530 
localservertoclient(int chan,uchar * buf,int len,bool demo)1531 void localservertoclient(int chan, uchar *buf, int len, bool demo)   // processes any updates from the server
1532 {
1533 //    pktlogger.queue(enet_packet_create (buf, len, 0));  // log local & demo packets
1534     servertoclient(chan, buf, len, demo);
1535 }
1536