1 // server.cpp: little more than enhanced multicaster
2 // runs dedicated or as client coroutine
3 
4 #include "cube.h"
5 
6 #define DEBUGCOND (true)
7 
8 #include "server.h"
9 #include "servercontroller.h"
10 #include "serverfiles.h"
11 // 2011feb05:ft: quitproc
12 #include "signal.h"
13 // config
14 servercontroller *svcctrl = NULL;
15 servercommandline scl;
16 servermaprot maprot;
17 serveripblacklist ipblacklist;
18 servernickblacklist nickblacklist;
19 serverforbiddenlist forbiddenlist;
20 serverpasswords passwords;
21 serverinfofile infofiles;
22 killmessagesfile killmsgs;
23 
24 // server state
25 bool isdedicated = false;
26 ENetHost *serverhost = NULL;
27 
28 int nextstatus = 0, servmillis = 0, lastfillup = 0;
29 
30 vector<client *> clients;
31 vector<worldstate *> worldstates;
32 vector<savedscore> savedscores;
33 vector<ban> bans;
34 vector<demofile> demofiles;
35 
36 int mastermode = MM_OPEN;
37 static bool autoteam = true;
38 int matchteamsize = 0;
39 
40 long int incoming_size = 0;
41 
42 static bool forceintermission = false;
43 
44 string servdesc_current;
45 ENetAddress servdesc_caller;
46 bool custom_servdesc = false;
47 
48 // current game
49 string smapname, nextmapname;
50 int smode = 0, nextgamemode;
51 int interm = 0;
52 static int minremain = 0, gamemillis = 0, gamelimit = 0, /*lmsitemtype = 0,*/ nextsendscore = 0;
53 mapstats smapstats;
54 vector<server_entity> sents;
55 char *maplayout = NULL, *testlayout = NULL;
56 int maplayout_factor, testlayout_factor, maplayoutssize;
57 servermapbuffer mapbuffer;
58 
59 // cmod
60 char *global_name;
61 int totalclients = 0;
62 int cn2boot;
63 int servertime = 0, serverlagged = 0;
64 
valid_client(int cn)65 bool valid_client(int cn)
66 {
67     return clients.inrange(cn) && clients[cn]->type != ST_EMPTY;
68 }
69 
cleanworldstate(ENetPacket * packet)70 void cleanworldstate(ENetPacket *packet)
71 {
72    loopv(worldstates)
73    {
74        worldstate *ws = worldstates[i];
75        if(ws->positions.inbuf(packet->data) || ws->messages.inbuf(packet->data)) ws->uses--;
76        else continue;
77        if(!ws->uses)
78        {
79            delete ws;
80            worldstates.remove(i);
81        }
82        break;
83    }
84 }
85 
sendpacket(int n,int chan,ENetPacket * packet,int exclude,bool demopacket)86 void sendpacket(int n, int chan, ENetPacket *packet, int exclude, bool demopacket)
87 {
88     if(n<0)
89     {
90         recordpacket(chan, packet->data, (int)packet->dataLength);
91         loopv(clients) if(i!=exclude && (clients[i]->type!=ST_TCPIP || clients[i]->isauthed)) sendpacket(i, chan, packet, -1, demopacket);
92         return;
93     }
94     switch(clients[n]->type)
95     {
96         case ST_TCPIP:
97         {
98             enet_peer_send(clients[n]->peer, chan, packet);
99             break;
100         }
101 
102         case ST_LOCAL:
103             localservertoclient(chan, packet->data, (int)packet->dataLength, demopacket);
104             break;
105     }
106 }
107 
108 static bool reliablemessages = false;
109 
buildworldstate()110 bool buildworldstate()
111 {
112     static struct { int posoff, poslen, msgoff, msglen; } pkt[MAXCLIENTS];
113     worldstate &ws = *new worldstate;
114     loopv(clients)
115     {
116         client &c = *clients[i];
117         if(c.type!=ST_TCPIP || !c.isauthed) continue;
118         c.overflow = 0;
119         if(c.position.empty()) pkt[i].posoff = -1;
120         else
121         {
122             pkt[i].posoff = ws.positions.length();
123             ws.positions.put(c.position.getbuf(), c.position.length());
124             pkt[i].poslen = ws.positions.length() - pkt[i].posoff;
125             c.position.setsize(0);
126         }
127         if(c.messages.empty()) pkt[i].msgoff = -1;
128         else
129         {
130             pkt[i].msgoff = ws.messages.length();
131             putint(ws.messages, SV_CLIENT);
132             putint(ws.messages, c.clientnum);
133             putuint(ws.messages, c.messages.length());
134             ws.messages.put(c.messages.getbuf(), c.messages.length());
135             pkt[i].msglen = ws.messages.length() - pkt[i].msgoff;
136             c.messages.setsize(0);
137         }
138     }
139     int psize = ws.positions.length(), msize = ws.messages.length();
140     if(psize)
141     {
142         recordpacket(0, ws.positions.getbuf(), psize);
143         ucharbuf p = ws.positions.reserve(psize);
144         p.put(ws.positions.getbuf(), psize);
145         ws.positions.addbuf(p);
146     }
147     if(msize)
148     {
149         recordpacket(1, ws.messages.getbuf(), msize);
150         ucharbuf p = ws.messages.reserve(msize);
151         p.put(ws.messages.getbuf(), msize);
152         ws.messages.addbuf(p);
153     }
154     ws.uses = 0;
155     loopv(clients)
156     {
157         client &c = *clients[i];
158         if(c.type!=ST_TCPIP || !c.isauthed) continue;
159         ENetPacket *packet;
160         if(psize && (pkt[i].posoff<0 || psize-pkt[i].poslen>0))
161         {
162             packet = enet_packet_create(&ws.positions[pkt[i].posoff<0 ? 0 : pkt[i].posoff+pkt[i].poslen],
163                                         pkt[i].posoff<0 ? psize : psize-pkt[i].poslen,
164                                         ENET_PACKET_FLAG_NO_ALLOCATE);
165             sendpacket(c.clientnum, 0, packet);
166             if(!packet->referenceCount) enet_packet_destroy(packet);
167             else { ++ws.uses; packet->freeCallback = cleanworldstate; }
168         }
169 
170         if(msize && (pkt[i].msgoff<0 || msize-pkt[i].msglen>0))
171         {
172             packet = enet_packet_create(&ws.messages[pkt[i].msgoff<0 ? 0 : pkt[i].msgoff+pkt[i].msglen],
173                                         pkt[i].msgoff<0 ? msize : msize-pkt[i].msglen,
174                                         (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE);
175             sendpacket(c.clientnum, 1, packet);
176             if(!packet->referenceCount) enet_packet_destroy(packet);
177             else { ++ws.uses; packet->freeCallback = cleanworldstate; }
178         }
179     }
180     reliablemessages = false;
181     if(!ws.uses)
182     {
183         delete &ws;
184         return false;
185     }
186     else
187     {
188         worldstates.add(&ws);
189         return true;
190     }
191 }
192 
countclients(int type,bool exclude=false)193 int countclients(int type, bool exclude = false)
194 {
195     int num = 0;
196     loopv(clients) if((clients[i]->type!=type)==exclude) num++;
197     return num;
198 }
199 
numclients()200 int numclients() { return countclients(ST_EMPTY, true); }
numlocalclients()201 int numlocalclients() { return countclients(ST_LOCAL); }
numnonlocalclients()202 int numnonlocalclients() { return countclients(ST_TCPIP); }
203 
numauthedclients()204 int numauthedclients()
205 {
206     int num = 0;
207     loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed) num++;
208     return num;
209 }
210 
numactiveclients()211 int numactiveclients()
212 {
213     int num = 0;
214     loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed && clients[i]->isonrightmap && team_isactive(clients[i]->team)) num++;
215     return num;
216 }
217 
numteamclients(int exclude=-1)218 int *numteamclients(int exclude = -1)
219 {
220     static int num[TEAM_NUM];
221     loopi(TEAM_NUM) num[i] = 0;
222     loopv(clients) if(i != exclude && clients[i]->type!=ST_EMPTY && clients[i]->isauthed && clients[i]->isonrightmap && team_isvalid(clients[i]->team)) num[clients[i]->team]++;
223     return num;
224 }
225 
sendservermode(bool send=true)226 int sendservermode(bool send = true)
227 {
228     int sm = (autoteam ? AT_ENABLED : AT_DISABLED) | ((mastermode & MM_MASK) << 2) | (matchteamsize << 4);
229     if(send) sendf(-1, 1, "ri2", SV_SERVERMODE, sm);
230     return sm;
231 }
232 
changematchteamsize(int newteamsize)233 void changematchteamsize(int newteamsize)
234 {
235     if(newteamsize < 0) return;
236     if(matchteamsize != newteamsize)
237     {
238         matchteamsize = newteamsize;
239         sendservermode();
240     }
241     if(mastermode == MM_MATCH && matchteamsize && m_teammode)
242     {
243         int size[2] = { 0 };
244         loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed && clients[i]->isonrightmap)
245         {
246             if(team_isactive(clients[i]->team))
247             {
248                 if(++size[clients[i]->team] > matchteamsize) updateclientteam(i, team_tospec(clients[i]->team), FTR_SILENTFORCE);
249             }
250         }
251     }
252 }
253 
changemastermode(int newmode)254 void changemastermode(int newmode)
255 {
256     if(mastermode != newmode)
257     {
258         mastermode = newmode;
259         senddisconnectedscores(-1);
260         if(mastermode != MM_MATCH)
261         {
262             loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed)
263             {
264                 if(clients[i]->team == TEAM_CLA_SPECT || clients[i]->team == TEAM_RVSF_SPECT) updateclientteam(i, TEAM_SPECT, FTR_SILENTFORCE);
265             }
266         }
267         else if(matchteamsize) changematchteamsize(matchteamsize);
268     sendservermode();
269     }
270 }
271 
findcnbyaddress(ENetAddress * address)272 int findcnbyaddress(ENetAddress *address)
273 {
274     loopv(clients)
275     {
276         if(clients[i]->type == ST_TCPIP && clients[i]->peer->address.host == address->host && clients[i]->peer->address.port == address->port)
277             return i;
278     }
279     return -1;
280 }
281 
findscore(client & c,bool insert)282 savedscore *findscore(client &c, bool insert)
283 {
284     if(c.type!=ST_TCPIP) return NULL;
285     enet_uint32 mask = ENET_HOST_TO_NET_32(mastermode == MM_MATCH ? 0xFFFF0000 : 0xFFFFFFFF); // in match mode, reconnecting from /16 subnet is allowed
286     if(!insert)
287     {
288         loopv(clients)
289         {
290             client &o = *clients[i];
291             if(o.type!=ST_TCPIP || !o.isauthed) continue;
292             if(o.clientnum!=c.clientnum && o.peer->address.host==c.peer->address.host && !strcmp(o.name, c.name))
293             {
294                 static savedscore curscore;
295                 curscore.save(o.state, o.team);
296                 return &curscore;
297             }
298         }
299     }
300     loopv(savedscores)
301     {
302         savedscore &sc = savedscores[i];
303         if(!strcmp(sc.name, c.name) && (sc.ip & mask) == (c.peer->address.host & mask)) return &sc;
304     }
305     if(!insert) return NULL;
306     savedscore &sc = savedscores.add();
307     copystring(sc.name, c.name);
308     sc.ip = c.peer->address.host;
309     return &sc;
310 }
311 
restoreserverstate(vector<entity> & ents)312 void restoreserverstate(vector<entity> &ents)   // hack: called from savegame code, only works in SP
313 {
314     loopv(sents)
315     {
316         sents[i].spawned = ents[i].spawned;
317         sents[i].spawntime = 0;
318     }
319 }
320 
sendf(int cn,int chan,const char * format,...)321 void sendf(int cn, int chan, const char *format, ...)
322 {
323     int exclude = -1;
324     bool reliable = false;
325     if(*format=='r') { reliable = true; ++format; }
326     packetbuf p(MAXTRANS, reliable ? ENET_PACKET_FLAG_RELIABLE : 0);
327     va_list args;
328     va_start(args, format);
329     while(*format) switch(*format++)
330     {
331         case 'x':
332             exclude = va_arg(args, int);
333             break;
334 
335         case 'v':
336         {
337             int n = va_arg(args, int);
338             int *v = va_arg(args, int *);
339             loopi(n) putint(p, v[i]);
340             break;
341         }
342 
343         case 'i':
344         {
345             int n = isdigit(*format) ? *format++-'0' : 1;
346             loopi(n) putint(p, va_arg(args, int));
347             break;
348         }
349         case 's': sendstring(va_arg(args, const char *), p); break;
350         case 'm':
351         {
352             int n = va_arg(args, int);
353             p.put(va_arg(args, uchar *), n);
354             break;
355         }
356     }
357     va_end(args);
358     sendpacket(cn, chan, p.finalize(), exclude);
359 }
360 
sendextras()361 void sendextras()
362 {
363     if ( gamemillis < nextsendscore ) return;
364     int count = 0, list[MAXCLIENTS];
365     loopv(clients)
366     {
367         client &c = *clients[i];
368         if ( c.type!=ST_TCPIP || !c.isauthed || !(c.md.updated && c.md.upmillis < gamemillis) ) continue;
369         if ( c.md.combosend )
370         {
371             sendf(c.clientnum, 1, "ri2", SV_HUDEXTRAS, min(c.md.combo,c.md.combofrags)-1 + HE_COMBO);
372             c.md.combosend = false;
373         }
374         if ( c.md.dpt )
375         {
376             list[count] = i;
377             count++;
378         }
379     }
380     nextsendscore = gamemillis + 160; // about 4 cicles
381     if ( !count ) return;
382 
383     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
384     putint(p, SV_POINTS);
385     putint(p,count);
386     int *v = list;
387     loopi(count)
388     {
389         client &c = *clients[*v];
390         putint(p,c.clientnum); putint(p,c.md.dpt); c.md.updated = false; c.md.upmillis = c.md.dpt = 0;
391         v++;
392     }
393 
394     sendpacket(-1, 1, p.finalize());
395 }
396 
sendservmsg(const char * msg,int cn=-1)397 void sendservmsg(const char *msg, int cn = -1)
398 {
399     sendf(cn, 1, "ris", SV_SERVMSG, msg);
400 }
401 
sendspawn(client * c)402 void sendspawn(client *c)
403 {
404     if(team_isspect(c->team)) return;
405     clientstate &gs = c->state;
406     gs.respawn();
407     gs.spawnstate(smode);
408     gs.lifesequence++;
409     sendf(c->clientnum, 1, "ri7vv", SV_SPAWNSTATE, gs.lifesequence,
410         gs.health, gs.armour,
411         gs.primary, gs.gunselect, m_arena ? c->spawnindex : -1,
412         NUMGUNS, gs.ammo, NUMGUNS, gs.mag);
413     gs.lastspawn = gamemillis;
414 }
415 
416 // demo
417 stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL;
418 bool recordpackets = false;
419 int nextplayback = 0;
420 
writedemo(int chan,void * data,int len)421 void writedemo(int chan, void *data, int len)
422 {
423     if(!demorecord) return;
424     int stamp[3] = { gamemillis, chan, len };
425     lilswap(stamp, 3);
426     demorecord->write(stamp, sizeof(stamp));
427     demorecord->write(data, len);
428 }
429 
recordpacket(int chan,void * data,int len)430 void recordpacket(int chan, void *data, int len)
431 {
432     if(recordpackets) writedemo(chan, data, len);
433 }
434 
recordpacket(int chan,ENetPacket * packet)435 void recordpacket(int chan, ENetPacket *packet)
436 {
437     if(recordpackets) writedemo(chan, packet->data, (int)packet->dataLength);
438 }
439 
440 #ifdef STANDALONE
currentserver(int i)441 const char *currentserver(int i)
442 {
443     static string curSRVinfo;
444     string r;
445     r[0] = '\0';
446     switch(i)
447     {
448         case 1: { copystring(r, scl.ip[0] ? scl.ip : "local"); break; } // IP
449         case 2: { copystring(r, scl.logident[0] ? scl.logident : "local"); break; } // HOST
450         case 3: { formatstring(r)("%d", scl.serverport); break; } // PORT
451         // the following are used by a client, a server will simply return empty strings for them
452         case 4:
453         case 5:
454         case 6:
455         case 7:
456         case 8:
457         {
458             break;
459         }
460         default:
461         {
462             formatstring(r)("%s %d", scl.ip[0] ? scl.ip : "local", scl.serverport);
463             break;
464         }
465     }
466     copystring(curSRVinfo, r);
467     return curSRVinfo;
468 }
469 #endif
470 
471 // these are actually the values used by the client, the server ones are in "scl".
472 string demofilenameformat = DEFDEMOFILEFMT;
473 string demotimestampformat = DEFDEMOTIMEFMT;
474 int demotimelocal = 0;
475 
476 #ifdef STANDALONE
477 #define DEMOFORMAT scl.demofilenameformat
478 #define DEMOTSFORMAT scl.demotimestampformat
479 #else
480 #define DEMOFORMAT demofilenameformat
481 #define DEMOTSFORMAT demotimestampformat
482 #endif
483 
getDemoFilename(int gmode,int mplay,int mdrop,int tstamp,char * srvmap)484 const char *getDemoFilename(int gmode, int mplay, int mdrop, int tstamp, char *srvmap)
485 {
486     // we use the following internal mapping of formatchars:
487     // %g : gamemode (int)      %G : gamemode (chr)             %F : gamemode (full)
488     // %m : minutes remaining   %M : minutes played
489     // %s : seconds remaining   %S : seconds played
490     // %h : IP of server        %H : hostname of server
491     // %n : mapName
492     // %w : timestamp "when"
493     static string dmofn;
494     copystring(dmofn, "");
495 
496     int cc = 0;
497     int mc = strlen(DEMOFORMAT);
498 
499     while(cc<mc)
500     {
501         switch(DEMOFORMAT[cc])
502         {
503             case '%':
504             {
505                 if(cc<(mc-1))
506                 {
507                     string cfspp;
508                     switch(DEMOFORMAT[cc+1])
509                     {
510                         case 'F': formatstring(cfspp)("%s", fullmodestr(gmode)); break;
511                         case 'g': formatstring(cfspp)("%d", gmode); break;
512                         case 'G': formatstring(cfspp)("%s", acronymmodestr(gmode)); break;
513                         case 'h': formatstring(cfspp)("%s", currentserver(1)); break; // client/server have different implementations
514                         case 'H': formatstring(cfspp)("%s", currentserver(2)); break; // client/server have different implementations
515                         case 'm': formatstring(cfspp)("%d", mdrop/60); break;
516                         case 'M': formatstring(cfspp)("%d", mplay/60); break;
517                         case 'n': formatstring(cfspp)("%s", srvmap); break;
518                         case 's': formatstring(cfspp)("%d", mdrop); break;
519                         case 'S': formatstring(cfspp)("%d", mplay); break;
520                         case 'w':
521                         {
522                             time_t t = tstamp;
523                             struct tm * timeinfo;
524                             timeinfo = demotimelocal ? localtime(&t) : gmtime (&t);
525                             strftime(cfspp, sizeof(string) - 1, DEMOTSFORMAT, timeinfo);
526                             break;
527                         }
528                         default: logline(ACLOG_INFO, "bad formatstring: demonameformat @ %d", cc); cc-=1; break; // don't drop the bad char
529                     }
530                     concatstring(dmofn, cfspp);
531                 }
532                 else
533                 {
534                     logline(ACLOG_INFO, "trailing %%-sign in demonameformat");
535                 }
536                 cc+=1;
537                 break;
538             }
539             default:
540             {
541                 defformatstring(fsbuf)("%s%c", dmofn, DEMOFORMAT[cc]);
542                 copystring(dmofn, fsbuf);
543                 break;
544             }
545         }
546         cc+=1;
547     }
548     return dmofn;
549 }
550 #undef DEMOFORMAT
551 #undef DEMOTSFORMAT
552 
enddemorecord()553 void enddemorecord()
554 {
555     if(!demorecord) return;
556 
557     delete demorecord;
558     recordpackets = false;
559     demorecord = NULL;
560 
561     if(!demotmp) return;
562 
563     if(gamemillis < DEMO_MINTIME)
564     {
565         delete demotmp;
566         demotmp = NULL;
567         logline(ACLOG_INFO, "Demo discarded.");
568         return;
569     }
570 
571     int len = demotmp->size();
572     demotmp->seek(0, SEEK_SET);
573     if(demofiles.length() >= scl.maxdemos)
574     {
575         delete[] demofiles[0].data;
576         demofiles.remove(0);
577     }
578     int mr = gamemillis >= gamelimit ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000;
579     demofile &d = demofiles.add();
580 
581     //2010oct10:ft: suggests : formatstring(d.info)("%s, %s, %.2f%s", modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); // the datetime bit is pretty useless in the servmesg, no?!
582     formatstring(d.info)("%s: %s, %s, %.2f%s", asctime(), modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB");
583     if(mr) { concatformatstring(d.info, ", %d mr", mr); concatformatstring(d.file, "_%dmr", mr); }
584     defformatstring(msg)("Demo \"%s\" recorded\nPress F10 to download it from the server..", d.info);
585     sendservmsg(msg);
586     logline(ACLOG_INFO, "Demo \"%s\" recorded.", d.info);
587 
588     // 2011feb05:ft: previously these two static formatstrings were used ..
589     //formatstring(d.file)("%s_%s_%s", timestring(), behindpath(smapname), modestr(gamemode, true)); // 20100522_10.08.48_ac_mines_DM.dmo
590     //formatstring(d.file)("%s_%s_%s", modestr(gamemode, true), behindpath(smapname), timestring( true, "%Y.%m.%d_%H%M")); // DM_ac_mines.2010.05.22_1008.dmo
591     // .. now we use client-side parseable fileattribs
592     int mPLAY = gamemillis >= gamelimit ? gamelimit/1000 : gamemillis/1000;
593     int mDROP = gamemillis >= gamelimit ? 0 : (gamelimit - gamemillis)/1000;
594     int iTIME = time(NULL);
595     const char *mTIME = numtime();
596     const char *sMAPN = behindpath(smapname);
597     string iMAPN;
598     copystring(iMAPN, sMAPN);
599     formatstring(d.file)( "%d:%d:%d:%s:%s", gamemode, mPLAY, mDROP, mTIME, iMAPN);
600 
601     d.data = new uchar[len];
602     d.len = len;
603     demotmp->read(d.data, len);
604     delete demotmp;
605     demotmp = NULL;
606     if(scl.demopath[0])
607     {
608         formatstring(msg)("%s%s.dmo", scl.demopath, getDemoFilename(gamemode, mPLAY, mDROP, iTIME, iMAPN)); //d.file);
609         path(msg);
610         stream *demo = openfile(msg, "wb");
611         if(demo)
612         {
613             int wlen = (int) demo->write(d.data, d.len);
614             delete demo;
615             logline(ACLOG_INFO, "demo written to file \"%s\" (%d bytes)", msg, wlen);
616         }
617         else
618         {
619             logline(ACLOG_INFO, "failed to write demo to file \"%s\"", msg);
620         }
621     }
622 }
623 
setupdemorecord()624 void setupdemorecord()
625 {
626     if(numlocalclients() || !m_mp(gamemode) || gamemode == GMODE_COOPEDIT) return;
627 
628     defformatstring(demotmppath)("demos/demorecord_%s_%d", scl.ip[0] ? scl.ip : "local", scl.serverport);
629     demotmp = opentempfile(demotmppath, "w+b");
630     if(!demotmp) return;
631 
632     stream *f = opengzfile(NULL, "wb", demotmp);
633     if(!f)
634     {
635         delete demotmp;
636         demotmp = NULL;
637         return;
638     }
639 
640     sendservmsg("recording demo");
641     logline(ACLOG_INFO, "Demo recording started.");
642 
643     demorecord = f;
644     recordpackets = false;
645 
646     demoheader hdr;
647     memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic));
648     hdr.version = DEMO_VERSION;
649     hdr.protocol = SERVER_PROTOCOL_VERSION;
650     lilswap(&hdr.version, 1);
651     lilswap(&hdr.protocol, 1);
652     memset(hdr.desc, 0, DHDR_DESCCHARS);
653     defformatstring(desc)("%s, %s, %s %s", modestr(gamemode, false), behindpath(smapname), asctime(), servdesc_current);
654     if(strlen(desc) > DHDR_DESCCHARS)
655         formatstring(desc)("%s, %s, %s %s", modestr(gamemode, true), behindpath(smapname), asctime(), servdesc_current);
656     desc[DHDR_DESCCHARS - 1] = '\0';
657     strcpy(hdr.desc, desc);
658     memset(hdr.plist, 0, DHDR_PLISTCHARS);
659     const char *bl = "";
660     loopv(clients)
661     {
662         client *ci = clients[i];
663         if(ci->type==ST_EMPTY) continue;
664         if(strlen(hdr.plist) + strlen(ci->name) < DHDR_PLISTCHARS - 2) { strcat(hdr.plist, bl); strcat(hdr.plist, ci->name); }
665         bl = " ";
666     }
667     demorecord->write(&hdr, sizeof(demoheader));
668 
669     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
670     welcomepacket(p, -1);
671     writedemo(1, p.buf, p.len);
672 }
673 
listdemos(int cn)674 void listdemos(int cn)
675 {
676     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
677     putint(p, SV_SENDDEMOLIST);
678     putint(p, demofiles.length());
679     loopv(demofiles) sendstring(demofiles[i].info, p);
680     sendpacket(cn, 1, p.finalize());
681 }
682 
cleardemos(int n)683 static void cleardemos(int n)
684 {
685     if(!n)
686     {
687         loopv(demofiles) delete[] demofiles[i].data;
688         demofiles.shrink(0);
689         sendservmsg("cleared all demos");
690     }
691     else if(demofiles.inrange(n-1))
692     {
693         delete[] demofiles[n-1].data;
694         demofiles.remove(n-1);
695         defformatstring(msg)("cleared demo %d", n);
696         sendservmsg(msg);
697     }
698 }
699 
700 bool sending_demo = false;
701 
senddemo(int cn,int num)702 void senddemo(int cn, int num)
703 {
704     client *cl = cn>=0 ? clients[cn] : NULL;
705     bool is_admin = (cl && cl->role == CR_ADMIN);
706     if(scl.demo_interm && (!interm || totalclients > 2) && !is_admin)
707     {
708         sendservmsg("\f3sorry, but this server only sends demos at intermission.\n wait for the end of this game, please", cn);
709         return;
710     }
711     if(!num) num = demofiles.length();
712     if(!demofiles.inrange(num-1))
713     {
714         if(demofiles.empty()) sendservmsg("no demos available", cn);
715         else
716         {
717             defformatstring(msg)("no demo %d available", num);
718             sendservmsg(msg, cn);
719         }
720         return;
721     }
722     demofile &d = demofiles[num-1];
723     loopv(d.clientssent) if(d.clientssent[i].ip == cl->peer->address.host && d.clientssent[i].clientnum == cl->clientnum)
724     {
725         sendservmsg("\f3Sorry, you have already downloaded this demo.", cl->clientnum);
726         return;
727     }
728     clientidentity &ci = d.clientssent.add();
729     ci.ip = cl->peer->address.host;
730     ci.clientnum = cl->clientnum;
731 
732     if (interm) sending_demo = true;
733     packetbuf p(MAXTRANS + d.len, ENET_PACKET_FLAG_RELIABLE);
734     putint(p, SV_SENDDEMO);
735     sendstring(d.file, p);
736     putint(p, d.len);
737     p.put(d.data, d.len);
738     sendpacket(cn, 2, p.finalize());
739 }
740 
741 int demoprotocol;
742 bool watchingdemo = false;
743 
enddemoplayback()744 void enddemoplayback()
745 {
746     if(!demoplayback) return;
747     delete demoplayback;
748     demoplayback = NULL;
749     watchingdemo = false;
750 
751     loopv(clients) sendf(i, 1, "risi", SV_DEMOPLAYBACK, "", i);
752 
753     sendservmsg("demo playback finished");
754 
755     loopv(clients) sendwelcome(clients[i]);
756 }
757 
setupdemoplayback()758 void setupdemoplayback()
759 {
760     demoheader hdr;
761     string msg;
762     msg[0] = '\0';
763     defformatstring(file)("demos/%s.dmo", smapname);
764     path(file);
765     demoplayback = opengzfile(file, "rb");
766     if(!demoplayback) formatstring(msg)("could not read demo \"%s\"", file);
767     else if(demoplayback->read(&hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)))
768         formatstring(msg)("\"%s\" is not a demo file", file);
769     else
770     {
771         lilswap(&hdr.version, 1);
772         lilswap(&hdr.protocol, 1);
773         if(hdr.version!=DEMO_VERSION) formatstring(msg)("demo \"%s\" requires an %s version of AssaultCube", file, hdr.version<DEMO_VERSION ? "older" : "newer");
774         else if(hdr.protocol != PROTOCOL_VERSION && !(hdr.protocol < 0 && hdr.protocol == -PROTOCOL_VERSION) && hdr.protocol != 1132) formatstring(msg)("demo \"%s\" requires an %s version of AssaultCube", file, hdr.protocol<PROTOCOL_VERSION ? "older" : "newer");
775         else if(hdr.protocol == 1132) sendservmsg("WARNING: using experimental compatibility mode for older demo protocol, expect breakage");
776         demoprotocol = hdr.protocol;
777     }
778     if(msg[0])
779     {
780         if(demoplayback) { delete demoplayback; demoplayback = NULL; }
781         sendservmsg(msg);
782         return;
783     }
784 
785     formatstring(msg)("playing demo \"%s\"", file);
786     sendservmsg(msg);
787     sendf(-1, 1, "risi", SV_DEMOPLAYBACK, smapname, -1);
788     watchingdemo = true;
789 
790     if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
791     {
792         enddemoplayback();
793         return;
794     }
795     lilswap(&nextplayback, 1);
796 }
797 
readdemo()798 void readdemo()
799 {
800     if(!demoplayback) return;
801     while(gamemillis>=nextplayback)
802     {
803         int chan, len;
804         if(demoplayback->read(&chan, sizeof(chan))!=sizeof(chan) ||
805            demoplayback->read(&len, sizeof(len))!=sizeof(len))
806         {
807             enddemoplayback();
808             return;
809         }
810         lilswap(&chan, 1);
811         lilswap(&len, 1);
812         ENetPacket *packet = enet_packet_create(NULL, len, 0);
813         if(!packet || demoplayback->read(packet->data, len)!=len)
814         {
815             if(packet) enet_packet_destroy(packet);
816             enddemoplayback();
817             return;
818         }
819         sendpacket(-1, chan, packet, -1, true);
820         if(!packet->referenceCount) enet_packet_destroy(packet);
821         if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
822         {
823             enddemoplayback();
824             return;
825         }
826         lilswap(&nextplayback, 1);
827     }
828 }
829 
830 struct sflaginfo
831 {
832     int state;
833     int actor_cn;
834     float pos[3];
835     int lastupdate;
836     int stolentime;
837     short x, y;          // flag entity location
838 
sflaginfosflaginfo839     sflaginfo() { actor_cn = -1; }
840 } sflaginfos[2];
841 
putflaginfo(packetbuf & p,int flag)842 void putflaginfo(packetbuf &p, int flag)
843 {
844     sflaginfo &f = sflaginfos[flag];
845     putint(p, SV_FLAGINFO);
846     putint(p, flag);
847     putint(p, f.state);
848     switch(f.state)
849     {
850         case CTFF_STOLEN:
851             putint(p, f.actor_cn);
852             break;
853         case CTFF_DROPPED:
854             loopi(3) putuint(p, (int)(f.pos[i]*DMF));
855             break;
856     }
857 }
858 
send_item_list(packetbuf & p)859 inline void send_item_list(packetbuf &p)
860 {
861     putint(p, SV_ITEMLIST);
862     loopv(sents) if(sents[i].spawned) putint(p, i);
863     putint(p, -1);
864     if(m_flags) loopi(2) putflaginfo(p, i);
865 }
866 
867 #include "serverchecks.h"
868 
flagdistance(sflaginfo & f,int cn)869 bool flagdistance(sflaginfo &f, int cn)
870 {
871     if(!valid_client(cn) || m_demo) return false;
872     client &c = *clients[cn];
873     vec v(-1, -1, c.state.o.z);
874     switch(f.state)
875     {
876         case CTFF_INBASE:
877             v.x = f.x; v.y = f.y;
878             break;
879         case CTFF_DROPPED:
880             v.x = f.pos[0]; v.y = f.pos[1];
881             break;
882     }
883     bool lagging = (c.ping > 1000 || c.spj > 100);
884     if(v.x < 0 && !lagging) return true;
885     float dist = c.state.o.dist(v);
886     int pdist = check_pdist(&c,dist);
887     if(pdist)
888     {
889         c.farpickups++;
890         logline(ACLOG_INFO, "[%s] %s %s the %s flag at distance %.2f (%d)",
891                 c.hostname, c.name, (pdist==2?"tried to touch":"touched"), team_string(&f == sflaginfos + 1), dist, c.farpickups);
892         if (pdist==2) return false;
893     }
894     return lagging ? false : true; // today I found a lag hacker :: Brahma, 19-oct-2010... lets test it a bit
895 }
896 
sendflaginfo(int flag=-1,int cn=-1)897 void sendflaginfo(int flag = -1, int cn = -1)
898 {
899     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
900     if(flag >= 0) putflaginfo(p, flag);
901     else loopi(2) putflaginfo(p, i);
902     sendpacket(cn, 1, p.finalize());
903 }
904 
flagmessage(int flag,int message,int actor,int cn=-1)905 void flagmessage(int flag, int message, int actor, int cn = -1)
906 {
907     if(message == FM_KTFSCORE)
908         sendf(cn, 1, "riiiii", SV_FLAGMSG, flag, message, actor, (gamemillis - sflaginfos[flag].stolentime) / 1000);
909     else
910         sendf(cn, 1, "riiii", SV_FLAGMSG, flag, message, actor);
911 }
912 
flagaction(int flag,int action,int actor)913 void flagaction(int flag, int action, int actor)
914 {
915     if(!valid_flag(flag)) return;
916     sflaginfo &f = sflaginfos[flag];
917     sflaginfo &of = sflaginfos[team_opposite(flag)];
918     bool deadactor = valid_client(actor) ? clients[actor]->state.state != CS_ALIVE || team_isspect(clients[actor]->team): true;
919     int abort = 0;
920     int score = 0;
921     int message = -1;
922 
923     if(m_ctf || m_htf)
924     {
925         switch(action)
926         {
927             case FA_PICKUP:  // ctf: f = enemy team    htf: f = own team
928             case FA_STEAL:
929             {
930                 if(deadactor || f.state != (action == FA_STEAL ? CTFF_INBASE : CTFF_DROPPED) || !flagdistance(f, actor)) { abort = 10; break; }
931                 int team = team_base(clients[actor]->team);
932                 if(m_ctf) team = team_opposite(team);
933                 if(team != flag) { abort = 11; break; }
934                 f.state = CTFF_STOLEN;
935                 f.actor_cn = actor;
936                 message = FM_PICKUP;
937                 break;
938             }
939             case FA_LOST:
940                 if(actor == -1) actor = f.actor_cn;
941             case FA_DROP:
942                 if(f.state!=CTFF_STOLEN || f.actor_cn != actor) { abort = 12; break; }
943                 f.state = CTFF_DROPPED;
944                 loopi(3) f.pos[i] = clients[actor]->state.o[i];
945                 message = action == FA_LOST ? FM_LOST : FM_DROP;
946                 break;
947             case FA_RETURN:
948                 if(f.state!=CTFF_DROPPED || m_htf) { abort = 13; break; }
949                 f.state = CTFF_INBASE;
950                 message = FM_RETURN;
951                 break;
952             case FA_SCORE:  // ctf: f = carried by actor flag,  htf: f = hunted flag (run over by actor)
953                 if(m_ctf)
954                 {
955                     if(f.state != CTFF_STOLEN || f.actor_cn != actor || of.state != CTFF_INBASE || !flagdistance(of, actor)) { abort = 14; break; }
956                     score = 1;
957                     message = FM_SCORE;
958                 }
959                 else // m_htf
960                 {
961                     if(f.state != CTFF_DROPPED || !flagdistance(f, actor)) { abort = 15; break; }
962                     score = (of.state == CTFF_STOLEN) ? 1 : 0;
963                     message = score ? FM_SCORE : FM_SCOREFAIL;
964                     if(of.actor_cn == actor) score *= 2;
965                 }
966                 f.state = CTFF_INBASE;
967                 break;
968 
969             case FA_RESET:
970                 f.state = CTFF_INBASE;
971                 message = FM_RESET;
972                 break;
973         }
974     }
975     else if(m_ktf)  // f: active flag, of: idle flag
976     {
977         switch(action)
978         {
979             case FA_STEAL:
980                 if(deadactor || f.state != CTFF_INBASE || !flagdistance(f, actor)) { abort = 20; break; }
981                 f.state = CTFF_STOLEN;
982                 f.actor_cn = actor;
983                 f.stolentime = gamemillis;
984                 message = FM_PICKUP;
985                 break;
986             case FA_SCORE:  // f = carried by actor flag
987                 if(actor != -1 || f.state != CTFF_STOLEN) { abort = 21; break; } // no client msg allowed here
988                 if(valid_client(f.actor_cn) && clients[f.actor_cn]->state.state == CS_ALIVE && !team_isspect(clients[f.actor_cn]->team))
989                 {
990                     actor = f.actor_cn;
991                     score = 1;
992                     message = FM_KTFSCORE;
993                     break;
994                 }
995             case FA_LOST:
996                 if(actor == -1) actor = f.actor_cn;
997             case FA_DROP:
998                 if(f.actor_cn != actor || f.state != CTFF_STOLEN) { abort = 22; break; }
999             case FA_RESET:
1000                 if(f.state == CTFF_STOLEN)
1001                 {
1002                     actor = f.actor_cn;
1003                     message = FM_LOST;
1004                 }
1005                 f.state = CTFF_IDLE;
1006                 of.state = CTFF_INBASE;
1007                 sendflaginfo(team_opposite(flag));
1008                 break;
1009         }
1010     }
1011     if(abort)
1012     {
1013         logline(ACLOG_DEBUG,"aborting flagaction(flag %d, action %d, actor %d), reason %d, resending flag states", flag, action, actor, abort);  // FIXME: remove this logline after some time - it will only show a few bad ping effects
1014         sendflaginfo();
1015         return;
1016     }
1017     if(score)
1018     {
1019         client *c = clients[actor];
1020         c->state.flagscore += score;
1021         sendf(-1, 1, "riii", SV_FLAGCNT, actor, c->state.flagscore);
1022         if (m_teammode) computeteamwork(c->team, c->clientnum); /** WIP */
1023     }
1024     if(valid_client(actor))
1025     {
1026         client &c = *clients[actor];
1027         switch(message)
1028         {
1029             case FM_PICKUP:
1030                 logline(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, action == FA_STEAL ? "stole" : "picked up");
1031                 break;
1032             case FM_DROP:
1033             case FM_LOST:
1034                 logline(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, message == FM_LOST ? "lost" : "dropped");
1035                 break;
1036             case FM_RETURN:
1037                 logline(ACLOG_INFO,"[%s] %s returned the flag", c.hostname, c.name);
1038                 break;
1039             case FM_SCORE:
1040                 if(m_htf)
1041                     logline(ACLOG_INFO, "[%s] %s hunted the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore);
1042                 else
1043                    logline(ACLOG_INFO, "[%s] %s scored with the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore);
1044                 break;
1045             case FM_KTFSCORE:
1046                 logline(ACLOG_INFO, "[%s] %s scored, carrying for %d seconds, new score %d", c.hostname, c.name, (gamemillis - f.stolentime) / 1000, c.state.flagscore);
1047                 break;
1048             case FM_SCOREFAIL:
1049                 logline(ACLOG_INFO, "[%s] %s failed to score", c.hostname, c.name);
1050                 break;
1051             default:
1052                 logline(ACLOG_INFO, "flagaction %d, actor %d, flag %d, message %d", action, actor, flag, message);
1053                 break;
1054         }
1055         flagpoints (&c, message);
1056     }
1057     else
1058     {
1059         switch(message)
1060         {
1061             case FM_RESET:
1062                 logline(ACLOG_INFO,"the server reset the flag for team %s", team_string(flag));
1063                 break;
1064             default:
1065                 logline(ACLOG_INFO, "flagaction %d with invalid actor cn %d, flag %d, message %d", action, actor, flag, message);
1066                 break;
1067         }
1068     }
1069 
1070     f.lastupdate = gamemillis;
1071     sendflaginfo(flag);
1072     if(message >= 0)
1073         flagmessage(flag, message, valid_client(actor) ? actor : -1);
1074 }
1075 
clienthasflag(int cn)1076 int clienthasflag(int cn)
1077 {
1078     if(m_flags && valid_client(cn))
1079     {
1080         loopi(2) { if(sflaginfos[i].state==CTFF_STOLEN && sflaginfos[i].actor_cn==cn) return i; }
1081     }
1082     return -1;
1083 }
1084 
ctfreset()1085 void ctfreset()
1086 {
1087     int idleflag = m_ktf ? rnd(2) : -1;
1088     loopi(2)
1089     {
1090         sflaginfos[i].actor_cn = -1;
1091         sflaginfos[i].state = i == idleflag ? CTFF_IDLE : CTFF_INBASE;
1092         sflaginfos[i].lastupdate = -1;
1093     }
1094 }
1095 
sdropflag(int cn)1096 void sdropflag(int cn)
1097 {
1098     int fl = clienthasflag(cn);
1099     if(fl >= 0) flagaction(fl, FA_LOST, cn);
1100 }
1101 
resetflag(int cn)1102 void resetflag(int cn)
1103 {
1104     int fl = clienthasflag(cn);
1105     if(fl >= 0) flagaction(fl, FA_RESET, -1);
1106 }
1107 
htf_forceflag(int flag)1108 void htf_forceflag(int flag)
1109 {
1110     sflaginfo &f = sflaginfos[flag];
1111     int besthealth = 0;
1112     vector<int> clientnumbers;
1113     loopv(clients) if(clients[i]->type!=ST_EMPTY)
1114     {
1115         if(clients[i]->state.state == CS_ALIVE && team_base(clients[i]->team) == flag)
1116         {
1117             if(clients[i]->state.health == besthealth)
1118                 clientnumbers.add(i);
1119             else
1120             {
1121                 if(clients[i]->state.health > besthealth)
1122                 {
1123                     besthealth = clients[i]->state.health;
1124                     clientnumbers.shrink(0);
1125                     clientnumbers.add(i);
1126                 }
1127             }
1128         }
1129     }
1130 
1131     if(clientnumbers.length())
1132     {
1133         int pick = rnd(clientnumbers.length());
1134         client *cl = clients[clientnumbers[pick]];
1135         f.state = CTFF_STOLEN;
1136         f.actor_cn = cl->clientnum;
1137         sendflaginfo(flag);
1138         flagmessage(flag, FM_PICKUP, cl->clientnum);
1139         logline(ACLOG_INFO,"[%s] %s got forced to pickup the flag", cl->hostname, cl->name);
1140     }
1141     f.lastupdate = gamemillis;
1142 }
1143 
1144 int arenaround = 0, arenaroundstartmillis = 0;
1145 
1146 struct twoint { int index, value; };
cmpscore(const int * a,const int * b)1147 int cmpscore(const int *a, const int *b) { return clients[*a]->at3_score - clients[*b]->at3_score; }
cmptwoint(const struct twoint * a,const struct twoint * b)1148 int cmptwoint(const struct twoint *a, const struct twoint *b) { return a->value - b->value; }
1149 vector<int> tdistrib;
1150 vector<twoint> sdistrib;
1151 
distributeteam(int team)1152 void distributeteam(int team)
1153 {
1154     int numsp = team == 100 ? smapstats.spawns[2] : smapstats.spawns[team];
1155     if(!numsp) numsp = 30; // no spawns: try to distribute anyway
1156     twoint ti;
1157     tdistrib.shrink(0);
1158     loopv(clients) if(clients[i]->type!=ST_EMPTY)
1159     {
1160         if(team == 100 || team == clients[i]->team)
1161         {
1162             tdistrib.add(i);
1163             clients[i]->at3_score = rnd(0x1000000);
1164         }
1165     }
1166     tdistrib.sort(cmpscore); // random player order
1167     sdistrib.shrink(0);
1168     loopi(numsp)
1169     {
1170         ti.index = i;
1171         ti.value = rnd(0x1000000);
1172         sdistrib.add(ti);
1173     }
1174     sdistrib.sort(cmptwoint); // random spawn order
1175     int x = 0;
1176     loopv(tdistrib)
1177     {
1178         clients[tdistrib[i]]->spawnindex = sdistrib[x++].index;
1179         x %= sdistrib.length();
1180     }
1181 }
1182 
distributespawns()1183 void distributespawns()
1184 {
1185     loopv(clients) if(clients[i]->type!=ST_EMPTY)
1186     {
1187         clients[i]->spawnindex = -1;
1188     }
1189     if(m_teammode)
1190     {
1191         distributeteam(0);
1192         distributeteam(1);
1193     }
1194     else
1195     {
1196         distributeteam(100);
1197     }
1198 }
1199 
1200 bool items_blocked = false;
free_items(int n)1201 bool free_items(int n)
1202 {
1203     client *c = clients[n];
1204     int waitspawn = min(c->ping,200) + c->state.spawn; // flowtron to Brahma: can't this be removed now? (re: rev. 5270)
1205     return !items_blocked && (waitspawn < gamemillis);
1206 }
1207 
1208 void checkitemspawns(int);
1209 
arenacheck()1210 void arenacheck()
1211 {
1212     if(!m_arena || interm || gamemillis<arenaround || !numactiveclients()) return;
1213 
1214     if(arenaround)
1215     {   // start new arena round
1216         arenaround = 0;
1217         arenaroundstartmillis = gamemillis;
1218         distributespawns();
1219         checkitemspawns(60*1000); // the server will respawn all items now
1220         loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed)
1221         {
1222             if(clients[i]->isonrightmap && team_isactive(clients[i]->team))
1223                 sendspawn(clients[i]);
1224         }
1225         items_blocked = false;
1226         return;
1227     }
1228 
1229 #ifndef STANDALONE
1230     if(m_botmode && clients[0]->type==ST_LOCAL)
1231     {
1232         int enemies = 0, alive_enemies = 0;
1233         playerent *alive = NULL;
1234         loopv(players) if(players[i] && (!m_teammode || players[i]->team == team_opposite(player1->team)))
1235         {
1236             enemies++;
1237             if(players[i]->state == CS_ALIVE)
1238             {
1239                 alive = players[i];
1240                 alive_enemies++;
1241             }
1242         }
1243         if(player1->state != CS_DEAD) alive = player1;
1244         if(enemies && (!alive_enemies || player1->state == CS_DEAD))
1245         {
1246             sendf(-1, 1, "ri2", SV_ARENAWIN, m_teammode ? (alive ? alive->clientnum : -1) : (alive && alive->type == ENT_BOT ? -2 : player1->state == CS_ALIVE ? player1->clientnum : -1));
1247             arenaround = gamemillis+5000;
1248         }
1249         return;
1250     }
1251 #endif
1252     client *alive = NULL;
1253     bool dead = false;
1254     int lastdeath = 0;
1255     loopv(clients)
1256     {
1257         client &c = *clients[i];
1258         if(c.type==ST_EMPTY || !c.isauthed || !c.isonrightmap || team_isspect(c.team)) continue; /// TODO: simplify the team/state sysmtem, it is not smart to have SPECTATE in both, for example
1259         if (c.state.lastspawn < 0 && (c.state.state==CS_DEAD || c.state.state==CS_SPECTATE))
1260         {
1261             dead = true;
1262             lastdeath = max(lastdeath, c.state.lastdeath);
1263         }
1264         else if(c.state.state==CS_ALIVE)
1265         {
1266             if(!alive) alive = &c;
1267             else if(!m_teammode || alive->team != c.team) return;
1268         }
1269     }
1270 
1271     if(!dead || gamemillis < lastdeath + 500) return;
1272     items_blocked = true;
1273     sendf(-1, 1, "ri2", SV_ARENAWIN, alive ? alive->clientnum : -1);
1274     arenaround = gamemillis+5000;
1275     if(autoteam && m_teammode) refillteams(true);
1276 }
1277 
1278 #define SPAMREPEATINTERVAL  20   // detect doubled lines only if interval < 20 seconds
1279 #define SPAMMAXREPEAT       3    // 4th time is SPAM
1280 #define SPAMCHARPERMINUTE   220  // good typist
1281 #define SPAMCHARINTERVAL    30   // allow 20 seconds typing at maxspeed
1282 
spamdetect(client * cl,char * text)1283 bool spamdetect(client *cl, char *text) // checks doubled lines and average typing speed
1284 {
1285     if(cl->type != ST_TCPIP || cl->role == CR_ADMIN) return false;
1286     bool spam = false;
1287     int pause = servmillis - cl->lastsay;
1288     if(pause < 0 || pause > 90*1000) pause = 90*1000;
1289     cl->saychars -= (SPAMCHARPERMINUTE * pause) / (60*1000);
1290     cl->saychars += (int)strlen(text);
1291     if(cl->saychars < 0) cl->saychars = 0;
1292     if(text[0] && !strcmp(text, cl->lastsaytext) && servmillis - cl->lastsay < SPAMREPEATINTERVAL*1000)
1293     {
1294         spam = ++cl->spamcount > SPAMMAXREPEAT;
1295     }
1296     else
1297     {
1298          copystring(cl->lastsaytext, text);
1299          cl->spamcount = 0;
1300     }
1301     cl->lastsay = servmillis;
1302     if(cl->saychars > (SPAMCHARPERMINUTE * SPAMCHARINTERVAL) / 60)
1303         spam = true;
1304     return spam;
1305 }
1306 
1307 // chat message distribution matrix:
1308 //
1309 // /------------------------ common chat          C c C c c C C c C
1310 // |/----------------------- RVSF chat            T
1311 // ||/---------------------- CLA chat                 T
1312 // |||/--------------------- spect chat             t   t t T   t T
1313 // ||||                                           | | | | | | | | |
1314 // ||||                                           | | | | | | | | |      C: normal chat
1315 // ||||   team modes:                chat goes to | | | | | | | | |      T: team chat
1316 // XX     -->   RVSF players                >-----/ | | | | | | | |      c: normal chat in all mastermodes except 'match'
1317 // XX X   -->   RVSF spect players          >-------/ | | | | | | |      t: all chat in mastermode 'match', otherwise only team chat
1318 // X X    -->   CLA players                 >---------/ | | | | | |
1319 // X XX   -->   CLA spect players           >-----------/ | | | | |
1320 // X  X   -->   SPECTATORs                  >-------------/ | | | |
1321 // XXXX   -->   SPECTATORs (admin)          >---------------/ | | |
1322 //        ffa modes:                                          | | |
1323 // X      -->   any player (ffa mode)       >-----------------/ | |
1324 // X  X   -->   any spectator (ffa mode)    >-------------------/ |
1325 // X  X   -->   admin spectator             >---------------------/
1326 
1327 // purpose:
1328 //  a) give spects a possibility to chat without annoying the players (even in ffa),
1329 //  b) no hidden messages from spects to active teams,
1330 //  c) no spect talk to players during 'match'
1331 
sendteamtext(char * text,int sender,int msgtype)1332 void sendteamtext(char *text, int sender, int msgtype)
1333 {
1334     if(!valid_client(sender) || clients[sender]->team == TEAM_VOID) return;
1335     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1336     putint(p, msgtype);
1337     putint(p, sender);
1338     sendstring(text, p);
1339     ENetPacket *packet = p.finalize();
1340     recordpacket(1, packet);
1341     int &st = clients[sender]->team;
1342     loopv(clients) if(i!=sender)
1343     {
1344         int &rt = clients[i]->team;
1345         if((rt == TEAM_SPECT && clients[i]->role == CR_ADMIN) ||  // spect-admin reads all
1346            (team_isactive(st) && st == team_group(rt)) ||         // player to own team + own spects
1347            (team_isspect(st) && team_isspect(rt)))                // spectator to other spectators
1348             sendpacket(i, 1, packet);
1349     }
1350 }
1351 
sendvoicecomteam(int sound,int sender)1352 void sendvoicecomteam(int sound, int sender)
1353 {
1354     if(!valid_client(sender) || clients[sender]->team == TEAM_VOID) return;
1355     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1356     putint(p, SV_VOICECOMTEAM);
1357     putint(p, sender);
1358     putint(p, sound);
1359     ENetPacket *packet = p.finalize();
1360     loopv(clients) if(i!=sender)
1361     {
1362         if(clients[i]->team == clients[sender]->team || !m_teammode)
1363             sendpacket(i, 1, packet);
1364     }
1365 }
1366 
numplayers()1367 int numplayers()
1368 {
1369     int count = 1;
1370 #ifdef STANDALONE
1371     count = numclients();
1372 #else
1373     if(m_botmode)
1374     {
1375         extern vector<botent *> bots;
1376         loopv(bots) if(bots[i]) count++;
1377     }
1378     if(m_demo)
1379     {
1380         count = numclients();
1381     }
1382 #endif
1383     return count;
1384 }
1385 
spawntime(int type)1386 int spawntime(int type)
1387 {
1388     int np = numplayers();
1389     np = np<3 ? 4 : (np>4 ? 2 : 3);    // Some spawn times are dependent on the number of players.
1390     int sec = 0;
1391     switch(type)
1392     {
1393     // Please update ./ac_website/htdocs/docs/introduction.html if these times change.
1394         case I_CLIPS:
1395         case I_AMMO: sec = np*2; break;
1396         case I_GRENADE: sec = np + 5; break;
1397         case I_HEALTH: sec = np*5; break;
1398         case I_HELMET:
1399         case I_ARMOUR: sec = 25; break;
1400         case I_AKIMBO: sec = 60; break;
1401     }
1402     return sec*1000;
1403 }
1404 
serverpickup(int i,int sender)1405 bool serverpickup(int i, int sender)         // server side item pickup, acknowledge first client that gets it
1406 {
1407     const char *hn = sender >= 0 && clients[sender]->type == ST_TCPIP ? clients[sender]->hostname : NULL;
1408     if(!sents.inrange(i))
1409     {
1410         if(hn && !m_coop) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d - doesn't exist on this map", hn, i);
1411         return false;
1412     }
1413     server_entity &e = sents[i];
1414     if(!e.spawned)
1415     {
1416         if(!e.legalpickup && hn && !m_demo) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d (%s) - can't be picked up in this gamemode or at all", hn, i, entnames[e.type]);
1417         return false;
1418     }
1419     if(sender>=0)
1420     {
1421         client *cl = clients[sender];
1422         if(cl->type==ST_TCPIP)
1423         {
1424             if( cl->state.state!=CS_ALIVE || !cl->state.canpickup(e.type) || ( m_arena && !free_items(sender) ) ) return false;
1425             vec v(e.x, e.y, cl->state.o.z);
1426             float dist = cl->state.o.dist(v);
1427             int pdist = check_pdist(cl,dist);
1428             if (pdist)
1429             {
1430                 cl->farpickups++;
1431                 if (!m_demo) logline(ACLOG_INFO, "[%s] %s %s up entity #%d (%s), distance %.2f (%d)",
1432                      cl->hostname, cl->name, (pdist==2?"tried to pick":"picked"), i, entnames[e.type], dist, cl->farpickups);
1433                 if (pdist==2) return false;
1434             }
1435         }
1436         sendf(-1, 1, "ri3", SV_ITEMACC, i, sender);
1437         cl->state.pickup(sents[i].type);
1438         if (m_lss && sents[i].type == I_GRENADE) cl->state.pickup(sents[i].type); // get two nades at lss
1439     }
1440     e.spawned = false;
1441     if(!m_lms) e.spawntime = spawntime(e.type);
1442     return true;
1443 }
1444 
checkitemspawns(int diff)1445 void checkitemspawns(int diff)
1446 {
1447     if(!diff) return;
1448     loopv(sents) if(sents[i].spawntime)
1449     {
1450         sents[i].spawntime -= diff;
1451         if(sents[i].spawntime<=0)
1452         {
1453             sents[i].spawntime = 0;
1454             sents[i].spawned = true;
1455             sendf(-1, 1, "ri2", SV_ITEMSPAWN, i);
1456         }
1457     }
1458 }
1459 
serverdamage(client * target,client * actor,int damage,int gun,bool gib,const vec & hitpush=vec (0,0,0))1460 void serverdamage(client *target, client *actor, int damage, int gun, bool gib, const vec &hitpush = vec(0, 0, 0))
1461 {
1462     if (!m_demo && !m_coop && !validdamage(target, actor, damage, gun, gib)) return;
1463     if ( m_arena && gun == GUN_GRENADE && arenaroundstartmillis + 2000 > gamemillis && target != actor ) return;
1464     clientstate &ts = target->state;
1465     ts.dodamage(damage, gun);
1466     if(damage < INT_MAX)
1467     {
1468         actor->state.damage += damage;
1469         sendf(-1, 1, "ri7", gib ? SV_GIBDAMAGE : SV_DAMAGE, target->clientnum, actor->clientnum, gun, damage, ts.armour, ts.health);
1470         if(target!=actor)
1471         {
1472             checkcombo (target, actor, damage, gun);
1473             if(!hitpush.iszero())
1474             {
1475                 vec v(hitpush);
1476                 if(!v.iszero()) v.normalize();
1477                 sendf(target->clientnum, 1, "ri6", SV_HITPUSH, gun, damage,
1478                       int(v.x*DNF), int(v.y*DNF), int(v.z*DNF));
1479             }
1480         }
1481     }
1482     if(ts.health<=0)
1483     {
1484         int targethasflag = clienthasflag(target->clientnum);
1485         bool tk = false, suic = false;
1486         target->state.deaths++;
1487         checkfrag(target, actor, gun, gib);
1488         if(target!=actor)
1489         {
1490             if(!isteam(target->team, actor->team)) actor->state.frags += gib && gun != GUN_GRENADE && gun != GUN_SHOTGUN ? 2 : 1;
1491             else
1492             {
1493                 actor->state.frags--;
1494                 actor->state.teamkills++;
1495                 tk = true;
1496             }
1497         }
1498         else
1499         { // suicide
1500             actor->state.frags--;
1501             suic = true;
1502             logline(ACLOG_INFO, "[%s] %s suicided", actor->hostname, actor->name);
1503         }
1504         sendf(-1, 1, "ri5", gib ? SV_GIBDIED : SV_DIED, target->clientnum, actor->clientnum, actor->state.frags, gun);
1505         if((suic || tk) && (m_htf || m_ktf) && targethasflag >= 0)
1506         {
1507             actor->state.flagscore--;
1508             sendf(-1, 1, "riii", SV_FLAGCNT, actor->clientnum, actor->state.flagscore);
1509         }
1510         target->position.setsize(0);
1511         ts.state = CS_DEAD;
1512         ts.lastdeath = gamemillis;
1513         if(!suic) logline(ACLOG_INFO, "[%s] %s %s%s %s", actor->hostname, actor->name, killmessage(gun, gib), tk ? " their teammate" : "", target->name);
1514         if(m_flags && targethasflag >= 0)
1515         {
1516             if(m_ctf)
1517                 flagaction(targethasflag, tk ? FA_RESET : FA_LOST, -1);
1518             else if(m_htf)
1519                 flagaction(targethasflag, FA_LOST, -1);
1520             else // ktf || tktf
1521                 flagaction(targethasflag, FA_RESET, -1);
1522         }
1523         // don't issue respawn yet until DEATHMILLIS has elapsed
1524         // ts.respawn();
1525 
1526         if(isdedicated && actor->type == ST_TCPIP && tk)
1527         {
1528             if( actor->state.frags < scl.banthreshold ||
1529                 /** teamkilling more than 6 (defaults), more than 2 per minute and less than 4 frags per tk */
1530                 ( actor->state.teamkills >= -scl.banthreshold &&
1531                   actor->state.teamkills * 30 * 1000 > gamemillis &&
1532                   actor->state.frags < 4 * actor->state.teamkills ) )
1533             {
1534                 addban(actor, DISC_AUTOBAN);
1535             }
1536             else if( actor->state.frags < scl.kickthreshold ||
1537                      /** teamkilling more than 5 (defaults), more than 1 tk per minute and less than 4 frags per tk */
1538                      ( actor->state.teamkills >= -scl.kickthreshold &&
1539                        actor->state.teamkills * 60 * 1000 > gamemillis &&
1540                        actor->state.frags < 4 * actor->state.teamkills ) ) disconnect_client(actor->clientnum, DISC_AUTOKICK);
1541         }
1542     } else if ( target!=actor && isteam(target->team, actor->team) ) check_ffire (target, actor, damage); // friendly fire counter
1543 }
1544 
1545 #include "serverevents.h"
1546 
updatedescallowed(void)1547 bool updatedescallowed(void) { return scl.servdesc_pre[0] || scl.servdesc_suf[0]; }
1548 
updatesdesc(const char * newdesc,ENetAddress * caller=NULL)1549 void updatesdesc(const char *newdesc, ENetAddress *caller = NULL)
1550 {
1551     if(!newdesc || !newdesc[0] || !updatedescallowed())
1552     {
1553         copystring(servdesc_current, scl.servdesc_full);
1554         custom_servdesc = false;
1555     }
1556     else
1557     {
1558         formatstring(servdesc_current)("%s%s%s", scl.servdesc_pre, newdesc, scl.servdesc_suf);
1559         custom_servdesc = true;
1560         if(caller) servdesc_caller = *caller;
1561     }
1562 }
1563 
canspawn(client * c)1564 int canspawn(client *c)   // beware: canspawn() doesn't check m_arena!
1565 {
1566     if(!c || c->type == ST_EMPTY || !c->isauthed || !team_isvalid(c->team) ||
1567         (c->type == ST_TCPIP && (c->state.lastdeath > 0 ? gamemillis - c->state.lastdeath : servmillis - c->connectmillis) < (m_arena ? 0 : (m_flags ? 5000 : 2000))) ||
1568         (servmillis - c->connectmillis < 1000 + c->state.reconnections * 2000 &&
1569           gamemillis > 10000 && totalclients > 3 && !team_isspect(c->team))) return SP_OK_NUM; // equivalent to SP_DENY
1570     if(!c->isonrightmap) return SP_WRONGMAP;
1571     if(mastermode == MM_MATCH && matchteamsize)
1572     {
1573         if(c->team == TEAM_SPECT || (team_isspect(c->team) && !m_teammode)) return SP_SPECT;
1574         if(c->team == TEAM_CLA_SPECT || c->team == TEAM_RVSF_SPECT)
1575         {
1576             if(numteamclients()[team_base(c->team)] >= matchteamsize) return SP_SPECT;
1577             else return SP_REFILLMATCH;
1578         }
1579     }
1580     return SP_OK;
1581 }
1582 
1583 /** FIXME: this function is unnecessarily complicated */
updateclientteam(int cln,int newteam,int ftr)1584 bool updateclientteam(int cln, int newteam, int ftr)
1585 {
1586     if(!valid_client(cln)) return false;
1587     if(!team_isvalid(newteam) && newteam != TEAM_ANYACTIVE) newteam = TEAM_SPECT;
1588     client &cl = *clients[cln];
1589     if(cl.team == newteam && ftr != FTR_AUTOTEAM) return true; // no change
1590     int *teamsizes = numteamclients(cln);
1591     if( mastermode == MM_OPEN && cl.state.forced && ftr == FTR_PLAYERWISH &&
1592         newteam < TEAM_SPECT && team_base(cl.team) != team_base(newteam) ) return false; // no free will changes to forced people
1593     if(newteam == TEAM_ANYACTIVE) // when spawning from spect
1594     {
1595         if(mastermode == MM_MATCH && cl.team < TEAM_SPECT)
1596         {
1597             newteam = team_base(cl.team);
1598         }
1599         else
1600         {
1601             if(autoteam && teamsizes[TEAM_CLA] != teamsizes[TEAM_RVSF]) newteam = teamsizes[TEAM_CLA] < teamsizes[TEAM_RVSF] ? TEAM_CLA : TEAM_RVSF;
1602             else
1603             { // join weaker team
1604                 int teamscore[2] = {0, 0}, sum = calcscores();
1605                 loopv(clients) if(clients[i]->type!=ST_EMPTY && i != cln && clients[i]->isauthed && clients[i]->team != TEAM_SPECT)
1606                 {
1607                     teamscore[team_base(clients[i]->team)] += clients[i]->at3_score;
1608                 }
1609                 newteam = sum > 200 ? (teamscore[TEAM_CLA] < teamscore[TEAM_RVSF] ? TEAM_CLA : TEAM_RVSF) : rnd(2);
1610             }
1611         }
1612     }
1613     if(ftr == FTR_PLAYERWISH)
1614     {
1615         if(mastermode == MM_MATCH && matchteamsize && m_teammode)
1616         {
1617             if(newteam != TEAM_SPECT && (team_base(newteam) != team_base(cl.team) || !m_teammode)) return false; // no switching sides in match mode when teamsize is set
1618         }
1619         if(team_isactive(newteam))
1620         {
1621             if(!m_teammode && cl.state.state == CS_ALIVE) return false;  // no comments
1622             if(mastermode == MM_MATCH)
1623             {
1624                 if(m_teammode && matchteamsize && teamsizes[newteam] >= matchteamsize) return false;  // ensure maximum team size
1625             }
1626             else
1627             {
1628                 if(m_teammode && autoteam && teamsizes[newteam] > teamsizes[team_opposite(newteam)]) return false; // don't switch to an already bigger team
1629             }
1630         }
1631         else if(mastermode != MM_MATCH || !m_teammode) newteam = TEAM_SPECT; // only match mode (team) has more than one spect team
1632     }
1633     if(cl.team == newteam && ftr != FTR_AUTOTEAM) return true; // no change
1634     if(cl.team != newteam) sdropflag(cl.clientnum);
1635     if(ftr != FTR_INFO && (team_isspect(newteam) || (team_isactive(newteam) && team_isactive(cl.team)))) forcedeath(&cl);
1636     sendf(-1, 1, "riii", SV_SETTEAM, cln, newteam | ((ftr == FTR_SILENTFORCE ? FTR_INFO : ftr) << 4));
1637     if(ftr != FTR_INFO && !team_isspect(newteam) && team_isspect(cl.team)) sendspawn(&cl);
1638     if (team_isspect(newteam)) cl.state.state = CS_SPECTATE;
1639     cl.team = newteam;
1640     return true;
1641 }
1642 
calcscores()1643 int calcscores() // skill eval
1644 {
1645     int sum = 0;
1646     loopv(clients) if(clients[i]->type!=ST_EMPTY)
1647     {
1648         clientstate &cs = clients[i]->state;
1649         sum += clients[i]->at3_score = cs.points > 0 ? ufSqrt((float)cs.points) : -ufSqrt((float)-cs.points);
1650     }
1651     return sum;
1652 }
1653 
1654 vector<int> shuffle;
1655 
shuffleteams(int ftr=FTR_AUTOTEAM)1656 void shuffleteams(int ftr = FTR_AUTOTEAM)
1657 {
1658     int numplayers = numclients();
1659     int team, sums = calcscores();
1660     if(gamemillis < 2 * 60 *1000)
1661     { // random
1662         int teamsize[2] = {0, 0};
1663         loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isonrightmap && !team_isspect(clients[i]->team)) // only shuffle active players
1664         {
1665             sums += rnd(1000);
1666             team = sums & 1;
1667             if(teamsize[team] >= numplayers/2) team = team_opposite(team);
1668             updateclientteam(i, team, ftr);
1669             teamsize[team]++;
1670             sums >>= 1;
1671         }
1672     }
1673     else
1674     { // skill sorted
1675         shuffle.shrink(0);
1676         sums /= 4 * numplayers + 2;
1677         team = rnd(2);
1678         loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isonrightmap && !team_isspect(clients[i]->team))
1679         {
1680             clients[i]->at3_score += rnd(sums | 1);
1681             shuffle.add(i);
1682         }
1683         shuffle.sort(cmpscore);
1684         loopi(shuffle.length())
1685         {
1686             updateclientteam(shuffle[i], team, ftr);
1687             team = !team;
1688         }
1689     }
1690 }
1691 
balanceteams(int ftr)1692 bool balanceteams(int ftr)  // pro vs noobs never more
1693 {
1694     if(mastermode != MM_OPEN || totalclients < 3 ) return true;
1695     int tsize[2] = {0, 0}, tscore[2] = {0, 0};
1696     int totalscore = 0, nplayers = 0;
1697     int flagmult = (m_ctf ? 50 : (m_htf ? 25 : 12));
1698 
1699     loopv(clients) if(clients[i]->type!=ST_EMPTY)
1700     {
1701         client *c = clients[i];
1702         if(c->isauthed && team_isactive(c->team))
1703         {
1704             int time = servmillis - c->connectmillis + 5000;
1705             if ( time > gamemillis ) time = gamemillis + 5000;
1706             tsize[c->team]++;
1707             // effective score per minute, thanks to wtfthisgame for the nice idea
1708             // in a normal game, normal players will do 500 points in 10 minutes
1709             c->eff_score = c->state.points * 60 * 1000 / time + c->state.points / 6 + c->state.flagscore * flagmult;
1710             tscore[c->team] += c->eff_score;
1711             nplayers++;
1712             totalscore += c->state.points;
1713         }
1714     }
1715 
1716     int h = 0, l = 1;
1717     if ( tscore[1] > tscore[0] ) { h = 1; l = 0; }
1718     if ( 2 * tscore[h] < 3 * tscore[l] || totalscore < nplayers * 100 ) return true;
1719     if ( tscore[h] > 3 * tscore[l] && tscore[h] > 150 * nplayers )
1720     {
1721 //        sendf(-1, 1, "ri2", SV_SERVERMODE, sendservermode(false) | AT_SHUFFLE);
1722         shuffleteams();
1723         return true;
1724     }
1725 
1726     float diffscore = tscore[h] - tscore[l];
1727 
1728     int besth = 0, hid = -1;
1729     int bestdiff = 0, bestpair[2] = {-1, -1};
1730     if ( tsize[h] - tsize[l] > 0 ) // the h team has more players, so we will force only one player
1731     {
1732         loopv(clients) if( clients[i]->type!=ST_EMPTY )
1733         {
1734             client *c = clients[i]; // loop for h
1735             // client from the h team, not forced in this game, and without the flag
1736             if( c->isauthed && c->team == h && !c->state.forced && clienthasflag(i) < 0 )
1737             {
1738                 // do not exchange in the way that weaker team becomes the stronger or the change is less than 20% effective
1739                 if ( 2 * c->eff_score <= diffscore && 10 * c->eff_score >= diffscore && c->eff_score > besth )
1740                 {
1741                     besth = c->eff_score;
1742                     hid = i;
1743                 }
1744             }
1745         }
1746         if ( hid >= 0 )
1747         {
1748             updateclientteam(hid, l, ftr);
1749             clients[hid]->at3_lastforce = gamemillis;
1750             clients[hid]->state.forced = true;
1751             return true;
1752         }
1753     } else { // the h score team has less or the same player number, so, lets exchange
1754         loopv(clients) if(clients[i]->type!=ST_EMPTY)
1755         {
1756             client *c = clients[i]; // loop for h
1757             if( c->isauthed && c->team == h && !c->state.forced && clienthasflag(i) < 0 )
1758             {
1759                 loopvj(clients) if(clients[j]->type!=ST_EMPTY && j != i )
1760                 {
1761                     client *cj = clients[j]; // loop for l
1762                     if( cj->isauthed && cj->team == l && !cj->state.forced && clienthasflag(j) < 0 )
1763                     {
1764                         int pairdiff = 2 * (c->eff_score - cj->eff_score);
1765                         if ( pairdiff <= diffscore && 5 * pairdiff >= diffscore && pairdiff > bestdiff )
1766                         {
1767                             bestdiff = pairdiff;
1768                             bestpair[h] = i;
1769                             bestpair[l] = j;
1770                         }
1771                     }
1772                 }
1773             }
1774         }
1775         if ( bestpair[h] >= 0 && bestpair[l] >= 0 )
1776         {
1777             updateclientteam(bestpair[h], l, ftr);
1778             updateclientteam(bestpair[l], h, ftr);
1779             clients[bestpair[h]]->at3_lastforce = clients[bestpair[l]]->at3_lastforce = gamemillis;
1780             clients[bestpair[h]]->state.forced = clients[bestpair[l]]->state.forced = true;
1781             return true;
1782         }
1783     }
1784     return false;
1785 }
1786 
1787 int lastbalance = 0, waitbalance = 2 * 60 * 1000;
1788 
refillteams(bool now,int ftr)1789 bool refillteams(bool now, int ftr)  // force only minimal amounts of players
1790 {
1791     if(mastermode == MM_MATCH) return false;
1792     static int lasttime_eventeams = 0;
1793     int teamsize[2] = {0, 0}, teamscore[2] = {0, 0}, moveable[2] = {0, 0};
1794     bool switched = false;
1795 
1796     calcscores();
1797     loopv(clients) if(clients[i]->type!=ST_EMPTY)     // playerlist stocktaking
1798     {
1799         client *c = clients[i];
1800         c->at3_dontmove = true;
1801         if(c->isauthed)
1802         {
1803             if(team_isactive(c->team)) // only active players count
1804             {
1805                 teamsize[c->team]++;
1806                 teamscore[c->team] += c->at3_score;
1807                 if(clienthasflag(i) < 0)
1808                 {
1809                     c->at3_dontmove = false;
1810                     moveable[c->team]++;
1811                 }
1812             }
1813         }
1814     }
1815     int bigteam = teamsize[1] > teamsize[0];
1816     int allplayers = teamsize[0] + teamsize[1];
1817     int diffnum = teamsize[bigteam] - teamsize[!bigteam];
1818     int diffscore = teamscore[bigteam] - teamscore[!bigteam];
1819     if(lasttime_eventeams > gamemillis) lasttime_eventeams = 0;
1820     if(diffnum > 1)
1821     {
1822         if(now || gamemillis - lasttime_eventeams > 8000 + allplayers * 1000 || diffnum > 2 + allplayers / 10)
1823         {
1824             // time to even out teams
1825             loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->team != bigteam) clients[i]->at3_dontmove = true;  // dont move small team players
1826             while(diffnum > 1 && moveable[bigteam] > 0)
1827             {
1828                 // pick best fitting cn
1829                 int pick = -1;
1830                 int bestfit = 1000000000;
1831                 int targetscore = diffscore / (diffnum & ~1);
1832                 loopv(clients) if(clients[i]->type!=ST_EMPTY && !clients[i]->at3_dontmove) // try all still movable players
1833                 {
1834                     int fit = targetscore - clients[i]->at3_score;
1835                     if(fit < 0 ) fit = -(fit * 15) / 10;       // avoid too good players
1836                     int forcedelay = clients[i]->at3_lastforce ? (1000 - (gamemillis - clients[i]->at3_lastforce) / (5 * 60)) : 0;
1837                     if(forcedelay > 0) fit += (fit * forcedelay) / 600;   // avoid lately forced players
1838                     if(fit < bestfit + fit * rnd(100) / 400)   // search 'almost' best fit
1839                     {
1840                         bestfit = fit;
1841                         pick = i;
1842                     }
1843                 }
1844                 if(pick < 0) break; // should really never happen
1845                 // move picked player
1846                 clients[pick]->at3_dontmove = true;
1847                 moveable[bigteam]--;
1848                 if(updateclientteam(pick, !bigteam, ftr))
1849                 {
1850                     diffnum -= 2;
1851                     diffscore -= 2 * clients[pick]->at3_score;
1852                     clients[pick]->at3_lastforce = gamemillis;  // try not to force this player again for the next 5 minutes
1853                     switched = true;
1854                 }
1855             }
1856         }
1857     }
1858     if(diffnum < 2)
1859     {
1860         if ( ( gamemillis - lastbalance ) > waitbalance && ( gamelimit - gamemillis ) > 4*60*1000 )
1861         {
1862             if ( balanceteams (ftr) )
1863             {
1864                 waitbalance = 2 * 60 * 1000 + gamemillis / 3;
1865                 switched = true;
1866             }
1867             else waitbalance = 20 * 1000;
1868             lastbalance = gamemillis;
1869         }
1870         else if ( lastbalance > gamemillis )
1871         {
1872             lastbalance = 0;
1873             waitbalance = 2 * 60 * 1000;
1874         }
1875         lasttime_eventeams = gamemillis;
1876     }
1877     return switched;
1878 }
1879 
resetserver(const char * newname,int newmode,int newtime)1880 void resetserver(const char *newname, int newmode, int newtime)
1881 {
1882     if(m_demo) enddemoplayback();
1883     else enddemorecord();
1884 
1885     smode = newmode;
1886     copystring(smapname, newname);
1887 
1888     minremain = newtime > 0 ? newtime : defaultgamelimit(newmode);
1889     gamemillis = 0;
1890     gamelimit = minremain*60000;
1891     arenaround = arenaroundstartmillis = 0;
1892     memset(&smapstats, 0, sizeof(smapstats));
1893 
1894     interm = nextsendscore = 0;
1895     lastfillup = servmillis;
1896     sents.shrink(0);
1897     if(mastermode == MM_PRIVATE)
1898     {
1899         loopv(savedscores) savedscores[i].valid = false;
1900     }
1901     else savedscores.shrink(0);
1902     ctfreset();
1903 
1904     nextmapname[0] = '\0';
1905     forceintermission = false;
1906 }
1907 
startdemoplayback(const char * newname)1908 void startdemoplayback(const char *newname)
1909 {
1910     if(isdedicated) return;
1911     resetserver(newname, GMODE_DEMO, -1);
1912     setupdemoplayback();
1913 }
1914 
startgame(const char * newname,int newmode,int newtime,bool notify)1915 void startgame(const char *newname, int newmode, int newtime, bool notify)
1916 {
1917     if(!newname || !*newname || (newmode == GMODE_DEMO && isdedicated)) fatal("startgame() abused");
1918     if(newmode == GMODE_DEMO)
1919     {
1920         startdemoplayback(newname);
1921     }
1922     else
1923     {
1924         bool lastteammode = m_teammode;
1925         resetserver(newname, newmode, newtime);   // beware: may clear *newname
1926 
1927         if(custom_servdesc && findcnbyaddress(&servdesc_caller) < 0)
1928         {
1929             updatesdesc(NULL);
1930             if(notify)
1931             {
1932                 sendservmsg("server description reset to default");
1933                 logline(ACLOG_INFO, "server description reset to '%s'", servdesc_current);
1934             }
1935         }
1936 
1937         int maploc = MAP_VOID;
1938         mapstats *ms = getservermapstats(smapname, isdedicated, &maploc);
1939         mapbuffer.clear();
1940         if(isdedicated && distributablemap(maploc)) mapbuffer.load();
1941         if(ms)
1942         {
1943             smapstats = *ms;
1944             loopi(2)
1945             {
1946                 sflaginfo &f = sflaginfos[i];
1947                 if(smapstats.flags[i] == 1)    // don't check flag positions, if there is more than one flag per team
1948                 {
1949                     short *fe = smapstats.entposs + smapstats.flagents[i] * 3;
1950                     f.x = *fe;
1951                     fe++;
1952                     f.y = *fe;
1953                 }
1954                 else f.x = f.y = -1;
1955             }
1956             if (smapstats.flags[0] == 1 && smapstats.flags[1] == 1)
1957             {
1958                 sflaginfo &f0 = sflaginfos[0], &f1 = sflaginfos[1];
1959                 FlagFlag = pow2(f0.x - f1.x) + pow2(f0.y - f1.y);
1960                 coverdist = FlagFlag > 6 * COVERDIST ? COVERDIST : FlagFlag / 6;
1961             }
1962             entity e;
1963             loopi(smapstats.hdr.numents)
1964             {
1965                 e.type = smapstats.enttypes[i];
1966                 e.transformtype(smode);
1967                 server_entity se = { e.type, false, false, false, 0, smapstats.entposs[i * 3], smapstats.entposs[i * 3 + 1]};
1968                 sents.add(se);
1969                 if(e.fitsmode(smode)) sents[i].spawned = sents[i].legalpickup = true;
1970             }
1971             mapbuffer.setrevision();
1972             logline(ACLOG_INFO, "Map height density information for %s: H = %.2f V = %d, A = %d and MA = %d", smapname, Mheight, Mvolume, Marea, Mopen);
1973             items_blocked = false;
1974         }
1975         else if(isdedicated) sendservmsg("\f3server error: map not found - please start another map or send this map to the server");
1976         if(notify)
1977         {
1978             // change map
1979             sendf(-1, 1, "risiii", SV_MAPCHANGE, smapname, smode, mapbuffer.available(), mapbuffer.revision);
1980             if(smode>1 || (smode==0 && numnonlocalclients()>0)) sendf(-1, 1, "ri3", SV_TIMEUP, gamemillis, gamelimit);
1981         }
1982         packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1983         send_item_list(q); // always send the item list when a game starts
1984         sendpacket(-1, 1, q.finalize());
1985         defformatstring(gsmsg)("Game start: %s on %s, %d players, %d minutes, mastermode %d, ", modestr(smode), smapname, numclients(), minremain, mastermode);
1986         if(mastermode == MM_MATCH) concatformatstring(gsmsg, "teamsize %d, ", matchteamsize);
1987         if(ms) concatformatstring(gsmsg, "(map rev %d/%d, %s, 'getmap' %sprepared)", smapstats.hdr.maprevision, smapstats.cgzsize, maplocstr[maploc], mapbuffer.available() ? "" : "not ");
1988         else concatformatstring(gsmsg, "error: failed to preload map (map: %s)", maplocstr[maploc]);
1989         logline(ACLOG_INFO, "\n%s", gsmsg);
1990         if(m_arena) distributespawns();
1991         if(notify)
1992         {
1993             // shuffle if previous mode wasn't a team-mode
1994             if(m_teammode)
1995             {
1996                 if(!lastteammode)
1997                     shuffleteams(FTR_INFO);
1998                 else if(autoteam)
1999                     refillteams(true, FTR_INFO);
2000             }
2001             // prepare spawns; players will spawn, once they've loaded the correct map
2002             loopv(clients) if(clients[i]->type!=ST_EMPTY)
2003             {
2004                 client *c = clients[i];
2005                 c->mapchange();
2006                 forcedeath(c);
2007             }
2008         }
2009         if(numnonlocalclients() > 0) setupdemorecord();
2010         if(notify && m_ktf) sendflaginfo();
2011         if(notify) senddisconnectedscores(-1);
2012     }
2013 }
2014 
2015 struct gbaninfo
2016 {
2017     enet_uint32 ip, mask;
2018 };
2019 
2020 vector<gbaninfo> gbans;
2021 
cleargbans()2022 void cleargbans()
2023 {
2024     gbans.shrink(0);
2025 }
2026 
checkgban(uint ip)2027 bool checkgban(uint ip)
2028 {
2029     loopv(gbans) if((ip & gbans[i].mask) == gbans[i].ip) return true;
2030     return false;
2031 }
2032 
addgban(const char * name)2033 void addgban(const char *name)
2034 {
2035     union { uchar b[sizeof(enet_uint32)]; enet_uint32 i; } ip, mask;
2036     ip.i = 0;
2037     mask.i = 0;
2038     loopi(4)
2039     {
2040         char *end = NULL;
2041         int n = strtol(name, &end, 10);
2042         if(!end) break;
2043         if(end > name) { ip.b[i] = n; mask.b[i] = 0xFF; }
2044         name = end;
2045         while(*name && *name++ != '.');
2046     }
2047     gbaninfo &ban = gbans.add();
2048     ban.ip = ip.i;
2049     ban.mask = mask.i;
2050 
2051     loopvrev(clients)
2052     {
2053         client &c = *clients[i];
2054         if(c.type!=ST_TCPIP) continue;
2055         if(checkgban(c.peer->address.host)) disconnect_client(c.clientnum, DISC_BANREFUSE);
2056     }
2057 }
2058 
addban(client * cl,int reason,int type)2059 inline void addban(client *cl, int reason, int type)
2060 {
2061     if(!cl) return;
2062     ban b = { cl->peer->address, servmillis+scl.ban_time, type };
2063     bans.add(b);
2064     disconnect_client(cl->clientnum, reason);
2065 }
2066 
getbantype(int cn)2067 int getbantype(int cn)
2068 {
2069     if(!valid_client(cn)) return BAN_NONE;
2070     client &c = *clients[cn];
2071     if(c.type==ST_LOCAL) return BAN_NONE;
2072     if(checkgban(c.peer->address.host)) return BAN_MASTER;
2073     if(ipblacklist.check(c.peer->address.host)) return BAN_BLACKLIST;
2074     loopv(bans)
2075     {
2076         ban &b = bans[i];
2077         if(b.millis < servmillis) { bans.remove(i--); }
2078         if(b.address.host == c.peer->address.host) return b.type;
2079     }
2080     return BAN_NONE;
2081 }
2082 
serveroperator()2083 int serveroperator()
2084 {
2085     loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->role > CR_DEFAULT) return i;
2086     return -1;
2087 }
2088 
sendserveropinfo(int receiver)2089 void sendserveropinfo(int receiver)
2090 {
2091     int op = serveroperator();
2092     sendf(receiver, 1, "riii", SV_SERVOPINFO, op, op >= 0 ? clients[op]->role : -1);
2093 }
2094 
2095 #include "serveractions.h"
2096 
2097 struct voteinfo
2098 {
2099     int boot;
2100     int owner, callmillis, result, num1, num2, type;
2101     char text[MAXTRANS];
2102     serveraction *action;
2103     bool gonext;
2104     enet_uint32 host;
2105 
voteinfovoteinfo2106     voteinfo() : boot(0), owner(0), callmillis(0), result(VOTE_NEUTRAL), action(NULL), gonext(false), host(0) {}
~voteinfovoteinfo2107     ~voteinfo() { delete action; }
2108 
endvoteinfo2109     void end(int result)
2110     {
2111         if(action && !action->isvalid()) result = VOTE_NO; // don't perform() invalid votes
2112         sendf(-1, 1, "ri2", SV_VOTERESULT, result);
2113         this->result = result;
2114         if(result == VOTE_YES)
2115         {
2116             if(valid_client(owner)) clients[owner]->lastvotecall = 0;
2117             if(action) action->perform();
2118         }
2119         loopv(clients) clients[i]->vote = VOTE_NEUTRAL;
2120     }
2121 
isvalidvoteinfo2122     bool isvalid() { return valid_client(owner) && action != NULL && action->isvalid(); }
isalivevoteinfo2123     bool isalive() { return servmillis - callmillis < 30*1000; }
2124 
evaluatevoteinfo2125     void evaluate(bool forceend = false)
2126     {
2127         if(result!=VOTE_NEUTRAL) return; // block double action
2128         if(action && !action->isvalid()) end(VOTE_NO);
2129         int stats[VOTE_NUM] = {0};
2130         int adminvote = VOTE_NEUTRAL;
2131         loopv(clients)
2132             if(clients[i]->type!=ST_EMPTY /*&& clients[i]->connectmillis < callmillis*/) // new connected people will vote now
2133             {
2134                 stats[clients[i]->vote]++;
2135                 if(clients[i]->role==CR_ADMIN) adminvote = clients[i]->vote;
2136             };
2137 
2138         bool admin = clients[owner]->role==CR_ADMIN || (!isdedicated && clients[owner]->type==ST_LOCAL);
2139         int total = stats[VOTE_NO]+stats[VOTE_YES]+stats[VOTE_NEUTRAL];
2140         const float requiredcount = 0.51f;
2141         bool min_time = servmillis - callmillis > 10*1000;
2142 #define yes_condition ((min_time && stats[VOTE_YES] - stats[VOTE_NO] > 0.34f*total && totalclients > 4) || stats[VOTE_YES] > requiredcount*total)
2143 #define no_condition (forceend || !valid_client(owner) || stats[VOTE_NO] >= stats[VOTE_YES]+stats[VOTE_NEUTRAL] || adminvote == VOTE_NO)
2144 #define boot_condition (!boot || (boot && valid_client(num1) && clients[num1]->peer->address.host == host))
2145         if( (yes_condition || admin || adminvote == VOTE_YES) && boot_condition ) end(VOTE_YES);
2146         else if( no_condition || (min_time && !boot_condition)) end(VOTE_NO);
2147         else return;
2148 #undef boot_condition
2149 #undef no_condition
2150 #undef yes_condition
2151     }
2152 };
2153 
2154 static voteinfo *curvote = NULL;
2155 
svote(int sender,int vote,ENetPacket * msg)2156 bool svote(int sender, int vote, ENetPacket *msg) // true if the vote was placed successfully
2157 {
2158     if(!curvote || !valid_client(sender) || vote < VOTE_YES || vote > VOTE_NO) return false;
2159     if(clients[sender]->vote != VOTE_NEUTRAL)
2160     {
2161         sendf(sender, 1, "ri2", SV_CALLVOTEERR, VOTEE_MUL);
2162         return false;
2163     }
2164     else
2165     {
2166         sendpacket(-1, 1, msg, sender);
2167 
2168         clients[sender]->vote = vote;
2169         logline(ACLOG_DEBUG,"[%s] client %s voted %s", clients[sender]->hostname, clients[sender]->name, vote == VOTE_NO ? "no" : "yes");
2170         curvote->evaluate();
2171         return true;
2172     }
2173 }
2174 
scallvotesuc(voteinfo * v)2175 void scallvotesuc(voteinfo *v)
2176 {
2177     if(!v->isvalid()) return;
2178     DELETEP(curvote);
2179     curvote = v;
2180     clients[v->owner]->lastvotecall = servmillis;
2181     clients[v->owner]->nvotes--; // successful votes do not count as abuse
2182     sendf(v->owner, 1, "ri", SV_CALLVOTESUC);
2183     logline(ACLOG_INFO, "[%s] client %s called a vote: %s", clients[v->owner]->hostname, clients[v->owner]->name, v->action && v->action->desc ? v->action->desc : "[unknown]");
2184 }
2185 
scallvoteerr(voteinfo * v,int error)2186 void scallvoteerr(voteinfo *v, int error)
2187 {
2188     if(!valid_client(v->owner)) return;
2189     sendf(v->owner, 1, "ri2", SV_CALLVOTEERR, error);
2190     logline(ACLOG_INFO, "[%s] client %s failed to call a vote: %s (%s)", clients[v->owner]->hostname, clients[v->owner]->name, v->action && v->action->desc ? v->action->desc : "[unknown]", voteerrorstr(error));
2191 }
2192 
2193 bool map_queued = false;
2194 void callvotepacket (int, voteinfo *);
2195 
scallvote(voteinfo * v,ENetPacket * msg)2196 bool scallvote(voteinfo *v, ENetPacket *msg) // true if a regular vote was called
2197 {
2198     if (!v) return false;
2199     int area = isdedicated ? EE_DED_SERV : EE_LOCAL_SERV;
2200     int error = -1;
2201     client *c = clients[v->owner], *b = ( v->boot && valid_client(cn2boot) ? clients[cn2boot] : NULL );
2202     v->host = v->boot && b ? b->peer->address.host : 0;
2203 
2204     int time = servmillis - c->lastvotecall;
2205     if ( c->nvotes > 0 && time > 4*60*1000 ) c->nvotes -= time/(4*60*1000);
2206     if ( c->nvotes < 0 || c->role == CR_ADMIN ) c->nvotes = 0;
2207     c->nvotes++;
2208 
2209     if( !v || !v->isvalid() || (v->boot && (!b || cn2boot == v->owner) ) ) error = VOTEE_INVALID;
2210     else if( v->action->role > c->role ) error = VOTEE_PERMISSION;
2211     else if( !(area & v->action->area) ) error = VOTEE_AREA;
2212     else if( curvote && curvote->result==VOTE_NEUTRAL ) error = VOTEE_CUR;
2213     else if( v->type == SA_MAP && v->num1 >= GMODE_NUM && map_queued ) error = VOTEE_NEXT;
2214     else if( c->role == CR_DEFAULT && v->action->isdisabled() ) error = VOTEE_DISABLED;
2215     else if( (c->lastvotecall && servmillis - c->lastvotecall < 60*1000 && c->role != CR_ADMIN && numclients()>1) || c->nvotes > 3 ) error = VOTEE_MAX;
2216     else if( ( ( v->boot == 1 && c->role < roleconf('w') ) || ( v->boot == 2 && c->role < roleconf('X') ) )
2217                   && !is_lagging(b) && !b->mute && b->spamcount < 2 )
2218     {
2219         /** not same team, with low ratio, not lagging, and not spamming... so, why to kick? */
2220         if ( !isteam(c->team, b->team) && b->state.frags < ( b->state.deaths > 0 ? b->state.deaths : 1 ) * 3 ) error = VOTEE_WEAK;
2221         /** same team, with low tk, not lagging, and not spamming... so, why to kick? */
2222         else if ( isteam(c->team, b->team) && b->state.teamkills < c->state.teamkills ) error = VOTEE_WEAK;
2223     }
2224 
2225     if(error>=0)
2226     {
2227         scallvoteerr(v, error);
2228         return false;
2229     }
2230     else
2231     {
2232         if ( v->type == SA_MAP && v->num1 >= GMODE_NUM ) map_queued = true;
2233         if (!v->gonext) sendpacket(-1, 1, msg, v->owner); // FIXME in fact, all votes should go to the server, registered, and then go back to the clients
2234         else callvotepacket (-1, v);                      // also, no vote should exclude the caller... these would provide many code advantages/facilities
2235         scallvotesuc(v);                                  // but we cannot change the vote system now for compatibility issues... so, TODO
2236         return true;
2237     }
2238 }
2239 
callvotepacket(int cn,voteinfo * v=curvote)2240 void callvotepacket (int cn, voteinfo *v = curvote)
2241 { // FIXME, it would be far smart if the msg buffer from SV_CALLVOTE was simply saved
2242     int n_yes = 0, n_no = 0;
2243     loopv(clients) if(clients[i]->type!=ST_EMPTY)
2244     {
2245         if ( clients[i]->vote == VOTE_YES ) n_yes++;
2246         else if ( clients[i]->vote == VOTE_NO ) n_no++;
2247     }
2248 
2249     packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2250     putint(q, SV_CALLVOTE);
2251     putint(q, -1);
2252     putint(q, v->owner);
2253     putint(q, n_yes);
2254     putint(q, n_no);
2255     putint(q, v->type);
2256     switch(v->type)
2257     {
2258         case SA_KICK:
2259         case SA_BAN:
2260             putint(q, v->num1);
2261             sendstring(v->text, q);
2262             break;
2263         case SA_MAP:
2264             sendstring(v->text, q);
2265             putint(q, v->num1);
2266             putint(q, v->num2);
2267             break;
2268         case SA_SERVERDESC:
2269             sendstring(v->text, q);
2270             break;
2271         case SA_STOPDEMO:
2272             // compatibility
2273             break;
2274         case SA_REMBANS:
2275         case SA_SHUFFLETEAMS:
2276             break;
2277         case SA_FORCETEAM:
2278             putint(q, v->num1);
2279             putint(q, v->num2);
2280             break;
2281         default:
2282             putint(q, v->num1);
2283             break;
2284     }
2285     sendpacket(cn, 1, q.finalize());
2286 }
2287 
2288 // TODO: use AUTH code
changeclientrole(int client,int role,char * pwd,bool force)2289 void changeclientrole(int client, int role, char *pwd, bool force)
2290 {
2291     pwddetail pd;
2292     if(!isdedicated || !valid_client(client)) return;
2293     pd.line = -1;
2294     if(force || role == CR_DEFAULT || (role == CR_ADMIN && pwd && pwd[0] && passwords.check(clients[client]->name, pwd, clients[client]->salt, &pd) && !pd.denyadmin))
2295     {
2296         if(role == clients[client]->role) return;
2297         if(role > CR_DEFAULT)
2298         {
2299             loopv(clients) clients[i]->role = CR_DEFAULT;
2300         }
2301         clients[client]->role = role;
2302         sendserveropinfo(-1);
2303         if(pd.line > -1)
2304             logline(ACLOG_INFO,"[%s] player %s used admin password in line %d", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", pd.line);
2305         logline(ACLOG_INFO,"[%s] set role of player %s to %s", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", role == CR_ADMIN ? "admin" : "normal player"); // flowtron : connecting players haven't got a name yet (connectadmin)
2306         if(role > CR_DEFAULT) sendiplist(client);
2307     }
2308     else if(pwd && pwd[0]) disconnect_client(client, DISC_SOPLOGINFAIL); // avoid brute-force
2309     if(curvote) curvote->evaluate();
2310 }
2311 
senddisconnectedscores(int cn)2312 void senddisconnectedscores(int cn)
2313 {
2314     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2315     putint(p, SV_DISCSCORES);
2316     if(mastermode == MM_MATCH)
2317     {
2318         loopv(savedscores)
2319         {
2320             savedscore &sc = savedscores[i];
2321             if(sc.valid)
2322             {
2323                 putint(p, sc.team);
2324                 sendstring(sc.name, p);
2325                 putint(p, sc.flagscore);
2326                 putint(p, sc.frags);
2327                 putint(p, sc.deaths);
2328                 putint(p, sc.points);
2329             }
2330         }
2331     }
2332     putint(p, -1);
2333     sendpacket(cn, 1, p.finalize());
2334 }
2335 
disc_reason(int reason)2336 const char *disc_reason(int reason)
2337 {
2338     static const char *disc_reasons[] = { "normal", "error - end of packet", "error - client num", "vote-kicked from the server", "vote-banned from the server", "error - tag type", "connection refused - you have been banned from this server", "incorrect password", "unsuccessful administrator login", "the server is FULL - try again later", "servers mastermode is \"private\" - wait until the servers mastermode is \"open\"", "auto-kick - your score dropped below the servers threshold", "auto-ban - your score dropped below the servers threshold", "duplicate connection", "inappropriate nickname", "error - packet flood", "auto-kick - excess spam detected", "auto-kick - inactivity detected", "auto-kick - team killing detected", "auto-kick - abnormal client behavior detected" };
2339     return reason >= 0 && (size_t)reason < sizeof(disc_reasons)/sizeof(disc_reasons[0]) ? disc_reasons[reason] : "unknown";
2340 }
2341 
disconnect_client(int n,int reason)2342 void disconnect_client(int n, int reason)
2343 {
2344     if(!clients.inrange(n) || clients[n]->type!=ST_TCPIP) return;
2345     sdropflag(n);
2346     client &c = *clients[n];
2347     c.state.lastdisc = servmillis;
2348     const char *scoresaved = "";
2349     if(c.haswelcome)
2350     {
2351         savedscore *sc = findscore(c, true);
2352         if(sc)
2353         {
2354             sc->save(c.state, c.team);
2355             scoresaved = ", score saved";
2356         }
2357     }
2358     int sp = (servmillis - c.connectmillis) / 1000;
2359     if(reason>=0) logline(ACLOG_INFO, "[%s] disconnecting client %s (%s) cn %d, %d seconds played%s", c.hostname, c.name, disc_reason(reason), n, sp, scoresaved);
2360     else logline(ACLOG_INFO, "[%s] disconnected client %s cn %d, %d seconds played%s", c.hostname, c.name, n, sp, scoresaved);
2361     totalclients--;
2362     c.peer->data = (void *)-1;
2363     if(reason>=0) enet_peer_disconnect(c.peer, reason);
2364     clients[n]->zap();
2365     sendf(-1, 1, "rii", SV_CDIS, n);
2366     if(curvote) curvote->evaluate();
2367     if(*scoresaved && mastermode == MM_MATCH) senddisconnectedscores(-1);
2368 }
2369 
2370 // for AUTH: WIP
2371 
findauth(uint id)2372 client *findauth(uint id)
2373 {
2374     loopv(clients) if(clients[i]->authreq == id) return clients[i];
2375     return NULL;
2376 }
2377 
authfailed(uint id)2378 void authfailed(uint id)
2379 {
2380     client *cl = findauth(id);
2381     if(!cl) return;
2382     cl->authreq = 0;
2383 }
2384 
authsucceeded(uint id)2385 void authsucceeded(uint id)
2386 {
2387     client *cl = findauth(id);
2388     if(!cl) return;
2389     cl->authreq = 0;
2390     logline(ACLOG_INFO, "player authenticated: %s", cl->name);
2391     defformatstring(auth4u)("player authenticated: %s", cl->name);
2392     sendf(-1, 1, "ris", SV_SERVMSG, auth4u);
2393     //setmaster(cl, true, "", ci->authname);//TODO? compare to sauerbraten
2394 }
2395 
authchallenged(uint id,const char * val)2396 void authchallenged(uint id, const char *val)
2397 {
2398     client *cl = findauth(id);
2399     if(!cl) return;
2400     sendf(cl->clientnum, 1, "risis", SV_AUTHCHAL, "", id, val);
2401 }
2402 
2403 uint nextauthreq = 0;
2404 
tryauth(client * cl,const char * user)2405 void tryauth(client *cl, const char *user)
2406 {
2407     extern bool requestmasterf(const char *fmt, ...);
2408     if(!nextauthreq) nextauthreq = 1;
2409     cl->authreq = nextauthreq++;
2410     filtertext(cl->authname, user, false, 100);
2411     if(!requestmasterf("reqauth %u %s\n", cl->authreq, cl->authname))
2412     {
2413         cl->authreq = 0;
2414         sendf(cl->clientnum, 1, "ris", SV_SERVMSG, "not connected to authentication server");
2415     }
2416 }
2417 
answerchallenge(client * cl,uint id,char * val)2418 void answerchallenge(client *cl, uint id, char *val)
2419 {
2420     if(cl->authreq != id) return;
2421     extern bool requestmasterf(const char *fmt, ...);
2422     for(char *s = val; *s; s++)
2423     {
2424         if(!isxdigit(*s)) { *s = '\0'; break; }
2425     }
2426     if(!requestmasterf("confauth %u %s\n", id, val))
2427     {
2428         cl->authreq = 0;
2429         sendf(cl->clientnum, 1, "ris", SV_SERVMSG, "not connected to authentication server");
2430     }
2431 }
2432 
2433 // :for AUTH
2434 
sendiplist(int receiver,int cn)2435 void sendiplist(int receiver, int cn)
2436 {
2437     if(!valid_client(receiver)) return;
2438     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2439     putint(p, SV_IPLIST);
2440     loopv(clients) if(valid_client(i) && clients[i]->type == ST_TCPIP && i != receiver
2441         && (clients[i]->clientnum == cn || cn == -1))
2442     {
2443         putint(p, i);
2444         putint(p, isbigendian() ? endianswap(clients[i]->peer->address.host) : clients[i]->peer->address.host);
2445     }
2446     putint(p, -1);
2447     sendpacket(receiver, 1, p.finalize());
2448 }
2449 
sendresume(client & c,bool broadcast)2450 void sendresume(client &c, bool broadcast)
2451 {
2452     sendf(broadcast ? -1 : c.clientnum, 1, "ri3i9ivvi", SV_RESUME,
2453             c.clientnum,
2454             c.state.state,
2455             c.state.lifesequence,
2456             c.state.primary,
2457             c.state.gunselect,
2458             c.state.flagscore,
2459             c.state.frags,
2460             c.state.deaths,
2461             c.state.health,
2462             c.state.armour,
2463             c.state.points,
2464             c.state.teamkills,
2465             NUMGUNS, c.state.ammo,
2466             NUMGUNS, c.state.mag,
2467             -1);
2468 }
2469 
restorescore(client & c)2470 bool restorescore(client &c)
2471 {
2472     //if(ci->local) return false;
2473     savedscore *sc = findscore(c, false);
2474     if(sc && sc->valid)
2475     {
2476         sc->restore(c.state);
2477         sc->valid = false;
2478         if ( c.connectmillis - c.state.lastdisc < 5000 ) c.state.reconnections++;
2479         else if ( c.state.reconnections ) c.state.reconnections--;
2480         return true;
2481     }
2482     return false;
2483 }
2484 
sendservinfo(client & c)2485 void sendservinfo(client &c)
2486 {
2487     sendf(c.clientnum, 1, "ri5", SV_SERVINFO, c.clientnum, isdedicated ? SERVER_PROTOCOL_VERSION : PROTOCOL_VERSION, c.salt, scl.serverpassword[0] ? 1 : 0);
2488 }
2489 
putinitclient(client & c,packetbuf & p)2490 void putinitclient(client &c, packetbuf &p)
2491 {
2492     putint(p, SV_INITCLIENT);
2493     putint(p, c.clientnum);
2494     sendstring(c.name, p);
2495     putint(p, c.skin[TEAM_CLA]);
2496     putint(p, c.skin[TEAM_RVSF]);
2497     putint(p, c.team);
2498     enet_uint32 ip = 0;
2499     if(c.type == ST_TCPIP) ip = c.peer->address.host & 0xFFFFFF;
2500     putint(p, isbigendian() ? endianswap(ip) : ip);
2501 }
2502 
sendinitclient(client & c)2503 void sendinitclient(client &c)
2504 {
2505     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2506     putinitclient(c, p);
2507     sendpacket(-1, 1, p.finalize(), c.clientnum);
2508 }
2509 
welcomeinitclient(packetbuf & p,int exclude=-1)2510 void welcomeinitclient(packetbuf &p, int exclude = -1)
2511 {
2512     loopv(clients)
2513     {
2514         client &c = *clients[i];
2515         if(c.type!=ST_TCPIP || !c.isauthed || c.clientnum == exclude) continue;
2516         putinitclient(c, p);
2517     }
2518 }
2519 
welcomepacket(packetbuf & p,int n)2520 void welcomepacket(packetbuf &p, int n)
2521 {
2522     if(!smapname[0]) maprot.next(false);
2523 
2524     client *c = valid_client(n) ? clients[n] : NULL;
2525     int numcl = numclients();
2526 
2527     putint(p, SV_WELCOME);
2528     putint(p, smapname[0] && !m_demo ? numcl : -1);
2529     if(smapname[0] && !m_demo)
2530     {
2531         putint(p, SV_MAPCHANGE);
2532         sendstring(smapname, p);
2533         putint(p, smode);
2534         putint(p, mapbuffer.available());
2535         putint(p, mapbuffer.revision);
2536         if(smode>1 || (smode==0 && numnonlocalclients()>0))
2537         {
2538             putint(p, SV_TIMEUP);
2539             putint(p, (gamemillis>=gamelimit || forceintermission) ? gamelimit : gamemillis);
2540             putint(p, gamelimit);
2541             //putint(p, minremain*60);
2542         }
2543         send_item_list(p); // this includes the flags
2544     }
2545     savedscore *sc = NULL;
2546     if(c)
2547     {
2548         if(c->type == ST_TCPIP && serveroperator() != -1) sendserveropinfo(n);
2549         c->team = mastermode == MM_MATCH && sc ? team_tospec(sc->team) : TEAM_SPECT;
2550         putint(p, SV_SETTEAM);
2551         putint(p, n);
2552         putint(p, c->team | (FTR_INFO << 4));
2553 
2554         putint(p, SV_FORCEDEATH);
2555         putint(p, n);
2556         sendf(-1, 1, "ri2x", SV_FORCEDEATH, n, n);
2557     }
2558     if(!c || clients.length()>1)
2559     {
2560         putint(p, SV_RESUME);
2561         loopv(clients)
2562         {
2563             client &c = *clients[i];
2564             if(c.type!=ST_TCPIP || c.clientnum==n) continue;
2565             putint(p, c.clientnum);
2566             putint(p, c.state.state);
2567             putint(p, c.state.lifesequence);
2568             putint(p, c.state.primary);
2569             putint(p, c.state.gunselect);
2570             putint(p, c.state.flagscore);
2571             putint(p, c.state.frags);
2572             putint(p, c.state.deaths);
2573             putint(p, c.state.health);
2574             putint(p, c.state.armour);
2575             putint(p, c.state.points);
2576             putint(p, c.state.teamkills);
2577             loopi(NUMGUNS) putint(p, c.state.ammo[i]);
2578             loopi(NUMGUNS) putint(p, c.state.mag[i]);
2579         }
2580         putint(p, -1);
2581         welcomeinitclient(p, n);
2582     }
2583     putint(p, SV_SERVERMODE);
2584     putint(p, sendservermode(false));
2585     const char *motd = scl.motd[0] ? scl.motd : infofiles.getmotd(c ? c->lang : "");
2586     if(motd)
2587     {
2588         putint(p, SV_TEXT);
2589         sendstring(motd, p);
2590     }
2591 }
2592 
sendwelcome(client * cl,int chan)2593 void sendwelcome(client *cl, int chan)
2594 {
2595     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2596     welcomepacket(p, cl->clientnum);
2597     sendpacket(cl->clientnum, chan, p.finalize());
2598     cl->haswelcome = true;
2599 }
2600 
forcedeath(client * cl)2601 void forcedeath(client *cl)
2602 {
2603     sdropflag(cl->clientnum);
2604     cl->state.state = CS_DEAD;
2605     cl->state.respawn();
2606     sendf(-1, 1, "rii", SV_FORCEDEATH, cl->clientnum);
2607 }
2608 
checktype(int type,client * cl)2609 int checktype(int type, client *cl)
2610 {
2611     if(cl && cl->type==ST_LOCAL) return type;
2612     if(type < 0 || type >= SV_NUM) return -1;
2613     // server only messages
2614     static int servtypes[] = { SV_SERVINFO, SV_WELCOME, SV_INITCLIENT, SV_POSN, SV_CDIS, SV_GIBDIED, SV_DIED,
2615                         SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_AUTHREQ, SV_AUTHCHAL,
2616                         SV_SPAWNSTATE, SV_SPAWNDENY, SV_FORCEDEATH, SV_RESUME,
2617                         SV_DISCSCORES, SV_TIMEUP, SV_ITEMACC, SV_MAPCHANGE, SV_ITEMSPAWN, SV_PONG,
2618                         SV_SERVMSG, SV_ITEMLIST, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT,
2619                         SV_ARENAWIN, SV_SERVOPINFO,
2620                         SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTERESULT,
2621                         SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, SV_IPLIST,
2622                         SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK,
2623                         SV_CLIENT, SV_HUDEXTRAS, SV_POINTS };
2624     // only allow edit messages in coop-edit mode
2625     static int edittypes[] = { SV_EDITENT, SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, SV_NEWMAP };
2626     if(cl)
2627     {
2628         loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1;
2629         loopi(sizeof(edittypes)/sizeof(int)) if(type == edittypes[i]) return smode==GMODE_COOPEDIT ? type : -1;
2630         if(++cl->overflow >= 200) return -2;
2631     }
2632     return type;
2633 }
2634 
2635 // server side processing of updates: does very little and most state is tracked client only
2636 // could be extended to move more gameplay to server (at expense of lag)
2637 
process(ENetPacket * packet,int sender,int chan)2638 void process(ENetPacket *packet, int sender, int chan)
2639 {
2640     ucharbuf p(packet->data, packet->dataLength);
2641     char text[MAXTRANS];
2642     client *cl = sender>=0 ? clients[sender] : NULL;
2643     pwddetail pd;
2644     int type;
2645 
2646     if(cl && !cl->isauthed)
2647     {
2648         int clientrole = CR_DEFAULT;
2649 
2650         if(chan==0) return;
2651         else if(chan!=1 || getint(p)!=SV_CONNECT) disconnect_client(sender, DISC_TAGT);
2652         else
2653         {
2654             cl->acversion = getint(p);
2655             cl->acbuildtype = getint(p);
2656             defformatstring(tags)(", AC: %d|%x", cl->acversion, cl->acbuildtype);
2657             getstring(text, p);
2658             filtertext(text, text, 0, MAXNAMELEN);
2659             if(!text[0]) copystring(text, "unarmed");
2660             copystring(cl->name, text, MAXNAMELEN+1);
2661             getstring(text, p);
2662             copystring(cl->pwd, text);
2663             getstring(text, p);
2664             filterlang(cl->lang, text);
2665             int wantrole = getint(p);
2666             cl->state.nextprimary = getint(p);
2667             loopi(2) cl->skin[i] = getint(p);
2668             int bantype = getbantype(sender);
2669             bool banned = bantype > BAN_NONE;
2670             bool srvfull = numnonlocalclients() > scl.maxclients;
2671             bool srvprivate = mastermode == MM_PRIVATE || mastermode == MM_MATCH;
2672             bool matchreconnect = mastermode == MM_MATCH && findscore(*cl, false);
2673             int bl = 0, wl = nickblacklist.checkwhitelist(*cl);
2674             if(wl == NWL_PASS) concatstring(tags, ", nickname whitelist match");
2675             if(wl == NWL_UNLISTED) bl = nickblacklist.checkblacklist(cl->name);
2676             if(matchreconnect && !banned)
2677             { // former player reconnecting to a server in match mode
2678                 cl->isauthed = true;
2679                 logline(ACLOG_INFO, "[%s] %s logged in (reconnect to match)%s", cl->hostname, cl->name, tags);
2680             }
2681             else if(wl == NWL_IPFAIL || wl == NWL_PWDFAIL)
2682             { // nickname matches whitelist, but IP is not in the required range or PWD doesn't match
2683                 logline(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong %s%s", cl->hostname, cl->name, wl == NWL_IPFAIL ? "IP" : "PWD", tags);
2684                 disconnect_client(sender, DISC_BADNICK);
2685             }
2686             else if(bl > 0)
2687             { // nickname matches blacklist
2688                 logline(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d%s", cl->hostname, cl->name, bl, tags);
2689                 disconnect_client(sender, DISC_BADNICK);
2690             }
2691             else if(passwords.check(cl->name, cl->pwd, cl->salt, &pd, (cl->type==ST_TCPIP ? cl->peer->address.host : 0)) && (!pd.denyadmin || (banned && !srvfull && !srvprivate)) && bantype != BAN_MASTER) // pass admins always through
2692             { // admin (or deban) password match
2693                 cl->isauthed = true;
2694                 if(!pd.denyadmin && wantrole == CR_ADMIN) clientrole = CR_ADMIN;
2695                 if(bantype == BAN_VOTE)
2696                 {
2697                     loopv(bans) if(bans[i].address.host == cl->peer->address.host) { bans.remove(i); concatstring(tags, ", ban removed"); break; } // remove admin bans
2698                 }
2699                 if(srvfull)
2700                 {
2701                     loopv(clients) if(i != sender && clients[i]->type==ST_TCPIP)
2702                     {
2703                         disconnect_client(i, DISC_MAXCLIENTS); // disconnect someone else to fit maxclients again
2704                         break;
2705                     }
2706                 }
2707                 logline(ACLOG_INFO, "[%s] %s logged in using the admin password in line %d%s", cl->hostname, cl->name, pd.line, tags);
2708             }
2709             else if(scl.serverpassword[0] && !(srvprivate || srvfull || banned))
2710             { // server password required
2711                 if(!strcmp(genpwdhash(cl->name, scl.serverpassword, cl->salt), cl->pwd))
2712                 {
2713                     cl->isauthed = true;
2714                     logline(ACLOG_INFO, "[%s] %s client logged in (using serverpassword)%s", cl->hostname, cl->name, tags);
2715                 }
2716                 else disconnect_client(sender, DISC_WRONGPW);
2717             }
2718             else if(srvprivate) disconnect_client(sender, DISC_MASTERMODE);
2719             else if(srvfull) disconnect_client(sender, DISC_MAXCLIENTS);
2720             else if(banned) disconnect_client(sender, DISC_BANREFUSE);
2721             else
2722             {
2723                 cl->isauthed = true;
2724                 logline(ACLOG_INFO, "[%s] %s logged in (default)%s", cl->hostname, cl->name, tags);
2725             }
2726         }
2727         if(!cl->isauthed) return;
2728 
2729         if(cl->type==ST_TCPIP)
2730         {
2731             loopv(clients) if(i != sender)
2732             {
2733                 client *dup = clients[i];
2734                 if(dup->type==ST_TCPIP && dup->peer->address.host==cl->peer->address.host && dup->peer->address.port==cl->peer->address.port)
2735                     disconnect_client(i, DISC_DUP);
2736             }
2737         }
2738 
2739         sendwelcome(cl);
2740         if(restorescore(*cl)) { sendresume(*cl, true); senddisconnectedscores(-1); }
2741         else if(cl->type==ST_TCPIP) senddisconnectedscores(sender);
2742         sendinitclient(*cl);
2743         if(clientrole != CR_DEFAULT) changeclientrole(sender, clientrole, NULL, true);
2744         if( curvote && curvote->result == VOTE_NEUTRAL ) callvotepacket (cl->clientnum);
2745 
2746         // send full IPs to admins
2747         loopv(clients)
2748         {
2749             if(clients[i] && clients[i]->clientnum != cl->clientnum && (clients[i]->role == CR_ADMIN || clients[i]->type == ST_LOCAL))
2750                 sendiplist(clients[i]->clientnum, cl->clientnum);
2751         }
2752     }
2753 
2754     if(!cl) { logline(ACLOG_ERROR, "<NULL> client in process()"); return; }  // should never happen anyway
2755 
2756     if(packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true;
2757 
2758     #define QUEUE_MSG { if(cl->type==ST_TCPIP) while(curmsg<p.length()) cl->messages.add(p.buf[curmsg++]); }
2759     #define QUEUE_BUF(body) \
2760     { \
2761         if(cl->type==ST_TCPIP) \
2762         { \
2763             curmsg = p.length(); \
2764             { body; } \
2765         } \
2766     }
2767     #define QUEUE_INT(n) QUEUE_BUF(putint(cl->messages, n))
2768     #define QUEUE_UINT(n) QUEUE_BUF(putuint(cl->messages, n))
2769     #define QUEUE_STR(text) QUEUE_BUF(sendstring(text, cl->messages))
2770     #define MSG_PACKET(packet) \
2771         packetbuf buf(16 + p.length() - curmsg, ENET_PACKET_FLAG_RELIABLE); \
2772         putint(buf, SV_CLIENT); \
2773         putint(buf, cl->clientnum); \
2774         putuint(buf, p.length() - curmsg); \
2775         buf.put(&p.buf[curmsg], p.length() - curmsg); \
2776         ENetPacket *packet = buf.finalize();
2777 
2778     int curmsg;
2779     while((curmsg = p.length()) < p.maxlen)
2780     {
2781         type = checktype(getint(p), cl);
2782 
2783         #ifdef _DEBUG
2784         if(type!=SV_POS && type!=SV_POSC && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_CLIENT)
2785         {
2786             DEBUGVAR(cl->name);
2787             ASSERT(type>=0 && type<SV_NUM);
2788             DEBUGVAR(messagenames[type]);
2789             protocoldebug(true);
2790         }
2791         else protocoldebug(false);
2792         #endif
2793 
2794         type = checkmessage(cl,type);
2795         switch(type)
2796         {
2797             case SV_TEAMTEXTME:
2798             case SV_TEAMTEXT:
2799                 getstring(text, p);
2800                 filtertext(text, text);
2801                 trimtrailingwhitespace(text);
2802                 if(*text)
2803                 {
2804                     bool canspeech = forbiddenlist.canspeech(text);
2805                     if(!spamdetect(cl, text) && canspeech) // team chat
2806                     {
2807                         logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text);
2808                         sendteamtext(text, sender, type);
2809                     }
2810                     else
2811                     {
2812                         logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s', %s", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "",
2813                                 cl->name, team_string(cl->team), text, canspeech ? "SPAM detected" : "Forbidden speech");
2814                         if (canspeech)
2815                         {
2816                             sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
2817                             if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE);
2818                         }
2819                         else
2820                         {
2821                             sendservmsg("\f3Watch your language! Your message was not delivered.", sender);
2822                             kick_abuser(cl->clientnum, cl->badmillis, cl->badspeech, 3);
2823                         }
2824                     }
2825                 }
2826                 break;
2827 
2828             case SV_TEXTME:
2829             case SV_TEXT:
2830             {
2831                 int mid1 = curmsg, mid2 = p.length();
2832                 getstring(text, p);
2833                 filtertext(text, text);
2834                 trimtrailingwhitespace(text);
2835                 if(*text)
2836                 {
2837                     bool canspeech = forbiddenlist.canspeech(text);
2838                     if(!spamdetect(cl, text) && canspeech)
2839                     {
2840                         if(mastermode != MM_MATCH || !matchteamsize || team_isactive(cl->team) || (cl->team == TEAM_SPECT && cl->role == CR_ADMIN)) // common chat
2841                         {
2842                             logline(ACLOG_INFO, "[%s] %s%s says: '%s'", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text);
2843                             if(cl->type==ST_TCPIP) while(mid1<mid2) cl->messages.add(p.buf[mid1++]);
2844                             QUEUE_STR(text);
2845                         }
2846                         else // spect chat
2847                         {
2848                             logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text);
2849                             sendteamtext(text, sender, type == SV_TEXTME ? SV_TEAMTEXTME : SV_TEAMTEXT);
2850                         }
2851                     }
2852                     else
2853                     {
2854                         logline(ACLOG_INFO, "[%s] %s%s says: '%s', %s", cl->hostname, type == SV_TEXTME ? "(me) " : "",
2855                                 cl->name, text, canspeech ? "SPAM detected" : "Forbidden speech");
2856                         if (canspeech)
2857                         {
2858                             sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
2859                             if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE);
2860                         }
2861                         else
2862                         {
2863                             sendservmsg("\f3Watch your language! Your message was not delivered.", sender);
2864                             kick_abuser(cl->clientnum, cl->badmillis, cl->badspeech, 3);
2865                         }
2866                     }
2867                 }
2868                 break;
2869             }
2870 
2871             case SV_TEXTPRIVATE:
2872             {
2873                 int targ = getint(p);
2874                 getstring(text, p);
2875                 filtertext(text, text);
2876                 trimtrailingwhitespace(text);
2877 
2878                 if(!valid_client(targ)) break;
2879                 client *target = clients[targ];
2880 
2881                 if(*text)
2882                 {
2883                     bool canspeech = forbiddenlist.canspeech(text);
2884                     if(!spamdetect(cl, text) && canspeech)
2885                     {
2886                         bool allowed = !(mastermode == MM_MATCH && cl->team != target->team) && cl->role >= roleconf('t');
2887                         logline(ACLOG_INFO, "[%s] %s says to %s: '%s' (%s)", cl->hostname, cl->name, target->name, text, allowed ? "allowed":"disallowed");
2888                         if(allowed) sendf(target->clientnum, 1, "riis", SV_TEXTPRIVATE, cl->clientnum, text);
2889                     }
2890                     else
2891                     {
2892                         logline(ACLOG_INFO, "[%s] %s says to %s: '%s', %s", cl->hostname, cl->name, target->name, text, canspeech ? "SPAM detected" : "Forbidden speech");
2893                         if (canspeech)
2894                         {
2895                             sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
2896                             if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE);
2897                         }
2898                         else
2899                         {
2900                             sendservmsg("\f3Watch your language! Your message was not delivered.", sender);
2901                             kick_abuser(cl->clientnum, cl->badmillis, cl->badspeech, 3);
2902                         }
2903                     }
2904                 }
2905             }
2906             break;
2907 
2908             case SV_VOICECOM:
2909             case SV_VOICECOMTEAM:
2910             {
2911                 int s = getint(p);
2912                 /* spam filter */
2913                 if ( servmillis > cl->mute ) // client is not muted
2914                 {
2915                     if( s < S_AFFIRMATIVE || s >= S_NULL ) cl->mute = servmillis + 10000; // vc is invalid
2916                     else if ( cl->lastvc + 4000 < servmillis ) { if ( cl->spam > 0 ) cl->spam -= (servmillis - cl->lastvc) / 4000; } // no vc in the last 4 seconds
2917                     else cl->spam++; // the guy is spamming
2918                     if ( cl->spam < 0 ) cl->spam = 0;
2919                     cl->lastvc = servmillis; // register
2920                     if ( cl->spam > 4 ) { cl->mute = servmillis + 10000; break; } // 5 vcs in less than 20 seconds... shut up please
2921                     if ( m_teammode ) checkteamplay(s,sender); // finally here we check the teamplay comm
2922                     if ( type == SV_VOICECOM ) { QUEUE_MSG; }
2923                     else sendvoicecomteam(s, sender);
2924                 }
2925             }
2926             break;
2927 
2928             case SV_MAPIDENT:
2929             {
2930                 int gzs = getint(p);
2931                 int rev = getint(p);
2932                 if(!isdedicated || (smapstats.cgzsize == gzs && smapstats.hdr.maprevision == rev))
2933                 { // here any game really starts for a client: spawn, if it's a new game - don't spawn if the game was already running
2934                     cl->isonrightmap = true;
2935                     int sp = canspawn(cl);
2936                     sendf(sender, 1, "rii", SV_SPAWNDENY, sp);
2937                     cl->spawnperm = sp;
2938                     if(cl->loggedwrongmap) logline(ACLOG_INFO, "[%s] %s is now on the right map: revision %d/%d", cl->hostname, cl->name, rev, gzs);
2939                     bool spawn = false;
2940                     if(team_isspect(cl->team))
2941                     {
2942                         if(numclients() < 2 && !m_demo && mastermode != MM_MATCH) // spawn on empty servers
2943                         {
2944                             spawn = updateclientteam(cl->clientnum, TEAM_ANYACTIVE, FTR_INFO);
2945                         }
2946                     }
2947                     else
2948                     {
2949                         if((cl->freshgame || numclients() < 2) && !m_demo) spawn = true;
2950                     }
2951                     cl->freshgame = false;
2952                     if(spawn) sendspawn(cl);
2953 
2954                 }
2955                 else
2956                 {
2957                     forcedeath(cl);
2958                     logline(ACLOG_INFO, "[%s] %s is on the wrong map: revision %d/%d", cl->hostname, cl->name, rev, gzs);
2959                     cl->loggedwrongmap = true;
2960                     sendf(sender, 1, "rii", SV_SPAWNDENY, SP_WRONGMAP);
2961                 }
2962                 QUEUE_MSG;
2963                 break;
2964             }
2965 
2966             case SV_ITEMPICKUP:
2967             {
2968                 int n = getint(p);
2969                 if(!arenaround || arenaround - gamemillis > 2000)
2970                 {
2971                     gameevent &pickup = cl->addevent();
2972                     pickup.type = GE_PICKUP;
2973                     pickup.pickup.ent = n;
2974                 }
2975                 break;
2976             }
2977 
2978             case SV_WEAPCHANGE:
2979             {
2980                 int gunselect = getint(p);
2981                 if(gunselect<0 || gunselect>=NUMGUNS || gunselect == GUN_CPISTOL) break;
2982                 if(!m_demo && !m_coop) checkweapon(type,gunselect);
2983                 cl->state.gunselect = gunselect;
2984                 QUEUE_MSG;
2985                 break;
2986             }
2987 
2988             case SV_PRIMARYWEAP:
2989             {
2990                 int nextprimary = getint(p);
2991                 if(nextprimary<0 && nextprimary>=NUMGUNS) break;
2992                 cl->state.nextprimary = nextprimary;
2993                 break;
2994             }
2995 
2996             case SV_SWITCHNAME:
2997             {
2998                 QUEUE_MSG;
2999                 getstring(text, p);
3000                 filtertext(text, text, 0, MAXNAMELEN);
3001                 if(!text[0]) copystring(text, "unarmed");
3002                 QUEUE_STR(text);
3003                 bool namechanged = strcmp(cl->name, text) != 0;
3004                 if(namechanged) logline(ACLOG_INFO,"[%s] %s changed name to %s", cl->hostname, cl->name, text);
3005                 copystring(cl->name, text, MAXNAMELEN+1);
3006                 if(namechanged)
3007                 {
3008                     // very simple spam detection (possible FIXME: centralize spam detection)
3009                     if(servmillis - cl->lastprofileupdate < 1000)
3010                     {
3011                         ++cl->fastprofileupdates;
3012                         if(cl->fastprofileupdates == 3) sendservmsg("\f3Please do not spam");
3013                         if(cl->fastprofileupdates >= 5) { disconnect_client(sender, DISC_ABUSE); break; }
3014                     }
3015                     else if(servmillis - cl->lastprofileupdate > 10000) cl->fastprofileupdates = 0;
3016                     cl->lastprofileupdate = servmillis;
3017 
3018                     switch(nickblacklist.checkwhitelist(*cl))
3019                     {
3020                         case NWL_PWDFAIL:
3021                         case NWL_IPFAIL:
3022                             logline(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong IP/PWD", cl->hostname, cl->name);
3023                             disconnect_client(sender, DISC_BADNICK);
3024                             break;
3025 
3026                         case NWL_UNLISTED:
3027                         {
3028                             int l = nickblacklist.checkblacklist(cl->name);
3029                             if(l >= 0)
3030                             {
3031                                 logline(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d", cl->hostname, cl->name, l);
3032                                 disconnect_client(sender, DISC_BADNICK);
3033                             }
3034                             break;
3035                         }
3036                     }
3037                 }
3038                 break;
3039             }
3040 
3041             case SV_SWITCHTEAM:
3042             {
3043                 int t = getint(p);
3044                 if(!updateclientteam(sender, team_isvalid(t) ? t : TEAM_SPECT, FTR_PLAYERWISH)) sendf(sender, 1, "rii", SV_TEAMDENY, t);
3045                 break;
3046             }
3047 
3048             case SV_SWITCHSKIN:
3049             {
3050                 loopi(2) cl->skin[i] = getint(p);
3051                 QUEUE_MSG;
3052 
3053                 if(servmillis - cl->lastprofileupdate < 1000)
3054                 {
3055                     ++cl->fastprofileupdates;
3056                     if(cl->fastprofileupdates == 3) sendservmsg("\f3Please do not spam");
3057                     if(cl->fastprofileupdates >= 5) disconnect_client(sender, DISC_ABUSE);
3058                 }
3059                 else if(servmillis - cl->lastprofileupdate > 10000) cl->fastprofileupdates = 0;
3060                 cl->lastprofileupdate = servmillis;
3061                 break;
3062             }
3063 
3064             case SV_TRYSPAWN:
3065             {
3066                 int sp = canspawn(cl);
3067                 if(team_isspect(cl->team) && sp < SP_OK_NUM)
3068                 {
3069                     updateclientteam(sender, TEAM_ANYACTIVE, FTR_PLAYERWISH);
3070                     sp = canspawn(cl);
3071                 }
3072                 if( !m_arena && sp < SP_OK_NUM && gamemillis > cl->state.lastspawn + 1000 ) sendspawn(cl);
3073                 break;
3074             }
3075 
3076             case SV_SPAWN:
3077             {
3078                 int ls = getint(p), gunselect = getint(p);
3079                 if((cl->state.state!=CS_ALIVE && cl->state.state!=CS_DEAD && cl->state.state!=CS_SPECTATE) ||
3080                     ls!=cl->state.lifesequence || cl->state.lastspawn<0 || gunselect<0 || gunselect>=NUMGUNS || gunselect == GUN_CPISTOL) break;
3081                 cl->state.lastspawn = -1;
3082                 cl->state.spawn = gamemillis;
3083                 cl->upspawnp = false;
3084                 cl->state.state = CS_ALIVE;
3085                 cl->state.gunselect = gunselect;
3086                 QUEUE_BUF(
3087                 {
3088                     putint(cl->messages, SV_SPAWN);
3089                     putint(cl->messages, cl->state.lifesequence);
3090                     putint(cl->messages, cl->state.health);
3091                     putint(cl->messages, cl->state.armour);
3092                     putint(cl->messages, cl->state.gunselect);
3093                     loopi(NUMGUNS) putint(cl->messages, cl->state.ammo[i]);
3094                     loopi(NUMGUNS) putint(cl->messages, cl->state.mag[i]);
3095                 });
3096                 break;
3097             }
3098 
3099             case SV_SUICIDE:
3100             {
3101                 gameevent &suicide = cl->addevent();
3102                 suicide.type = GE_SUICIDE;
3103                 break;
3104             }
3105 
3106             case SV_SHOOT:
3107             {
3108                 gameevent &shot = cl->addevent();
3109                 shot.type = GE_SHOT;
3110                 #define seteventmillis(event) \
3111                 { \
3112                     event.id = getint(p); \
3113                     if(!cl->timesync || (cl->events.length()==1 && cl->state.waitexpired(gamemillis))) \
3114                     { \
3115                         cl->timesync = true; \
3116                         cl->gameoffset = gamemillis - event.id; \
3117                         event.millis = gamemillis; \
3118                     } \
3119                     else event.millis = cl->gameoffset + event.id; \
3120                 }
3121                 seteventmillis(shot.shot);
3122                 shot.shot.gun = getint(p);
3123                 loopk(3) { shot.shot.from[k] = cl->state.o.v[k] + ( k == 2 ? (((cl->f>>7)&1)?2.2f:4.2f) : 0); }
3124                 loopk(3) { float v = getint(p)/DMF; shot.shot.to[k] = ((k<2 && v<0.0f)?0.0f:v); }
3125                 int hits = getint(p);
3126                 int tcn = -1;
3127                 loopk(hits)
3128                 {
3129                     gameevent &hit = cl->addevent();
3130                     hit.type = GE_HIT;
3131                     tcn = hit.hit.target = getint(p);
3132                     hit.hit.lifesequence = getint(p);
3133                     hit.hit.info = getint(p);
3134                     loopk(3) hit.hit.dir[k] = getint(p)/DNF;
3135                 }
3136                 if(!m_demo && !m_coop) checkshoot(sender, shot, hits, tcn);
3137                 break;
3138             }
3139 
3140             case SV_EXPLODE: // Brahma says: FIXME handle explosion by location and deal damage from server
3141             {
3142                 gameevent &exp = cl->addevent();
3143                 exp.type = GE_EXPLODE;
3144                 seteventmillis(exp.explode);
3145                 exp.explode.gun = getint(p);
3146                 exp.explode.id = getint(p);
3147                 int hits = getint(p);
3148                 loopk(hits)
3149                 {
3150                     gameevent &hit = cl->addevent();
3151                     hit.type = GE_HIT;
3152                     hit.hit.target = getint(p);
3153                     hit.hit.lifesequence = getint(p);
3154                     hit.hit.dist = getint(p)/DMF;
3155                     loopk(3) hit.hit.dir[k] = getint(p)/DNF;
3156                 }
3157                 break;
3158             }
3159 
3160             case SV_AKIMBO:
3161             {
3162                 gameevent &akimbo = cl->addevent();
3163                 akimbo.type = GE_AKIMBO;
3164                 seteventmillis(akimbo.akimbo);
3165                 break;
3166             }
3167 
3168             case SV_RELOAD:
3169             {
3170                 gameevent &reload = cl->addevent();
3171                 reload.type = GE_RELOAD;
3172                 seteventmillis(reload.reload);
3173                 reload.reload.gun = getint(p);
3174                 break;
3175             }
3176 
3177             // for AUTH:
3178 
3179             case SV_AUTHTRY:
3180             {
3181                 string desc, name;
3182                 getstring(desc, p, sizeof(desc)); // unused for now
3183                 getstring(name, p, sizeof(name));
3184                 if(!desc[0]) tryauth(cl, name);
3185                 break;
3186             }
3187 
3188             case SV_AUTHANS:
3189             {
3190                 string desc, ans;
3191                 getstring(desc, p, sizeof(desc)); // unused for now
3192                 uint id = (uint)getint(p);
3193                 getstring(ans, p, sizeof(ans));
3194                 if(!desc[0]) answerchallenge(cl, id, ans);
3195                 break;
3196             }
3197 
3198 
3199             case SV_AUTHT:
3200             {
3201 /*                int n = getint(p);
3202                 loopi(n) getint(p);*/
3203 //                 if (cl) disconnect_client(cl->clientnum, DISC_TAGT); // remove this in the future, when auth is complete
3204                 break;
3205             }
3206             // :for AUTH
3207 
3208             case SV_PING:
3209                 sendf(sender, 1, "ii", SV_PONG, getint(p));
3210                 break;
3211 
3212             case SV_CLIENTPING:
3213             {
3214                 int ping = getint(p);
3215                 if(cl) cl->ping = cl->ping == 9999 ? ping : (cl->ping * 4 + ping) / 5;
3216                 QUEUE_MSG;
3217                 break;
3218             }
3219 
3220             case SV_POS:
3221             {
3222                 int cn = getint(p);
3223                 if(cn!=sender)
3224                 {
3225                     disconnect_client(sender, DISC_CN);
3226     #ifndef STANDALONE
3227                     conoutf("ERROR: invalid client (msg %i)", type);
3228     #endif
3229                     return;
3230                 }
3231                 loopi(3) cl->state.o[i] = getuint(p)/DMF;
3232                 cl->y = getuint(p);
3233                 cl->p = getint(p);
3234                 cl->g = getuint(p);
3235                 loopi(4) if ( (cl->g >> i) & 1 ) getint(p);
3236                 cl->f = getuint(p);
3237                 if(!cl->isonrightmap) break;
3238                 if(cl->type==ST_TCPIP && (cl->state.state==CS_ALIVE || cl->state.state==CS_EDITING))
3239                 {
3240                     cl->position.setsize(0);
3241                     while(curmsg<p.length()) cl->position.add(p.buf[curmsg++]);
3242                 }
3243                 if(!m_demo && !m_coop) checkmove(cl);
3244                 break;
3245             }
3246 
3247             case SV_POSC:
3248             {
3249                 bitbuf<ucharbuf> q(p);
3250                 int cn = q.getbits(5);
3251                 if(cn!=sender)
3252                 {
3253                     disconnect_client(sender, DISC_CN);
3254     #ifndef STANDALONE
3255                     conoutf("ERROR: invalid client (msg %i)", type);
3256     #endif
3257                     return;
3258                 }
3259                 int usefactor = q.getbits(2) + 7;
3260                 int xt = q.getbits(usefactor + 4);
3261                 int yt = q.getbits(usefactor + 4);
3262                 cl->y = (q.getbits(9)*360)/512;
3263                 cl->p = ((q.getbits(8)-128)*90)/127;
3264                 if(!q.getbits(1)) q.getbits(6);
3265                 if(!q.getbits(1)) q.getbits(4 + 4 + 4);
3266                 cl->f = q.getbits(8);
3267                 int negz = q.getbits(1);
3268                 int zfull = q.getbits(1);
3269                 int s = q.rembits();
3270                 if(s < 3) s += 8;
3271                 if(zfull) s = 11;
3272                 int zt = q.getbits(s);
3273                 if(negz) zt = -zt;
3274                 int g1 = q.getbits(1); // scoping
3275                 int g2 = q.getbits(1); // shooting
3276                 cl->g = (g1<<4) | (g2<<5);
3277 
3278                 if(!cl->isonrightmap || p.remaining() || p.overread()) { p.flags = 0; break; }
3279                 if(((cl->f >> 6) & 1) != (cl->state.lifesequence & 1) || usefactor != (smapstats.hdr.sfactor < 7 ? 7 : smapstats.hdr.sfactor)) break;
3280                 cl->state.o[0] = xt / DMF;
3281                 cl->state.o[1] = yt / DMF;
3282                 cl->state.o[2] = zt / DMF;
3283                 if(cl->type==ST_TCPIP && (cl->state.state==CS_ALIVE || cl->state.state==CS_EDITING))
3284                 {
3285                     cl->position.setsize(0);
3286                     while(curmsg<p.length()) cl->position.add(p.buf[curmsg++]);
3287                 }
3288                 if(!m_demo && !m_coop) checkmove(cl);
3289                 break;
3290             }
3291 
3292             case SV_SENDMAP:
3293             {
3294                 getstring(text, p);
3295                 filtertext(text, text);
3296                 const char *sentmap = behindpath(text), *reject = NULL;
3297                 int mapsize = getint(p);
3298                 int cfgsize = getint(p);
3299                 int cfgsizegz = getint(p);
3300                 int revision = getint(p);
3301                 if(p.remaining() < mapsize + cfgsizegz || MAXMAPSENDSIZE < mapsize + cfgsizegz)
3302                 {
3303                     p.forceoverread();
3304                     break;
3305                 }
3306                 int mp = findmappath(sentmap);
3307                 if(readonlymap(mp))
3308                 {
3309                     reject = "map is ro";
3310                     defformatstring(msg)("\f3map upload rejected: map %s is readonly", sentmap);
3311                     sendservmsg(msg, sender);
3312                 }
3313                 else if( scl.incoming_limit && ( scl.incoming_limit << 20 ) < incoming_size + mapsize + cfgsizegz )
3314                 {
3315                     reject = "server incoming reached its limits";
3316                     sendservmsg("\f3server does not support more incomings: limit reached", sender);
3317                 }
3318                 else if(mp == MAP_NOTFOUND && strchr(scl.mapperm, 'C') && cl->role < CR_ADMIN)
3319                 {
3320                     reject = "no permission for initial upload";
3321                     sendservmsg("\f3initial map upload rejected: you need to be admin", sender);
3322                 }
3323                 else if(mp == MAP_TEMP && revision >= mapbuffer.revision && !strchr(scl.mapperm, 'u') && cl->role < CR_ADMIN) // default: only admins can update maps
3324                 {
3325                     reject = "no permission to update";
3326                     sendservmsg("\f3map update rejected: you need to be admin", sender);
3327                 }
3328                 else if(mp == MAP_TEMP && revision < mapbuffer.revision && !strchr(scl.mapperm, 'r') && cl->role < CR_ADMIN) // default: only admins can revert maps to older revisions
3329                 {
3330                     reject = "no permission to revert revision";
3331                     sendservmsg("\f3map revert to older revision rejected: you need to be admin to upload an older map", sender);
3332                 }
3333                 else
3334                 {
3335                     if(mapbuffer.sendmap(sentmap, mapsize, cfgsize, cfgsizegz, &p.buf[p.len]))
3336                     {
3337                         incoming_size += mapsize + cfgsizegz;
3338                         logline(ACLOG_INFO,"[%s] %s sent map %s, rev %d, %d + %d(%d) bytes written",
3339                                     clients[sender]->hostname, clients[sender]->name, sentmap, revision, mapsize, cfgsize, cfgsizegz);
3340                         defformatstring(msg)("%s (%d) up%sed map %s, rev %d%s", clients[sender]->name, sender, mp == MAP_NOTFOUND ? "load": "dat", sentmap, revision,
3341                             /*strcmp(sentmap, behindpath(smapname)) || smode == GMODE_COOPEDIT ? "" :*/ "\f3 (restart game to use new map version)");
3342                         sendservmsg(msg);
3343                     }
3344                     else
3345                     {
3346                         reject = "write failed (no 'incoming'?)";
3347                         sendservmsg("\f3map upload failed", sender);
3348                     }
3349                 }
3350                 if (reject)
3351                 {
3352                     logline(ACLOG_INFO,"[%s] %s sent map %s rev %d, rejected: %s",
3353                                 clients[sender]->hostname, clients[sender]->name, sentmap, revision, reject);
3354                 }
3355                 p.len += mapsize + cfgsizegz;
3356                 break;
3357             }
3358 
3359             case SV_RECVMAP:
3360             {
3361                 if(mapbuffer.available())
3362                 {
3363                     resetflag(cl->clientnum); // drop ctf flag
3364                     savedscore *sc = findscore(*cl, true); // save score
3365                     if(sc) sc->save(cl->state, cl->team);
3366                     mapbuffer.sendmap(cl, 2);
3367                     cl->mapchange(true);
3368                     sendwelcome(cl, 2); // resend state properly
3369                 }
3370                 else sendservmsg("no map to get", cl->clientnum);
3371                 break;
3372             }
3373 
3374             case SV_REMOVEMAP:
3375             {
3376                 getstring(text, p);
3377                 filtertext(text, text);
3378                 string filename;
3379                 const char *rmmap = behindpath(text), *reject = NULL;
3380                 int mp = findmappath(rmmap);
3381                 int reqrole = strchr(scl.mapperm, 'D') ? CR_ADMIN : (strchr(scl.mapperm, 'd') ? CR_DEFAULT : CR_ADMIN + 100);
3382                 if(cl->role < reqrole) reject = "no permission";
3383                 else if(readonlymap(mp)) reject = "map is readonly";
3384                 else if(mp == MAP_NOTFOUND) reject = "map not found";
3385                 else
3386                 {
3387                     formatstring(filename)(SERVERMAP_PATH_INCOMING "%s.cgz", rmmap);
3388                     remove(filename);
3389                     formatstring(filename)(SERVERMAP_PATH_INCOMING "%s.cfg", rmmap);
3390                     remove(filename);
3391                     defformatstring(msg)("map '%s' deleted", rmmap);
3392                     sendservmsg(msg, sender);
3393                     logline(ACLOG_INFO,"[%s] deleted map %s", clients[sender]->hostname, rmmap);
3394                 }
3395                 if (reject)
3396                 {
3397                     logline(ACLOG_INFO,"[%s] deleting map %s failed: %s", clients[sender]->hostname, rmmap, reject);
3398                     defformatstring(msg)("\f3can't delete map '%s', %s", rmmap, reject);
3399                     sendservmsg(msg, sender);
3400                 }
3401                 break;
3402             }
3403 
3404             case SV_FLAGACTION:
3405             {
3406                 int action = getint(p);
3407                 int flag = getint(p);
3408                 if(!m_flags || flag < 0 || flag > 1 || action < 0 || action > FA_NUM) break;
3409                 flagaction(flag, action, sender);
3410                 break;
3411             }
3412 
3413             case SV_SETADMIN:
3414             {
3415                 bool claim = getint(p) != 0;
3416                 if(claim)
3417                 {
3418                     getstring(text, p);
3419                     changeclientrole(sender, CR_ADMIN, text);
3420                 } else changeclientrole(sender, CR_DEFAULT);
3421                 break;
3422             }
3423 
3424             case SV_CALLVOTE:
3425             {
3426                 voteinfo *vi = new voteinfo;
3427                 vi->boot = 0;
3428                 vi->type = getint(p);
3429                 switch(vi->type)
3430                 {
3431                     case SA_MAP:
3432                     {
3433                         getstring(text, p);
3434                         filtertext(text, text);
3435                         int mode = getint(p), time = getint(p);
3436                         if(time <= 0) time = -1;
3437                         time = min(time, 60);
3438                         vi->gonext = text[0]=='+' && text[1]=='1';
3439                         if (vi->gonext)
3440                         {
3441                             int ccs = mode ? maprot.next(false,false) : maprot.get_next();
3442                             configset *c = maprot.get(ccs);
3443                             if(c)
3444                             {
3445                                 strcpy(vi->text,c->mapname);
3446                                 mode = vi->num1 = c->mode;
3447                             }
3448                             else fatal("unable to get next map in maprot");
3449                         }
3450                         else
3451                         {
3452                             strncpy(vi->text,text,MAXTRANS-1);
3453                             vi->num1 = mode;
3454                             vi->num2 = time;
3455                         }
3456                         int qmode = (mode >= GMODE_NUM ? mode - GMODE_NUM : mode);
3457                         if(mode==GMODE_DEMO) vi->action = new demoplayaction(newstring(text));
3458                         else
3459                         {
3460                             char *vmap = newstring(vi->text ? behindpath(vi->text) : "");
3461                             vi->action = new mapaction(vmap, qmode, time, sender, qmode!=mode);
3462                         }
3463                         break;
3464                     }
3465                     case SA_KICK:
3466                     {
3467                         vi->num1 = cn2boot = getint(p);
3468                         getstring(text, p);
3469                         strncpy(vi->text,text,128);
3470                         filtertext(text, text);
3471                         trimtrailingwhitespace(text);
3472                         vi->action = new kickaction(cn2boot, newstring(text, 128));
3473                         vi->boot = 1;
3474                         break;
3475                     }
3476                     case SA_BAN:
3477                     {
3478                         vi->num1 = cn2boot = getint(p);
3479                         getstring(text, p);
3480                         strncpy(vi->text,text,128);
3481                         filtertext(text, text);
3482                         trimtrailingwhitespace(text);
3483                         vi->action = new banaction(cn2boot, newstring(text, 128));
3484                         vi->boot = 2;
3485                         break;
3486                     }
3487                     case SA_REMBANS:
3488                         vi->action = new removebansaction();
3489                         break;
3490                     case SA_MASTERMODE:
3491                         vi->action = new mastermodeaction(vi->num1 = getint(p));
3492                         break;
3493                     case SA_AUTOTEAM:
3494                         vi->action = new autoteamaction((vi->num1 = getint(p)) > 0);
3495                         break;
3496                     case SA_SHUFFLETEAMS:
3497                         vi->action = new shuffleteamaction();
3498                         break;
3499                     case SA_FORCETEAM:
3500                         vi->num1 = getint(p);
3501                         vi->num2 = getint(p);
3502                         vi->action = new forceteamaction(vi->num1, sender, vi->num2);
3503                         break;
3504                     case SA_GIVEADMIN:
3505                         vi->action = new giveadminaction(vi->num1 = getint(p));
3506                         break;
3507                     case SA_RECORDDEMO:
3508                         vi->action = new recorddemoaction((vi->num1 = getint(p))!=0);
3509                         break;
3510                     case SA_STOPDEMO:
3511                         // compatibility
3512                         break;
3513                     case SA_CLEARDEMOS:
3514                         vi->action = new cleardemosaction(vi->num1 = getint(p));
3515                         break;
3516                     case SA_SERVERDESC:
3517                         getstring(text, p);
3518                         strncpy(vi->text,text,MAXTRANS-1);
3519                         filtertext(text, text);
3520                         vi->action = new serverdescaction(newstring(text), sender);
3521                         break;
3522                 }
3523                 vi->owner = sender;
3524                 vi->callmillis = servmillis;
3525                 MSG_PACKET(msg);
3526                 if(!scallvote(vi, msg)) delete vi;
3527                 break;
3528             }
3529 
3530             case SV_VOTE:
3531             {
3532                 int n = getint(p);
3533                 MSG_PACKET(msg);
3534                 ++msg->referenceCount; // need to increase reference count in case a vote disconnects a player after packet is queued to prevent double-freeing by packetbuf
3535                 svote(sender, n, msg);
3536                 --msg->referenceCount;
3537                 break;
3538             }
3539 
3540             case SV_LISTDEMOS:
3541                 listdemos(sender);
3542                 break;
3543 
3544             case SV_GETDEMO:
3545                 senddemo(sender, getint(p));
3546                 break;
3547 
3548             case SV_EXTENSION:
3549             {
3550                 // AC server extensions
3551                 //
3552                 // rules:
3553                 // 1. extensions MUST NOT modify gameplay or the behavior of the game in any way
3554                 // 2. extensions may ONLY be used to extend or automate server administration tasks
3555                 // 3. extensions may ONLY operate on the server and must not send any additional data to the connected clients
3556                 // 4. extensions not adhering to these rules may cause the hosting server being banned from the masterserver
3557                 //
3558                 // also note that there is no guarantee that custom extensions will work in future AC versions
3559 
3560 
3561                 getstring(text, p, 64);
3562                 char *ext = text;   // extension specifier in the form of OWNER::EXTENSION, see sample below
3563                 int n = getint(p);  // length of data after the specifier
3564                 if(n < 0 || n > 50) return;
3565 
3566                 // sample
3567                 if(!strcmp(ext, "driAn::writelog"))
3568                 {
3569                     // owner:       driAn - root@sprintf.org
3570                     // extension:   writelog - WriteLog v1.0
3571                     // description: writes a custom string to the server log
3572                     // access:      requires admin privileges
3573                     // usage:       /serverextension driAn::writelog "your log message here.."
3574                     // note:        There is a 49 character limit. The server will ignore messages with 50+ characters.
3575 
3576                     getstring(text, p, n);
3577                     if(valid_client(sender) && clients[sender]->role==CR_ADMIN)
3578                     {
3579                         logline(ACLOG_INFO, "[%s] %s writes to log: %s", cl->hostname, cl->name, text);
3580                         sendservmsg("your message has been logged", sender);
3581                     }
3582                 }
3583                 else if(!strcmp(ext, "set::teamsize"))
3584                 {
3585                     // intermediate solution to set the teamsize (will be voteable)
3586 
3587                     getstring(text, p, n);
3588                     if(valid_client(sender) && clients[sender]->role==CR_ADMIN && mastermode == MM_MATCH)
3589                     {
3590                         changematchteamsize(atoi(text));
3591                         defformatstring(msg)("match team size set to %d", matchteamsize);
3592                         sendservmsg(msg, -1);
3593                     }
3594                 }
3595                 // else if()
3596 
3597                 // add other extensions here
3598 
3599                 else for(; n > 0; n--) getint(p); // ignore unknown extensions
3600 
3601                 break;
3602             }
3603 
3604             case -1:
3605                 disconnect_client(sender, DISC_TAGT);
3606                 return;
3607 
3608             case -2:
3609                 disconnect_client(sender, DISC_OVERFLOW);
3610                 return;
3611 
3612             default:
3613             {
3614                 int size = msgsizelookup(type);
3615                 if(size<=0) { if(sender>=0) disconnect_client(sender, DISC_TAGT); return; }
3616                 loopi(size-1) getint(p);
3617                 QUEUE_MSG;
3618                 break;
3619             }
3620         }
3621     }
3622 
3623     if(p.overread() && sender>=0) disconnect_client(sender, DISC_EOP);
3624 
3625     #ifdef _DEBUG
3626     protocoldebug(false);
3627     #endif
3628 }
3629 
localclienttoserver(int chan,ENetPacket * packet)3630 void localclienttoserver(int chan, ENetPacket *packet)
3631 {
3632     process(packet, 0, chan);
3633 }
3634 
addclient()3635 client &addclient()
3636 {
3637     client *c = NULL;
3638     loopv(clients) if(clients[i]->type==ST_EMPTY) { c = clients[i]; break; }
3639     if(!c)
3640     {
3641         c = new client;
3642         c->clientnum = clients.length();
3643         clients.add(c);
3644     }
3645     c->reset();
3646     return *c;
3647 }
3648 
checkintermission()3649 void checkintermission()
3650 {
3651     if(minremain>0)
3652     {
3653         minremain = (gamemillis>=gamelimit || forceintermission) ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000;
3654         sendf(-1, 1, "ri3", SV_TIMEUP, (gamemillis>=gamelimit || forceintermission) ? gamelimit : gamemillis, gamelimit);
3655     }
3656     if(!interm && minremain<=0) interm = gamemillis+10000;
3657     forceintermission = false;
3658 }
3659 
resetserverifempty()3660 void resetserverifempty()
3661 {
3662     loopv(clients) if(clients[i]->type!=ST_EMPTY) return;
3663     resetserver("", 0, 10);
3664     matchteamsize = 0;
3665     autoteam = true;
3666     changemastermode(MM_OPEN);
3667     nextmapname[0] = '\0';
3668 }
3669 
sendworldstate()3670 void sendworldstate()
3671 {
3672     static enet_uint32 lastsend = 0;
3673     if(clients.empty()) return;
3674     enet_uint32 curtime = enet_time_get()-lastsend;
3675     if(curtime<40) return;
3676     bool flush = buildworldstate();
3677     lastsend += curtime - (curtime%40);
3678     if(flush) enet_host_flush(serverhost);
3679     if(demorecord) recordpackets = true; // enable after 'old' worldstate is sent
3680 }
3681 
rereadcfgs(void)3682 void rereadcfgs(void)
3683 {
3684     maprot.read();
3685     ipblacklist.read();
3686     nickblacklist.read();
3687     forbiddenlist.read();
3688     passwords.read();
3689     killmsgs.read();
3690 }
3691 
loggamestatus(const char * reason)3692 void loggamestatus(const char *reason)
3693 {
3694     int fragscore[2] = {0, 0}, flagscore[2] = {0, 0}, pnum[2] = {0, 0};
3695     string text;
3696     formatstring(text)("%d minutes remaining", minremain);
3697     logline(ACLOG_INFO, "");
3698     logline(ACLOG_INFO, "Game status: %s on %s, %s, %s, %d clients%c %s",
3699                       modestr(gamemode), smapname, reason ? reason : text, mmfullname(mastermode), totalclients, custom_servdesc ? ',' : '\0', servdesc_current);
3700     if(!scl.loggamestatus) return;
3701     logline(ACLOG_INFO, "cn name             %s%s score frag death %sping role    host", m_teammode ? "team " : "", m_flags ? "flag " : "", m_teammode ? "tk " : "");
3702     loopv(clients)
3703     {
3704         client &c = *clients[i];
3705         if(c.type == ST_EMPTY || !c.name[0]) continue;
3706         formatstring(text)("%2d %-16s ", c.clientnum, c.name);                 // cn name
3707         if(m_teammode) concatformatstring(text, "%-4s ", team_string(c.team, true)); // teamname (abbreviated)
3708         if(m_flags) concatformatstring(text, "%4d ", c.state.flagscore);             // flag
3709         concatformatstring(text, "%6d ", c.state.points);                            // score
3710         concatformatstring(text, "%4d %5d", c.state.frags, c.state.deaths);          // frag death
3711         if(m_teammode) concatformatstring(text, " %2d", c.state.teamkills);          // tk
3712         logline(ACLOG_INFO, "%s%5d %s  %s", text, c.ping, c.role == CR_ADMIN ? "admin " : "normal", c.hostname);
3713         if(c.team != TEAM_SPECT)
3714         {
3715             int t = team_base(c.team);
3716             flagscore[t] += c.state.flagscore;
3717             fragscore[t] += c.state.frags;
3718             pnum[t] += 1;
3719         }
3720     }
3721     if(mastermode == MM_MATCH)
3722     {
3723         loopv(savedscores)
3724         {
3725             savedscore &sc = savedscores[i];
3726             if(sc.valid)
3727             {
3728                 formatstring(text)(m_teammode ? "%-4s " : "", team_string(sc.team, true));
3729                 if(m_flags) concatformatstring(text, "%4d ", sc.flagscore);
3730                 logline(ACLOG_INFO, "   %-16s %s%4d %5d%s    - disconnected", sc.name, text, sc.frags, sc.deaths, m_teammode ? "  -" : "");
3731                 if(sc.team != TEAM_SPECT)
3732                 {
3733                     int t = team_base(sc.team);
3734                     flagscore[t] += sc.flagscore;
3735                     fragscore[t] += sc.frags;
3736                     pnum[t] += 1;
3737                 }
3738             }
3739         }
3740     }
3741     if(m_teammode)
3742     {
3743         loopi(2) logline(ACLOG_INFO, "Team %4s:%3d players,%5d frags%c%5d flags", team_string(i), pnum[i], fragscore[i], m_flags ? ',' : '\0', flagscore[i]);
3744     }
3745     logline(ACLOG_INFO, "");
3746 }
3747 
3748 static unsigned char chokelog[MAXCLIENTS + 1] = { 0 };
3749 
linequalitystats(int elapsed)3750 void linequalitystats(int elapsed)
3751 {
3752     static unsigned int chokes[MAXCLIENTS + 1] = { 0 }, spent[MAXCLIENTS + 1] = { 0 }, chokes_raw[MAXCLIENTS + 1] = { 0 }, spent_raw[MAXCLIENTS + 1] = { 0 };
3753     if(elapsed)
3754     { // collect data
3755         int c1 = 0, c2 = 0, r1 = 0, numc = 0;
3756         loopv(clients)
3757         {
3758             client &c = *clients[i];
3759             if(c.type != ST_TCPIP) continue;
3760             numc++;
3761             enet_uint32 &rtt = c.peer->lastRoundTripTime, &throttle = c.peer->packetThrottle;
3762             if(rtt < c.bottomRTT + c.bottomRTT / 3)
3763             {
3764                 if(servmillis - c.connectmillis < 5000)
3765                     c.bottomRTT = rtt;
3766                 else
3767                     c.bottomRTT = (c.bottomRTT * 15 + rtt) / 16; // simple IIR
3768             }
3769             if(throttle < 22) c1++;
3770             if(throttle < 11) c2++;
3771             if(rtt > c.bottomRTT * 2 && rtt - c.bottomRTT > 300) r1++;
3772         }
3773         spent_raw[numc] += elapsed;
3774         int t = numc < 7 ? numc : (numc + 1) / 2 + 3;
3775         chokes_raw[numc] +=  ((c1 >= t ? c1 + c2 : 0) + (r1 >= t ? r1 : 0)) * elapsed;
3776     }
3777     else
3778     { // calculate compressed statistics
3779         defformatstring(msg)("Uplink quality [ ");
3780         int ncs = 0;
3781         loopj(scl.maxclients)
3782         {
3783             int i = j + 1;
3784             int nc = chokes_raw[i] / 1000 / i;
3785             chokes[i] += nc;
3786             ncs += nc;
3787             spent[i] += spent_raw[i] / 1000;
3788             chokes_raw[i] = spent_raw[i] = 0;
3789             int s = 0, c = 0;
3790             if(spent[i])
3791             {
3792                 frexp((double)spent[i] / 30, &s);
3793                 if(s < 0) s = 0;
3794                 if(s > 15) s = 15;
3795                 if(chokes[i])
3796                 {
3797                     frexp(((double)chokes[i]) / spent[i], &c);
3798                     c = 15 + c;
3799                     if(c < 0) c = 0;
3800                     if(c > 15) c = 15;
3801                 }
3802             }
3803             chokelog[i] = (s << 4) + c;
3804             concatformatstring(msg, "%02X ", chokelog[i]);
3805         }
3806         logline(ACLOG_DEBUG, "%s] +%d", msg, ncs);
3807     }
3808 }
3809 
serverslice(uint timeout)3810 void serverslice(uint timeout)   // main server update, called from cube main loop in sp, or dedicated server loop
3811 {
3812     static int msend = 0, mrec = 0, csend = 0, crec = 0, mnum = 0, cnum = 0;
3813 #ifdef STANDALONE
3814     int nextmillis = (int)enet_time_get();
3815     if(svcctrl) svcctrl->keepalive();
3816 #else
3817     int nextmillis = isdedicated ? (int)enet_time_get() : lastmillis;
3818 #endif
3819     int diff = nextmillis - servmillis;
3820     gamemillis += diff;
3821     servmillis = nextmillis;
3822     servertime = ((diff + 3 * servertime)>>2);
3823     if (servertime > 40) serverlagged = servmillis;
3824 
3825 #ifndef STANDALONE
3826     if(m_demo)
3827     {
3828         readdemo();
3829         extern void silenttimeupdate(int milliscur, int millismax);
3830         silenttimeupdate(gamemillis, gametimemaximum);
3831     }
3832 #endif
3833 
3834     if(minremain>0)
3835     {
3836         processevents();
3837         checkitemspawns(diff);
3838         bool ktfflagingame = false;
3839         if(m_flags) loopi(2)
3840         {
3841             sflaginfo &f = sflaginfos[i];
3842             if(f.state == CTFF_DROPPED && gamemillis-f.lastupdate > (m_ctf ? 30000 : 10000)) flagaction(i, FA_RESET, -1);
3843             if(m_htf && f.state == CTFF_INBASE && gamemillis-f.lastupdate > (smapstats.hasflags ? 10000 : 1000))
3844             {
3845                 htf_forceflag(i);
3846             }
3847             if(m_ktf && f.state == CTFF_STOLEN && gamemillis-f.lastupdate > 15000)
3848             {
3849                 flagaction(i, FA_SCORE, -1);
3850             }
3851             if(f.state == CTFF_INBASE || f.state == CTFF_STOLEN) ktfflagingame = true;
3852         }
3853         if(m_ktf && !ktfflagingame) flagaction(rnd(2), FA_RESET, -1); // ktf flag watchdog
3854         if(m_arena) arenacheck();
3855 //        if(m_lms) lmscheck();
3856         sendextras();
3857         if ( scl.afk_limit && mastermode == MM_OPEN && next_afk_check < servmillis && gamemillis > 20 * 1000 ) check_afk();
3858     }
3859 
3860     if(curvote)
3861     {
3862         if(!curvote->isalive()) curvote->evaluate(true);
3863         if(curvote->result!=VOTE_NEUTRAL) DELETEP(curvote);
3864     }
3865 
3866     int nonlocalclients = numnonlocalclients();
3867 
3868     if(forceintermission || ((smode>1 || (gamemode==0 && nonlocalclients)) && gamemillis-diff>0 && gamemillis/60000!=(gamemillis-diff)/60000))
3869         checkintermission();
3870     if(m_demo && !demoplayback) maprot.restart();
3871     else if(interm && ( (scl.demo_interm && sending_demo) ? gamemillis>(interm<<1) : gamemillis>interm ) )
3872     {
3873         sending_demo = false;
3874         loggamestatus("game finished");
3875         if(demorecord) enddemorecord();
3876         interm = nextsendscore = 0;
3877 
3878         //start next game
3879         if(nextmapname[0]) startgame(nextmapname, nextgamemode);
3880         else maprot.next();
3881         nextmapname[0] = '\0';
3882         map_queued = false;
3883     }
3884 
3885     resetserverifempty();
3886 
3887     if(!isdedicated) return;     // below is network only
3888 
3889     serverms(smode, numclients(), minremain, smapname, servmillis, serverhost->address, &mnum, &msend, &mrec, &cnum, &csend, &crec, SERVER_PROTOCOL_VERSION);
3890 
3891     if(autoteam && m_teammode && !m_arena && !interm && servmillis - lastfillup > 5000 && refillteams()) lastfillup = servmillis;
3892 
3893     static unsigned int lastThrottleEpoch = 0;
3894     if(serverhost->bandwidthThrottleEpoch != lastThrottleEpoch)
3895     {
3896         if(lastThrottleEpoch) linequalitystats(serverhost->bandwidthThrottleEpoch - lastThrottleEpoch);
3897         lastThrottleEpoch = serverhost->bandwidthThrottleEpoch;
3898     }
3899 
3900     if(servmillis>nextstatus)   // display bandwidth stats, useful for server ops
3901     {
3902         nextstatus = servmillis + 60 * 1000;
3903         rereadcfgs();
3904         if(nonlocalclients || serverhost->totalSentData || serverhost->totalReceivedData)
3905         {
3906             if(nonlocalclients) loggamestatus(NULL);
3907             logline(ACLOG_INFO, "Status at %s: %d remote clients, %.1f send, %.1f rec (K/sec);"
3908                                          " Ping: #%d|%d|%d; CSL: #%d|%d|%d (bytes)",
3909                                           timestring(true, "%d-%m-%Y %H:%M:%S"), nonlocalclients, serverhost->totalSentData/60.0f/1024, serverhost->totalReceivedData/60.0f/1024,
3910                                           mnum, msend, mrec, cnum, csend, crec);
3911             mnum = msend = mrec = cnum = csend = crec = 0;
3912             linequalitystats(0);
3913         }
3914         serverhost->totalSentData = serverhost->totalReceivedData = 0;
3915     }
3916 
3917     ENetEvent event;
3918     bool serviced = false;
3919     while(!serviced)
3920     {
3921         if(enet_host_check_events(serverhost, &event) <= 0)
3922         {
3923             if(enet_host_service(serverhost, &event, timeout) <= 0) break;
3924             serviced = true;
3925         }
3926         switch(event.type)
3927         {
3928             case ENET_EVENT_TYPE_CONNECT:
3929             {
3930                 client &c = addclient();
3931                 c.type = ST_TCPIP;
3932                 c.peer = event.peer;
3933                 c.peer->data = (void *)(size_t)c.clientnum;
3934                 c.connectmillis = servmillis;
3935                 c.state.state = CS_SPECTATE;
3936                 c.salt = rnd(0x1000000)*((servmillis%1000)+1);
3937                 char hn[1024];
3938                 copystring(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown");
3939                 logline(ACLOG_INFO,"[%s] client connected", c.hostname);
3940                 sendservinfo(c);
3941                 totalclients++;
3942                 break;
3943             }
3944 
3945             case ENET_EVENT_TYPE_RECEIVE:
3946             {
3947                 int cn = (int)(size_t)event.peer->data;
3948                 if(valid_client(cn)) process(event.packet, cn, event.channelID);
3949                 if(event.packet->referenceCount==0) enet_packet_destroy(event.packet);
3950                 break;
3951             }
3952 
3953             case ENET_EVENT_TYPE_DISCONNECT:
3954             {
3955                 int cn = (int)(size_t)event.peer->data;
3956                 if(!valid_client(cn)) break;
3957                 disconnect_client(cn);
3958                 break;
3959             }
3960 
3961             default:
3962                 break;
3963         }
3964     }
3965     sendworldstate();
3966 }
3967 
cleanupserver()3968 void cleanupserver()
3969 {
3970     if(serverhost) { enet_host_destroy(serverhost); serverhost = NULL; }
3971     if(svcctrl)
3972     {
3973         svcctrl->stop();
3974         DELETEP(svcctrl);
3975     }
3976     exitlogging();
3977 }
3978 
getpongflags(enet_uint32 ip)3979 int getpongflags(enet_uint32 ip)
3980 {
3981     int flags = mastermode << PONGFLAG_MASTERMODE;
3982     flags |= scl.serverpassword[0] ? 1 << PONGFLAG_PASSWORD : 0;
3983     loopv(bans) if(bans[i].address.host == ip) { flags |= 1 << PONGFLAG_BANNED; break; }
3984     flags |= ipblacklist.check(ip) ? 1 << PONGFLAG_BLACKLIST : 0;
3985     return flags;
3986 }
3987 
extping_namelist(ucharbuf & p)3988 void extping_namelist(ucharbuf &p)
3989 {
3990     loopv(clients)
3991     {
3992         if(clients[i]->type == ST_TCPIP && clients[i]->isauthed) sendstring(clients[i]->name, p);
3993     }
3994     sendstring("", p);
3995 }
3996 
extping_serverinfo(ucharbuf & pi,ucharbuf & po)3997 void extping_serverinfo(ucharbuf &pi, ucharbuf &po)
3998 {
3999     char lang[3];
4000     lang[0] = tolower(getint(pi)); lang[1] = tolower(getint(pi)); lang[2] = '\0';
4001     const char *reslang = lang, *buf = infofiles.getinfo(lang); // try client language
4002     if(!buf) buf = infofiles.getinfo(reslang = "en");     // try english
4003     sendstring(buf ? reslang : "", po);
4004     if(buf)
4005     {
4006         for(const char *c = buf; *c && po.remaining() > MAXINFOLINELEN + 10; c += strlen(c) + 1) sendstring(c, po);
4007         sendstring("", po);
4008     }
4009 }
4010 
extping_maprot(ucharbuf & po)4011 void extping_maprot(ucharbuf &po)
4012 {
4013     putint(po, CONFIG_MAXPAR);
4014     string text;
4015     bool abort = false;
4016     loopv(maprot.configsets)
4017     {
4018         if(po.remaining() < 100) abort = true;
4019         configset &c = maprot.configsets[i];
4020         filtertext(text, c.mapname, 0);
4021         text[30] = '\0';
4022         sendstring(abort ? "-- list truncated --" : text, po);
4023         loopi(CONFIG_MAXPAR) putint(po, c.par[i]);
4024         if(abort) break;
4025     }
4026     sendstring("", po);
4027 }
4028 
extping_uplinkstats(ucharbuf & po)4029 void extping_uplinkstats(ucharbuf &po)
4030 {
4031     if(scl.maxclients > 3)
4032         po.put(chokelog + 4, scl.maxclients - 3); // send logs for 4..n used slots
4033 }
4034 
extinfo_cnbuf(ucharbuf & p,int cn)4035 void extinfo_cnbuf(ucharbuf &p, int cn)
4036 {
4037     if(cn == -1) // add all available player ids
4038     {
4039         loopv(clients) if(clients[i]->type != ST_EMPTY)
4040             putint(p,clients[i]->clientnum);
4041     }
4042     else if(valid_client(cn)) // add single player only
4043     {
4044         putint(p,clients[cn]->clientnum);
4045     }
4046 }
4047 
extinfo_statsbuf(ucharbuf & p,int pid,int bpos,ENetSocket & pongsock,ENetAddress & addr,ENetBuffer & buf,int len,int * csend)4048 void extinfo_statsbuf(ucharbuf &p, int pid, int bpos, ENetSocket &pongsock, ENetAddress &addr, ENetBuffer &buf, int len, int *csend)
4049 {
4050     loopv(clients)
4051     {
4052         if(clients[i]->type != ST_TCPIP) continue;
4053         if(pid>-1 && clients[i]->clientnum!=pid) continue;
4054 
4055         bool ismatch = mastermode == MM_MATCH;
4056         putint(p,EXT_PLAYERSTATS_RESP_STATS);  // send player stats following
4057         putint(p,clients[i]->clientnum);  //add player id
4058         putint(p,clients[i]->ping);             //Ping
4059         sendstring(clients[i]->name,p);         //Name
4060         sendstring(team_string(clients[i]->team),p); //Team
4061         // "team_string(clients[i]->team)" sometimes return NULL according to RK, causing the server to crash. WTF ?
4062         putint(p,clients[i]->state.frags);      //Frags
4063         putint(p,clients[i]->state.flagscore);  //Flagscore
4064         putint(p,clients[i]->state.deaths);     //Death
4065         putint(p,clients[i]->state.teamkills);  //Teamkills
4066         putint(p,ismatch ? 0 : clients[i]->state.damage*100/max(clients[i]->state.shotdamage,1)); //Accuracy
4067         putint(p,ismatch ? 0 : clients[i]->state.health);     //Health
4068         putint(p,ismatch ? 0 : clients[i]->state.armour);     //Armour
4069         putint(p,ismatch ? 0 : clients[i]->state.gunselect);  //Gun selected
4070         putint(p,clients[i]->role);             //Role
4071         putint(p,clients[i]->state.state);      //State (Alive,Dead,Spawning,Lagged,Editing)
4072         uint ip = clients[i]->peer->address.host; // only 3 byte of the ip address (privacy protected)
4073         p.put((uchar*)&ip,3);
4074 
4075         buf.dataLength = len + p.length();
4076         enet_socket_send(pongsock, &addr, &buf, 1);
4077         *csend += (int)buf.dataLength;
4078 
4079         if(pid>-1) break;
4080         p.len=bpos;
4081     }
4082 }
4083 
extinfo_teamscorebuf(ucharbuf & p)4084 void extinfo_teamscorebuf(ucharbuf &p)
4085 {
4086     putint(p, m_teammode ? EXT_ERROR_NONE : EXT_ERROR);
4087     putint(p, gamemode);
4088     putint(p, minremain); // possible TODO: use gamemillis, gamelimit here too?
4089     if(!m_teammode) return;
4090 
4091     int teamsizes[TEAM_NUM] = { 0 }, fragscores[TEAM_NUM] = { 0 }, flagscores[TEAM_NUM] = { 0 };
4092     loopv(clients) if(clients[i]->type!=ST_EMPTY && team_isvalid(clients[i]->team))
4093     {
4094         teamsizes[clients[i]->team] += 1;
4095         fragscores[clients[i]->team] += clients[i]->state.frags;
4096         flagscores[clients[i]->team] += clients[i]->state.flagscore;
4097     }
4098 
4099     loopi(TEAM_NUM) if(teamsizes[i])
4100     {
4101         sendstring(team_string(i), p); // team name
4102         putint(p, fragscores[i]); // add fragscore per team
4103         putint(p, m_flags ? flagscores[i] : -1); // add flagscore per team
4104         putint(p, -1); // ?
4105     }
4106 }
4107 
4108 
4109 #ifndef STANDALONE
localdisconnect()4110 void localdisconnect()
4111 {
4112     loopv(clients) if(clients[i]->type==ST_LOCAL) clients[i]->zap();
4113 }
4114 
localconnect()4115 void localconnect()
4116 {
4117     modprotocol = false;
4118     servstate.reset();
4119     client &c = addclient();
4120     c.type = ST_LOCAL;
4121     c.role = CR_ADMIN;
4122     copystring(c.hostname, "local");
4123     sendservinfo(c);
4124 }
4125 #endif
4126 
processmasterinput(const char * cmd,int cmdlen,const char * args)4127 void processmasterinput(const char *cmd, int cmdlen, const char *args)
4128 {
4129 // AUTH WiP
4130     uint id;
4131     string val;
4132     if(sscanf(cmd, "failauth %u", &id) == 1) authfailed(id);
4133     else if(sscanf(cmd, "succauth %u", &id) == 1) authsucceeded(id);
4134     else if(sscanf(cmd, "chalauth %u %s", &id, val) == 2) authchallenged(id, val);
4135     else if(!strncmp(cmd, "cleargbans", cmdlen)) cleargbans();
4136     else if(sscanf(cmd, "addgban %s", val) == 1) addgban(val);
4137 }
4138 
4139 string server_name = "unarmed server";
4140 
quitproc(int param)4141 void quitproc(int param)
4142 {
4143     // this triggers any "atexit"-calls:
4144     exit(param == 2 ? EXIT_SUCCESS : EXIT_FAILURE); // 3 is the only reply on Win32 apparently, SIGINT == 2 == Ctrl-C
4145 }
4146 
initserver(bool dedicated,int argc,char ** argv)4147 void initserver(bool dedicated, int argc, char **argv)
4148 {
4149     const char *service = NULL;
4150 
4151     for(int i = 1; i<argc; i++)
4152     {
4153         if(!scl.checkarg(argv[i]))
4154         {
4155             char *a = &argv[i][2];
4156             if(!scl.checkarg(argv[i]) && argv[i][0]=='-') switch(argv[i][1])
4157             {
4158                 case '-': break;
4159                 case 'S': service = a; break;
4160                 default: break; /*printf("WARNING: unknown commandline option\n");*/ // less warnings - 2011feb05:ft: who disabled this - I think this should be on - more warnings == more clarity
4161             }
4162             else if (strncmp(argv[i], "assaultcube://", 13)) printf("WARNING: unknown commandline argument\n");
4163         }
4164     }
4165 
4166     if(service && !svcctrl)
4167     {
4168         #ifdef WIN32
4169         svcctrl = new winservice(service);
4170         #endif
4171         if(svcctrl)
4172         {
4173             svcctrl->argc = argc; svcctrl->argv = argv;
4174             svcctrl->start();
4175         }
4176     }
4177 
4178     if ( strlen(scl.servdesc_full) ) global_name = scl.servdesc_full;
4179     else global_name = server_name;
4180 
4181     smapname[0] = '\0';
4182 
4183     string identity;
4184     if(scl.logident[0]) filtertext(identity, scl.logident, 0);
4185     else formatstring(identity)("%s#%d", scl.ip[0] ? scl.ip : "local", scl.serverport);
4186     int conthres = scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO);
4187     if(dedicated && !initlogging(identity, scl.syslogfacility, conthres, scl.filethres, scl.syslogthres, scl.logtimestamp))
4188         printf("WARNING: logging not started!\n");
4189     logline(ACLOG_INFO, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, SERVER_PROTOCOL_VERSION, EXT_VERSION);
4190 
4191     copystring(servdesc_current, scl.servdesc_full);
4192     servermsinit(scl.master ? scl.master : AC_MASTER_URI, scl.ip, CUBE_SERVINFO_PORT(scl.serverport), dedicated);
4193 
4194     if((isdedicated = dedicated))
4195     {
4196         ENetAddress address = { ENET_HOST_ANY, (enet_uint16)scl.serverport };
4197         if(scl.ip[0] && enet_address_set_host(&address, scl.ip)<0) logline(ACLOG_WARNING, "server ip not resolved!");
4198         serverhost = enet_host_create(&address, scl.maxclients+1, 3, 0, scl.uprate);
4199         if(!serverhost) fatal("could not create server host");
4200         loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1;
4201 
4202         maprot.init(scl.maprot);
4203         maprot.next(false, true); // ensure minimum maprot length of '1'
4204         passwords.init(scl.pwdfile, scl.adminpasswd);
4205         ipblacklist.init(scl.blfile);
4206         nickblacklist.init(scl.nbfile);
4207         forbiddenlist.init(scl.forbidden);
4208         killmsgs.init(scl.killmessages);
4209         infofiles.init(scl.infopath, scl.motdpath);
4210         infofiles.getinfo("en"); // cache 'en' serverinfo
4211         logline(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", scl.maxdemos);
4212         if(scl.demopath[0]) logline(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", scl.demopath);
4213         if(scl.voteperm[0]) logline(ACLOG_VERBOSE,"vote permission string: \"%s\"", scl.voteperm);
4214         if(scl.mapperm[0]) logline(ACLOG_VERBOSE,"map permission string: \"%s\"", scl.mapperm);
4215         logline(ACLOG_VERBOSE,"server description: \"%s\"", scl.servdesc_full);
4216         if(scl.servdesc_pre[0] || scl.servdesc_suf[0]) logline(ACLOG_VERBOSE,"custom server description: \"%sCUSTOMPART%s\"", scl.servdesc_pre, scl.servdesc_suf);
4217         logline(ACLOG_VERBOSE,"maxclients: %d, kick threshold: %d, ban threshold: %d", scl.maxclients, scl.kickthreshold, scl.banthreshold);
4218         if(scl.master) logline(ACLOG_VERBOSE,"master server URL: \"%s\"", scl.master);
4219         if(scl.serverpassword[0]) logline(ACLOG_VERBOSE,"server password: \"%s\"", hiddenpwd(scl.serverpassword));
4220 #ifdef ACAC
4221         logline(ACLOG_INFO, "anticheat: enabled");
4222 #else
4223         logline(ACLOG_INFO, "anticheat: disabled");
4224 #endif
4225     }
4226 
4227     resetserverifempty();
4228 
4229     if(isdedicated)       // do not return, this becomes main loop
4230     {
4231         #ifdef WIN32
4232         SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
4233         #endif
4234         // kill -2 / Ctrl-C - see http://msdn.microsoft.com/en-us/library/xdkz3x12%28v=VS.100%29.aspx (or VS-2008?) for caveat (seems not to pertain to AC - 2011feb05:ft)
4235         if (signal(SIGINT, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGINT!");
4236         // kill -15 / probably process-manager on Win32 *shrug*
4237         if (signal(SIGTERM, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGTERM!");
4238         #ifndef WIN32
4239         // kill -1
4240         if (signal(SIGHUP, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGHUP!");
4241         // kill -9 is uncatchable - http://en.wikipedia.org/wiki/SIGKILL
4242         //if (signal(SIGKILL, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGKILL!");
4243         #endif
4244         logline(ACLOG_INFO, "dedicated server started, waiting for clients...");
4245         logline(ACLOG_INFO, "Ctrl-C to exit"); // this will now actually call the atexit-hooks below - thanks to SIGINT hooked above - noticed and signal-code-docs found by SKB:2011feb05:ft:
4246         atexit(enet_deinitialize);
4247         atexit(cleanupserver);
4248         enet_time_set(0);
4249         for(;;) serverslice(5);
4250     }
4251 }
4252 
4253 #ifdef STANDALONE
4254 
localservertoclient(int chan,uchar * buf,int len,bool demo)4255 void localservertoclient(int chan, uchar *buf, int len, bool demo) {}
fatal(const char * s,...)4256 void fatal(const char *s, ...)
4257 {
4258     defvformatstring(msg,s,s);
4259     defformatstring(out)("AssaultCube fatal error: %s", msg);
4260     if (logline(ACLOG_ERROR, "%s", out));
4261     else puts(out);
4262     cleanupserver();
4263     exit(EXIT_FAILURE);
4264 }
4265 
main(int argc,char ** argv)4266 int main(int argc, char **argv)
4267 {
4268     #ifdef WIN32
4269     //atexit((void (__cdecl *)(void))_CrtDumpMemoryLeaks);
4270     #ifndef _DEBUG
4271     #ifndef __GNUC__
4272     __try {
4273     #endif
4274     #endif
4275     #endif
4276 
4277     for(int i = 1; i<argc; i++)
4278     {
4279         if (!strncmp(argv[i],"--wizard",8)) return wizardmain(argc, argv);
4280     }
4281 
4282     if(enet_initialize()<0) fatal("Unable to initialise network module");
4283     initserver(true, argc, argv);
4284     return EXIT_SUCCESS;
4285 
4286     #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__)
4287     } __except(stackdumper(0, GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) { return 0; }
4288     #endif
4289 }
4290 #endif
4291 
4292