1 #include "game.h"
2 
3 namespace game
4 {
parseoptions(vector<const char * > & args)5     void parseoptions(vector<const char *> &args)
6     {
7         loopv(args)
8 #ifndef STANDALONE
9             if(!game::clientoption(args[i]))
10 #endif
11             if(!server::serveroption(args[i]))
12                 conoutf(CON_ERROR, "unknown command-line option: %s", args[i]);
13     }
14 
gameident()15     const char *gameident() { return "fps"; }
16 }
17 
18 VAR(regenbluearmour, 0, 1, 1);
19 
20 extern ENetAddress masteraddress;
21 
22 namespace server
23 {
24     struct server_entity            // server side version of "entity" type
25     {
26         int type;
27         int spawntime;
28         bool spawned;
29     };
30 
31     static const int DEATHMILLIS = 300;
32 
33     struct clientinfo;
34 
35     struct gameevent
36     {
~gameeventserver::gameevent37         virtual ~gameevent() {}
38 
39         virtual bool flush(clientinfo *ci, int fmillis);
processserver::gameevent40         virtual void process(clientinfo *ci) {}
41 
keepableserver::gameevent42         virtual bool keepable() const { return false; }
43     };
44 
45     struct timedevent : gameevent
46     {
47         int millis;
48 
49         bool flush(clientinfo *ci, int fmillis);
50     };
51 
52     struct hitinfo
53     {
54         int target;
55         int lifesequence;
56         int rays;
57         float dist;
58         vec dir;
59     };
60 
61     struct shotevent : timedevent
62     {
63         int id, gun;
64         vec from, to;
65         vector<hitinfo> hits;
66 
67         void process(clientinfo *ci);
68     };
69 
70     struct explodeevent : timedevent
71     {
72         int id, gun;
73         vector<hitinfo> hits;
74 
keepableserver::explodeevent75         bool keepable() const { return true; }
76 
77         void process(clientinfo *ci);
78     };
79 
80     struct suicideevent : gameevent
81     {
82         void process(clientinfo *ci);
83     };
84 
85     struct pickupevent : gameevent
86     {
87         int ent;
88 
89         void process(clientinfo *ci);
90     };
91 
92     template <int N>
93     struct projectilestate
94     {
95         int projs[N];
96         int numprojs;
97 
projectilestateserver::projectilestate98         projectilestate() : numprojs(0) {}
99 
resetserver::projectilestate100         void reset() { numprojs = 0; }
101 
addserver::projectilestate102         void add(int val)
103         {
104             if(numprojs>=N) numprojs = 0;
105             projs[numprojs++] = val;
106         }
107 
removeserver::projectilestate108         bool remove(int val)
109         {
110             loopi(numprojs) if(projs[i]==val)
111             {
112                 projs[i] = projs[--numprojs];
113                 return true;
114             }
115             return false;
116         }
117     };
118 
119     struct gamestate : fpsstate
120     {
121         vec o;
122         int state, editstate;
123         int lastdeath, deadflush, lastspawn, lifesequence;
124         int lastshot;
125         projectilestate<8> rockets, grenades;
126         int frags, flags, deaths, teamkills, shotdamage, damage, tokens;
127         int lasttimeplayed, timeplayed;
128         float effectiveness;
129 
gamestateserver::gamestate130         gamestate() : state(CS_DEAD), editstate(CS_DEAD), lifesequence(0) {}
131 
isaliveserver::gamestate132         bool isalive(int gamemillis)
133         {
134             return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS);
135         }
136 
waitexpiredserver::gamestate137         bool waitexpired(int gamemillis)
138         {
139             return gamemillis - lastshot >= gunwait;
140         }
141 
resetserver::gamestate142         void reset()
143         {
144             if(state!=CS_SPECTATOR) state = editstate = CS_DEAD;
145             maxhealth = 100;
146             rockets.reset();
147             grenades.reset();
148 
149             timeplayed = 0;
150             effectiveness = 0;
151             frags = flags = deaths = teamkills = shotdamage = damage = tokens = 0;
152 
153             lastdeath = 0;
154 
155             respawn();
156         }
157 
respawnserver::gamestate158         void respawn()
159         {
160             fpsstate::respawn();
161             o = vec(-1e10f, -1e10f, -1e10f);
162             deadflush = 0;
163             lastspawn = -1;
164             lastshot = 0;
165             tokens = 0;
166         }
167 
reassignserver::gamestate168         void reassign()
169         {
170             respawn();
171             rockets.reset();
172             grenades.reset();
173         }
174     };
175 
176     struct savedscore
177     {
178         uint ip;
179         string name;
180         int frags, flags, deaths, teamkills, shotdamage, damage;
181         int timeplayed;
182         float effectiveness;
183 
saveserver::savedscore184         void save(gamestate &gs)
185         {
186             frags = gs.frags;
187             flags = gs.flags;
188             deaths = gs.deaths;
189             teamkills = gs.teamkills;
190             shotdamage = gs.shotdamage;
191             damage = gs.damage;
192             timeplayed = gs.timeplayed;
193             effectiveness = gs.effectiveness;
194         }
195 
restoreserver::savedscore196         void restore(gamestate &gs)
197         {
198             gs.frags = frags;
199             gs.flags = flags;
200             gs.deaths = deaths;
201             gs.teamkills = teamkills;
202             gs.shotdamage = shotdamage;
203             gs.damage = damage;
204             gs.timeplayed = timeplayed;
205             gs.effectiveness = effectiveness;
206         }
207     };
208 
209     extern int gamemillis, nextexceeded;
210 
211     struct clientinfo
212     {
213         int clientnum, ownernum, connectmillis, sessionid, overflow;
214         string name, team, mapvote;
215         int playermodel;
216         int modevote;
217         int privilege;
218         bool connected, local, timesync;
219         int gameoffset, lastevent, pushed, exceeded;
220         gamestate state;
221         vector<gameevent *> events;
222         vector<uchar> position, messages;
223         uchar *wsdata;
224         int wslen;
225         vector<clientinfo *> bots;
226         int ping, aireinit;
227         string clientmap;
228         int mapcrc;
229         bool warned, gameclip;
230         ENetPacket *getdemo, *getmap, *clipboard;
231         int lastclipboard, needclipboard;
232         int connectauth;
233         uint authreq;
234         string authname, authdesc;
235         void *authchallenge;
236         int authkickvictim;
237         char *authkickreason;
238 
clientinfoserver::clientinfo239         clientinfo() : getdemo(NULL), getmap(NULL), clipboard(NULL), authchallenge(NULL), authkickreason(NULL) { reset(); }
~clientinfoserver::clientinfo240         ~clientinfo() { events.deletecontents(); cleanclipboard(); cleanauth(); }
241 
addeventserver::clientinfo242         void addevent(gameevent *e)
243         {
244             if(state.state==CS_SPECTATOR || events.length()>100) delete e;
245             else events.add(e);
246         }
247 
248         enum
249         {
250             PUSHMILLIS = 3000
251         };
252 
calcpushrangeserver::clientinfo253         int calcpushrange()
254         {
255             ENetPeer *peer = getclientpeer(ownernum);
256             return PUSHMILLIS + (peer ? peer->roundTripTime + peer->roundTripTimeVariance : ENET_PEER_DEFAULT_ROUND_TRIP_TIME);
257         }
258 
checkpushedserver::clientinfo259         bool checkpushed(int millis, int range)
260         {
261             return millis >= pushed - range && millis <= pushed + range;
262         }
263 
scheduleexceededserver::clientinfo264         void scheduleexceeded()
265         {
266             if(state.state!=CS_ALIVE || !exceeded) return;
267             int range = calcpushrange();
268             if(!nextexceeded || exceeded + range < nextexceeded) nextexceeded = exceeded + range;
269         }
270 
setexceededserver::clientinfo271         void setexceeded()
272         {
273             if(state.state==CS_ALIVE && !exceeded && !checkpushed(gamemillis, calcpushrange())) exceeded = gamemillis;
274             scheduleexceeded();
275         }
276 
setpushedserver::clientinfo277         void setpushed()
278         {
279             pushed = max(pushed, gamemillis);
280             if(exceeded && checkpushed(exceeded, calcpushrange())) exceeded = 0;
281         }
282 
checkexceededserver::clientinfo283         bool checkexceeded()
284         {
285             return state.state==CS_ALIVE && exceeded && gamemillis > exceeded + calcpushrange();
286         }
287 
mapchangeserver::clientinfo288         void mapchange()
289         {
290             mapvote[0] = 0;
291             modevote = INT_MAX;
292             state.reset();
293             events.deletecontents();
294             overflow = 0;
295             timesync = false;
296             lastevent = 0;
297             exceeded = 0;
298             pushed = 0;
299             clientmap[0] = '\0';
300             mapcrc = 0;
301             warned = false;
302             gameclip = false;
303         }
304 
reassignserver::clientinfo305         void reassign()
306         {
307             state.reassign();
308             events.deletecontents();
309             timesync = false;
310             lastevent = 0;
311         }
312 
cleanclipboardserver::clientinfo313         void cleanclipboard(bool fullclean = true)
314         {
315             if(clipboard) { if(--clipboard->referenceCount <= 0) enet_packet_destroy(clipboard); clipboard = NULL; }
316             if(fullclean) lastclipboard = 0;
317         }
318 
cleanauthkickserver::clientinfo319         void cleanauthkick()
320         {
321             authkickvictim = -1;
322             DELETEA(authkickreason);
323         }
324 
cleanauthserver::clientinfo325         void cleanauth(bool full = true)
326         {
327             authreq = 0;
328             if(authchallenge) { freechallenge(authchallenge); authchallenge = NULL; }
329             if(full) cleanauthkick();
330         }
331 
resetserver::clientinfo332         void reset()
333         {
334             name[0] = team[0] = 0;
335             playermodel = -1;
336             privilege = PRIV_NONE;
337             connected = local = false;
338             connectauth = 0;
339             position.setsize(0);
340             messages.setsize(0);
341             ping = 0;
342             aireinit = 0;
343             needclipboard = 0;
344             cleanclipboard();
345             cleanauth();
346             mapchange();
347         }
348 
geteventmillisserver::clientinfo349         int geteventmillis(int servmillis, int clientmillis)
350         {
351             if(!timesync || (events.empty() && state.waitexpired(servmillis)))
352             {
353                 timesync = true;
354                 gameoffset = servmillis - clientmillis;
355                 return servmillis;
356             }
357             else return gameoffset + clientmillis;
358         }
359     };
360 
361     struct ban
362     {
363         int time, expire;
364         uint ip;
365     };
366 
367     namespace aiman
368     {
369         extern void removeai(clientinfo *ci);
370         extern void clearai();
371         extern void checkai();
372         extern void reqadd(clientinfo *ci, int skill);
373         extern void reqdel(clientinfo *ci);
374         extern void setbotlimit(clientinfo *ci, int limit);
375         extern void setbotbalance(clientinfo *ci, bool balance);
376         extern void changemap();
377         extern void addclient(clientinfo *ci);
378         extern void changeteam(clientinfo *ci);
379     }
380 
381     #define MM_MODE 0xF
382     #define MM_AUTOAPPROVE 0x1000
383     #define MM_PRIVSERV (MM_MODE | MM_AUTOAPPROVE)
384     #define MM_PUBSERV ((1<<MM_OPEN) | (1<<MM_VETO))
385     #define MM_COOPSERV (MM_AUTOAPPROVE | MM_PUBSERV | (1<<MM_LOCKED))
386 
387     bool notgotitems = true;        // true when map has changed and waiting for clients to send item
388     int gamemode = 0;
389     int gamemillis = 0, gamelimit = 0, nextexceeded = 0, gamespeed = 100;
390     bool gamepaused = false, shouldstep = true;
391 
392     string smapname = "";
393     int interm = 0;
394     enet_uint32 lastsend = 0;
395     int mastermode = MM_OPEN, mastermask = MM_PRIVSERV;
396     stream *mapdata = NULL;
397 
398     vector<uint> allowedips;
399     vector<ban> bannedips;
400 
addban(uint ip,int expire)401     void addban(uint ip, int expire)
402     {
403         allowedips.removeobj(ip);
404         ban b;
405         b.time = totalmillis;
406         b.expire = totalmillis + expire;
407         b.ip = ip;
408         loopv(bannedips) if(bannedips[i].expire - b.expire > 0) { bannedips.insert(i, b); return; }
409         bannedips.add(b);
410     }
411 
412     vector<clientinfo *> connects, clients, bots;
413 
kickclients(uint ip,clientinfo * actor=NULL,int priv=PRIV_NONE)414     void kickclients(uint ip, clientinfo *actor = NULL, int priv = PRIV_NONE)
415     {
416         loopvrev(clients)
417         {
418             clientinfo &c = *clients[i];
419             if(c.state.aitype != AI_NONE || c.privilege >= PRIV_ADMIN || c.local) continue;
420             if(actor && ((c.privilege > priv && !actor->local) || c.clientnum == actor->clientnum)) continue;
421             if(getclientip(c.clientnum) == ip) disconnect_client(c.clientnum, DISC_KICK);
422         }
423     }
424 
425     struct maprotation
426     {
427         static int exclude;
428         int modes;
429         string map;
430 
calcmodemaskserver::maprotation431         int calcmodemask() const { return modes&(1<<NUMGAMEMODES) ? modes & ~exclude : modes; }
hasmodeserver::maprotation432         bool hasmode(int mode, int offset = STARTGAMEMODE) const { return (calcmodemask() & (1 << (mode-offset))) != 0; }
433 
findmodeserver::maprotation434         int findmode(int mode) const
435         {
436             if(!hasmode(mode)) loopi(NUMGAMEMODES) if(hasmode(i, 0)) return i+STARTGAMEMODE;
437             return mode;
438         }
439 
matchserver::maprotation440         bool match(int reqmode, const char *reqmap) const
441         {
442             return hasmode(reqmode) && (!map[0] || !reqmap[0] || !strcmp(map, reqmap));
443         }
444 
includesserver::maprotation445         bool includes(const maprotation &rot) const
446         {
447             return rot.modes == modes ? rot.map[0] && !map[0] : (rot.modes & modes) == rot.modes;
448         }
449     };
450     int maprotation::exclude = 0;
451     vector<maprotation> maprotations;
452     int curmaprotation = 0;
453 
454     VAR(lockmaprotation, 0, 0, 2);
455 
maprotationreset()456     void maprotationreset()
457     {
458         maprotations.setsize(0);
459         curmaprotation = 0;
460         maprotation::exclude = 0;
461     }
462 
nextmaprotation()463     void nextmaprotation()
464     {
465         curmaprotation++;
466         if(maprotations.inrange(curmaprotation) && maprotations[curmaprotation].modes) return;
467         do curmaprotation--;
468         while(maprotations.inrange(curmaprotation) && maprotations[curmaprotation].modes);
469         curmaprotation++;
470     }
471 
findmaprotation(int mode,const char * map)472     int findmaprotation(int mode, const char *map)
473     {
474         for(int i = max(curmaprotation, 0); i < maprotations.length(); i++)
475         {
476             maprotation &rot = maprotations[i];
477             if(!rot.modes) break;
478             if(rot.match(mode, map)) return i;
479         }
480         int start;
481         for(start = max(curmaprotation, 0) - 1; start >= 0; start--) if(!maprotations[start].modes) break;
482         start++;
483         for(int i = start; i < curmaprotation; i++)
484         {
485             maprotation &rot = maprotations[i];
486             if(!rot.modes) break;
487             if(rot.match(mode, map)) return i;
488         }
489         int best = -1;
490         loopv(maprotations)
491         {
492             maprotation &rot = maprotations[i];
493             if(rot.match(mode, map) && (best < 0 || maprotations[best].includes(rot))) best = i;
494         }
495         return best;
496     }
497 
searchmodename(const char * haystack,const char * needle)498     bool searchmodename(const char *haystack, const char *needle)
499     {
500         if(!needle[0]) return true;
501         do
502         {
503             if(needle[0] != '.')
504             {
505                 haystack = strchr(haystack, needle[0]);
506                 if(!haystack) break;
507                 haystack++;
508             }
509             const char *h = haystack, *n = needle+1;
510             for(; *h && *n; h++)
511             {
512                 if(*h == *n) n++;
513                 else if(*h != ' ') break;
514             }
515             if(!*n) return true;
516             if(*n == '.') return !*h;
517         } while(needle[0] != '.');
518         return false;
519     }
520 
genmodemask(vector<char * > & modes)521     int genmodemask(vector<char *> &modes)
522     {
523         int modemask = 0;
524         loopv(modes)
525         {
526             const char *mode = modes[i];
527             int op = mode[0];
528             switch(mode[0])
529             {
530                 case '*':
531                     modemask |= 1<<NUMGAMEMODES;
532                     loopk(NUMGAMEMODES) if(m_checknot(k+STARTGAMEMODE, M_DEMO|M_EDIT|M_LOCAL)) modemask |= 1<<k;
533                     continue;
534                 case '!':
535                     mode++;
536                     if(mode[0] != '?') break;
537                 case '?':
538                     mode++;
539                     loopk(NUMGAMEMODES) if(searchmodename(gamemodes[k].name, mode))
540                     {
541                         if(op == '!') modemask &= ~(1<<k);
542                         else modemask |= 1<<k;
543                     }
544                     continue;
545             }
546             int modenum = INT_MAX;
547             if(isdigit(mode[0])) modenum = atoi(mode);
548             else loopk(NUMGAMEMODES) if(searchmodename(gamemodes[k].name, mode)) { modenum = k+STARTGAMEMODE; break; }
549             if(!m_valid(modenum)) continue;
550             switch(op)
551             {
552                 case '!': modemask &= ~(1 << (modenum - STARTGAMEMODE)); break;
553                 default: modemask |= 1 << (modenum - STARTGAMEMODE); break;
554             }
555         }
556         return modemask;
557     }
558 
addmaprotation(int modemask,const char * map)559     bool addmaprotation(int modemask, const char *map)
560     {
561         if(!map[0]) loopk(NUMGAMEMODES) if(modemask&(1<<k) && !m_check(k+STARTGAMEMODE, M_EDIT)) modemask &= ~(1<<k);
562         if(!modemask) return false;
563         if(!(modemask&(1<<NUMGAMEMODES))) maprotation::exclude |= modemask;
564         maprotation &rot = maprotations.add();
565         rot.modes = modemask;
566         copystring(rot.map, map);
567         return true;
568     }
569 
addmaprotations(tagval * args,int numargs)570     void addmaprotations(tagval *args, int numargs)
571     {
572         vector<char *> modes, maps;
573         for(int i = 0; i + 1 < numargs; i += 2)
574         {
575             explodelist(args[i].getstr(), modes);
576             explodelist(args[i+1].getstr(), maps);
577             int modemask = genmodemask(modes);
578             if(maps.length()) loopvj(maps) addmaprotation(modemask, maps[j]);
579             else addmaprotation(modemask, "");
580             modes.deletearrays();
581             maps.deletearrays();
582         }
583         if(maprotations.length() && maprotations.last().modes)
584         {
585             maprotation &rot = maprotations.add();
586             rot.modes = 0;
587             rot.map[0] = '\0';
588         }
589     }
590 
591     COMMAND(maprotationreset, "");
592     COMMANDN(maprotation, addmaprotations, "ss2V");
593 
594     struct demofile
595     {
596         string info;
597         uchar *data;
598         int len;
599     };
600 
601     vector<demofile> demos;
602 
603     bool demonextmatch = false;
604     stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL;
605     int nextplayback = 0;
606 
607     VAR(maxdemos, 0, 5, 25);
608     VAR(maxdemosize, 0, 16, 31);
609     VAR(restrictdemos, 0, 1, 1);
610     VARF(autorecorddemo, 0, 0, 1, demonextmatch = autorecorddemo!=0);
611 
612     VAR(restrictpausegame, 0, 1, 1);
613     VAR(restrictgamespeed, 0, 1, 1);
614 
615     SVAR(serverdesc, "");
616     SVAR(serverpass, "");
617     SVAR(adminpass, "");
618     VARF(publicserver, 0, 0, 2, {
619 		switch(publicserver)
620 		{
621 			case 0: default: mastermask = MM_PRIVSERV; break;
622 			case 1: mastermask = MM_PUBSERV; break;
623 			case 2: mastermask = MM_COOPSERV; break;
624 		}
625 	});
626     SVAR(servermotd, "");
627 
628     struct teamkillkick
629     {
630         int modes, limit, ban;
631 
matchserver::teamkillkick632         bool match(int mode) const
633         {
634             return (modes&(1<<(mode-STARTGAMEMODE)))!=0;
635         }
636 
includesserver::teamkillkick637         bool includes(const teamkillkick &tk) const
638         {
639             return tk.modes != modes && (tk.modes & modes) == tk.modes;
640         }
641     };
642     vector<teamkillkick> teamkillkicks;
643 
teamkillkickreset()644     void teamkillkickreset()
645     {
646         teamkillkicks.setsize(0);
647     }
648 
addteamkillkick(char * modestr,int * limit,int * ban)649     void addteamkillkick(char *modestr, int *limit, int *ban)
650     {
651         vector<char *> modes;
652         explodelist(modestr, modes);
653         teamkillkick &kick = teamkillkicks.add();
654         kick.modes = genmodemask(modes);
655         kick.limit = *limit;
656         kick.ban = *ban > 0 ? *ban*60000 : (*ban < 0 ? 0 : 30*60000);
657         modes.deletearrays();
658     }
659 
660     COMMAND(teamkillkickreset, "");
661     COMMANDN(teamkillkick, addteamkillkick, "sii");
662 
663     struct teamkillinfo
664     {
665         uint ip;
666         int teamkills;
667     };
668     vector<teamkillinfo> teamkills;
669     bool shouldcheckteamkills = false;
670 
addteamkill(clientinfo * actor,clientinfo * victim,int n)671     void addteamkill(clientinfo *actor, clientinfo *victim, int n)
672     {
673         if(!m_timed || actor->state.aitype != AI_NONE || actor->local || actor->privilege || (victim && victim->state.aitype != AI_NONE)) return;
674         shouldcheckteamkills = true;
675         uint ip = getclientip(actor->clientnum);
676         loopv(teamkills) if(teamkills[i].ip == ip)
677         {
678             teamkills[i].teamkills += n;
679             return;
680         }
681         teamkillinfo &tk = teamkills.add();
682         tk.ip = ip;
683         tk.teamkills = n;
684     }
685 
checkteamkills()686     void checkteamkills()
687     {
688         teamkillkick *kick = NULL;
689         if(m_timed) loopv(teamkillkicks) if(teamkillkicks[i].match(gamemode) && (!kick || kick->includes(teamkillkicks[i])))
690             kick = &teamkillkicks[i];
691         if(kick) loopvrev(teamkills)
692         {
693             teamkillinfo &tk = teamkills[i];
694             if(tk.teamkills >= kick->limit)
695             {
696                 if(kick->ban > 0) addban(tk.ip, kick->ban);
697                 kickclients(tk.ip);
698                 teamkills.removeunordered(i);
699             }
700         }
701         shouldcheckteamkills = false;
702     }
703 
newclientinfo()704     void *newclientinfo() { return new clientinfo; }
deleteclientinfo(void * ci)705     void deleteclientinfo(void *ci) { delete (clientinfo *)ci; }
706 
getinfo(int n)707     clientinfo *getinfo(int n)
708     {
709         if(n < MAXCLIENTS) return (clientinfo *)getclientinfo(n);
710         n -= MAXCLIENTS;
711         return bots.inrange(n) ? bots[n] : NULL;
712     }
713 
714     uint mcrc = 0;
715     vector<entity> ments;
716     vector<server_entity> sents;
717     vector<savedscore> scores;
718 
msgsizelookup(int msg)719     int msgsizelookup(int msg)
720     {
721         static int sizetable[NUMMSG] = { -1 };
722         if(sizetable[0] < 0)
723         {
724             memset(sizetable, -1, sizeof(sizetable));
725             for(const int *p = msgsizes; *p >= 0; p += 2) sizetable[p[0]] = p[1];
726         }
727         return msg >= 0 && msg < NUMMSG ? sizetable[msg] : -1;
728     }
729 
modename(int n,const char * unknown)730     const char *modename(int n, const char *unknown)
731     {
732         if(m_valid(n)) return gamemodes[n - STARTGAMEMODE].name;
733         return unknown;
734     }
735 
mastermodename(int n,const char * unknown)736     const char *mastermodename(int n, const char *unknown)
737     {
738         return (n>=MM_START && size_t(n-MM_START)<sizeof(mastermodenames)/sizeof(mastermodenames[0])) ? mastermodenames[n-MM_START] : unknown;
739     }
740 
privname(int type)741     const char *privname(int type)
742     {
743         switch(type)
744         {
745             case PRIV_ADMIN: return "admin";
746             case PRIV_AUTH: return "auth";
747             case PRIV_MASTER: return "master";
748             default: return "unknown";
749         }
750     }
751 
sendservmsg(const char * s)752     void sendservmsg(const char *s) { sendf(-1, 1, "ris", N_SERVMSG, s); }
sendservmsgf(const char * fmt,...)753     void sendservmsgf(const char *fmt, ...)
754     {
755          defvformatstring(s, fmt, fmt);
756          sendf(-1, 1, "ris", N_SERVMSG, s);
757     }
758 
resetitems()759     void resetitems()
760     {
761         mcrc = 0;
762         ments.setsize(0);
763         sents.setsize(0);
764         //cps.reset();
765     }
766 
serveroption(const char * arg)767     bool serveroption(const char *arg)
768     {
769         if(arg[0]=='-') switch(arg[1])
770         {
771             case 'n': setsvar("serverdesc", &arg[2]); return true;
772             case 'y': setsvar("serverpass", &arg[2]); return true;
773             case 'p': setsvar("adminpass", &arg[2]); return true;
774             case 'o': setvar("publicserver", atoi(&arg[2])); return true;
775         }
776         return false;
777     }
778 
serverinit()779     void serverinit()
780     {
781         smapname[0] = '\0';
782         resetitems();
783     }
784 
numclients(int exclude=-1,bool nospec=true,bool noai=true,bool priv=false)785     int numclients(int exclude = -1, bool nospec = true, bool noai = true, bool priv = false)
786     {
787         int n = 0;
788         loopv(clients)
789         {
790             clientinfo *ci = clients[i];
791             if(ci->clientnum!=exclude && (!nospec || ci->state.state!=CS_SPECTATOR || (priv && (ci->privilege || ci->local))) && (!noai || ci->state.aitype == AI_NONE)) n++;
792         }
793         return n;
794     }
795 
duplicatename(clientinfo * ci,const char * name)796     bool duplicatename(clientinfo *ci, const char *name)
797     {
798         if(!name) name = ci->name;
799         loopv(clients) if(clients[i]!=ci && !strcmp(name, clients[i]->name)) return true;
800         return false;
801     }
802 
colorname(clientinfo * ci,const char * name=NULL)803     const char *colorname(clientinfo *ci, const char *name = NULL)
804     {
805         if(!name) name = ci->name;
806         if(name[0] && !duplicatename(ci, name) && ci->state.aitype == AI_NONE) return name;
807         static string cname[3];
808         static int cidx = 0;
809         cidx = (cidx+1)%3;
810         formatstring(cname[cidx], ci->state.aitype == AI_NONE ? "%s \fs\f5(%d)\fr" : "%s \fs\f5[%d]\fr", name, ci->clientnum);
811         return cname[cidx];
812     }
813 
814     struct servmode
815     {
~servmodeserver::servmode816         virtual ~servmode() {}
817 
entergameserver::servmode818         virtual void entergame(clientinfo *ci) {}
leavegameserver::servmode819         virtual void leavegame(clientinfo *ci, bool disconnecting = false) {}
820 
movedserver::servmode821         virtual void moved(clientinfo *ci, const vec &oldpos, bool oldclip, const vec &newpos, bool newclip) {}
canspawnserver::servmode822         virtual bool canspawn(clientinfo *ci, bool connecting = false) { return true; }
spawnedserver::servmode823         virtual void spawned(clientinfo *ci) {}
fragvalueserver::servmode824         virtual int fragvalue(clientinfo *victim, clientinfo *actor)
825         {
826             if(victim==actor || isteam(victim->team, actor->team)) return -1;
827             return 1;
828         }
diedserver::servmode829         virtual void died(clientinfo *victim, clientinfo *actor) {}
canchangeteamserver::servmode830         virtual bool canchangeteam(clientinfo *ci, const char *oldteam, const char *newteam) { return true; }
changeteamserver::servmode831         virtual void changeteam(clientinfo *ci, const char *oldteam, const char *newteam) {}
initclientserver::servmode832         virtual void initclient(clientinfo *ci, packetbuf &p, bool connecting) {}
updateserver::servmode833         virtual void update() {}
cleanupserver::servmode834         virtual void cleanup() {}
setupserver::servmode835         virtual void setup() {}
newmapserver::servmode836         virtual void newmap() {}
intermissionserver::servmode837         virtual void intermission() {}
hidefragsserver::servmode838         virtual bool hidefrags() { return false; }
getteamscoreserver::servmode839         virtual int getteamscore(const char *team) { return 0; }
getteamscoresserver::servmode840         virtual void getteamscores(vector<teamscore> &scores) {}
extinfoteamserver::servmode841         virtual bool extinfoteam(const char *team, ucharbuf &p) { return false; }
842     };
843 
844     #define SERVMODE 1
845     #include "capture.h"
846     #include "ctf.h"
847     #include "collect.h"
848 
849     captureservmode capturemode;
850     ctfservmode ctfmode;
851     collectservmode collectmode;
852     servmode *smode = NULL;
853 
canspawnitem(int type)854     bool canspawnitem(int type) { return !m_noitems && (type>=I_SHELLS && type<=I_QUAD && (!m_noammo || type<I_SHELLS || type>I_CARTRIDGES)); }
855 
spawntime(int type)856     int spawntime(int type)
857     {
858         if(m_classicsp) return INT_MAX;
859         int np = numclients(-1, true, false);
860         np = np<3 ? 4 : (np>4 ? 2 : 3);         // spawn times are dependent on number of players
861         int sec = 0;
862         switch(type)
863         {
864             case I_SHELLS:
865             case I_BULLETS:
866             case I_ROCKETS:
867             case I_ROUNDS:
868             case I_GRENADES:
869             case I_CARTRIDGES: sec = np*4; break;
870             case I_HEALTH: sec = np*5; break;
871             case I_GREENARMOUR: sec = 20; break;
872             case I_YELLOWARMOUR: sec = 30; break;
873             case I_BOOST: sec = 60; break;
874             case I_QUAD: sec = 70; break;
875         }
876         return sec*1000;
877     }
878 
delayspawn(int type)879     bool delayspawn(int type)
880     {
881         switch(type)
882         {
883             case I_GREENARMOUR:
884             case I_YELLOWARMOUR:
885                 return !m_classicsp;
886             case I_BOOST:
887             case I_QUAD:
888                 return true;
889             default:
890                 return false;
891         }
892     }
893 
pickup(int i,int sender)894     bool pickup(int i, int sender)         // server side item pickup, acknowledge first client that gets it
895     {
896         if((m_timed && gamemillis>=gamelimit) || !sents.inrange(i) || !sents[i].spawned) return false;
897         clientinfo *ci = getinfo(sender);
898         if(!ci) return false;
899         if(!ci->local && !ci->state.canpickup(sents[i].type))
900         {
901             sendf(sender, 1, "ri3", N_ITEMACC, i, -1);
902             return false;
903         }
904         sents[i].spawned = false;
905         sents[i].spawntime = spawntime(sents[i].type);
906         sendf(-1, 1, "ri3", N_ITEMACC, i, sender);
907         ci->state.pickup(sents[i].type);
908         return true;
909     }
910 
911     static hashset<teaminfo> teaminfos;
912 
clearteaminfo()913     void clearteaminfo()
914     {
915         teaminfos.clear();
916     }
917 
teamhasplayers(const char * team)918     bool teamhasplayers(const char *team) { loopv(clients) if(!strcmp(clients[i]->team, team)) return true; return false; }
919 
pruneteaminfo()920     bool pruneteaminfo()
921     {
922         int oldteams = teaminfos.numelems;
923         enumerate(teaminfos, teaminfo, old,
924             if(!old.frags && !teamhasplayers(old.team)) teaminfos.remove(old.team);
925         );
926         return teaminfos.numelems < oldteams;
927     }
928 
addteaminfo(const char * team)929     teaminfo *addteaminfo(const char *team)
930     {
931         teaminfo *t = teaminfos.access(team);
932         if(!t)
933         {
934             if(teaminfos.numelems >= MAXTEAMS && !pruneteaminfo()) return NULL;
935             t = &teaminfos[team];
936             copystring(t->team, team, sizeof(t->team));
937             t->frags = 0;
938         }
939         return t;
940     }
941 
choosebestclient(float & bestrank)942     clientinfo *choosebestclient(float &bestrank)
943     {
944         clientinfo *best = NULL;
945         bestrank = -1;
946         loopv(clients)
947         {
948             clientinfo *ci = clients[i];
949             if(ci->state.timeplayed<0) continue;
950             float rank = ci->state.state!=CS_SPECTATOR ? ci->state.effectiveness/max(ci->state.timeplayed, 1) : -1;
951             if(!best || rank > bestrank) { best = ci; bestrank = rank; }
952         }
953         return best;
954     }
955 
956     VAR(persistteams, 0, 0, 1);
957 
autoteam()958     void autoteam()
959     {
960         static const char * const teamnames[2] = {"good", "evil"};
961         vector<clientinfo *> team[2];
962         float teamrank[2] = {0, 0};
963         for(int round = 0, remaining = clients.length(); remaining>=0; round++)
964         {
965             int first = round&1, second = (round+1)&1, selected = 0;
966             while(teamrank[first] <= teamrank[second])
967             {
968                 float rank;
969                 clientinfo *ci = choosebestclient(rank);
970                 if(!ci) break;
971                 if(smode && smode->hidefrags()) rank = 1;
972                 else if(selected && rank<=0) break;
973                 ci->state.timeplayed = -1;
974                 team[first].add(ci);
975                 if(rank>0) teamrank[first] += rank;
976                 selected++;
977                 if(rank<=0) break;
978             }
979             if(!selected) break;
980             remaining -= selected;
981         }
982         loopi(sizeof(team)/sizeof(team[0]))
983         {
984             addteaminfo(teamnames[i]);
985             loopvj(team[i])
986             {
987                 clientinfo *ci = team[i][j];
988                 if(!strcmp(ci->team, teamnames[i])) continue;
989                 if(persistteams && ci->team[0] && (!smode || smode->canchangeteam(ci, teamnames[i], ci->team)))
990                 {
991                     addteaminfo(ci->team);
992                     continue;
993                 }
994                 copystring(ci->team, teamnames[i], MAXTEAMLEN+1);
995                 sendf(-1, 1, "riisi", N_SETTEAM, ci->clientnum, teamnames[i], -1);
996             }
997         }
998     }
999 
1000     struct teamrank
1001     {
1002         const char *name;
1003         float rank;
1004         int clients;
1005 
teamrankserver::teamrank1006         teamrank(const char *name) : name(name), rank(0), clients(0) {}
1007     };
1008 
chooseworstteam(const char * suggest=NULL,clientinfo * exclude=NULL)1009     const char *chooseworstteam(const char *suggest = NULL, clientinfo *exclude = NULL)
1010     {
1011         teamrank teamranks[2] = { teamrank("good"), teamrank("evil") };
1012         const int numteams = sizeof(teamranks)/sizeof(teamranks[0]);
1013         loopv(clients)
1014         {
1015             clientinfo *ci = clients[i];
1016             if(ci==exclude || ci->state.aitype!=AI_NONE || ci->state.state==CS_SPECTATOR || !ci->team[0]) continue;
1017             ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
1018             ci->state.lasttimeplayed = lastmillis;
1019 
1020             loopj(numteams) if(!strcmp(ci->team, teamranks[j].name))
1021             {
1022                 teamrank &ts = teamranks[j];
1023                 ts.rank += ci->state.effectiveness/max(ci->state.timeplayed, 1);
1024                 ts.clients++;
1025                 break;
1026             }
1027         }
1028         teamrank *worst = &teamranks[numteams-1];
1029         loopi(numteams-1)
1030         {
1031             teamrank &ts = teamranks[i];
1032             if(smode && smode->hidefrags())
1033             {
1034                 if(ts.clients < worst->clients || (ts.clients == worst->clients && ts.rank < worst->rank)) worst = &ts;
1035             }
1036             else if(ts.rank < worst->rank || (ts.rank == worst->rank && ts.clients < worst->clients)) worst = &ts;
1037         }
1038         return worst->name;
1039     }
1040 
prunedemos(int extra=0)1041     void prunedemos(int extra = 0)
1042     {
1043         int n = clamp(demos.length() + extra - maxdemos, 0, demos.length());
1044         if(n <= 0) return;
1045         loopi(n) delete[] demos[i].data;
1046         demos.remove(0, n);
1047     }
1048 
adddemo()1049     void adddemo()
1050     {
1051         if(!demotmp) return;
1052         int len = (int)min(demotmp->size(), stream::offset((maxdemosize<<20) + 0x10000));
1053         demofile &d = demos.add();
1054         time_t t = time(NULL);
1055         char *timestr = ctime(&t), *trim = timestr + strlen(timestr);
1056         while(trim>timestr && iscubespace(*--trim)) *trim = '\0';
1057         formatstring(d.info, "%s: %s, %s, %.2f%s", timestr, modename(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB");
1058         sendservmsgf("demo \"%s\" recorded", d.info);
1059         d.data = new uchar[len];
1060         d.len = len;
1061         demotmp->seek(0, SEEK_SET);
1062         demotmp->read(d.data, len);
1063         DELETEP(demotmp);
1064     }
1065 
enddemorecord()1066     void enddemorecord()
1067     {
1068         if(!demorecord) return;
1069 
1070         DELETEP(demorecord);
1071 
1072         if(!demotmp) return;
1073         if(!maxdemos || !maxdemosize) { DELETEP(demotmp); return; }
1074 
1075         prunedemos(1);
1076         adddemo();
1077     }
1078 
writedemo(int chan,void * data,int len)1079     void writedemo(int chan, void *data, int len)
1080     {
1081         if(!demorecord) return;
1082         int stamp[3] = { gamemillis, chan, len };
1083         lilswap(stamp, 3);
1084         demorecord->write(stamp, sizeof(stamp));
1085         demorecord->write(data, len);
1086         if(demorecord->rawtell() >= (maxdemosize<<20)) enddemorecord();
1087     }
1088 
recordpacket(int chan,void * data,int len)1089     void recordpacket(int chan, void *data, int len)
1090     {
1091         writedemo(chan, data, len);
1092     }
1093 
1094     int welcomepacket(packetbuf &p, clientinfo *ci);
1095     void sendwelcome(clientinfo *ci);
1096 
setupdemorecord()1097     void setupdemorecord()
1098     {
1099         if(!m_mp(gamemode) || m_edit) return;
1100 
1101         demotmp = opentempfile("demorecord", "w+b");
1102         if(!demotmp) return;
1103 
1104         stream *f = opengzfile(NULL, "wb", demotmp);
1105         if(!f) { DELETEP(demotmp); return; }
1106 
1107         sendservmsg("recording demo");
1108 
1109         demorecord = f;
1110 
1111         demoheader hdr;
1112         memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic));
1113         hdr.version = DEMO_VERSION;
1114         hdr.protocol = PROTOCOL_VERSION;
1115         lilswap(&hdr.version, 2);
1116         demorecord->write(&hdr, sizeof(demoheader));
1117 
1118         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1119         welcomepacket(p, NULL);
1120         writedemo(1, p.buf, p.len);
1121     }
1122 
listdemos(int cn)1123     void listdemos(int cn)
1124     {
1125         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1126         putint(p, N_SENDDEMOLIST);
1127         putint(p, demos.length());
1128         loopv(demos) sendstring(demos[i].info, p);
1129         sendpacket(cn, 1, p.finalize());
1130     }
1131 
cleardemos(int n)1132     void cleardemos(int n)
1133     {
1134         if(!n)
1135         {
1136             loopv(demos) delete[] demos[i].data;
1137             demos.shrink(0);
1138             sendservmsg("cleared all demos");
1139         }
1140         else if(demos.inrange(n-1))
1141         {
1142             delete[] demos[n-1].data;
1143             demos.remove(n-1);
1144             sendservmsgf("cleared demo %d", n);
1145         }
1146     }
1147 
freegetmap(ENetPacket * packet)1148     static void freegetmap(ENetPacket *packet)
1149     {
1150         loopv(clients)
1151         {
1152             clientinfo *ci = clients[i];
1153             if(ci->getmap == packet) ci->getmap = NULL;
1154         }
1155     }
1156 
freegetdemo(ENetPacket * packet)1157     static void freegetdemo(ENetPacket *packet)
1158     {
1159         loopv(clients)
1160         {
1161             clientinfo *ci = clients[i];
1162             if(ci->getdemo == packet) ci->getdemo = NULL;
1163         }
1164     }
1165 
senddemo(clientinfo * ci,int num,int tag)1166     void senddemo(clientinfo *ci, int num, int tag)
1167     {
1168         if(ci->getdemo) return;
1169         if(!num) num = demos.length();
1170         if(!demos.inrange(num-1)) return;
1171         demofile &d = demos[num-1];
1172         if((ci->getdemo = sendf(ci->clientnum, 2, "riim", N_SENDDEMO, tag, d.len, d.data)))
1173             ci->getdemo->freeCallback = freegetdemo;
1174     }
1175 
enddemoplayback()1176     void enddemoplayback()
1177     {
1178         if(!demoplayback) return;
1179         DELETEP(demoplayback);
1180 
1181         loopv(clients) sendf(clients[i]->clientnum, 1, "ri3", N_DEMOPLAYBACK, 0, clients[i]->clientnum);
1182 
1183         sendservmsg("demo playback finished");
1184 
1185         loopv(clients) sendwelcome(clients[i]);
1186     }
1187 
1188     SVARP(demodir, "demo");
1189 
getdemofile(const char * file,bool init)1190     const char *getdemofile(const char *file, bool init)
1191     {
1192         if(!demodir[0]) return NULL;
1193         static string buf;
1194         copystring(buf, demodir);
1195         int dirlen = strlen(buf);
1196         if(buf[dirlen] != '/' && buf[dirlen] != '\\' && dirlen+1 < (int)sizeof(buf)) { buf[dirlen++] = '/'; buf[dirlen] = '\0'; }
1197         if(init)
1198         {
1199             const char *dir = findfile(buf, "w");
1200             if(!fileexists(dir, "w")) createdir(dir);
1201         }
1202         concatstring(buf, file);
1203         return buf;
1204     }
1205 
setupdemoplayback()1206     void setupdemoplayback()
1207     {
1208         if(demoplayback) return;
1209         demoheader hdr;
1210         string msg;
1211         msg[0] = '\0';
1212         string file;
1213         copystring(file, smapname);
1214         int len = strlen(file);
1215         if(len < 4 || strcasecmp(&file[len-4], ".dmo")) concatstring(file, ".dmo");
1216         if(const char *buf = getdemofile(file, false)) demoplayback = opengzfile(buf, "rb");
1217         if(!demoplayback) demoplayback = opengzfile(file, "rb");
1218         if(!demoplayback) formatstring(msg, "could not read demo \"%s\"", file);
1219         else if(demoplayback->read(&hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)))
1220             formatstring(msg, "\"%s\" is not a demo file", file);
1221         else
1222         {
1223             lilswap(&hdr.version, 2);
1224             if(hdr.version!=DEMO_VERSION) formatstring(msg, "demo \"%s\" requires an %s version of Cube 2: Sauerbraten", file, hdr.version<DEMO_VERSION ? "older" : "newer");
1225             else if(hdr.protocol!=PROTOCOL_VERSION) formatstring(msg, "demo \"%s\" requires an %s version of Cube 2: Sauerbraten", file, hdr.protocol<PROTOCOL_VERSION ? "older" : "newer");
1226         }
1227         if(msg[0])
1228         {
1229             DELETEP(demoplayback);
1230             sendservmsg(msg);
1231             return;
1232         }
1233 
1234         sendservmsgf("playing demo \"%s\"", file);
1235 
1236         sendf(-1, 1, "ri3", N_DEMOPLAYBACK, 1, -1);
1237 
1238         if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
1239         {
1240             enddemoplayback();
1241             return;
1242         }
1243         lilswap(&nextplayback, 1);
1244     }
1245 
readdemo()1246     void readdemo()
1247     {
1248         if(!demoplayback) return;
1249         while(gamemillis>=nextplayback)
1250         {
1251             int chan, len;
1252             if(demoplayback->read(&chan, sizeof(chan))!=sizeof(chan) ||
1253                demoplayback->read(&len, sizeof(len))!=sizeof(len))
1254             {
1255                 enddemoplayback();
1256                 return;
1257             }
1258             lilswap(&chan, 1);
1259             lilswap(&len, 1);
1260             ENetPacket *packet = enet_packet_create(NULL, len+1, 0);
1261             if(!packet || demoplayback->read(packet->data+1, len)!=size_t(len))
1262             {
1263                 if(packet) enet_packet_destroy(packet);
1264                 enddemoplayback();
1265                 return;
1266             }
1267             packet->data[0] = N_DEMOPACKET;
1268             sendpacket(-1, chan, packet);
1269             if(!packet->referenceCount) enet_packet_destroy(packet);
1270             if(!demoplayback) break;
1271             if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
1272             {
1273                 enddemoplayback();
1274                 return;
1275             }
1276             lilswap(&nextplayback, 1);
1277         }
1278     }
1279 
timeupdate(int secs)1280     void timeupdate(int secs)
1281     {
1282         if(!demoplayback) return;
1283         if(secs <= 0) interm = -1;
1284         else gamelimit = max(gamelimit, nextplayback + secs*1000);
1285     }
1286 
seekdemo(char * t)1287     void seekdemo(char *t)
1288     {
1289         if(!demoplayback) return;
1290         bool rev = *t == '-';
1291         if(rev) t++;
1292         int mins = strtoul(t, &t, 10), secs = 0, millis = 0;
1293         if(*t == ':') secs = strtoul(t+1, &t, 10);
1294         else { secs = mins; mins = 0; }
1295         if(*t == '.') millis = strtoul(t+1, &t, 10);
1296         int offset = max(millis + (mins*60 + secs)*1000, 0), prevmillis = gamemillis;
1297         if(rev) while(gamelimit - offset > gamemillis)
1298         {
1299             gamemillis = gamelimit - offset;
1300             readdemo();
1301         }
1302         else if(offset > gamemillis)
1303         {
1304             gamemillis = offset;
1305             readdemo();
1306         }
1307         if(gamemillis > prevmillis)
1308         {
1309             if(!interm) sendf(-1, 1, "ri2", N_TIMEUP, max((gamelimit - gamemillis)/1000, 1));
1310 #ifndef STANDALONE
1311             cleardamagescreen();
1312 #endif
1313         }
1314     }
1315 
1316     ICOMMAND(seekdemo, "sN$", (char *t, int *numargs, ident *id),
1317     {
1318         if(*numargs > 0) seekdemo(t);
1319         else
1320         {
1321             int secs = gamemillis/1000;
1322             defformatstring(str, "%d:%02d.%03d", secs/60, secs%60, gamemillis%1000);
1323             if(*numargs < 0) result(str);
1324             else printsvar(id, str);
1325         }
1326     });
1327 
stopdemo()1328     void stopdemo()
1329     {
1330         if(m_demo) enddemoplayback();
1331         else enddemorecord();
1332     }
1333 
pausegame(bool val,clientinfo * ci=NULL)1334     void pausegame(bool val, clientinfo *ci = NULL)
1335     {
1336         if(gamepaused==val) return;
1337         gamepaused = val;
1338         sendf(-1, 1, "riii", N_PAUSEGAME, gamepaused ? 1 : 0, ci ? ci->clientnum : -1);
1339     }
1340 
checkpausegame()1341     void checkpausegame()
1342     {
1343         if(!gamepaused) return;
1344         int admins = 0;
1345         loopv(clients) if(clients[i]->privilege >= (restrictpausegame ? PRIV_ADMIN : PRIV_MASTER) || clients[i]->local) admins++;
1346         if(!admins) pausegame(false);
1347     }
1348 
forcepaused(bool paused)1349     void forcepaused(bool paused)
1350     {
1351         pausegame(paused);
1352     }
1353 
ispaused()1354     bool ispaused() { return gamepaused; }
1355 
changegamespeed(int val,clientinfo * ci=NULL)1356     void changegamespeed(int val, clientinfo *ci = NULL)
1357     {
1358         val = clamp(val, 10, 1000);
1359         if(gamespeed==val) return;
1360         gamespeed = val;
1361         sendf(-1, 1, "riii", N_GAMESPEED, gamespeed, ci ? ci->clientnum : -1);
1362     }
1363 
forcegamespeed(int speed)1364     void forcegamespeed(int speed)
1365     {
1366         changegamespeed(speed);
1367     }
1368 
scaletime(int t)1369     int scaletime(int t) { return t*gamespeed; }
1370 
1371     SVAR(serverauth, "");
1372 
1373     struct userkey
1374     {
1375         char *name;
1376         char *desc;
1377 
userkeyserver::userkey1378         userkey() : name(NULL), desc(NULL) {}
userkeyserver::userkey1379         userkey(char *name, char *desc) : name(name), desc(desc) {}
1380     };
1381 
hthash(const userkey & k)1382     static inline uint hthash(const userkey &k) { return ::hthash(k.name); }
htcmp(const userkey & x,const userkey & y)1383     static inline bool htcmp(const userkey &x, const userkey &y) { return !strcmp(x.name, y.name) && !strcmp(x.desc, y.desc); }
1384 
1385     struct userinfo : userkey
1386     {
1387         void *pubkey;
1388         int privilege;
1389 
userinfoserver::userinfo1390         userinfo() : pubkey(NULL), privilege(PRIV_NONE) {}
~userinfoserver::userinfo1391         ~userinfo() { delete[] name; delete[] desc; if(pubkey) freepubkey(pubkey); }
1392     };
1393     hashset<userinfo> users;
1394 
adduser(char * name,char * desc,char * pubkey,char * priv)1395     void adduser(char *name, char *desc, char *pubkey, char *priv)
1396     {
1397         userkey key(name, desc);
1398         userinfo &u = users[key];
1399         if(u.pubkey) { freepubkey(u.pubkey); u.pubkey = NULL; }
1400         if(!u.name) u.name = newstring(name);
1401         if(!u.desc) u.desc = newstring(desc);
1402         u.pubkey = parsepubkey(pubkey);
1403         switch(priv[0])
1404         {
1405             case 'a': case 'A': u.privilege = PRIV_ADMIN; break;
1406             case 'm': case 'M': default: u.privilege = PRIV_AUTH; break;
1407             case 'n': case 'N': u.privilege = PRIV_NONE; break;
1408         }
1409     }
1410     COMMAND(adduser, "ssss");
1411 
clearusers()1412     void clearusers()
1413     {
1414         users.clear();
1415     }
1416     COMMAND(clearusers, "");
1417 
hashpassword(int cn,int sessionid,const char * pwd,char * result,int maxlen)1418     void hashpassword(int cn, int sessionid, const char *pwd, char *result, int maxlen)
1419     {
1420         char buf[2*sizeof(string)];
1421         formatstring(buf, "%d %d ", cn, sessionid);
1422         concatstring(buf, pwd, sizeof(buf));
1423         if(!hashstring(buf, result, maxlen)) *result = '\0';
1424     }
1425 
checkpassword(clientinfo * ci,const char * wanted,const char * given)1426     bool checkpassword(clientinfo *ci, const char *wanted, const char *given)
1427     {
1428         string hash;
1429         hashpassword(ci->clientnum, ci->sessionid, wanted, hash, sizeof(hash));
1430         return !strcmp(hash, given);
1431     }
1432 
revokemaster(clientinfo * ci)1433     void revokemaster(clientinfo *ci)
1434     {
1435         ci->privilege = PRIV_NONE;
1436         if(ci->state.state==CS_SPECTATOR && !ci->local) aiman::removeai(ci);
1437     }
1438 
1439     extern void connected(clientinfo *ci);
1440 
setmaster(clientinfo * ci,bool val,const char * pass="",const char * authname=NULL,const char * authdesc=NULL,int authpriv=PRIV_MASTER,bool force=false,bool trial=false)1441     bool setmaster(clientinfo *ci, bool val, const char *pass = "", const char *authname = NULL, const char *authdesc = NULL, int authpriv = PRIV_MASTER, bool force = false, bool trial = false)
1442     {
1443         if(authname && !val) return false;
1444         const char *name = "";
1445         if(val)
1446         {
1447             bool haspass = adminpass[0] && checkpassword(ci, adminpass, pass);
1448             int wantpriv = ci->local || haspass ? PRIV_ADMIN : authpriv;
1449             if(wantpriv <= ci->privilege) return true;
1450             else if(wantpriv <= PRIV_MASTER && !force)
1451             {
1452                 if(ci->state.state==CS_SPECTATOR)
1453                 {
1454                     sendf(ci->clientnum, 1, "ris", N_SERVMSG, "Spectators may not claim master.");
1455                     return false;
1456                 }
1457                 loopv(clients) if(ci!=clients[i] && clients[i]->privilege)
1458                 {
1459                     sendf(ci->clientnum, 1, "ris", N_SERVMSG, "Master is already claimed.");
1460                     return false;
1461                 }
1462                 if(!authname && !(mastermask&MM_AUTOAPPROVE) && !ci->privilege && !ci->local)
1463                 {
1464                     sendf(ci->clientnum, 1, "ris", N_SERVMSG, "This server requires you to use the \"/auth\" command to claim master.");
1465                     return false;
1466                 }
1467             }
1468             if(trial) return true;
1469             ci->privilege = wantpriv;
1470             name = privname(ci->privilege);
1471         }
1472         else
1473         {
1474             if(!ci->privilege) return false;
1475             if(trial) return true;
1476             name = privname(ci->privilege);
1477             revokemaster(ci);
1478         }
1479         bool hasmaster = false;
1480         loopv(clients) if(clients[i]->local || clients[i]->privilege >= PRIV_MASTER) hasmaster = true;
1481         if(!hasmaster)
1482         {
1483             mastermode = MM_OPEN;
1484             allowedips.shrink(0);
1485         }
1486         string msg;
1487         if(val && authname)
1488         {
1489             if(authdesc && authdesc[0]) formatstring(msg, "%s claimed %s as '\fs\f5%s\fr' [\fs\f0%s\fr]", colorname(ci), name, authname, authdesc);
1490             else formatstring(msg, "%s claimed %s as '\fs\f5%s\fr'", colorname(ci), name, authname);
1491         }
1492         else formatstring(msg, "%s %s %s", colorname(ci), val ? "claimed" : "relinquished", name);
1493         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1494         putint(p, N_SERVMSG);
1495         sendstring(msg, p);
1496         putint(p, N_CURRENTMASTER);
1497         putint(p, mastermode);
1498         loopv(clients) if(clients[i]->privilege >= PRIV_MASTER)
1499         {
1500             putint(p, clients[i]->clientnum);
1501             putint(p, clients[i]->privilege);
1502         }
1503         putint(p, -1);
1504         sendpacket(-1, 1, p.finalize());
1505         checkpausegame();
1506         return true;
1507     }
1508 
trykick(clientinfo * ci,int victim,const char * reason=NULL,const char * authname=NULL,const char * authdesc=NULL,int authpriv=PRIV_NONE,bool trial=false)1509     bool trykick(clientinfo *ci, int victim, const char *reason = NULL, const char *authname = NULL, const char *authdesc = NULL, int authpriv = PRIV_NONE, bool trial = false)
1510     {
1511         int priv = ci->privilege;
1512         if(authname)
1513         {
1514             if(priv >= authpriv || ci->local) authname = authdesc = NULL;
1515             else priv = authpriv;
1516         }
1517         if((priv || ci->local) && ci->clientnum!=victim)
1518         {
1519             clientinfo *vinfo = (clientinfo *)getclientinfo(victim);
1520             if(vinfo && vinfo->connected && (priv >= vinfo->privilege || ci->local) && vinfo->privilege < PRIV_ADMIN && !vinfo->local)
1521             {
1522                 if(trial) return true;
1523                 string kicker;
1524                 if(authname)
1525                 {
1526                     if(authdesc && authdesc[0]) formatstring(kicker, "%s as '\fs\f5%s\fr' [\fs\f0%s\fr]", colorname(ci), authname, authdesc);
1527                     else formatstring(kicker, "%s as '\fs\f5%s\fr'", colorname(ci), authname);
1528                 }
1529                 else copystring(kicker, colorname(ci));
1530                 if(reason && reason[0]) sendservmsgf("%s kicked %s because: %s", kicker, colorname(vinfo), reason);
1531                 else sendservmsgf("%s kicked %s", kicker, colorname(vinfo));
1532                 uint ip = getclientip(victim);
1533                 addban(ip, 4*60*60000);
1534                 kickclients(ip, ci, priv);
1535             }
1536         }
1537         return false;
1538     }
1539 
findscore(clientinfo * ci,bool insert)1540     savedscore *findscore(clientinfo *ci, bool insert)
1541     {
1542         uint ip = getclientip(ci->clientnum);
1543         if(!ip && !ci->local) return 0;
1544         if(!insert)
1545         {
1546             loopv(clients)
1547             {
1548                 clientinfo *oi = clients[i];
1549                 if(oi->clientnum != ci->clientnum && getclientip(oi->clientnum) == ip && !strcmp(oi->name, ci->name))
1550                 {
1551                     oi->state.timeplayed += lastmillis - oi->state.lasttimeplayed;
1552                     oi->state.lasttimeplayed = lastmillis;
1553                     static savedscore curscore;
1554                     curscore.save(oi->state);
1555                     return &curscore;
1556                 }
1557             }
1558         }
1559         loopv(scores)
1560         {
1561             savedscore &sc = scores[i];
1562             if(sc.ip == ip && !strcmp(sc.name, ci->name)) return &sc;
1563         }
1564         if(!insert) return 0;
1565         savedscore &sc = scores.add();
1566         sc.ip = ip;
1567         copystring(sc.name, ci->name);
1568         return &sc;
1569     }
1570 
savescore(clientinfo * ci)1571     void savescore(clientinfo *ci)
1572     {
1573         savedscore *sc = findscore(ci, true);
1574         if(sc) sc->save(ci->state);
1575     }
1576 
1577     static struct msgfilter
1578     {
1579         uchar msgmask[NUMMSG];
1580 
msgfilterserver::msgfilter1581         msgfilter(int msg, ...)
1582         {
1583             memset(msgmask, 0, sizeof(msgmask));
1584             va_list msgs;
1585             va_start(msgs, msg);
1586             for(uchar val = 1; msg < NUMMSG; msg = va_arg(msgs, int))
1587             {
1588                 if(msg < 0) val = uchar(-msg);
1589                 else msgmask[msg] = val;
1590             }
1591             va_end(msgs);
1592         }
1593 
operator []server::msgfilter1594         uchar operator[](int msg) const { return msg >= 0 && msg < NUMMSG ? msgmask[msg] : 0; }
1595     } msgfilter(-1, N_CONNECT, N_SERVINFO, N_INITCLIENT, N_WELCOME, N_MAPCHANGE, N_SERVMSG, N_DAMAGE, N_HITPUSH, N_SHOTFX, N_EXPLODEFX, N_DIED, N_SPAWNSTATE, N_FORCEDEATH, N_TEAMINFO, N_ITEMACC, N_ITEMSPAWN, N_TIMEUP, N_CDIS, N_CURRENTMASTER, N_PONG, N_RESUME, N_BASESCORE, N_BASEINFO, N_BASEREGEN, N_ANNOUNCE, N_SENDDEMOLIST, N_SENDDEMO, N_DEMOPLAYBACK, N_SENDMAP, N_DROPFLAG, N_SCOREFLAG, N_RETURNFLAG, N_RESETFLAG, N_INVISFLAG, N_CLIENT, N_AUTHCHAL, N_INITAI, N_EXPIRETOKENS, N_DROPTOKENS, N_STEALTOKENS, N_DEMOPACKET, -2, N_REMIP, N_NEWMAP, N_GETMAP, N_SENDMAP, N_CLIPBOARD, -3, N_EDITENT, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, N_DELCUBE, N_EDITVAR, N_EDITVSLOT, N_UNDO, N_REDO, -4, N_POS, NUMMSG),
1596       connectfilter(-1, N_CONNECT, -2, N_AUTHANS, -3, N_PING, NUMMSG);
1597 
checktype(int type,clientinfo * ci)1598     int checktype(int type, clientinfo *ci)
1599     {
1600         if(ci)
1601         {
1602             if(!ci->connected) switch(connectfilter[type])
1603             {
1604                 // allow only before authconnect
1605                 case 1: return !ci->connectauth ? type : -1;
1606                 // allow only during authconnect
1607                 case 2: return ci->connectauth ? type : -1;
1608                 // always allow
1609                 case 3: return type;
1610                 // never allow
1611                 default: return -1;
1612             }
1613             if(ci->local) return type;
1614         }
1615         switch(msgfilter[type])
1616         {
1617             // server-only messages
1618             case 1: return ci ? -1 : type;
1619             // only allowed in coop-edit
1620             case 2: if(m_edit) break; return -1;
1621             // only allowed in coop-edit, no overflow check
1622             case 3: return m_edit ? type : -1;
1623             // no overflow check
1624             case 4: return type;
1625         }
1626         if(ci && ++ci->overflow >= 200) return -2;
1627         return type;
1628     }
1629 
1630     struct worldstate
1631     {
1632         int uses, len;
1633         uchar *data;
1634 
worldstateserver::worldstate1635         worldstate() : uses(0), len(0), data(NULL) {}
1636 
setupserver::worldstate1637         void setup(int n) { len = n; data = new uchar[n]; }
cleanupserver::worldstate1638         void cleanup() { DELETEA(data); len = 0; }
containsserver::worldstate1639         bool contains(const uchar *p) const { return p >= data && p < &data[len]; }
1640     };
1641     vector<worldstate> worldstates;
1642     bool reliablemessages = false;
1643 
cleanworldstate(ENetPacket * packet)1644     void cleanworldstate(ENetPacket *packet)
1645     {
1646         loopv(worldstates)
1647         {
1648             worldstate &ws = worldstates[i];
1649             if(!ws.contains(packet->data)) continue;
1650             ws.uses--;
1651             if(ws.uses <= 0)
1652             {
1653                 ws.cleanup();
1654                 worldstates.removeunordered(i);
1655             }
1656             break;
1657         }
1658     }
1659 
flushclientposition(clientinfo & ci)1660     void flushclientposition(clientinfo &ci)
1661     {
1662         if(ci.position.empty() || (!hasnonlocalclients() && !demorecord)) return;
1663         packetbuf p(ci.position.length(), 0);
1664         p.put(ci.position.getbuf(), ci.position.length());
1665         ci.position.setsize(0);
1666         sendpacket(-1, 0, p.finalize(), ci.ownernum);
1667     }
1668 
sendpositions(worldstate & ws,ucharbuf & wsbuf)1669     static void sendpositions(worldstate &ws, ucharbuf &wsbuf)
1670     {
1671         if(wsbuf.empty()) return;
1672         int wslen = wsbuf.length();
1673         recordpacket(0, wsbuf.buf, wslen);
1674         wsbuf.put(wsbuf.buf, wslen);
1675         loopv(clients)
1676         {
1677             clientinfo &ci = *clients[i];
1678             if(ci.state.aitype != AI_NONE) continue;
1679             uchar *data = wsbuf.buf;
1680             int size = wslen;
1681             if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; }
1682             if(size <= 0) continue;
1683             ENetPacket *packet = enet_packet_create(data, size, ENET_PACKET_FLAG_NO_ALLOCATE);
1684             sendpacket(ci.clientnum, 0, packet);
1685             if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; }
1686             else enet_packet_destroy(packet);
1687         }
1688         wsbuf.offset(wsbuf.length());
1689     }
1690 
addposition(worldstate & ws,ucharbuf & wsbuf,int mtu,clientinfo & bi,clientinfo & ci)1691     static inline void addposition(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci)
1692     {
1693         if(bi.position.empty()) return;
1694         if(wsbuf.length() + bi.position.length() > mtu) sendpositions(ws, wsbuf);
1695         int offset = wsbuf.length();
1696         wsbuf.put(bi.position.getbuf(), bi.position.length());
1697         bi.position.setsize(0);
1698         int len = wsbuf.length() - offset;
1699         if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; }
1700         else ci.wslen += len;
1701     }
1702 
sendmessages(worldstate & ws,ucharbuf & wsbuf)1703     static void sendmessages(worldstate &ws, ucharbuf &wsbuf)
1704     {
1705         if(wsbuf.empty()) return;
1706         int wslen = wsbuf.length();
1707         recordpacket(1, wsbuf.buf, wslen);
1708         wsbuf.put(wsbuf.buf, wslen);
1709         loopv(clients)
1710         {
1711             clientinfo &ci = *clients[i];
1712             if(ci.state.aitype != AI_NONE) continue;
1713             uchar *data = wsbuf.buf;
1714             int size = wslen;
1715             if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; }
1716             if(size <= 0) continue;
1717             ENetPacket *packet = enet_packet_create(data, size, (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE);
1718             sendpacket(ci.clientnum, 1, packet);
1719             if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; }
1720             else enet_packet_destroy(packet);
1721         }
1722         wsbuf.offset(wsbuf.length());
1723     }
1724 
addmessages(worldstate & ws,ucharbuf & wsbuf,int mtu,clientinfo & bi,clientinfo & ci)1725     static inline void addmessages(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci)
1726     {
1727         if(bi.messages.empty()) return;
1728         if(wsbuf.length() + 10 + bi.messages.length() > mtu) sendmessages(ws, wsbuf);
1729         int offset = wsbuf.length();
1730         putint(wsbuf, N_CLIENT);
1731         putint(wsbuf, bi.clientnum);
1732         putuint(wsbuf, bi.messages.length());
1733         wsbuf.put(bi.messages.getbuf(), bi.messages.length());
1734         bi.messages.setsize(0);
1735         int len = wsbuf.length() - offset;
1736         if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; }
1737         else ci.wslen += len;
1738     }
1739 
buildworldstate()1740     bool buildworldstate()
1741     {
1742         int wsmax = 0;
1743         loopv(clients)
1744         {
1745             clientinfo &ci = *clients[i];
1746             ci.overflow = 0;
1747             ci.wsdata = NULL;
1748             wsmax += ci.position.length();
1749             if(ci.messages.length()) wsmax += 10 + ci.messages.length();
1750         }
1751         if(wsmax <= 0)
1752         {
1753             reliablemessages = false;
1754             return false;
1755         }
1756         worldstate &ws = worldstates.add();
1757         ws.setup(2*wsmax);
1758         int mtu = getservermtu() - 100;
1759         if(mtu <= 0) mtu = ws.len;
1760         ucharbuf wsbuf(ws.data, ws.len);
1761         loopv(clients)
1762         {
1763             clientinfo &ci = *clients[i];
1764             if(ci.state.aitype != AI_NONE) continue;
1765             addposition(ws, wsbuf, mtu, ci, ci);
1766             loopvj(ci.bots) addposition(ws, wsbuf, mtu, *ci.bots[j], ci);
1767         }
1768         sendpositions(ws, wsbuf);
1769         loopv(clients)
1770         {
1771             clientinfo &ci = *clients[i];
1772             if(ci.state.aitype != AI_NONE) continue;
1773             addmessages(ws, wsbuf, mtu, ci, ci);
1774             loopvj(ci.bots) addmessages(ws, wsbuf, mtu, *ci.bots[j], ci);
1775         }
1776         sendmessages(ws, wsbuf);
1777         reliablemessages = false;
1778         if(ws.uses) return true;
1779         ws.cleanup();
1780         worldstates.drop();
1781         return false;
1782     }
1783 
sendpackets(bool force)1784     bool sendpackets(bool force)
1785     {
1786         if(clients.empty() || (!hasnonlocalclients() && !demorecord)) return false;
1787         enet_uint32 curtime = enet_time_get()-lastsend;
1788         if(curtime<33 && !force) return false;
1789         bool flush = buildworldstate();
1790         lastsend += curtime - (curtime%33);
1791         return flush;
1792     }
1793 
1794     template<class T>
sendstate(gamestate & gs,T & p)1795     void sendstate(gamestate &gs, T &p)
1796     {
1797         putint(p, gs.lifesequence);
1798         putint(p, gs.health);
1799         putint(p, gs.maxhealth);
1800         putint(p, gs.armour);
1801         putint(p, gs.armourtype);
1802         putint(p, gs.gunselect);
1803         loopi(GUN_PISTOL-GUN_SG+1) putint(p, gs.ammo[GUN_SG+i]);
1804     }
1805 
spawnstate(clientinfo * ci)1806     void spawnstate(clientinfo *ci)
1807     {
1808         gamestate &gs = ci->state;
1809         gs.spawnstate(gamemode);
1810         gs.lifesequence = (gs.lifesequence + 1)&0x7F;
1811     }
1812 
sendspawn(clientinfo * ci)1813     void sendspawn(clientinfo *ci)
1814     {
1815         gamestate &gs = ci->state;
1816         spawnstate(ci);
1817         sendf(ci->ownernum, 1, "rii7v", N_SPAWNSTATE, ci->clientnum, gs.lifesequence,
1818             gs.health, gs.maxhealth,
1819             gs.armour, gs.armourtype,
1820             gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG]);
1821         gs.lastspawn = gamemillis;
1822     }
1823 
sendwelcome(clientinfo * ci)1824     void sendwelcome(clientinfo *ci)
1825     {
1826         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1827         int chan = welcomepacket(p, ci);
1828         sendpacket(ci->clientnum, chan, p.finalize());
1829     }
1830 
putinitclient(clientinfo * ci,packetbuf & p)1831     void putinitclient(clientinfo *ci, packetbuf &p)
1832     {
1833         if(ci->state.aitype != AI_NONE)
1834         {
1835             putint(p, N_INITAI);
1836             putint(p, ci->clientnum);
1837             putint(p, ci->ownernum);
1838             putint(p, ci->state.aitype);
1839             putint(p, ci->state.skill);
1840             putint(p, ci->playermodel);
1841             sendstring(ci->name, p);
1842             sendstring(ci->team, p);
1843         }
1844         else
1845         {
1846             putint(p, N_INITCLIENT);
1847             putint(p, ci->clientnum);
1848             sendstring(ci->name, p);
1849             sendstring(ci->team, p);
1850             putint(p, ci->playermodel);
1851         }
1852     }
1853 
welcomeinitclient(packetbuf & p,int exclude=-1)1854     void welcomeinitclient(packetbuf &p, int exclude = -1)
1855     {
1856         loopv(clients)
1857         {
1858             clientinfo *ci = clients[i];
1859             if(!ci->connected || ci->clientnum == exclude) continue;
1860 
1861             putinitclient(ci, p);
1862         }
1863     }
1864 
hasmap(clientinfo * ci)1865     bool hasmap(clientinfo *ci)
1866     {
1867         return (m_edit && (clients.length() > 0 || ci->local)) ||
1868                (smapname[0] && (!m_timed || gamemillis < gamelimit || (ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || numclients(ci->clientnum, true, true, true)));
1869     }
1870 
welcomepacket(packetbuf & p,clientinfo * ci)1871     int welcomepacket(packetbuf &p, clientinfo *ci)
1872     {
1873         putint(p, N_WELCOME);
1874         putint(p, N_MAPCHANGE);
1875         sendstring(smapname, p);
1876         putint(p, gamemode);
1877         putint(p, notgotitems ? 1 : 0);
1878         if(!ci || (m_timed && smapname[0]))
1879         {
1880             putint(p, N_TIMEUP);
1881             putint(p, gamemillis < gamelimit && !interm ? max((gamelimit - gamemillis)/1000, 1) : 0);
1882         }
1883         if(!notgotitems)
1884         {
1885             putint(p, N_ITEMLIST);
1886             loopv(sents) if(sents[i].spawned)
1887             {
1888                 putint(p, i);
1889                 putint(p, sents[i].type);
1890             }
1891             putint(p, -1);
1892         }
1893         bool hasmaster = false;
1894         if(mastermode != MM_OPEN)
1895         {
1896             putint(p, N_CURRENTMASTER);
1897             putint(p, mastermode);
1898             hasmaster = true;
1899         }
1900         loopv(clients) if(clients[i]->privilege >= PRIV_MASTER)
1901         {
1902             if(!hasmaster)
1903             {
1904                 putint(p, N_CURRENTMASTER);
1905                 putint(p, mastermode);
1906                 hasmaster = true;
1907             }
1908             putint(p, clients[i]->clientnum);
1909             putint(p, clients[i]->privilege);
1910         }
1911         if(hasmaster) putint(p, -1);
1912         if(gamepaused)
1913         {
1914             putint(p, N_PAUSEGAME);
1915             putint(p, 1);
1916             putint(p, -1);
1917         }
1918         if(gamespeed != 100)
1919         {
1920             putint(p, N_GAMESPEED);
1921             putint(p, gamespeed);
1922             putint(p, -1);
1923         }
1924         if(m_teammode)
1925         {
1926             putint(p, N_TEAMINFO);
1927             enumerate(teaminfos, teaminfo, t,
1928                 if(t.frags) { sendstring(t.team, p); putint(p, t.frags); }
1929             );
1930             sendstring("", p);
1931         }
1932         if(ci)
1933         {
1934             putint(p, N_SETTEAM);
1935             putint(p, ci->clientnum);
1936             sendstring(ci->team, p);
1937             putint(p, -1);
1938         }
1939         if(ci && (m_demo || m_mp(gamemode)) && ci->state.state!=CS_SPECTATOR)
1940         {
1941             if(smode && !smode->canspawn(ci, true))
1942             {
1943                 ci->state.state = CS_DEAD;
1944                 putint(p, N_FORCEDEATH);
1945                 putint(p, ci->clientnum);
1946                 sendf(-1, 1, "ri2x", N_FORCEDEATH, ci->clientnum, ci->clientnum);
1947             }
1948             else
1949             {
1950                 gamestate &gs = ci->state;
1951                 spawnstate(ci);
1952                 putint(p, N_SPAWNSTATE);
1953                 putint(p, ci->clientnum);
1954                 sendstate(gs, p);
1955                 gs.lastspawn = gamemillis;
1956             }
1957         }
1958         if(ci && ci->state.state==CS_SPECTATOR)
1959         {
1960             putint(p, N_SPECTATOR);
1961             putint(p, ci->clientnum);
1962             putint(p, 1);
1963             sendf(-1, 1, "ri3x", N_SPECTATOR, ci->clientnum, 1, ci->clientnum);
1964         }
1965         if(!ci || clients.length()>1)
1966         {
1967             putint(p, N_RESUME);
1968             loopv(clients)
1969             {
1970                 clientinfo *oi = clients[i];
1971                 if(ci && oi->clientnum==ci->clientnum) continue;
1972                 putint(p, oi->clientnum);
1973                 putint(p, oi->state.state);
1974                 putint(p, oi->state.frags);
1975                 putint(p, oi->state.flags);
1976                 putint(p, oi->state.deaths);
1977                 putint(p, oi->state.quadmillis);
1978                 sendstate(oi->state, p);
1979             }
1980             putint(p, -1);
1981             welcomeinitclient(p, ci ? ci->clientnum : -1);
1982         }
1983         if(smode) smode->initclient(ci, p, true);
1984         return 1;
1985     }
1986 
restorescore(clientinfo * ci)1987     bool restorescore(clientinfo *ci)
1988     {
1989         //if(ci->local) return false;
1990         savedscore *sc = findscore(ci, false);
1991         if(sc)
1992         {
1993             sc->restore(ci->state);
1994             return true;
1995         }
1996         return false;
1997     }
1998 
sendresume(clientinfo * ci)1999     void sendresume(clientinfo *ci)
2000     {
2001         gamestate &gs = ci->state;
2002         sendf(-1, 1, "ri3i4i6vi", N_RESUME, ci->clientnum, gs.state,
2003             gs.frags, gs.flags, gs.deaths, gs.quadmillis,
2004             gs.lifesequence,
2005             gs.health, gs.maxhealth,
2006             gs.armour, gs.armourtype,
2007             gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG], -1);
2008     }
2009 
sendinitclient(clientinfo * ci)2010     void sendinitclient(clientinfo *ci)
2011     {
2012         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2013         putinitclient(ci, p);
2014         sendpacket(-1, 1, p.finalize(), ci->clientnum);
2015     }
2016 
loaditems()2017     void loaditems()
2018     {
2019         resetitems();
2020         notgotitems = true;
2021         if(m_edit || !loadents(smapname, ments, &mcrc))
2022             return;
2023         loopv(ments) if(canspawnitem(ments[i].type))
2024         {
2025             server_entity se = { NOTUSED, 0, false };
2026             while(sents.length()<=i) sents.add(se);
2027             sents[i].type = ments[i].type;
2028             if(m_mp(gamemode) && delayspawn(sents[i].type)) sents[i].spawntime = spawntime(sents[i].type);
2029             else sents[i].spawned = true;
2030         }
2031         notgotitems = false;
2032     }
2033 
changemap(const char * s,int mode)2034     void changemap(const char *s, int mode)
2035     {
2036         stopdemo();
2037         pausegame(false);
2038         changegamespeed(100);
2039         if(smode) smode->cleanup();
2040         aiman::clearai();
2041 
2042         gamemode = mode;
2043         gamemillis = 0;
2044         gamelimit = 10*60000;
2045         interm = 0;
2046         nextexceeded = 0;
2047         copystring(smapname, s);
2048         loaditems();
2049         scores.shrink(0);
2050         shouldcheckteamkills = false;
2051         teamkills.shrink(0);
2052         loopv(clients)
2053         {
2054             clientinfo *ci = clients[i];
2055             ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
2056         }
2057 
2058         if(!m_mp(gamemode)) kicknonlocalclients(DISC_LOCAL);
2059 
2060         sendf(-1, 1, "risii", N_MAPCHANGE, smapname, gamemode, 1);
2061 
2062         if(m_capture) smode = &capturemode;
2063         else if(m_ctf) smode = &ctfmode;
2064         else if(m_collect) smode = &collectmode;
2065         else smode = NULL;
2066 
2067         clearteaminfo();
2068         if(m_teammode) autoteam();
2069 
2070         if(m_timed && smapname[0]) sendf(-1, 1, "ri2", N_TIMEUP, gamemillis < gamelimit && !interm ? max((gamelimit - gamemillis)/1000, 1) : 0);
2071         loopv(clients)
2072         {
2073             clientinfo *ci = clients[i];
2074             ci->mapchange();
2075             ci->state.lasttimeplayed = lastmillis;
2076             if(m_mp(gamemode) && ci->state.state!=CS_SPECTATOR) sendspawn(ci);
2077         }
2078 
2079         aiman::changemap();
2080 
2081         if(m_demo)
2082         {
2083             if(clients.length()) setupdemoplayback();
2084         }
2085         else
2086         {
2087             if(demonextmatch) setupdemorecord();
2088             demonextmatch = autorecorddemo!=0;
2089         }
2090 
2091         if(smode) smode->setup();
2092     }
2093 
rotatemap(bool next)2094     void rotatemap(bool next)
2095     {
2096         if(!maprotations.inrange(curmaprotation))
2097         {
2098             changemap("", 1);
2099             return;
2100         }
2101         if(next)
2102         {
2103             curmaprotation = findmaprotation(gamemode, smapname);
2104             if(curmaprotation >= 0) nextmaprotation();
2105             else curmaprotation = smapname[0] ? max(findmaprotation(gamemode, ""), 0) : 0;
2106         }
2107         maprotation &rot = maprotations[curmaprotation];
2108         changemap(rot.map, rot.findmode(gamemode));
2109     }
2110 
2111     struct votecount
2112     {
2113         char *map;
2114         int mode, count;
votecountserver::votecount2115         votecount() {}
votecountserver::votecount2116         votecount(char *s, int n) : map(s), mode(n), count(0) {}
2117     };
2118 
checkvotes(bool force=false)2119     void checkvotes(bool force = false)
2120     {
2121         vector<votecount> votes;
2122         int maxvotes = 0;
2123         loopv(clients)
2124         {
2125             clientinfo *oi = clients[i];
2126             if(oi->state.state==CS_SPECTATOR && !oi->privilege && !oi->local) continue;
2127             if(oi->state.aitype!=AI_NONE) continue;
2128             maxvotes++;
2129             if(!m_valid(oi->modevote)) continue;
2130             votecount *vc = NULL;
2131             loopvj(votes) if(!strcmp(oi->mapvote, votes[j].map) && oi->modevote==votes[j].mode)
2132             {
2133                 vc = &votes[j];
2134                 break;
2135             }
2136             if(!vc) vc = &votes.add(votecount(oi->mapvote, oi->modevote));
2137             vc->count++;
2138         }
2139         votecount *best = NULL;
2140         loopv(votes) if(!best || votes[i].count > best->count || (votes[i].count == best->count && rnd(2))) best = &votes[i];
2141         if(force || (best && best->count > maxvotes/2))
2142         {
2143             sendpackets(true);
2144             if(demorecord) enddemorecord();
2145             if(best && (best->count > (force ? 1 : maxvotes/2)))
2146             {
2147                 sendservmsg(force ? "vote passed by default" : "vote passed by majority");
2148                 changemap(best->map, best->mode);
2149             }
2150             else rotatemap(true);
2151         }
2152     }
2153 
forcemap(const char * map,int mode)2154     void forcemap(const char *map, int mode)
2155     {
2156         stopdemo();
2157         if(!map[0] && !m_check(mode, M_EDIT))
2158         {
2159             int idx = findmaprotation(mode, smapname);
2160             if(idx < 0 && smapname[0]) idx = findmaprotation(mode, "");
2161             if(idx < 0) return;
2162             map = maprotations[idx].map;
2163         }
2164         if(hasnonlocalclients()) sendservmsgf("local player forced %s on map %s", modename(mode), map[0] ? map : "[new map]");
2165         changemap(map, mode);
2166     }
2167 
vote(const char * map,int reqmode,int sender)2168     void vote(const char *map, int reqmode, int sender)
2169     {
2170         clientinfo *ci = getinfo(sender);
2171         if(!ci || (ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || (!ci->local && !m_mp(reqmode))) return;
2172         if(!m_valid(reqmode)) return;
2173         if(!map[0] && !m_check(reqmode, M_EDIT))
2174         {
2175             int idx = findmaprotation(reqmode, smapname);
2176             if(idx < 0 && smapname[0]) idx = findmaprotation(reqmode, "");
2177             if(idx < 0) return;
2178             map = maprotations[idx].map;
2179         }
2180         if(lockmaprotation && !ci->local && ci->privilege < (lockmaprotation > 1 ? PRIV_ADMIN : PRIV_MASTER) && findmaprotation(reqmode, map) < 0)
2181         {
2182             sendf(sender, 1, "ris", N_SERVMSG, "This server has locked the map rotation.");
2183             return;
2184         }
2185         copystring(ci->mapvote, map);
2186         ci->modevote = reqmode;
2187         if(ci->local || (ci->privilege && mastermode>=MM_VETO))
2188         {
2189             sendpackets(true);
2190             if(demorecord) enddemorecord();
2191             if(!ci->local || hasnonlocalclients())
2192                 sendservmsgf("%s forced %s on map %s", colorname(ci), modename(ci->modevote), ci->mapvote[0] ? ci->mapvote : "[new map]");
2193             changemap(ci->mapvote, ci->modevote);
2194         }
2195         else
2196         {
2197             sendservmsgf("%s suggests %s on map %s (select map to vote)", colorname(ci), modename(reqmode), map[0] ? map : "[new map]");
2198             checkvotes();
2199         }
2200     }
2201 
2202     VAR(overtime, 0, 0, 1);
2203 
checkovertime()2204     bool checkovertime()
2205     {
2206         if(!m_timed || !overtime) return false;
2207         const char* topteam = NULL;
2208         int topscore = INT_MIN;
2209         bool tied = false;
2210         if(m_teammode)
2211         {
2212             vector<teamscore> scores;
2213             if(smode && smode->hidefrags()) smode->getteamscores(scores);
2214             loopv(clients)
2215             {
2216                 clientinfo *ci = clients[i];
2217                 if(ci->state.state==CS_SPECTATOR || !ci->team[0]) continue;
2218                 int score = 0;
2219                 if(smode && smode->hidefrags())
2220                 {
2221                     int idx = scores.htfind(ci->team);
2222                     if(idx >= 0) score = scores[idx].score;
2223                 }
2224                 else if(teaminfo *ti = teaminfos.access(ci->team)) score = ti->frags;
2225                 if(!topteam || score > topscore) { topteam = ci->team; topscore = score; tied = false; }
2226                 else if(score == topscore && strcmp(ci->team, topteam)) tied = true;
2227             }
2228         }
2229         else
2230         {
2231             loopv(clients)
2232             {
2233                 clientinfo *ci = clients[i];
2234                 if(ci->state.state==CS_SPECTATOR) continue;
2235                 int score = ci->state.frags;
2236                 if(score > topscore) { topscore = score; tied = false; }
2237                 else if(score == topscore) tied = true;
2238             }
2239         }
2240         if(!tied) return false;
2241         sendservmsg("the game is tied with overtime");
2242         gamelimit = max(gamemillis, gamelimit) + 2*60000;
2243         sendf(-1, 1, "ri2", N_TIMEUP, max((gamelimit - gamemillis)/1000, 1));
2244         return true;
2245     }
2246 
checkintermission(bool force=false)2247     void checkintermission(bool force = false)
2248     {
2249         if(gamemillis >= gamelimit && !interm && (force || !checkovertime()))
2250         {
2251             sendf(-1, 1, "ri2", N_TIMEUP, 0);
2252             if(smode) smode->intermission();
2253             changegamespeed(100);
2254             interm = gamemillis + 10000;
2255         }
2256     }
2257 
startintermission()2258     void startintermission() { gamelimit = min(gamelimit, gamemillis); checkintermission(true); }
2259 
dodamage(clientinfo * target,clientinfo * actor,int damage,int gun,const vec & hitpush=vec (0,0,0))2260     void dodamage(clientinfo *target, clientinfo *actor, int damage, int gun, const vec &hitpush = vec(0, 0, 0))
2261     {
2262         gamestate &ts = target->state;
2263         ts.dodamage(damage);
2264         if(target!=actor && !isteam(target->team, actor->team)) actor->state.damage += damage;
2265         sendf(-1, 1, "ri6", N_DAMAGE, target->clientnum, actor->clientnum, damage, ts.armour, ts.health);
2266         if(target==actor) target->setpushed();
2267         else if(!hitpush.iszero())
2268         {
2269             ivec v(vec(hitpush).rescale(DNF));
2270             sendf(ts.health<=0 ? -1 : target->ownernum, 1, "ri7", N_HITPUSH, target->clientnum, gun, damage, v.x, v.y, v.z);
2271             target->setpushed();
2272         }
2273         if(ts.health<=0)
2274         {
2275             target->state.deaths++;
2276             int fragvalue = smode ? smode->fragvalue(target, actor) : (target==actor || isteam(target->team, actor->team) ? -1 : 1);
2277             actor->state.frags += fragvalue;
2278             if(fragvalue>0)
2279             {
2280                 int friends = 0, enemies = 0; // note: friends also includes the fragger
2281                 if(m_teammode) loopv(clients) if(strcmp(clients[i]->team, actor->team)) enemies++; else friends++;
2282                 else { friends = 1; enemies = clients.length()-1; }
2283                 actor->state.effectiveness += fragvalue*friends/float(max(enemies, 1));
2284             }
2285             teaminfo *t = m_teammode ? teaminfos.access(actor->team) : NULL;
2286             if(t) t->frags += fragvalue;
2287             sendf(-1, 1, "ri5", N_DIED, target->clientnum, actor->clientnum, actor->state.frags, t ? t->frags : 0);
2288             target->position.setsize(0);
2289             if(smode) smode->died(target, actor);
2290             ts.state = CS_DEAD;
2291             ts.lastdeath = gamemillis;
2292             if(actor!=target && isteam(actor->team, target->team))
2293             {
2294                 actor->state.teamkills++;
2295                 addteamkill(actor, target, 1);
2296             }
2297             ts.deadflush = ts.lastdeath + DEATHMILLIS;
2298             // don't issue respawn yet until DEATHMILLIS has elapsed
2299             // ts.respawn();
2300         }
2301     }
2302 
suicide(clientinfo * ci)2303     void suicide(clientinfo *ci)
2304     {
2305         gamestate &gs = ci->state;
2306         if(gs.state!=CS_ALIVE) return;
2307         int fragvalue = smode ? smode->fragvalue(ci, ci) : -1;
2308         ci->state.frags += fragvalue;
2309         ci->state.deaths++;
2310         teaminfo *t = m_teammode ? teaminfos.access(ci->team) : NULL;
2311         if(t) t->frags += fragvalue;
2312         sendf(-1, 1, "ri5", N_DIED, ci->clientnum, ci->clientnum, gs.frags, t ? t->frags : 0);
2313         ci->position.setsize(0);
2314         if(smode) smode->died(ci, NULL);
2315         gs.state = CS_DEAD;
2316         gs.lastdeath = gamemillis;
2317         gs.respawn();
2318     }
2319 
process(clientinfo * ci)2320     void suicideevent::process(clientinfo *ci)
2321     {
2322         suicide(ci);
2323     }
2324 
process(clientinfo * ci)2325     void explodeevent::process(clientinfo *ci)
2326     {
2327         gamestate &gs = ci->state;
2328         switch(gun)
2329         {
2330             case GUN_RL:
2331                 if(!gs.rockets.remove(id)) return;
2332                 break;
2333 
2334             case GUN_GL:
2335                 if(!gs.grenades.remove(id)) return;
2336                 break;
2337 
2338             default:
2339                 return;
2340         }
2341         sendf(-1, 1, "ri4x", N_EXPLODEFX, ci->clientnum, gun, id, ci->ownernum);
2342         loopv(hits)
2343         {
2344             hitinfo &h = hits[i];
2345             clientinfo *target = getinfo(h.target);
2346             if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>guns[gun].exprad) continue;
2347 
2348             bool dup = false;
2349             loopj(i) if(hits[j].target==h.target) { dup = true; break; }
2350             if(dup) continue;
2351 
2352             int damage = guns[gun].damage;
2353             if(gs.quadmillis) damage *= 4;
2354             damage = int(damage*(1-h.dist/EXP_DISTSCALE/guns[gun].exprad));
2355             if(target==ci) damage /= EXP_SELFDAMDIV;
2356             dodamage(target, ci, damage, gun, h.dir);
2357         }
2358     }
2359 
process(clientinfo * ci)2360     void shotevent::process(clientinfo *ci)
2361     {
2362         gamestate &gs = ci->state;
2363         int wait = millis - gs.lastshot;
2364         if(!gs.isalive(gamemillis) ||
2365            wait<gs.gunwait ||
2366            gun<GUN_FIST || gun>GUN_PISTOL ||
2367            gs.ammo[gun]<=0 || (guns[gun].range && from.dist(to) > guns[gun].range + 1))
2368             return;
2369         if(gun!=GUN_FIST) gs.ammo[gun]--;
2370         gs.lastshot = millis;
2371         gs.gunwait = guns[gun].attackdelay;
2372         sendf(-1, 1, "rii9x", N_SHOTFX, ci->clientnum, gun, id,
2373                 int(from.x*DMF), int(from.y*DMF), int(from.z*DMF),
2374                 int(to.x*DMF), int(to.y*DMF), int(to.z*DMF),
2375                 ci->ownernum);
2376         gs.shotdamage += guns[gun].damage*(gs.quadmillis ? 4 : 1)*guns[gun].rays;
2377         switch(gun)
2378         {
2379             case GUN_RL: gs.rockets.add(id); break;
2380             case GUN_GL: gs.grenades.add(id); break;
2381             default:
2382             {
2383                 int totalrays = 0, maxrays = guns[gun].rays;
2384                 loopv(hits)
2385                 {
2386                     hitinfo &h = hits[i];
2387                     clientinfo *target = getinfo(h.target);
2388                     if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.rays<1 || h.dist > guns[gun].range + 1) continue;
2389 
2390                     totalrays += h.rays;
2391                     if(totalrays>maxrays) continue;
2392                     int damage = h.rays*guns[gun].damage;
2393                     if(gs.quadmillis) damage *= 4;
2394                     dodamage(target, ci, damage, gun, h.dir);
2395                 }
2396                 break;
2397             }
2398         }
2399     }
2400 
process(clientinfo * ci)2401     void pickupevent::process(clientinfo *ci)
2402     {
2403         gamestate &gs = ci->state;
2404         if(m_mp(gamemode) && !gs.isalive(gamemillis)) return;
2405         pickup(ent, ci->clientnum);
2406     }
2407 
flush(clientinfo * ci,int fmillis)2408     bool gameevent::flush(clientinfo *ci, int fmillis)
2409     {
2410         process(ci);
2411         return true;
2412     }
2413 
flush(clientinfo * ci,int fmillis)2414     bool timedevent::flush(clientinfo *ci, int fmillis)
2415     {
2416         if(millis > fmillis) return false;
2417         else if(millis >= ci->lastevent)
2418         {
2419             ci->lastevent = millis;
2420             process(ci);
2421         }
2422         return true;
2423     }
2424 
clearevent(clientinfo * ci)2425     void clearevent(clientinfo *ci)
2426     {
2427         delete ci->events.remove(0);
2428     }
2429 
flushevents(clientinfo * ci,int millis)2430     void flushevents(clientinfo *ci, int millis)
2431     {
2432         while(ci->events.length())
2433         {
2434             gameevent *ev = ci->events[0];
2435             if(ev->flush(ci, millis)) clearevent(ci);
2436             else break;
2437         }
2438     }
2439 
processevents()2440     void processevents()
2441     {
2442         loopv(clients)
2443         {
2444             clientinfo *ci = clients[i];
2445             if(curtime>0 && ci->state.quadmillis) ci->state.quadmillis = max(ci->state.quadmillis-curtime, 0);
2446             flushevents(ci, gamemillis);
2447         }
2448     }
2449 
cleartimedevents(clientinfo * ci)2450     void cleartimedevents(clientinfo *ci)
2451     {
2452         int keep = 0;
2453         loopv(ci->events)
2454         {
2455             if(ci->events[i]->keepable())
2456             {
2457                 if(keep < i)
2458                 {
2459                     for(int j = keep; j < i; j++) delete ci->events[j];
2460                     ci->events.remove(keep, i - keep);
2461                     i = keep;
2462                 }
2463                 keep = i+1;
2464                 continue;
2465             }
2466         }
2467         while(ci->events.length() > keep) delete ci->events.pop();
2468         ci->timesync = false;
2469     }
2470 
serverupdate()2471     void serverupdate()
2472     {
2473         if(shouldstep && !gamepaused)
2474         {
2475             gamemillis += curtime;
2476 
2477             if(m_demo) readdemo();
2478             else if(!m_timed || gamemillis < gamelimit)
2479             {
2480                 processevents();
2481                 if(curtime)
2482                 {
2483                     loopv(sents) if(sents[i].spawntime) // spawn entities when timer reached
2484                     {
2485                         int oldtime = sents[i].spawntime;
2486                         sents[i].spawntime -= curtime;
2487                         if(sents[i].spawntime<=0)
2488                         {
2489                             sents[i].spawntime = 0;
2490                             sents[i].spawned = true;
2491                             sendf(-1, 1, "ri2", N_ITEMSPAWN, i);
2492                         }
2493                         else if(sents[i].spawntime<=10000 && oldtime>10000 && (sents[i].type==I_QUAD || sents[i].type==I_BOOST))
2494                         {
2495                             sendf(-1, 1, "ri2", N_ANNOUNCE, sents[i].type);
2496                         }
2497                     }
2498                 }
2499                 aiman::checkai();
2500                 if(smode) smode->update();
2501             }
2502         }
2503 
2504         while(bannedips.length() && bannedips[0].expire-totalmillis <= 0) bannedips.remove(0);
2505         loopv(connects) if(totalmillis-connects[i]->connectmillis>15000) disconnect_client(connects[i]->clientnum, DISC_TIMEOUT);
2506 
2507         if(nextexceeded && gamemillis > nextexceeded && (!m_timed || gamemillis < gamelimit))
2508         {
2509             nextexceeded = 0;
2510             loopvrev(clients)
2511             {
2512                 clientinfo &c = *clients[i];
2513                 if(c.state.aitype != AI_NONE) continue;
2514                 if(c.checkexceeded()) disconnect_client(c.clientnum, DISC_MSGERR);
2515                 else c.scheduleexceeded();
2516             }
2517         }
2518 
2519         if(shouldcheckteamkills) checkteamkills();
2520 
2521         if(shouldstep && !gamepaused)
2522         {
2523             if(m_timed && smapname[0] && gamemillis-curtime>0) checkintermission();
2524             if(interm > 0 && gamemillis>interm)
2525             {
2526                 if(demorecord) enddemorecord();
2527                 interm = -1;
2528                 checkvotes(true);
2529             }
2530         }
2531 
2532         shouldstep = clients.length() > 0;
2533     }
2534 
forcespectator(clientinfo * ci)2535     void forcespectator(clientinfo *ci)
2536     {
2537         if(ci->state.state==CS_ALIVE) suicide(ci);
2538         if(smode) smode->leavegame(ci);
2539         ci->state.state = CS_SPECTATOR;
2540         ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
2541         if(!ci->local && (!ci->privilege || ci->warned)) aiman::removeai(ci);
2542         sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, 1);
2543     }
2544 
2545     struct crcinfo
2546     {
2547         int crc, matches;
2548 
crcinfoserver::crcinfo2549         crcinfo() {}
crcinfoserver::crcinfo2550         crcinfo(int crc, int matches) : crc(crc), matches(matches) {}
2551 
compareserver::crcinfo2552         static bool compare(const crcinfo &x, const crcinfo &y) { return x.matches > y.matches; }
2553     };
2554 
2555     VAR(modifiedmapspectator, 0, 1, 2);
2556 
checkmaps(int req=-1)2557     void checkmaps(int req = -1)
2558     {
2559         if(m_edit || !smapname[0]) return;
2560         vector<crcinfo> crcs;
2561         int total = 0, unsent = 0, invalid = 0;
2562         if(mcrc) crcs.add(crcinfo(mcrc, clients.length() + 1));
2563         loopv(clients)
2564         {
2565             clientinfo *ci = clients[i];
2566             if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE) continue;
2567             total++;
2568             if(!ci->clientmap[0])
2569             {
2570                 if(ci->mapcrc < 0) invalid++;
2571                 else if(!ci->mapcrc) unsent++;
2572             }
2573             else
2574             {
2575                 crcinfo *match = NULL;
2576                 loopvj(crcs) if(crcs[j].crc == ci->mapcrc) { match = &crcs[j]; break; }
2577                 if(!match) crcs.add(crcinfo(ci->mapcrc, 1));
2578                 else match->matches++;
2579             }
2580         }
2581         if(!mcrc && total - unsent < min(total, 4)) return;
2582         crcs.sort(crcinfo::compare);
2583         string msg;
2584         loopv(clients)
2585         {
2586             clientinfo *ci = clients[i];
2587             if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE || ci->clientmap[0] || ci->mapcrc >= 0 || (req < 0 && ci->warned)) continue;
2588             formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname);
2589             sendf(req, 1, "ris", N_SERVMSG, msg);
2590             if(req < 0) ci->warned = true;
2591         }
2592         if(crcs.length() >= 2) loopv(crcs)
2593         {
2594             crcinfo &info = crcs[i];
2595             if(i || info.matches <= crcs[i+1].matches) loopvj(clients)
2596             {
2597                 clientinfo *ci = clients[j];
2598                 if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE || !ci->clientmap[0] || ci->mapcrc != info.crc || (req < 0 && ci->warned)) continue;
2599                 formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname);
2600                 sendf(req, 1, "ris", N_SERVMSG, msg);
2601                 if(req < 0) ci->warned = true;
2602             }
2603         }
2604         if(req < 0 && modifiedmapspectator && (mcrc || modifiedmapspectator > 1)) loopv(clients)
2605         {
2606             clientinfo *ci = clients[i];
2607             if(!ci->local && ci->warned && ci->state.state != CS_SPECTATOR) forcespectator(ci);
2608         }
2609     }
2610 
shouldspectate(clientinfo * ci)2611     bool shouldspectate(clientinfo *ci)
2612     {
2613         return !ci->local && ci->warned && modifiedmapspectator && (mcrc || modifiedmapspectator > 1);
2614     }
2615 
unspectate(clientinfo * ci)2616     void unspectate(clientinfo *ci)
2617     {
2618         if(shouldspectate(ci)) return;
2619         ci->state.state = CS_DEAD;
2620         ci->state.respawn();
2621         ci->state.lasttimeplayed = lastmillis;
2622         aiman::addclient(ci);
2623         sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, 0);
2624         if(ci->clientmap[0] || ci->mapcrc) checkmaps();
2625         if(!hasmap(ci)) rotatemap(true);
2626     }
2627 
sendservinfo(clientinfo * ci)2628     void sendservinfo(clientinfo *ci)
2629     {
2630         sendf(ci->clientnum, 1, "ri5ss", N_SERVINFO, ci->clientnum, PROTOCOL_VERSION, ci->sessionid, serverpass[0] ? 1 : 0, serverdesc, serverauth);
2631     }
2632 
noclients()2633     void noclients()
2634     {
2635         bannedips.shrink(0);
2636         aiman::clearai();
2637     }
2638 
localconnect(int n)2639     void localconnect(int n)
2640     {
2641         clientinfo *ci = getinfo(n);
2642         ci->clientnum = ci->ownernum = n;
2643         ci->connectmillis = totalmillis;
2644         ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF;
2645         ci->local = true;
2646 
2647         connects.add(ci);
2648         sendservinfo(ci);
2649     }
2650 
localdisconnect(int n)2651     void localdisconnect(int n)
2652     {
2653         if(m_demo) enddemoplayback();
2654         clientdisconnect(n);
2655     }
2656 
clientconnect(int n,uint ip)2657     int clientconnect(int n, uint ip)
2658     {
2659         clientinfo *ci = getinfo(n);
2660         ci->clientnum = ci->ownernum = n;
2661         ci->connectmillis = totalmillis;
2662         ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF;
2663 
2664         connects.add(ci);
2665         if(!m_mp(gamemode)) return DISC_LOCAL;
2666         sendservinfo(ci);
2667         return DISC_NONE;
2668     }
2669 
clientdisconnect(int n)2670     void clientdisconnect(int n)
2671     {
2672         clientinfo *ci = getinfo(n);
2673         loopv(clients) if(clients[i]->authkickvictim == ci->clientnum) clients[i]->cleanauth();
2674         if(ci->connected)
2675         {
2676             if(ci->privilege) setmaster(ci, false);
2677             if(smode) smode->leavegame(ci, true);
2678             ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
2679             savescore(ci);
2680             sendf(-1, 1, "ri2", N_CDIS, n);
2681             clients.removeobj(ci);
2682             aiman::removeai(ci);
2683             if(!numclients(-1, false, true)) noclients(); // bans clear when server empties
2684             if(ci->local) checkpausegame();
2685         }
2686         else connects.removeobj(ci);
2687     }
2688 
reserveclients()2689     int reserveclients() { return 3; }
2690 
2691     extern void verifybans();
2692 
2693     struct banlist
2694     {
2695         vector<ipmask> bans;
2696 
clearserver::banlist2697         void clear() { bans.shrink(0); }
2698 
checkserver::banlist2699         bool check(uint ip)
2700         {
2701             loopv(bans) if(bans[i].check(ip)) return true;
2702             return false;
2703         }
2704 
addserver::banlist2705         void add(const char *ipname)
2706         {
2707             ipmask ban;
2708             ban.parse(ipname);
2709             bans.add(ban);
2710 
2711             verifybans();
2712         }
2713     } ipbans, gbans;
2714 
checkbans(uint ip)2715     bool checkbans(uint ip)
2716     {
2717         loopv(bannedips) if(bannedips[i].ip==ip) return true;
2718         return ipbans.check(ip) || gbans.check(ip);
2719     }
2720 
verifybans()2721     void verifybans()
2722     {
2723         loopvrev(clients)
2724         {
2725             clientinfo *ci = clients[i];
2726             if(ci->state.aitype != AI_NONE || ci->local || ci->privilege >= PRIV_ADMIN) continue;
2727             if(checkbans(getclientip(ci->clientnum))) disconnect_client(ci->clientnum, DISC_IPBAN);
2728         }
2729     }
2730 
2731     ICOMMAND(clearipbans, "", (), ipbans.clear());
2732     ICOMMAND(ipban, "s", (const char *ipname), ipbans.add(ipname));
2733 
allowconnect(clientinfo * ci,const char * pwd="")2734     int allowconnect(clientinfo *ci, const char *pwd = "")
2735     {
2736         if(ci->local) return DISC_NONE;
2737         if(!m_mp(gamemode)) return DISC_LOCAL;
2738         if(serverpass[0])
2739         {
2740             if(!checkpassword(ci, serverpass, pwd)) return DISC_PASSWORD;
2741             return DISC_NONE;
2742         }
2743         if(adminpass[0] && checkpassword(ci, adminpass, pwd)) return DISC_NONE;
2744         if(numclients(-1, false, true)>=maxclients) return DISC_MAXCLIENTS;
2745         uint ip = getclientip(ci->clientnum);
2746         if(checkbans(ip)) return DISC_IPBAN;
2747         if(mastermode>=MM_PRIVATE && allowedips.find(ip)<0) return DISC_PRIVATE;
2748         return DISC_NONE;
2749     }
2750 
allowbroadcast(int n)2751     bool allowbroadcast(int n)
2752     {
2753         clientinfo *ci = getinfo(n);
2754         return ci && ci->connected;
2755     }
2756 
findauth(uint id)2757     clientinfo *findauth(uint id)
2758     {
2759         loopv(clients) if(clients[i]->authreq == id) return clients[i];
2760         return NULL;
2761     }
2762 
2763 
authfailed(clientinfo * ci)2764     void authfailed(clientinfo *ci)
2765     {
2766         if(!ci) return;
2767         ci->cleanauth();
2768         if(ci->connectauth) disconnect_client(ci->clientnum, ci->connectauth);
2769     }
2770 
authfailed(uint id)2771     void authfailed(uint id)
2772     {
2773         authfailed(findauth(id));
2774     }
2775 
authsucceeded(uint id)2776     void authsucceeded(uint id)
2777     {
2778         clientinfo *ci = findauth(id);
2779         if(!ci) return;
2780         ci->cleanauth(ci->connectauth!=0);
2781         if(ci->connectauth) connected(ci);
2782         if(ci->authkickvictim >= 0)
2783         {
2784             if(setmaster(ci, true, "", ci->authname, NULL, PRIV_AUTH, false, true))
2785                 trykick(ci, ci->authkickvictim, ci->authkickreason, ci->authname, NULL, PRIV_AUTH);
2786             ci->cleanauthkick();
2787         }
2788         else setmaster(ci, true, "", ci->authname, NULL, PRIV_AUTH);
2789     }
2790 
authchallenged(uint id,const char * val,const char * desc="")2791     void authchallenged(uint id, const char *val, const char *desc = "")
2792     {
2793         clientinfo *ci = findauth(id);
2794         if(!ci) return;
2795         sendf(ci->clientnum, 1, "risis", N_AUTHCHAL, desc, id, val);
2796     }
2797 
2798     uint nextauthreq = 0;
2799 
tryauth(clientinfo * ci,const char * user,const char * desc)2800     bool tryauth(clientinfo *ci, const char *user, const char *desc)
2801     {
2802         ci->cleanauth();
2803         if(!nextauthreq) nextauthreq = 1;
2804         ci->authreq = nextauthreq++;
2805         filtertext(ci->authname, user, false, false, 100);
2806         copystring(ci->authdesc, desc);
2807         if(ci->authdesc[0])
2808         {
2809             userinfo *u = users.access(userkey(ci->authname, ci->authdesc));
2810             if(u)
2811             {
2812                 uint seed[3] = { ::hthash(serverauth) + detrnd(size_t(ci) + size_t(user) + size_t(desc), 0x10000), uint(totalmillis), randomMT() };
2813                 vector<char> buf;
2814                 ci->authchallenge = genchallenge(u->pubkey, seed, sizeof(seed), buf);
2815                 sendf(ci->clientnum, 1, "risis", N_AUTHCHAL, desc, ci->authreq, buf.getbuf());
2816             }
2817             else ci->cleanauth();
2818         }
2819         else if(!requestmasterf("reqauth %u %s\n", ci->authreq, ci->authname))
2820         {
2821             ci->cleanauth();
2822             sendf(ci->clientnum, 1, "ris", N_SERVMSG, "not connected to authentication server");
2823         }
2824         if(ci->authreq) return true;
2825         if(ci->connectauth) disconnect_client(ci->clientnum, ci->connectauth);
2826         return false;
2827     }
2828 
answerchallenge(clientinfo * ci,uint id,char * val,const char * desc)2829     bool answerchallenge(clientinfo *ci, uint id, char *val, const char *desc)
2830     {
2831         if(ci->authreq != id || strcmp(ci->authdesc, desc))
2832         {
2833             ci->cleanauth();
2834             return !ci->connectauth;
2835         }
2836         for(char *s = val; *s; s++)
2837         {
2838             if(!isxdigit(*s)) { *s = '\0'; break; }
2839         }
2840         if(desc[0])
2841         {
2842             if(ci->authchallenge && checkchallenge(val, ci->authchallenge))
2843             {
2844                 userinfo *u = users.access(userkey(ci->authname, ci->authdesc));
2845                 if(u)
2846                 {
2847                     if(ci->connectauth) connected(ci);
2848                     if(ci->authkickvictim >= 0)
2849                     {
2850                         if(setmaster(ci, true, "", ci->authname, ci->authdesc, u->privilege, false, true))
2851                             trykick(ci, ci->authkickvictim, ci->authkickreason, ci->authname, ci->authdesc, u->privilege);
2852                     }
2853                     else setmaster(ci, true, "", ci->authname, ci->authdesc, u->privilege);
2854                 }
2855             }
2856             ci->cleanauth();
2857         }
2858         else if(!requestmasterf("confauth %u %s\n", id, val))
2859         {
2860             ci->cleanauth();
2861             sendf(ci->clientnum, 1, "ris", N_SERVMSG, "not connected to authentication server");
2862         }
2863         return ci->authreq || !ci->connectauth;
2864     }
2865 
masterconnected()2866     void masterconnected()
2867     {
2868     }
2869 
masterdisconnected()2870     void masterdisconnected()
2871     {
2872         loopvrev(clients)
2873         {
2874             clientinfo *ci = clients[i];
2875             if(ci->authreq) authfailed(ci);
2876         }
2877     }
2878 
processmasterinput(const char * cmd,int cmdlen,const char * args)2879     void processmasterinput(const char *cmd, int cmdlen, const char *args)
2880     {
2881         uint id;
2882         string val;
2883         if(sscanf(cmd, "failauth %u", &id) == 1)
2884             authfailed(id);
2885         else if(sscanf(cmd, "succauth %u", &id) == 1)
2886             authsucceeded(id);
2887         else if(sscanf(cmd, "chalauth %u %255s", &id, val) == 2)
2888             authchallenged(id, val);
2889         else if(matchstring(cmd, cmdlen, "cleargbans"))
2890             gbans.clear();
2891         else if(sscanf(cmd, "addgban %100s", val) == 1)
2892             gbans.add(val);
2893     }
2894 
receivefile(int sender,uchar * data,int len)2895     void receivefile(int sender, uchar *data, int len)
2896     {
2897         if(!m_edit || len <= 0 || len > 4*1024*1024) return;
2898         clientinfo *ci = getinfo(sender);
2899         if(ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) return;
2900         if(mapdata) DELETEP(mapdata);
2901         mapdata = opentempfile("mapdata", "w+b");
2902         if(!mapdata) { sendf(sender, 1, "ris", N_SERVMSG, "failed to open temporary file for map"); return; }
2903         mapdata->write(data, len);
2904         sendservmsgf("[%s sent a map to server, \"/getmap\" to receive it]", colorname(ci));
2905     }
2906 
sendclipboard(clientinfo * ci)2907     void sendclipboard(clientinfo *ci)
2908     {
2909         if(!ci->lastclipboard || !ci->clipboard) return;
2910         bool flushed = false;
2911         loopv(clients)
2912         {
2913             clientinfo &e = *clients[i];
2914             if(e.clientnum != ci->clientnum && e.needclipboard - ci->lastclipboard >= 0)
2915             {
2916                 if(!flushed) { flushserver(true); flushed = true; }
2917                 sendpacket(e.clientnum, 1, ci->clipboard);
2918             }
2919         }
2920     }
2921 
connected(clientinfo * ci)2922     void connected(clientinfo *ci)
2923     {
2924         if(m_demo) enddemoplayback();
2925 
2926         if(!hasmap(ci)) rotatemap(false);
2927 
2928         shouldstep = true;
2929 
2930         connects.removeobj(ci);
2931         clients.add(ci);
2932 
2933         ci->connectauth = 0;
2934         ci->connected = true;
2935         ci->needclipboard = totalmillis ? totalmillis : 1;
2936         if(mastermode>=MM_LOCKED) ci->state.state = CS_SPECTATOR;
2937         ci->state.lasttimeplayed = lastmillis;
2938 
2939         const char *worst = m_teammode ? chooseworstteam(NULL, ci) : NULL;
2940         copystring(ci->team, worst ? worst : "good", MAXTEAMLEN+1);
2941 
2942         sendwelcome(ci);
2943         if(restorescore(ci)) sendresume(ci);
2944         sendinitclient(ci);
2945 
2946         aiman::addclient(ci);
2947 
2948         if(m_demo) setupdemoplayback();
2949 
2950         if(servermotd[0]) sendf(ci->clientnum, 1, "ris", N_SERVMSG, servermotd);
2951     }
2952 
parsepacket(int sender,int chan,packetbuf & p)2953     void parsepacket(int sender, int chan, packetbuf &p)     // has to parse exactly each byte of the packet
2954     {
2955         if(sender<0 || p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED || chan > 2) return;
2956         char text[MAXTRANS];
2957         int type;
2958         clientinfo *ci = sender>=0 ? getinfo(sender) : NULL, *cq = ci, *cm = ci;
2959         if(ci && !ci->connected)
2960         {
2961             if(chan==0) return;
2962             else if(chan!=1) { disconnect_client(sender, DISC_MSGERR); return; }
2963             else while(p.length() < p.maxlen) switch(checktype(getint(p), ci))
2964             {
2965                 case N_CONNECT:
2966                 {
2967                     getstring(text, p);
2968                     filtertext(text, text, false, false, MAXNAMELEN);
2969                     if(!text[0]) copystring(text, "unnamed");
2970                     copystring(ci->name, text, MAXNAMELEN+1);
2971                     ci->playermodel = getint(p);
2972 
2973                     string password, authdesc, authname;
2974                     getstring(password, p, sizeof(password));
2975                     getstring(authdesc, p, sizeof(authdesc));
2976                     getstring(authname, p, sizeof(authname));
2977                     int disc = allowconnect(ci, password);
2978                     if(disc)
2979                     {
2980                         if(disc == DISC_LOCAL || !serverauth[0] || strcmp(serverauth, authdesc) || !tryauth(ci, authname, authdesc))
2981                         {
2982                             disconnect_client(sender, disc);
2983                             return;
2984                         }
2985                         ci->connectauth = disc;
2986                     }
2987                     else connected(ci);
2988                     break;
2989                 }
2990 
2991                 case N_AUTHANS:
2992                 {
2993                     string desc, ans;
2994                     getstring(desc, p, sizeof(desc));
2995                     uint id = (uint)getint(p);
2996                     getstring(ans, p, sizeof(ans));
2997                     if(!answerchallenge(ci, id, ans, desc))
2998                     {
2999                         disconnect_client(sender, ci->connectauth);
3000                         return;
3001                     }
3002                     break;
3003                 }
3004 
3005                 case N_PING:
3006                     getint(p);
3007                     break;
3008 
3009                 default:
3010                     disconnect_client(sender, DISC_MSGERR);
3011                     return;
3012             }
3013             return;
3014         }
3015         else if(chan==2)
3016         {
3017             receivefile(sender, p.buf, p.maxlen);
3018             return;
3019         }
3020 
3021         if(p.packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true;
3022         #define QUEUE_AI clientinfo *cm = cq;
3023         #define QUEUE_MSG { if(cm && (!cm->local || demorecord || hasnonlocalclients())) while(curmsg<p.length()) cm->messages.add(p.buf[curmsg++]); }
3024         #define QUEUE_BUF(body) { \
3025             if(cm && (!cm->local || demorecord || hasnonlocalclients())) \
3026             { \
3027                 curmsg = p.length(); \
3028                 { body; } \
3029             } \
3030         }
3031         #define QUEUE_INT(n) QUEUE_BUF(putint(cm->messages, n))
3032         #define QUEUE_UINT(n) QUEUE_BUF(putuint(cm->messages, n))
3033         #define QUEUE_STR(text) QUEUE_BUF(sendstring(text, cm->messages))
3034         int curmsg;
3035         while((curmsg = p.length()) < p.maxlen) switch(type = checktype(getint(p), ci))
3036         {
3037             case N_POS:
3038             {
3039                 int pcn = getuint(p);
3040                 p.get();
3041                 uint flags = getuint(p);
3042                 clientinfo *cp = getinfo(pcn);
3043                 if(cp && pcn != sender && cp->ownernum != sender) cp = NULL;
3044                 vec pos;
3045                 loopk(3)
3046                 {
3047                     int n = p.get(); n |= p.get()<<8; if(flags&(1<<k)) { n |= p.get()<<16; if(n&0x800000) n |= ~0U<<24; }
3048                     pos[k] = n/DMF;
3049                 }
3050                 loopk(3) p.get();
3051                 int mag = p.get(); if(flags&(1<<3)) mag |= p.get()<<8;
3052                 int dir = p.get(); dir |= p.get()<<8;
3053                 vec vel = vec((dir%360)*RAD, (clamp(dir/360, 0, 180)-90)*RAD).mul(mag/DVELF);
3054                 if(flags&(1<<4))
3055                 {
3056                     p.get(); if(flags&(1<<5)) p.get();
3057                     if(flags&(1<<6)) loopk(2) p.get();
3058                 }
3059                 if(cp)
3060                 {
3061                     if((!ci->local || demorecord || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING))
3062                     {
3063                         if(!ci->local && !m_edit && max(vel.magnitude2(), (float)fabs(vel.z)) >= 180)
3064                             cp->setexceeded();
3065                         cp->position.setsize(0);
3066                         while(curmsg<p.length()) cp->position.add(p.buf[curmsg++]);
3067                     }
3068                     if(smode && cp->state.state==CS_ALIVE) smode->moved(cp, cp->state.o, cp->gameclip, pos, (flags&0x80)!=0);
3069                     cp->state.o = pos;
3070                     cp->gameclip = (flags&0x80)!=0;
3071                 }
3072                 break;
3073             }
3074 
3075             case N_TELEPORT:
3076             {
3077                 int pcn = getint(p), teleport = getint(p), teledest = getint(p);
3078                 clientinfo *cp = getinfo(pcn);
3079                 if(cp && pcn != sender && cp->ownernum != sender) cp = NULL;
3080                 if(cp && (!ci->local || demorecord || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING))
3081                 {
3082                     flushclientposition(*cp);
3083                     sendf(-1, 0, "ri4x", N_TELEPORT, pcn, teleport, teledest, cp->ownernum);
3084                 }
3085                 break;
3086             }
3087 
3088             case N_JUMPPAD:
3089             {
3090                 int pcn = getint(p), jumppad = getint(p);
3091                 clientinfo *cp = getinfo(pcn);
3092                 if(cp && pcn != sender && cp->ownernum != sender) cp = NULL;
3093                 if(cp && (!ci->local || demorecord || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING))
3094                 {
3095                     cp->setpushed();
3096                     flushclientposition(*cp);
3097                     sendf(-1, 0, "ri3x", N_JUMPPAD, pcn, jumppad, cp->ownernum);
3098                 }
3099                 break;
3100             }
3101 
3102             case N_FROMAI:
3103             {
3104                 int qcn = getint(p);
3105                 if(qcn < 0) cq = ci;
3106                 else
3107                 {
3108                     cq = getinfo(qcn);
3109                     if(cq && qcn != sender && cq->ownernum != sender) cq = NULL;
3110                 }
3111                 break;
3112             }
3113 
3114             case N_EDITMODE:
3115             {
3116                 int val = getint(p);
3117                 if(!ci->local && !m_edit) break;
3118                 if(val ? ci->state.state!=CS_ALIVE && ci->state.state!=CS_DEAD : ci->state.state!=CS_EDITING) break;
3119                 if(smode)
3120                 {
3121                     if(val) smode->leavegame(ci);
3122                     else smode->entergame(ci);
3123                 }
3124                 if(val)
3125                 {
3126                     ci->state.editstate = ci->state.state;
3127                     ci->state.state = CS_EDITING;
3128                     ci->events.setsize(0);
3129                     ci->state.rockets.reset();
3130                     ci->state.grenades.reset();
3131                 }
3132                 else ci->state.state = ci->state.editstate;
3133                 QUEUE_MSG;
3134                 break;
3135             }
3136 
3137             case N_MAPCRC:
3138             {
3139                 getstring(text, p);
3140                 int crc = getint(p);
3141                 if(!ci) break;
3142                 if(strcmp(text, smapname))
3143                 {
3144                     if(ci->clientmap[0])
3145                     {
3146                         ci->clientmap[0] = '\0';
3147                         ci->mapcrc = 0;
3148                     }
3149                     else if(ci->mapcrc > 0) ci->mapcrc = 0;
3150                     break;
3151                 }
3152                 copystring(ci->clientmap, text);
3153                 ci->mapcrc = text[0] ? crc : 1;
3154                 checkmaps();
3155                 if(cq && cq != ci && cq->ownernum != ci->clientnum) cq = NULL;
3156                 break;
3157             }
3158 
3159             case N_CHECKMAPS:
3160                 checkmaps(sender);
3161                 break;
3162 
3163             case N_TRYSPAWN:
3164                 if(!ci || !cq || cq->state.state!=CS_DEAD || cq->state.lastspawn>=0 || (smode && !smode->canspawn(cq))) break;
3165                 if(!ci->clientmap[0] && !ci->mapcrc)
3166                 {
3167                     ci->mapcrc = -1;
3168                     checkmaps();
3169                     if(ci == cq) { if(ci->state.state != CS_DEAD) break; }
3170                     else if(cq->ownernum != ci->clientnum) { cq = NULL; break; }
3171                 }
3172                 if(cq->state.deadflush)
3173                 {
3174                     flushevents(cq, cq->state.deadflush);
3175                     cq->state.respawn();
3176                 }
3177                 cleartimedevents(cq);
3178                 sendspawn(cq);
3179                 break;
3180 
3181             case N_GUNSELECT:
3182             {
3183                 int gunselect = getint(p);
3184                 if(!cq || cq->state.state!=CS_ALIVE) break;
3185                 cq->state.gunselect = gunselect >= GUN_FIST && gunselect <= GUN_PISTOL ? gunselect : GUN_FIST;
3186                 QUEUE_AI;
3187                 QUEUE_MSG;
3188                 break;
3189             }
3190 
3191             case N_SPAWN:
3192             {
3193                 int ls = getint(p), gunselect = getint(p);
3194                 if(!cq || (cq->state.state!=CS_ALIVE && cq->state.state!=CS_DEAD && cq->state.state!=CS_EDITING) || ls!=cq->state.lifesequence || cq->state.lastspawn<0) break;
3195                 cq->state.lastspawn = -1;
3196                 cq->state.state = CS_ALIVE;
3197                 cq->state.gunselect = gunselect >= GUN_FIST && gunselect <= GUN_PISTOL ? gunselect : GUN_FIST;
3198                 cq->exceeded = 0;
3199                 if(smode) smode->spawned(cq);
3200                 QUEUE_AI;
3201                 QUEUE_BUF({
3202                     putint(cm->messages, N_SPAWN);
3203                     sendstate(cq->state, cm->messages);
3204                 });
3205                 break;
3206             }
3207 
3208             case N_SUICIDE:
3209             {
3210                 if(cq) cq->addevent(new suicideevent);
3211                 break;
3212             }
3213 
3214             case N_SHOOT:
3215             {
3216                 shotevent *shot = new shotevent;
3217                 shot->id = getint(p);
3218                 shot->millis = cq ? cq->geteventmillis(gamemillis, shot->id) : 0;
3219                 shot->gun = getint(p);
3220                 loopk(3) shot->from[k] = getint(p)/DMF;
3221                 loopk(3) shot->to[k] = getint(p)/DMF;
3222                 int hits = getint(p);
3223                 loopk(hits)
3224                 {
3225                     if(p.overread()) break;
3226                     hitinfo &hit = shot->hits.add();
3227                     hit.target = getint(p);
3228                     hit.lifesequence = getint(p);
3229                     hit.dist = getint(p)/DMF;
3230                     hit.rays = getint(p);
3231                     loopk(3) hit.dir[k] = getint(p)/DNF;
3232                 }
3233                 if(cq)
3234                 {
3235                     cq->addevent(shot);
3236                     cq->setpushed();
3237                 }
3238                 else delete shot;
3239                 break;
3240             }
3241 
3242             case N_EXPLODE:
3243             {
3244                 explodeevent *exp = new explodeevent;
3245                 int cmillis = getint(p);
3246                 exp->millis = cq ? cq->geteventmillis(gamemillis, cmillis) : 0;
3247                 exp->gun = getint(p);
3248                 exp->id = getint(p);
3249                 int hits = getint(p);
3250                 loopk(hits)
3251                 {
3252                     if(p.overread()) break;
3253                     hitinfo &hit = exp->hits.add();
3254                     hit.target = getint(p);
3255                     hit.lifesequence = getint(p);
3256                     hit.dist = getint(p)/DMF;
3257                     hit.rays = getint(p);
3258                     loopk(3) hit.dir[k] = getint(p)/DNF;
3259                 }
3260                 if(cq) cq->addevent(exp);
3261                 else delete exp;
3262                 break;
3263             }
3264 
3265             case N_ITEMPICKUP:
3266             {
3267                 int n = getint(p);
3268                 if(!cq) break;
3269                 pickupevent *pickup = new pickupevent;
3270                 pickup->ent = n;
3271                 cq->addevent(pickup);
3272                 break;
3273             }
3274 
3275             case N_TEXT:
3276             {
3277                 QUEUE_AI;
3278                 QUEUE_MSG;
3279                 getstring(text, p);
3280                 filtertext(text, text, true, true);
3281                 QUEUE_STR(text);
3282                 if(isdedicatedserver() && cq) logoutf("%s: %s", colorname(cq), text);
3283                 break;
3284             }
3285 
3286             case N_SAYTEAM:
3287             {
3288                 getstring(text, p);
3289                 if(!ci || !cq || (ci->state.state==CS_SPECTATOR && !ci->local && !ci->privilege) || !m_teammode || !cq->team[0]) break;
3290                 filtertext(text, text, true, true);
3291                 loopv(clients)
3292                 {
3293                     clientinfo *t = clients[i];
3294                     if(t==cq || t->state.state==CS_SPECTATOR || t->state.aitype != AI_NONE || strcmp(cq->team, t->team)) continue;
3295                     sendf(t->clientnum, 1, "riis", N_SAYTEAM, cq->clientnum, text);
3296                 }
3297                 if(isdedicatedserver() && cq) logoutf("%s <%s>: %s", colorname(cq), cq->team, text);
3298                 break;
3299             }
3300 
3301             case N_SWITCHNAME:
3302             {
3303                 QUEUE_MSG;
3304                 getstring(text, p);
3305                 filtertext(ci->name, text, false, false, MAXNAMELEN);
3306                 if(!ci->name[0]) copystring(ci->name, "unnamed");
3307                 QUEUE_STR(ci->name);
3308                 break;
3309             }
3310 
3311             case N_SWITCHMODEL:
3312             {
3313                 ci->playermodel = getint(p);
3314                 QUEUE_MSG;
3315                 break;
3316             }
3317 
3318             case N_SWITCHTEAM:
3319             {
3320                 getstring(text, p);
3321                 filtertext(text, text, false, false, MAXTEAMLEN);
3322                 if(m_teammode && text[0] && strcmp(ci->team, text) && (!smode || smode->canchangeteam(ci, ci->team, text)) && addteaminfo(text))
3323                 {
3324                     if(ci->state.state==CS_ALIVE) suicide(ci);
3325                     copystring(ci->team, text);
3326                     aiman::changeteam(ci);
3327                     sendf(-1, 1, "riisi", N_SETTEAM, sender, ci->team, ci->state.state==CS_SPECTATOR ? -1 : 0);
3328                 }
3329                 break;
3330             }
3331 
3332             case N_MAPVOTE:
3333             {
3334                 getstring(text, p);
3335                 filtertext(text, text, false);
3336                 fixmapname(text);
3337                 int reqmode = getint(p);
3338                 vote(text, reqmode, sender);
3339                 break;
3340             }
3341 
3342             case N_ITEMLIST:
3343             {
3344                 if((ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || !notgotitems || strcmp(ci->clientmap, smapname)) { while(getint(p)>=0 && !p.overread()) getint(p); break; }
3345                 int n;
3346                 while((n = getint(p))>=0 && n<MAXENTS && !p.overread())
3347                 {
3348                     server_entity se = { NOTUSED, 0, false };
3349                     while(sents.length()<=n) sents.add(se);
3350                     sents[n].type = getint(p);
3351                     if(canspawnitem(sents[n].type))
3352                     {
3353                         if(m_mp(gamemode) && delayspawn(sents[n].type)) sents[n].spawntime = spawntime(sents[n].type);
3354                         else sents[n].spawned = true;
3355                     }
3356                 }
3357                 notgotitems = false;
3358                 break;
3359             }
3360 
3361             case N_EDITENT:
3362             {
3363                 int i = getint(p);
3364                 loopk(3) getint(p);
3365                 int type = getint(p);
3366                 loopk(5) getint(p);
3367                 if(!ci || ci->state.state==CS_SPECTATOR) break;
3368                 QUEUE_MSG;
3369                 bool canspawn = canspawnitem(type);
3370                 if(i<MAXENTS && (sents.inrange(i) || canspawnitem(type)))
3371                 {
3372                     server_entity se = { NOTUSED, 0, false };
3373                     while(sents.length()<=i) sents.add(se);
3374                     sents[i].type = type;
3375                     if(canspawn ? !sents[i].spawned : (sents[i].spawned || sents[i].spawntime))
3376                     {
3377                         sents[i].spawntime = canspawn ? 1 : 0;
3378                         sents[i].spawned = false;
3379                     }
3380                 }
3381                 break;
3382             }
3383 
3384             case N_EDITVAR:
3385             {
3386                 int type = getint(p);
3387                 getstring(text, p);
3388                 switch(type)
3389                 {
3390                     case ID_VAR: getint(p); break;
3391                     case ID_FVAR: getfloat(p); break;
3392                     case ID_SVAR: getstring(text, p);
3393                 }
3394                 if(ci && ci->state.state!=CS_SPECTATOR) QUEUE_MSG;
3395                 break;
3396             }
3397 
3398             case N_PING:
3399                 sendf(sender, 1, "i2", N_PONG, getint(p));
3400                 break;
3401 
3402             case N_CLIENTPING:
3403             {
3404                 int ping = getint(p);
3405                 if(ci)
3406                 {
3407                     ci->ping = ping;
3408                     loopv(ci->bots) ci->bots[i]->ping = ping;
3409                 }
3410                 QUEUE_MSG;
3411                 break;
3412             }
3413 
3414             case N_MASTERMODE:
3415             {
3416                 int mm = getint(p);
3417                 if((ci->privilege || ci->local) && mm>=MM_OPEN && mm<=MM_PRIVATE)
3418                 {
3419                     if((ci->privilege>=PRIV_ADMIN || ci->local) || (mastermask&(1<<mm)))
3420                     {
3421                         mastermode = mm;
3422                         allowedips.shrink(0);
3423                         if(mm>=MM_PRIVATE)
3424                         {
3425                             loopv(clients) allowedips.add(getclientip(clients[i]->clientnum));
3426                         }
3427                         sendf(-1, 1, "rii", N_MASTERMODE, mastermode);
3428                         //sendservmsgf("mastermode is now %s (%d)", mastermodename(mastermode), mastermode);
3429                     }
3430                     else
3431                     {
3432                         defformatstring(s, "mastermode %d is disabled on this server", mm);
3433                         sendf(sender, 1, "ris", N_SERVMSG, s);
3434                     }
3435                 }
3436                 break;
3437             }
3438 
3439             case N_CLEARBANS:
3440             {
3441                 if(ci->privilege || ci->local)
3442                 {
3443                     bannedips.shrink(0);
3444                     sendservmsg("cleared all bans");
3445                 }
3446                 break;
3447             }
3448 
3449             case N_KICK:
3450             {
3451                 int victim = getint(p);
3452                 getstring(text, p);
3453                 filtertext(text, text);
3454                 trykick(ci, victim, text);
3455                 break;
3456             }
3457 
3458             case N_SPECTATOR:
3459             {
3460                 int spectator = getint(p), val = getint(p);
3461                 if(!ci->privilege && !ci->local && (spectator!=sender || (ci->state.state==CS_SPECTATOR && mastermode>=MM_LOCKED))) break;
3462                 clientinfo *spinfo = (clientinfo *)getclientinfo(spectator); // no bots
3463                 if(!spinfo || !spinfo->connected || (spinfo->state.state==CS_SPECTATOR ? val : !val)) break;
3464 
3465                 if(spinfo->state.state!=CS_SPECTATOR && val) forcespectator(spinfo);
3466                 else if(spinfo->state.state==CS_SPECTATOR && !val) unspectate(spinfo);
3467 
3468                 if(cq && cq != ci && cq->ownernum != ci->clientnum) cq = NULL;
3469                 break;
3470             }
3471 
3472             case N_SETTEAM:
3473             {
3474                 int who = getint(p);
3475                 getstring(text, p);
3476                 filtertext(text, text, false, false, MAXTEAMLEN);
3477                 if(!ci->privilege && !ci->local) break;
3478                 clientinfo *wi = getinfo(who);
3479                 if(!m_teammode || !text[0] || !wi || !wi->connected || !strcmp(wi->team, text)) break;
3480                 if((!smode || smode->canchangeteam(wi, wi->team, text)) && addteaminfo(text))
3481                 {
3482                     if(wi->state.state==CS_ALIVE) suicide(wi);
3483                     copystring(wi->team, text, MAXTEAMLEN+1);
3484                 }
3485                 aiman::changeteam(wi);
3486                 sendf(-1, 1, "riisi", N_SETTEAM, who, wi->team, 1);
3487                 break;
3488             }
3489 
3490             case N_FORCEINTERMISSION:
3491                 if(ci->local && !hasnonlocalclients()) startintermission();
3492                 break;
3493 
3494             case N_RECORDDEMO:
3495             {
3496                 int val = getint(p);
3497                 if(ci->privilege < (restrictdemos ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break;
3498                 if(!maxdemos || !maxdemosize)
3499                 {
3500                     sendf(ci->clientnum, 1, "ris", N_SERVMSG, "the server has disabled demo recording");
3501                     break;
3502                 }
3503                 demonextmatch = val!=0;
3504                 sendservmsgf("demo recording is %s for next match", demonextmatch ? "enabled" : "disabled");
3505                 break;
3506             }
3507 
3508             case N_STOPDEMO:
3509             {
3510                 if(ci->privilege < (restrictdemos ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break;
3511                 stopdemo();
3512                 break;
3513             }
3514 
3515             case N_CLEARDEMOS:
3516             {
3517                 int demo = getint(p);
3518                 if(ci->privilege < (restrictdemos ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break;
3519                 cleardemos(demo);
3520                 break;
3521             }
3522 
3523             case N_LISTDEMOS:
3524                 if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break;
3525                 listdemos(sender);
3526                 break;
3527 
3528             case N_GETDEMO:
3529             {
3530                 int n = getint(p), tag = getint(p);
3531                 if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break;
3532                 senddemo(ci, n, tag);
3533                 break;
3534             }
3535 
3536             case N_GETMAP:
3537                 if(!mapdata) sendf(sender, 1, "ris", N_SERVMSG, "no map to send");
3538                 else if(ci->getmap) sendf(sender, 1, "ris", N_SERVMSG, "already sending map");
3539                 else
3540                 {
3541                     sendservmsgf("[%s is getting the map]", colorname(ci));
3542                     if((ci->getmap = sendfile(sender, 2, mapdata, "ri", N_SENDMAP)))
3543                         ci->getmap->freeCallback = freegetmap;
3544                     ci->needclipboard = totalmillis ? totalmillis : 1;
3545                 }
3546                 break;
3547 
3548             case N_NEWMAP:
3549             {
3550                 int size = getint(p);
3551                 if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break;
3552                 if(size>=0)
3553                 {
3554                     smapname[0] = '\0';
3555                     resetitems();
3556                     notgotitems = false;
3557                     if(smode) smode->newmap();
3558                 }
3559                 QUEUE_MSG;
3560                 break;
3561             }
3562 
3563             case N_SETMASTER:
3564             {
3565                 int mn = getint(p), val = getint(p);
3566                 getstring(text, p);
3567                 if(mn != ci->clientnum)
3568                 {
3569                     if(!ci->privilege && !ci->local) break;
3570                     clientinfo *minfo = (clientinfo *)getclientinfo(mn);
3571                     if(!minfo || !minfo->connected || (!ci->local && minfo->privilege >= ci->privilege) || (val && minfo->privilege)) break;
3572                     setmaster(minfo, val!=0, "", NULL, NULL, PRIV_MASTER, true);
3573                 }
3574                 else setmaster(ci, val!=0, text);
3575                 // don't broadcast the master password
3576                 break;
3577             }
3578 
3579             case N_ADDBOT:
3580             {
3581                 aiman::reqadd(ci, getint(p));
3582                 break;
3583             }
3584 
3585             case N_DELBOT:
3586             {
3587                 aiman::reqdel(ci);
3588                 break;
3589             }
3590 
3591             case N_BOTLIMIT:
3592             {
3593                 int limit = getint(p);
3594                 if(ci) aiman::setbotlimit(ci, limit);
3595                 break;
3596             }
3597 
3598             case N_BOTBALANCE:
3599             {
3600                 int balance = getint(p);
3601                 if(ci) aiman::setbotbalance(ci, balance!=0);
3602                 break;
3603             }
3604 
3605             case N_AUTHTRY:
3606             {
3607                 string desc, name;
3608                 getstring(desc, p, sizeof(desc));
3609                 getstring(name, p, sizeof(name));
3610                 tryauth(ci, name, desc);
3611                 break;
3612             }
3613 
3614             case N_AUTHKICK:
3615             {
3616                 string desc, name;
3617                 getstring(desc, p, sizeof(desc));
3618                 getstring(name, p, sizeof(name));
3619                 int victim = getint(p);
3620                 getstring(text, p);
3621                 filtertext(text, text);
3622                 int authpriv = PRIV_AUTH;
3623                 if(desc[0])
3624                 {
3625                     userinfo *u = users.access(userkey(name, desc));
3626                     if(u) authpriv = u->privilege; else break;
3627                 }
3628                 if(ci->local || ci->privilege >= authpriv) trykick(ci, victim, text);
3629                 else if(trykick(ci, victim, text, name, desc, authpriv, true) && tryauth(ci, name, desc))
3630                 {
3631                     ci->authkickvictim = victim;
3632                     ci->authkickreason = newstring(text);
3633                 }
3634                 break;
3635             }
3636 
3637             case N_AUTHANS:
3638             {
3639                 string desc, ans;
3640                 getstring(desc, p, sizeof(desc));
3641                 uint id = (uint)getint(p);
3642                 getstring(ans, p, sizeof(ans));
3643                 answerchallenge(ci, id, ans, desc);
3644                 break;
3645             }
3646 
3647             case N_PAUSEGAME:
3648             {
3649                 int val = getint(p);
3650                 if(ci->privilege < (restrictpausegame ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break;
3651                 pausegame(val > 0, ci);
3652                 break;
3653             }
3654 
3655             case N_GAMESPEED:
3656             {
3657                 int val = getint(p);
3658                 if(ci->privilege < (restrictgamespeed ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break;
3659                 changegamespeed(val, ci);
3660                 break;
3661             }
3662 
3663             case N_COPY:
3664                 ci->cleanclipboard();
3665                 ci->lastclipboard = totalmillis ? totalmillis : 1;
3666                 goto genericmsg;
3667 
3668             case N_PASTE:
3669                 if(ci->state.state!=CS_SPECTATOR) sendclipboard(ci);
3670                 goto genericmsg;
3671 
3672             case N_CLIPBOARD:
3673             {
3674                 int unpacklen = getint(p), packlen = getint(p);
3675                 ci->cleanclipboard(false);
3676                 if(ci->state.state==CS_SPECTATOR)
3677                 {
3678                     if(packlen > 0) p.subbuf(packlen);
3679                     break;
3680                 }
3681                 if(packlen <= 0 || packlen > (1<<16) || unpacklen <= 0)
3682                 {
3683                     if(packlen > 0) p.subbuf(packlen);
3684                     packlen = unpacklen = 0;
3685                 }
3686                 packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE);
3687                 putint(q, N_CLIPBOARD);
3688                 putint(q, ci->clientnum);
3689                 putint(q, unpacklen);
3690                 putint(q, packlen);
3691                 if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen);
3692                 ci->clipboard = q.finalize();
3693                 ci->clipboard->referenceCount++;
3694                 break;
3695             }
3696 
3697             case N_EDITT:
3698             case N_REPLACE:
3699             case N_EDITVSLOT:
3700             {
3701                 int size = server::msgsizelookup(type);
3702                 if(size<=0) { disconnect_client(sender, DISC_MSGERR); return; }
3703                 loopi(size-1) getint(p);
3704                 if(p.remaining() < 2) { disconnect_client(sender, DISC_MSGERR); return; }
3705                 int extra = lilswap(*(const ushort *)p.pad(2));
3706                 if(p.remaining() < extra) { disconnect_client(sender, DISC_MSGERR); return; }
3707                 p.pad(extra);
3708                 if(ci && ci->state.state!=CS_SPECTATOR) QUEUE_MSG;
3709                 break;
3710             }
3711 
3712             case N_UNDO:
3713             case N_REDO:
3714             {
3715                 int unpacklen = getint(p), packlen = getint(p);
3716                 if(!ci || ci->state.state==CS_SPECTATOR || packlen <= 0 || packlen > (1<<16) || unpacklen <= 0)
3717                 {
3718                     if(packlen > 0) p.subbuf(packlen);
3719                     break;
3720                 }
3721                 if(p.remaining() < packlen) { disconnect_client(sender, DISC_MSGERR); return; }
3722                 packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE);
3723                 putint(q, type);
3724                 putint(q, ci->clientnum);
3725                 putint(q, unpacklen);
3726                 putint(q, packlen);
3727                 if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen);
3728                 sendpacket(-1, 1, q.finalize(), ci->clientnum);
3729                 break;
3730             }
3731 
3732             case N_SERVCMD:
3733                 getstring(text, p);
3734                 break;
3735 
3736             #define PARSEMESSAGES 1
3737             #include "capture.h"
3738             #include "ctf.h"
3739             #include "collect.h"
3740             #undef PARSEMESSAGES
3741 
3742             case -1:
3743                 disconnect_client(sender, DISC_MSGERR);
3744                 return;
3745 
3746             case -2:
3747                 disconnect_client(sender, DISC_OVERFLOW);
3748                 return;
3749 
3750             default: genericmsg:
3751             {
3752                 int size = server::msgsizelookup(type);
3753                 if(size<=0) { disconnect_client(sender, DISC_MSGERR); return; }
3754                 loopi(size-1) getint(p);
3755                 if(ci) switch(msgfilter[type])
3756                 {
3757                     case 2: case 3: if(ci->state.state != CS_SPECTATOR) QUEUE_MSG; break;
3758                     default: if(cq && (ci != cq || ci->state.state!=CS_SPECTATOR)) { QUEUE_AI; QUEUE_MSG; } break;
3759                 }
3760                 break;
3761             }
3762         }
3763     }
3764 
laninfoport()3765     int laninfoport() { return SAUERBRATEN_LANINFO_PORT; }
serverinfoport(int servport)3766     int serverinfoport(int servport) { return servport < 0 ? SAUERBRATEN_SERVINFO_PORT : servport+1; }
serverport(int infoport)3767     int serverport(int infoport) { return infoport < 0 ? SAUERBRATEN_SERVER_PORT : infoport-1; }
defaultmaster()3768     const char *defaultmaster() { return "master.sauerbraten.org"; }
masterport()3769     int masterport() { return SAUERBRATEN_MASTER_PORT; }
numchannels()3770     int numchannels() { return 3; }
3771 
3772     #include "extinfo.h"
3773 
serverinforeply(ucharbuf & req,ucharbuf & p)3774     void serverinforeply(ucharbuf &req, ucharbuf &p)
3775     {
3776         if(req.remaining() && !getint(req))
3777         {
3778             extserverinforeply(req, p);
3779             return;
3780         }
3781 
3782         putint(p, numclients(-1, false, true));
3783         putint(p, gamepaused || gamespeed != 100 ? 7 : 5);                   // number of attrs following
3784         putint(p, PROTOCOL_VERSION);    // generic attributes, passed back below
3785         putint(p, gamemode);
3786         putint(p, m_timed ? max((gamelimit - gamemillis)/1000, 0) : 0);
3787         putint(p, maxclients);
3788         putint(p, serverpass[0] ? MM_PASSWORD : (!m_mp(gamemode) ? MM_PRIVATE : (mastermode || mastermask&MM_AUTOAPPROVE ? mastermode : MM_AUTH)));
3789         if(gamepaused || gamespeed != 100)
3790         {
3791             putint(p, gamepaused ? 1 : 0);
3792             putint(p, gamespeed);
3793         }
3794         sendstring(smapname, p);
3795         sendstring(serverdesc, p);
3796         sendserverinforeply(p);
3797     }
3798 
servercompatible(char * name,char * sdec,char * map,int ping,const vector<int> & attr,int np)3799     bool servercompatible(char *name, char *sdec, char *map, int ping, const vector<int> &attr, int np)
3800     {
3801         return attr.length() && attr[0]==PROTOCOL_VERSION;
3802     }
3803 
3804     #include "aiman.h"
3805 }
3806 
3807