1 // WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING
2 // Before modifying this file, please read our Guidelines: ./docs/guidelines.txt
3 // The most recent version can be viewed at: https://redeclipse.net/guidelines
4 // WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING
5 
6 #define CPP_GAME_SERVER 1
7 #include "game.h"
8 #include "errno.h"
9 
10 namespace server
11 {
12     struct srventity
13     {
14         vec o;
15         int type;
16         bool spawned;
17         int millis, last;
18         attrvector attrs, kin;
19 
srventityserver::srventity20         srventity() :  o(0, 0, 0), type(NOTUSED), spawned(false), millis(0), last(0) { reset(); }
~srventityserver::srventity21         ~srventity() { reset(); }
22 
resetserver::srventity23         void reset()
24         {
25             o = vec(0, 0, 0);
26             attrs.shrink(0);
27             kin.shrink(0);
28         }
29     };
30 
31     struct clientinfo;
32 
33     struct gameevent
34     {
~gameeventserver::gameevent35         virtual ~gameevent() {}
36         virtual bool flush(clientinfo *ci, int fmillis);
processserver::gameevent37         virtual void process(clientinfo *ci) {}
keepableserver::gameevent38         virtual bool keepable() const { return false; }
39     };
40 
41     struct timedevent : gameevent
42     {
43         int millis;
44         bool flush(clientinfo *ci, int fmillis);
45     };
46 
47     struct shotevent : timedevent
48     {
49         int id, weap, flags, scale, target, num;
50         ivec from, dest;
51         vector<shotmsg> shots;
52         void process(clientinfo *ci);
53     };
54 
55     struct switchevent : timedevent
56     {
57         int id, weap;
58         void process(clientinfo *ci);
59     };
60 
61     struct cookevent : timedevent
62     {
63         int id, weap, etype, offtime;
64         void process(clientinfo *ci);
65     };
66 
67     struct dropevent : timedevent
68     {
69         int id, weap;
70         void process(clientinfo *ci);
71     };
72 
73     struct reloadevent : timedevent
74     {
75         int id, weap;
76         void process(clientinfo *ci);
77     };
78 
79     struct hitset
80     {
81         int flags, proj, target;
82         union
83         {
84             int rays;
85             int dist;
86         };
87         ivec dir, vel;
88     };
89 
90     struct destroyevent : timedevent
91     {
92         int id, type, weap, fromweap, fromflags, flags, radial, scale;
93         vector<hitset> hits;
keepableserver::destroyevent94         bool keepable() const { return true; }
95         void process(clientinfo *ci);
96     };
97 
98     struct suicideevent : gameevent
99     {
100         int flags, material;
101         void process(clientinfo *ci);
102     };
103 
104     struct useevent : timedevent
105     {
106         int id, cn, ent;
107         void process(clientinfo *ci);
108     };
109 
110     struct stickyevent : timedevent
111     {
112         int id, weap, flags, target;
113         ivec norm, pos;
keepableserver::stickyevent114         bool keepable() const { return true; }
115         void process(clientinfo *ci);
116     };
117 
118     int servprojid = 0;
getprojid()119     int getprojid()
120     {
121         servprojid++;
122         if(servprojid <= 0) servprojid = 1;
123         return servprojid;
124     }
125 
126     struct projectile
127     {
128         int id, ammo;
129 
projectileserver::projectile130         projectile(int n, int a) : id(n), ammo(a) {}
~projectileserver::projectile131         ~projectile() {}
132     };
133 
134     struct projectilestate
135     {
136         vector<projectile> projs;
137 
projectilestateserver::projectilestate138         projectilestate() { reset(); }
139 
resetserver::projectilestate140         void reset() { projs.shrink(0); }
141 
addserver::projectilestate142         void add(int id, int ammo = -1)
143         {
144             projs.add(projectile(id, ammo));
145         }
146 
removeserver::projectilestate147         bool remove(int id)
148         {
149             loopv(projs) if(projs[i].id == id)
150             {
151                 projs.remove(i);
152                 return true;
153             }
154             return false;
155         }
156 
removeallserver::projectilestate157         int removeall(int id)
158         {
159             int count = 0;
160             loopvrev(projs) if(projs[i].id == id)
161             {
162                 projs.remove(i);
163                 count++;
164             }
165             return count;
166         }
167 
findserver::projectilestate168         bool find(int id)
169         {
170             loopv(projs) if(projs[i].id == id) return true;
171             return false;
172         }
173 
valuesserver::projectilestate174         void values(int id, int &a)
175         {
176             a = -1;
177             loopv(projs) if(projs[i].id == id)
178             {
179                 a = projs[i].ammo;
180                 return;
181             }
182         }
183     };
184 
185     struct dmghist
186     {
187         int clientnum, millis;
188 
dmghistserver::dmghist189         dmghist() {}
dmghistserver::dmghist190         dmghist(int c, int m) : clientnum(c), millis(m) {}
~dmghistserver::dmghist191         ~dmghist() {}
192     };
193 
194     struct teamkill
195     {
196         int millis, team, points;
197 
teamkillserver::teamkill198         teamkill() {}
teamkillserver::teamkill199         teamkill(int m, int t, int p) : millis(m), team(t), points(p) {}
~teamkillserver::teamkill200         ~teamkill() {}
201     };
202 
203     struct weaponstats
204     {
205         int timewielded, timeloadout;
206         int hits1, hits2, flakhits1, flakhits2;
207         int shots1, shots2, flakshots1, flakshots2;
208         int frags1, frags2, damage1, damage2;
209 
weaponstatsserver::weaponstats210         weaponstats() { reset(); }
~weaponstatsserver::weaponstats211         ~weaponstats() {}
212 
resetserver::weaponstats213         void reset()
214         {
215             timewielded = timeloadout = 0;
216             hits1 = hits2 = flakhits1 = flakhits2 = 0;
217             shots1 = shots2 = flakshots1 = flakshots2 = 0;
218             frags1 = frags2 = damage1 = damage2 = 0;
219         }
220     };
221 
222     struct capturestats
223     {
224         int capturing;
225         int captured;
226 
capturestatsserver::capturestats227         capturestats() { reset(); }
~capturestatsserver::capturestats228         ~capturestats() {}
229 
resetserver::capturestats230         void reset()
231         {
232             capturing = captured = 0;
233         }
234     };
235 
236     struct bombstats
237     {
238         int bombing;
239         int bombed;
240 
bombstatsserver::bombstats241         bombstats() { reset(); }
~bombstatsserver::bombstats242         ~bombstats() {}
243 
resetserver::bombstats244         void reset()
245         {
246             bombing = bombed = 0;
247         }
248     };
249 
250     struct ffaroundstats
251     {
252         int round;
253         bool winner;
254 
ffaroundstatsserver::ffaroundstats255         ffaroundstats() { reset(); }
~ffaroundstatsserver::ffaroundstats256         ~ffaroundstats() {}
257 
resetserver::ffaroundstats258         void reset()
259         {
260             round = 0;
261             winner = false;
262         }
263     };
264 
265     extern int gamemode, mutators;
266 
267     enum { WARN_CHAT = 0, WARN_TEAMKILL, WARN_MAX };
268 
269     struct servstate : baseent, clientstate
270     {
271         int rewards[2], shotdamage, damage, lasttimewielded, lasttimeloadout[W_MAX], aireinit,
272             lastresowner[W_R_MAX], lasttimealive, timealive, lasttimeactive, timeactive, lastresweapon[W_R_MAX], lasthurt,
273             localtotalpoints, localtotalfrags, localtotaldeaths, localtotalavgposnum;
274         float localtotalavgpossum, totalavgpos, globaltotalavgpos;
275         bool lastresalt[W_MAX];
276         projectilestate dropped, weapshots[W_MAX][2];
277         vector<int> fraglog, fragmillis, cpnodes, chatmillis;
278         vector<dmghist> damagelog;
279         vector<teamkill> teamkills;
280 
281         weaponstats weapstats[W_MAX];
282         vector<capturestats> captures;
283         vector<bombstats> bombings;
284         vector<ffaroundstats> ffarounds;
285 
286         int warnings[WARN_MAX][2];
287 
servstateserver::servstate288         servstate() : lasttimewielded(0), aireinit(0), lasttimealive(0), timealive(0), lasttimeactive(0), timeactive(0), lasthurt(0), localtotalpoints(0), localtotalfrags(0), localtotaldeaths(0), localtotalavgposnum(0), localtotalavgpossum(0), totalavgpos(0), globaltotalavgpos(-1)
289         {
290             loopi(WARN_MAX) loopj(2) warnings[i][j] = 0;
291             loopi(W_MAX) lasttimeloadout[i] = 0;
292             resetresidualowner();
293         }
294 
isaliveserver::servstate295         bool isalive(int millis)
296         {
297             return state == CS_ALIVE || ((state == CS_DEAD || state == CS_WAITING) && lastdeath && millis-lastdeath <= DEATHMILLIS);
298         }
299 
mapchangeserver::servstate300         void mapchange(bool change = false)
301         {
302             if(state != CS_SPECTATOR) state = CS_DEAD;
303             dropped.reset();
304             loopi(W_MAX) loopj(2) weapshots[i][j].reset();
305             clientstate::mapchange(change);
306             rewards[0] = rewards[1] = shotdamage = damage = timealive = timeactive = lasthurt = 0;
307             fraglog.shrink(0);
308             fragmillis.shrink(0);
309             cpnodes.shrink(0);
310             damagelog.shrink(0);
311             teamkills.shrink(0);
312             loopi(W_MAX) weapstats[i].reset();
313             captures.shrink(0);
314             bombings.shrink(0);
315             ffarounds.shrink(0);
316             // condense localtotalavgpos so old games don't count as much as the current match
317             int div = max(localtotalavgposnum / 2, 1);
318             localtotalavgpossum /= (float)div;
319             localtotalavgposnum = ceil((float)localtotalavgposnum / div);
320             respawn(0);
321         }
322 
resetresidualownerserver::servstate323         void resetresidualowner(int n = -1)
324         {
325             if(n >= 0 && n < W_R_MAX) lastresowner[n] = -1;
326             else loopi(W_R_MAX) lastresowner[i] = -1;
327         }
328 
respawnserver::servstate329         void respawn(int millis)
330         {
331             baseent::reset();
332             rewards[1] = lasthurt = 0;
333             resetresidualowner();
334             clientstate::respawn(millis);
335         }
336 
updateweaptimeserver::servstate337         void updateweaptime()
338         {
339             extern int gamemillis;
340             if(lasttimewielded && isalive(gamemillis))
341             {
342                 int millis = totalmillis-lasttimewielded, secs = millis/1000;
343                 weapstats[weapselect].timewielded += secs;
344                 lasttimewielded = totalmillis+(secs*1000)-millis;
345                 loopi(W_MAX)
346                 {
347                     if(lasttimeloadout[i] && holdweap(i, m_weapon(actortype, gamemode, mutators), lastmillis))
348                     {
349                         int millis = totalmillis-lasttimeloadout[i], secs = millis/1000;
350                         weapstats[i].timeloadout += secs;
351                         lasttimeloadout[i] = totalmillis+(secs*1000)-millis;
352                     }
353                     else lasttimeloadout[i] = totalmillis ? totalmillis : 1;
354                 }
355             }
356             else
357             {
358                 lasttimewielded = totalmillis ? totalmillis : 1;
359                 loopi(W_MAX) lasttimeloadout[i] = totalmillis ? totalmillis : 1;
360             }
361         }
362 
updatetimeplayedserver::servstate363         void updatetimeplayed()
364         {
365             clientstate::updatetimeplayed();
366             extern int gamemillis;
367             if(lasttimealive && isalive(gamemillis))
368             {
369                 int millis = totalmillis-lasttimealive, secs = millis/1000;
370                 timealive += secs;
371                 lasttimealive = totalmillis+(secs*1000)-millis;
372             }
373             else lasttimealive = totalmillis ? totalmillis : 1;
374             if(lasttimeactive && (state == CS_ALIVE || state == CS_DEAD || state == CS_WAITING))
375             {
376                 int millis = totalmillis-lasttimeactive, secs = millis/1000;
377                 timeactive += secs;
378                 lasttimeactive = totalmillis+(secs*1000)-millis;
379             }
380             else lasttimeactive = totalmillis ? totalmillis : 1;
381             updateweaptime();
382         }
383 
feetposserver::servstate384         vec feetpos(float offset = 0) const { return vec(o).add(vec(0, 0, offset)); }
headposserver::servstate385         vec headpos(float offset = 0) const { return vec(o).add(vec(0, 0, offset+actors[actortype].height)); }
centerserver::servstate386         vec center() const { return vec(o).add(vec(0, 0, actors[actortype].height*0.5f)); }
387     };
388 
389     struct votecount
390     {
391         char *map;
392         int mode, muts, count;
votecountserver::votecount393         votecount() {}
votecountserver::votecount394         votecount(char *s, int n, int m) : map(s), mode(n), muts(m), count(0) {}
395     };
396 
397     struct clientinfo : servstate
398     {
399         string name, handle, steamid, mapvote, authname, authsteam, clientmap;
400         int clientnum, connectmillis, sessionid, overflow, ping, team, lastteam, lastplayerinfo,
401             modevote, mutsvote, lastvote, privilege, oldprivilege, gameoffset, lastevent, wslen, swapteam, clientcrc;
402         bool connected, ready, local, timesync, online, wantsmap, gettingmap, connectauth, connectsteam, kicked, needsresume;
403         vector<gameevent *> events;
404         vector<uchar> position, messages;
405         uchar *wsdata;
406         vector<clientinfo *> bots;
407         uint authreq;
408         ENetPacket *clipboard;
409         int lastclipboard, needclipboard;
410 
clientinfoserver::clientinfo411         clientinfo() : clipboard(NULL) { reset(); }
~clientinfoserver::clientinfo412         ~clientinfo() { events.deletecontents(); cleanclipboard(); }
413 
addeventserver::clientinfo414         void addevent(gameevent *e)
415         {
416             if(state == CS_SPECTATOR || events.length()>250) delete e;
417             else events.add(e);
418         }
419 
mapchangeserver::clientinfo420         void mapchange(bool change = true)
421         {
422             mapvote[0] = '\0';
423             modevote = mutsvote = -1;
424             servstate::mapchange(change);
425             events.deletecontents();
426             overflow = 0;
427             ready = timesync = wantsmap = gettingmap = needsresume = false;
428             lastevent = gameoffset = lastvote = clientcrc = 0;
429             if(!change) lastteam = T_NEUTRAL;
430             team = swapteam = T_NEUTRAL;
431             clientmap[0] = '\0';
432             if(handle[0])
433             {
434                 requestmasterf("reqauthstats \"%s\"\n", handle);
435                 flushmasteroutput();
436             }
437         }
438 
cleanclipboardserver::clientinfo439         void cleanclipboard(bool fullclean = true)
440         {
441             if(clipboard) { if(--clipboard->referenceCount <= 0) enet_packet_destroy(clipboard); clipboard = NULL; }
442             if(fullclean) lastclipboard = 0;
443         }
444 
resetserver::clientinfo445         void reset()
446         {
447             ping = lastplayerinfo = 0;
448             name[0] = handle[0] = steamid[0] = '\0';
449             privilege = PRIV_NONE;
450             oldprivilege = -1;
451             connected = ready = local = online = wantsmap = gettingmap = connectauth = connectsteam = kicked = false;
452             authreq = 0;
453             position.setsize(0);
454             messages.setsize(0);
455             needclipboard = 0;
456             cleanclipboard();
457             mapchange(false);
458         }
459 
getmillisserver::clientinfo460         int getmillis(int millis, int id)
461         {
462             if(!timesync)
463             {
464                 timesync = true;
465                 gameoffset = millis-id;
466                 return millis;
467             }
468             return gameoffset+id;
469         }
470 
isreadyserver::clientinfo471         bool isready()
472         {
473             return ready && !wantsmap;
474         }
475 
updateavgposserver::clientinfo476         void updateavgpos()
477         {
478             if(handle[0] && globaltotalavgpos >= 0)
479             {
480                 float localweight = localtotalavgposnum ? G(teambalanceavgposlocalweight) : 0;
481                 totalavgpos = (localtotalavgpossum / max(1, localtotalavgposnum)) * localweight + globaltotalavgpos * (1.0 - localweight);
482             }
483             else
484             {
485                 totalavgpos = localtotalavgpossum / max(1, localtotalavgposnum);
486             }
487         }
488 
sendburnserver::clientinfo489         void sendburn()
490         {
491             sendf(-1, 1, "ri5", N_BURNRES, clientnum, burntime, burndelay, burndamage);
492         }
493 
sendbleedserver::clientinfo494         void sendbleed()
495         {
496             sendf(-1, 1, "ri5", N_BLEEDRES, clientnum, bleedtime, bleeddelay, bleeddamage);
497         }
498 
sendshockserver::clientinfo499         void sendshock()
500         {
501             sendf(-1, 1, "ri6f2i", N_SHOCKRES, clientnum, shocktime, shockdelay, shockdamage, shockstun, shockstunscale, shockstunfall, shockstuntime);
502         }
503     };
504 
505     struct savedscore
506     {
507         uint ip;
508         string name, handle, steamid;
509         int points, frags, deaths, localtotalpoints, localtotalfrags, localtotaldeaths, spree, rewards, timeplayed, timealive, timeactive, shotdamage, damage, cptime, actortype;
510         float localtotalavgposnum, localtotalavgpossum;
511         int warnings[WARN_MAX][2];
512         bool quarantine;
513         weaponstats weapstats[W_MAX];
514         vector<teamkill> teamkills;
515         vector<capturestats> captures;
516         vector<bombstats> bombings;
517         vector<ffaroundstats> ffarounds;
518 
saveserver::savedscore519         void save(clientinfo *ci)
520         {
521             points = ci->points;
522             frags = ci->frags;
523             deaths = ci->deaths;
524             localtotalpoints = ci->localtotalpoints;
525             localtotalfrags = ci->localtotalfrags;
526             localtotaldeaths = ci->localtotaldeaths;
527             localtotalavgposnum = ci->localtotalavgposnum;
528             localtotalavgpossum = ci->localtotalavgpossum;
529             spree = ci->spree;
530             rewards = ci->rewards[0];
531             timeplayed = ci->timeplayed;
532             timealive = ci->timealive;
533             timeactive = ci->timeactive;
534             shotdamage = ci->shotdamage;
535             damage = ci->damage;
536             cptime = ci->cptime;
537             actortype = ci->actortype;
538             loopi(W_MAX) weapstats[i] = ci->weapstats[i];
539             loopi(WARN_MAX) loopj(2) warnings[i][j] = ci->warnings[i][j];
540             quarantine = ci->quarantine;
541             teamkills.shrink(0);
542             loopv(ci->teamkills) teamkills.add(ci->teamkills[i]);
543             captures.shrink(0);
544             loopv(ci->captures) captures.add(ci->captures[i]);
545             bombings.shrink(0);
546             loopv(ci->bombings) bombings.add(ci->bombings[i]);
547             ffarounds.shrink(0);
548             loopv(ci->ffarounds) ffarounds.add(ci->ffarounds[i]);
549         }
550 
restoreserver::savedscore551         void restore(clientinfo *ci)
552         {
553             ci->points = points;
554             ci->frags = frags;
555             ci->deaths = deaths;
556             ci->localtotalpoints = localtotalpoints;
557             ci->localtotalfrags = localtotalfrags;
558             ci->localtotaldeaths = localtotaldeaths;
559             ci->localtotalavgposnum = localtotalavgposnum;
560             ci->localtotalavgpossum = localtotalavgpossum;
561             ci->totalpoints = localtotalpoints;
562             ci->totalfrags = localtotalfrags;
563             ci->totaldeaths = localtotaldeaths;
564             ci->spree = spree;
565             ci->rewards[0] = rewards;
566             ci->timeplayed = timeplayed;
567             ci->timealive = timealive;
568             ci->timeactive = timeactive;
569             ci->shotdamage = shotdamage;
570             ci->damage = damage;
571             ci->cptime = cptime;
572             loopi(W_MAX) ci->weapstats[i] = weapstats[i];
573             loopi(WARN_MAX) loopj(2) ci->warnings[i][j] = warnings[i][j];
574             ci->quarantine = quarantine;
575             ci->teamkills.shrink(0);
576             loopv(teamkills) ci->teamkills.add(teamkills[i]);
577             ci->captures.shrink(0);
578             loopv(captures) ci->captures.add(captures[i]);
579             ci->bombings.shrink(0);
580             loopv(bombings) ci->bombings.add(bombings[i]);
581             ci->ffarounds.shrink(0);
582             loopv(ffarounds) ci->ffarounds.add(ffarounds[i]);
583         }
584 
mapchangeserver::savedscore585         void mapchange()
586         {
587             points = frags = spree = rewards = deaths = timeplayed = timealive = timeactive = shotdamage = damage = cptime = 0;
588             actortype = A_MAX;
589             teamkills.shrink(0);
590             captures.shrink(0);
591             bombings.shrink(0);
592             ffarounds.shrink(0);
593             loopi(W_MAX) weapstats[i].reset();
594         }
595     };
596 
597     namespace aiman {
598         extern void setskill(clientinfo *ci, bool init = false);
599         extern bool addai(int type, int ent = -1);
600         extern void deleteai(clientinfo *ci);
601         extern bool delai(int type, bool skip = true);
602         extern void removeai(clientinfo *ci, bool complete = false);
603         extern bool reassignai(clientinfo *exclude = NULL);
604         extern void clearai(int type = 0);
605         extern void checkai();
606         extern void poke();
607     }
608 
609     string smapname = "";
610     int smapcrc = 0, smapvariant = MPV_DEF, mapsending = -1, mapgameinfo = -1, gamestate = G_S_WAITING, gamemode = G_EDITMODE, mutators = 0, gamemillis = 0, gamelimit = 0,
611         mastermode = MM_OPEN, timeremaining = -1, oldtimelimit = -1, gamewaittime = 0, lastteambalance = 0, nextteambalance = 0, lastavgposcalc = 0, lastrotatecycle = 0;
612     bool hasgameinfo = false, updatecontrols = false, shouldcheckvotes = false, firstblood = false, sentstats = false;
613     enet_uint32 lastsend = 0;
614     stream *mapdata[SENDMAP_MAX] = { NULL };
615     vector<clientinfo *> clients, connects;
616 
617     struct demofile
618     {
619         string info;
620         uchar *data;
621         int ctime, len;
622     };
623 
624     vector<demofile> demos;
625 
626     bool demonextmatch = false;
627     stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL;
628     int nextplayback = 0, triggerid = 0;
629     struct triggergrp
630     {
631         int id;
632         vector<int> ents;
triggergrpserver::triggergrp633         triggergrp() { reset(); }
resetserver::triggergrp634         void reset(int n = 0) { id = n; ents.shrink(0); }
635     } triggers[TRIGGERIDS+1];
636 
canplay()637     bool canplay()
638     {
639         if(!demoplayback && !m_demo(gamemode))
640             if((m_play(gamemode) && !hasgameinfo) || !gs_playing(gamestate)) return false;
641         return true;
642     }
643 
644     struct servmode
645     {
servmodeserver::servmode646         servmode() {}
~servmodeserver::servmode647         virtual ~servmode() {}
spawnitemsserver::servmode648         virtual bool spawnitems() const { return true; }
649 
entergameserver::servmode650         virtual void entergame(clientinfo *ci) {}
leavegameserver::servmode651         virtual void leavegame(clientinfo *ci, bool disconnecting = false) {}
652 
movedserver::servmode653         virtual void moved(clientinfo *ci, const vec &oldpos, const vec &newpos) {}
canspawnserver::servmode654         virtual bool canspawn(clientinfo *ci, bool tryspawn = false) { return true; }
spawnedserver::servmode655         virtual void spawned(clientinfo *ci) {}
pointsserver::servmode656         virtual int points(clientinfo *m, clientinfo *v)
657         {
658             if(m == v || m->team == v->team) return -1;
659             return 1;
660         }
diedserver::servmode661         virtual void died(clientinfo *m, clientinfo *v = NULL) {}
changeteamserver::servmode662         virtual void changeteam(clientinfo *ci, int oldteam, int newteam) {}
initclientserver::servmode663         virtual void initclient(clientinfo *ci, packetbuf &p, bool connecting) {}
updateserver::servmode664         virtual void update() {}
resetserver::servmode665         virtual void reset() {}
layoutserver::servmode666         virtual void layout() {}
balanceserver::servmode667         virtual void balance(int oldbalance) {}
intermissionserver::servmode668         virtual void intermission() {}
wantsovertimeserver::servmode669         virtual bool wantsovertime() { return false; }
damageserver::servmode670         virtual bool damage(clientinfo *m, clientinfo *v, int damage, int weap, int flags, int material, const ivec &hitpush = ivec(0, 0, 0), const ivec &hitvel = ivec(0, 0, 0), float dist = 0) { return true; }
dodamageserver::servmode671         virtual void dodamage(clientinfo *m, clientinfo *v, int &damage, int &hurt, int &weap, int &flags, int &material, const ivec &hitpush = ivec(0, 0, 0), const ivec &hitvel = ivec(0, 0, 0), float dist = 0) { }
regenserver::servmode672         virtual void regen(clientinfo *ci, int &total, int &amt, int &delay) {}
checkclientserver::servmode673         virtual void checkclient(clientinfo *ci) {}
scoreaffinityserver::servmode674         virtual void scoreaffinity(clientinfo *ci, bool win = true) {}
canbalanceserver::servmode675         virtual bool canbalance() { return true; }
676     };
677 
678     vector<srventity> sents;
679     vector<savedscore> savedscores;
680     vector<savedscore> savedstatsscores;
681     servmode *smode;
682     vector<servmode *> smuts;
683     #define mutate(a,b) { loopvk(a) { servmode *mut = a[k]; { b; } } }
684     int curbalance = 0, nextbalance = 0, totalspawns = 0;
685     bool teamspawns = false;
686 
687     vector<score> scores;
teamscore(int team)688     score &teamscore(int team)
689     {
690         loopv(scores)
691         {
692             score &cs = scores[i];
693             if(cs.team == team) return cs;
694         }
695         score &cs = scores.add();
696         cs.team = team;
697         cs.total = 0;
698         return cs;
699     }
700 
checkmapvariant(int variant)701     bool checkmapvariant(int variant)
702     {
703         if(variant > 0 && smapvariant > 0 && smapvariant != variant) return false;
704         return true;
705     }
706 
chkloadweap(clientinfo * ci,bool request=true)707     bool chkloadweap(clientinfo *ci, bool request = true)
708     {
709         if(ci->actortype == A_PLAYER && ci->loadweap.empty())
710         {
711             if(request)
712             {
713                 ci->lastplayerinfo = 0;
714                 sendf(ci->clientnum, 1, "ri", N_LOADOUT);
715             }
716             return false;
717         }
718         return true;
719     }
720 
721     int attrmap[W_MAX] = {0};
setupattrmap()722     void setupattrmap()
723     {
724         int sweap = m_weapon(A_PLAYER, gamemode, mutators);
725         loopi(W_MAX)
726         {
727             if(m_edit(gamemode) || (i >= W_ITEM && i < W_ALL)) attrmap[i] = i;
728             else if(m_kaboom(gamemode, mutators) || m_medieval(gamemode, mutators)) attrmap[i] = i != W_MINE ? W_GRENADE : W_MINE;
729             else if(m_insta(gamemode, mutators)) attrmap[i] = sweap;
730             else if(i == sweap || i < W_OFFSET || i >= W_ALL) attrmap[i] = W_REPLACE;
731             else attrmap[i] = i;
732         }
733         sendf(-1, 1, "riv", N_ATTRMAP, W_MAX, &attrmap[0]);
734     }
735 
setspawn(int ent,bool spawned,bool clear=false,bool msg=false,int delay=0)736     void setspawn(int ent, bool spawned, bool clear = false, bool msg = false, int delay = 0)
737     {
738         if(!sents.inrange(ent)) return;
739         if(clear) loopvk(clients) if(clients[k]->dropped.removeall(ent))
740             sendf(-1, 1, "ri5", N_DESTROY, clients[k]->clientnum, PRJ_ENT, -1, ent);
741         bool oldspawn = sents[ent].spawned;
742         sents[ent].spawned = spawned;
743         sents[ent].millis = sents[ent].last = gamemillis;
744         if(delay) sents[ent].millis += delay;
745         else if(sents[ent].type == WEAPON)
746         {
747             int attr = m_attr(sents[ent].type, sents[ent].attrs[0]);
748             if(isweap(attr))
749             {
750                 if(!(sents[ent].attrs[1]&W_F_FORCED)) sents[ent].millis += W(attr, spawntime);
751                 else sents[ent].millis += PHYSMILLIS;
752             }
753         }
754         else sents[ent].millis += G(itemspawntime);
755         if(msg && sents[ent].spawned != oldspawn) sendf(-1, 1, "ri3", N_ITEMSPAWN, ent, sents[ent].spawned ? 1 : 0);
756     }
757 
takeammo(clientinfo * ci,int weap,int amt=1)758     void takeammo(clientinfo *ci, int weap, int amt = 1)
759     {
760         ci->weapammo[weap][W_A_CLIP] = max(ci->weapammo[weap][W_A_CLIP]-amt, 0);
761     }
762 
763     struct droplist { int weap, ent, ammo; };
764     enum
765     {
766         DROP_NONE = 0, DROP_WEAPONS = 1<<0, DROP_WCLR = 1<<1, DROP_KAMIKAZE = 1<<2, DROP_EXPLODE = 1<<3,
767         DROP_DEATH = DROP_WEAPONS|DROP_KAMIKAZE, DROP_EXPIRE = DROP_WEAPONS|DROP_EXPLODE, DROP_RESET = DROP_WEAPONS|DROP_WCLR
768     };
769 
dropweapon(clientinfo * ci,int flags,int weap,vector<droplist> & drop)770     void dropweapon(clientinfo *ci, int flags, int weap, vector<droplist> &drop)
771     {
772         if(!isweap(weap) || weap == m_weapon(ci->actortype, gamemode, mutators)) return;
773         if(m_arena(gamemode, mutators) && weap < W_ITEM) return; // only drop non-loadout weapons in arena
774         if(!ci->hasweap(weap, m_weapon(ci->actortype, gamemode, mutators)) || !sents.inrange(ci->weapent[weap])) return;
775         if(!m_classic(gamemode, mutators) && !W2(weap, ammosub, false) && !W2(weap, ammosub, true)) return;
776         int ammo = ci->getammo(weap, 0, true);
777         if(ammo <= 0) return;
778         droplist &d = drop.add();
779         d.weap = weap;
780         d.ent = ci->weapent[weap];
781         d.ammo = ammo;
782         ci->dropped.add(d.ent, d.ammo);
783         if(flags&DROP_WCLR)
784         {
785             ci->weapent[weap] = ci->weapammo[weap][W_A_CLIP] = -1;
786             ci->weapammo[weap][W_A_STORE] = 0;
787         }
788     }
789 
dropitems(clientinfo * ci,int flags=DROP_RESET)790     bool dropitems(clientinfo *ci, int flags = DROP_RESET)
791     {
792         bool kamikaze = false;
793         int ktype = AA(ci->actortype, abilities)&(1<<A_A_KAMIKAZE) ? 3 : G(kamikaze);
794         vector<droplist> drop;
795         if(flags&DROP_EXPLODE || (flags&DROP_KAMIKAZE && ktype && (ktype > 2 || (ci->hasweap(W_GRENADE, m_weapon(ci->actortype, gamemode, mutators)) && (ktype > 1 || ci->weapselect == W_GRENADE)))))
796         {
797             ci->weapshots[W_GRENADE][0].add(1);
798             droplist &d = drop.add();
799             d.weap = W_GRENADE;
800             d.ent = d.ammo = -1;
801             if(!(flags&DROP_EXPLODE)) takeammo(ci, W_GRENADE, W2(W_GRENADE, ammosub, false));
802             kamikaze = true;
803         }
804         if(flags&DROP_WEAPONS) loopi(W_ALL) dropweapon(ci, flags, i, drop);
805         if(!drop.empty())
806             sendf(-1, 1, "ri3iv", N_WEAPDROP, ci->clientnum, -1, drop.length(), drop.length()*sizeof(droplist)/sizeof(int), drop.getbuf());
807         return kamikaze;
808     }
809 
810     struct vampireservmode : servmode
811     {
vampireservmodeserver::vampireservmode812         vampireservmode() {}
dodamageserver::vampireservmode813         void dodamage(clientinfo *m, clientinfo *v, int &damage, int &hurt, int &weap, int &flags, int &material, const ivec &hitpush, const ivec &hitvel, float dist)
814         {
815             if(v != m && (!m_team(gamemode, mutators) || v->team != m->team) && v->state == CS_ALIVE && hurt > 0)
816             {
817                 int real = int(ceilf(hurt*G(vampirescale))), heal = min(v->health+real, v->gethealth(gamemode, mutators, true)), eff = heal-v->health;
818                 if(eff > 0)
819                 {
820                     v->health = heal;
821                     v->lastregen = gamemillis;
822                     v->lastregenamt = eff;
823                     sendf(-1, 1, "ri4", N_REGEN, v->clientnum, v->health, v->lastregenamt);
824                 }
825             }
826         }
827     } vampiremutator;
828 
829     extern bool canbalancenow();
830 
831     struct spawnservmode : servmode // pseudo-mutator to regulate spawning clients
832     {
833         vector<clientinfo *> spawnq, playing;
834 
spawnservmodeserver::spawnservmode835         spawnservmode() {}
836 
spawnqueueserver::spawnservmode837         bool spawnqueue(bool all = false, bool needinfo = true)
838         {
839             return m_play(gamemode) && !m_race(gamemode) && !m_duke(gamemode, mutators) && G(maxalive) > 0 && (!needinfo || canplay()) && (!all || G(maxalivequeue)) && numclients() > 1;
840         }
841 
queueserver::spawnservmode842         void queue(clientinfo *ci, bool msg = true, bool wait = true, bool top = false)
843         {
844             if(spawnqueue(true) && ci->online && ci->actortype < A_ENEMY && ci->state != CS_SPECTATOR && ci->state != CS_EDITING)
845             {
846                 int n = spawnq.find(ci);
847                 playing.removeobj(ci);
848                 if(top)
849                 {
850                     if(n >= 0) spawnq.remove(n);
851                     spawnq.insert(0, ci);
852                 }
853                 else if(n < 0) spawnq.add(ci);
854                 if(wait && ci->state != CS_WAITING) waiting(ci, DROP_RESET);
855                 if(msg && allowbroadcast(ci->clientnum) && !top)
856                 {
857                     int x = max(int(G(maxalive)*G(maxplayers)), max(int(numclients()*G(maxalivethreshold)), G(maxaliveminimum)));
858                     if(m_team(gamemode, mutators))
859                     {
860                         if(x%2) x++;
861                         x = x/2;
862                         if(m_coop(gamemode, mutators) && ci->actortype == A_BOT)
863                             x = int(x*G(coopbalance));
864                     }
865                     int slots = x;
866                     loopv(playing) if(playing[i] && ci->team == playing[i]->team) slots--;
867                     if(!slots)
868                     {
869                         int qn = 0;
870                         loopv(spawnq) if(spawnq[i] && spawnq[i]->team == ci->team && spawnq[i]->actortype == A_PLAYER)
871                         {
872                             qn++;
873                             if(spawnq[i] == ci)
874                             {
875                                 spawnq[i]->queuepos = qn;
876                                 sendf(-1, 1, "ri3", N_QUEUEPOS, spawnq[i]->clientnum, spawnq[i]->queuepos);
877                                 break;
878                             }
879                         }
880                     }
881                 }
882             }
883         }
884 
entergameserver::spawnservmode885         void entergame(clientinfo *ci)
886         {
887             spawnq.removeobj(ci);
888             playing.removeobj(ci);
889             queue(ci);
890         }
891 
leavegameserver::spawnservmode892         void leavegame(clientinfo *ci, bool disconnecting = false)
893         {
894             spawnq.removeobj(ci);
895             playing.removeobj(ci);
896         }
897 
canspawnserver::spawnservmode898         bool canspawn(clientinfo *ci, bool tryspawn = false)
899         {
900             if(ci->actortype >= A_ENEMY) return true;
901             else if(tryspawn)
902             {
903                 if(m_loadout(gamemode, mutators) && !chkloadweap(ci)) return false;
904                 if(spawnqueue(true) && spawnq.find(ci) < 0 && playing.find(ci) < 0) queue(ci);
905                 return true;
906             }
907             if(m_balance(gamemode, mutators, teamspawns) && G(balancenospawn) && nextbalance && m_balreset(gamemode, mutators) && canbalancenow()) return false;
908             int delay = m_delay(ci->actortype, gamemode, mutators, ci->team);
909             if(delay && ci->respawnwait(gamemillis, delay)) return false;
910             if(spawnqueue() && playing.find(ci) < 0)
911             {
912                 if(!canplay()) return false;
913                 if(G(maxalivequeue) && spawnq.find(ci) < 0) queue(ci);
914                 int x = max(int(G(maxalive)*G(maxplayers)), max(int(numclients()*G(maxalivethreshold)), G(maxaliveminimum)));
915                 if(m_team(gamemode, mutators))
916                 {
917                     if(x%2) x++;
918                     x = x/2;
919                     if(m_coop(gamemode, mutators) && ci->actortype == A_BOT)
920                         x = int(x*G(coopbalance));
921                 }
922                 int alive = 0;
923                 loopv(playing)
924                 {
925                     if(playing[i]->state != CS_DEAD && playing[i]->state != CS_ALIVE)
926                     {
927                         if(playing[i]->state != CS_WAITING || !G(maxalivequeue))
928                         {
929                             playing.removeobj(playing[i--]);
930                             continue;
931                         }
932                     }
933                     if(spawnq.find(playing[i]) >= 0) spawnq.removeobj(playing[i]);
934                     if(ci->team == playing[i]->team) alive++;
935                 }
936                 if(alive >= x)
937                 {
938                     if(ci->actortype == A_PLAYER) loopv(playing)
939                     { // kill off bots for the human
940                         if(playing[i]->actortype != A_BOT || ci->team != playing[i]->team)
941                             continue;
942                         queue(playing[i--]);
943                         if(--alive < x) break;
944                     }
945                     if(alive >= x) return false;
946                 }
947                 if(G(maxalivequeue))
948                 {
949                     if(ci->actortype == A_BOT) loopv(spawnq) if(spawnq[i]->team == ci->team)
950                     {
951                         if(spawnq[i] != ci && spawnq[i]->actortype == A_PLAYER) return false;
952                         break;
953                     }
954                     // at this point is where it decides this player is spawning, so tell everyone else their position
955                     if(x-alive == 1)
956                     {
957                         int qn = 0;
958                         loopv(spawnq) if(spawnq[i] != ci && spawnq[i]->team == ci->team && spawnq[i]->actortype == A_PLAYER)
959                         {
960                             qn++;
961                             if(allowbroadcast(spawnq[i]->clientnum))
962                             {
963                                 spawnq[i]->queuepos = qn;
964                                 sendf(-1, 1, "ri3", N_QUEUEPOS, spawnq[i]->clientnum, spawnq[i]->queuepos);
965                             }
966                         }
967                     }
968                 }
969                 spawnq.removeobj(ci);
970                 if(playing.find(ci) < 0) playing.add(ci);
971             }
972             return true;
973         }
974 
spawnedserver::spawnservmode975         void spawned(clientinfo *ci)
976         {
977             spawnq.removeobj(ci);
978             if(playing.find(ci) < 0) queue(ci);
979         }
980 
diedserver::spawnservmode981         void died(clientinfo *ci, clientinfo *at)
982         {
983             spawnq.removeobj(ci);
984             if(G(maxalivequeue)) playing.removeobj(ci);
985         }
986 
resetserver::spawnservmode987         void reset()
988         {
989             spawnq.shrink(0);
990             playing.shrink(0);
991         }
992     } spawnmutator;
993 
canbalancenow()994     bool canbalancenow()
995     {
996         bool ret = true;
997         if(smode) if(!smode->canbalance()) ret = false;
998         if(ret) mutate(smuts, if(!mut->canbalance()) { ret = false; break; });
999         return ret;
1000     }
1001 
1002     SVAR(0, serverpass, "");
1003     SVAR(0, adminpass, "");
1004 
1005     int sversion[2] = {0};
1006     ICOMMAND(0, setversion, "ii", (int *a, int *b), sversion[0] = *a; sversion[1] = *b);
1007 
mastermask()1008     int mastermask()
1009     {
1010         switch(G(serveropen))
1011         {
1012             case 0: default: return MM_FREESERV; break;
1013             case 1: return MM_OPENSERV; break;
1014             case 2: return MM_COOPSERV; break;
1015             case 3: return MM_VETOSERV; break;
1016         }
1017         return 0;
1018     }
1019 
1020     #define setmod(a,b) \
1021     { \
1022         if(a != b) \
1023         { \
1024             ident *id = getident(#a); \
1025             if(id && id->type == ID_VAR && id->flags&IDF_SERVER) \
1026             { \
1027                 if(id->flags&IDF_HEX && uint(id->maxval) == 0xFFFFFFFFU) \
1028                     *id->storage.i = clamp(uint(b), uint(id->minval), uint(id->maxval)); \
1029                 else *id->storage.i = clamp(b, id->minval, id->maxval); \
1030                 id->changed(); \
1031                 const char *sval = intstr(id); \
1032                 sendf(-1, 1, "ri2sis", N_COMMAND, -1, &id->name[3], strlen(sval), sval); \
1033             } \
1034         } \
1035     }
1036     #define setmodf(a,b) \
1037     { \
1038         if(a != b) \
1039         { \
1040             ident *id = getident(#a); \
1041             if(id && id->type == ID_FVAR && id->flags&IDF_SERVER) \
1042             { \
1043                 *id->storage.f = clamp(b, id->minvalf, id->maxvalf); \
1044                 id->changed(); \
1045                 const char *sval = floatstr(id); \
1046                 if(sval) sendf(-1, 1, "ri2sis", N_COMMAND, -1, &id->name[3], strlen(sval), sval); \
1047             } \
1048         } \
1049     }
1050     #define setmods(a,b) \
1051     { \
1052         if(strcmp(a, b)) \
1053         { \
1054             ident *id = getident(#a); \
1055             if(id && id->type == ID_SVAR && id->flags&IDF_SERVER) \
1056             { \
1057                 delete[] *id->storage.s; \
1058                 *id->storage.s = newstring(b); \
1059                 sendf(-1, 1, "ri2sis", N_COMMAND, -1, &id->name[3], strlen(*id->storage.s), *id->storage.s); \
1060             } \
1061         } \
1062     }
1063 
1064     int numgamevars = 0, numgamemods = 0;
resetgamevars(bool all)1065     void resetgamevars(bool all)
1066     {
1067         numgamevars = numgamemods = 0;
1068         enumerate(idents, ident, id,
1069         {
1070             if(id.flags&IDF_SERVER && !(id.flags&IDF_READONLY) && (all || !(id.flags&IDF_WORLD))) // reset vars
1071             {
1072                 const char *val = NULL;
1073                 if(id.flags&IDF_GAMEMOD) numgamevars++;
1074                 switch(id.type)
1075                 {
1076                     case ID_VAR:
1077                     {
1078                         if(*id.storage.i != id.def.i)
1079                         {
1080                             setvar(id.name, id.def.i, true);
1081                             val = intstr(&id);
1082                         }
1083                         if(id.flags&IDF_GAMEMOD && *id.storage.i != id.bin.i) numgamemods++;
1084                         break;
1085                     }
1086                     case ID_FVAR:
1087                     {
1088                         if(*id.storage.f != id.def.f)
1089                         {
1090                             setfvar(id.name, id.def.f, true);
1091                             val = floatstr(*id.storage.f);
1092                         }
1093                         if(id.flags&IDF_GAMEMOD && *id.storage.f != id.bin.f) numgamemods++;
1094                         break;
1095                     }
1096                     case ID_SVAR:
1097                     {
1098                         if(strcmp(*id.storage.s, id.def.s))
1099                         {
1100                             setsvar(id.name, id.def.s && *id.def.s ? id.def.s : "", true);
1101                             val = *id.storage.s;
1102                         }
1103                         if(id.flags&IDF_GAMEMOD && strcmp(*id.storage.s, id.bin.s)) numgamemods++;
1104                         break;
1105                     }
1106                     default: break;
1107                 }
1108                 if(val) sendf(-1, 1, "ri2sis", N_COMMAND, -1, &id.name[3], strlen(val), val);
1109             }
1110         });
1111     }
1112 
savegamevars()1113     void savegamevars()
1114     {
1115         enumerate(idents, ident, id,
1116         {
1117             if(id.flags&IDF_SERVER && !(id.flags&IDF_READONLY) && !(id.flags&IDF_WORLD)) switch(id.type)
1118             {
1119                 case ID_VAR: id.def.i = *id.storage.i; break;
1120                 case ID_FVAR: id.def.f = *id.storage.f; break;
1121                 case ID_SVAR:
1122                 {
1123                     delete[] id.def.s;
1124                     id.def.s = newstring(*id.storage.s);
1125                     break;
1126                 }
1127                 default: break;
1128             }
1129         });
1130     }
1131 
pickmap(const char * suggest,int mode,int muts,bool notry)1132     const char *pickmap(const char *suggest, int mode, int muts, bool notry)
1133     {
1134         const char *map = G(defaultmap);
1135         if(!notry)
1136         {
1137             if(!map || !*map) map = choosemap(suggest, mode, muts, G(rotatemaps), true);
1138             else if(strchr(map, ' '))
1139             {
1140                 static string defaultmap;
1141                 defaultmap[0] = '\0';
1142                 vector<char *> maps;
1143                 explodelist(map, maps);
1144                 if(*sv_previousmaps)
1145                 {
1146                     vector<char *> prev;
1147                     explodelist(sv_previousmaps, prev);
1148                     loopvj(prev) loopvrev(maps) if(strcmp(prev[j], maps[i]))
1149                     {
1150                         delete[] maps[i];
1151                         maps.remove(i);
1152                         if(maps.length() <= 1) break;
1153                     }
1154                     prev.deletearrays();
1155                 }
1156                 if(!maps.empty())
1157                 {
1158                     int r = rnd(maps.length());
1159                     copystring(defaultmap, maps[r]);
1160                 }
1161                 maps.deletearrays();
1162                 map = *defaultmap ? defaultmap : choosemap(suggest, mode, muts, G(rotatemaps), true);
1163             }
1164         }
1165         return map && *map ? map : "maps/untitled";
1166     }
1167 
setpause(bool on=false)1168     void setpause(bool on = false)
1169     {
1170         if(on) { setmod(sv_gamepaused, 1); }
1171         else { setmod(sv_gamepaused, 0); }
1172     }
1173 
setdemorecord(bool value,bool msg=false)1174     void setdemorecord(bool value, bool msg = false)
1175     {
1176         demonextmatch = value;
1177         if(msg) srvoutf(-3, "\fyDemo recording is \fs\fc%s\fS for next match", demonextmatch ? "enabled" : "disabled");
1178     }
1179 
1180     void enddemorecord(bool full);
checkdemorecord(bool full)1181     void checkdemorecord(bool full)
1182     {
1183         if(demorecord) enddemorecord(full);
1184         if(G(demoautorec) && !demonextmatch) setdemorecord(true);
1185     }
1186 
resetcontrols(int type)1187     void resetcontrols(int type)
1188     {
1189         loopvrev(control) if(control[i].type == type && control[i].flag <= ipinfo::INTERNAL) control.remove(i);
1190     }
1191 
cleanup(bool init=false)1192     void cleanup(bool init = false)
1193     {
1194         setpause(false);
1195         setmod(sv_botoffset, 0);
1196         if(G(resetmmonend)) { mastermode = MM_OPEN; resetcontrols(ipinfo::ALLOW); }
1197         if(G(resetbansonend)) resetcontrols(ipinfo::BAN);
1198         if(G(resetmutesonend)) resetcontrols(ipinfo::MUTE);
1199         if(G(resetlimitsonend)) resetcontrols(ipinfo::LIMIT);
1200         if(G(resetexceptsonend)) resetcontrols(ipinfo::EXCEPT);
1201         if(G(resetvarsonend) || init) resetgamevars(true);
1202         changemap();
1203         lastrotatecycle = clocktime;
1204     }
1205 
start()1206     void start()
1207     {
1208         cleanup(true);
1209     }
1210 
reload()1211     void reload()
1212     {
1213         extern void localopreset();
1214         localopreset();
1215     }
1216 
shutdown()1217     void shutdown()
1218     {
1219         srvoutf(-3, "\fyServer shutdown in progress..");
1220         aiman::clearai();
1221         loopv(clients) if(getinfo(i)) disconnect_client(i, DISC_SHUTDOWN);
1222     }
1223 
newinfo()1224     void *newinfo() { return new clientinfo; }
deleteinfo(void * ci)1225     void deleteinfo(void *ci) { delete (clientinfo *)ci; }
1226 
numchannels()1227     int numchannels() { return 3; }
spectatorslots()1228     int spectatorslots() { return clamp(G(serverspectators) >= 0 ? G(serverspectators) : G(serverclients), 1, MAXCLIENTS); }
maxslots()1229     int maxslots() { return clamp(G(serverclients)+spectatorslots(), 1, MAXCLIENTS); }
reserveclients()1230     int reserveclients() { return maxslots()+4; }
dupclients()1231     int dupclients() { return G(serverdupclients); }
1232 
hasclient(clientinfo * ci,clientinfo * cp=NULL)1233     bool hasclient(clientinfo *ci, clientinfo *cp = NULL)
1234     {
1235         if(!ci || (ci != cp && ci->clientnum != cp->clientnum && ci->ownernum != cp->clientnum)) return false;
1236         return true;
1237     }
1238 
peerowner(int n)1239     int peerowner(int n)
1240     {
1241         clientinfo *ci = (clientinfo *)getinfo(n);
1242         if(ci && ci->actortype > A_PLAYER) return ci->ownernum;
1243         return n;
1244     }
1245 
allowbroadcast(int n)1246     bool allowbroadcast(int n)
1247     {
1248         clientinfo *ci = (clientinfo *)getinfo(n);
1249         return ci && ci->connected && ci->actortype == A_PLAYER;
1250     }
1251 
numclients(int exclude,bool nospec,int actortype)1252     int numclients(int exclude, bool nospec, int actortype)
1253     {
1254         int n = 0;
1255         loopv(clients)
1256         {
1257             if(clients[i]->clientnum >= 0 && clients[i]->name[0] && clients[i]->clientnum != exclude &&
1258                 (!nospec || clients[i]->state != CS_SPECTATOR) &&
1259                     (clients[i]->actortype == A_PLAYER || (actortype > A_PLAYER && clients[i]->actortype <= actortype && clients[i]->ownernum >= 0)))
1260                         n++;
1261         }
1262         return n;
1263     }
1264 
numspectators(int exclude)1265     int numspectators(int exclude)
1266     {
1267         int n = 0;
1268         loopv(clients)
1269             if(clients[i]->clientnum >= 0 && clients[i]->name[0] && clients[i]->clientnum != exclude && clients[i]->actortype == A_PLAYER && clients[i]->state == CS_SPECTATOR)
1270                 n++;
1271         return n;
1272     }
1273 
duplicatename(clientinfo * ci,char * name)1274     bool duplicatename(clientinfo *ci, char *name)
1275     {
1276         if(!name) name = ci->name;
1277         loopv(clients) if(clients[i] != ci && !strcmp(name, clients[i]->name)) return true;
1278         return false;
1279     }
1280 
findcolour(clientinfo * ci,bool tone=true)1281     int findcolour(clientinfo *ci, bool tone = true)
1282     {
1283         if(tone)
1284         {
1285             int col = ci->actortype < A_ENEMY ? ci->colour : TEAM(T_ENEMY, colour);
1286             if(!col && isweap(ci->weapselect)) col = W(ci->weapselect, colour);
1287             if(col) return col;
1288         }
1289         return TEAM(ci->team, colour);
1290     }
1291 
privname(int priv,int actortype)1292     const char *privname(int priv, int actortype)
1293     {
1294         if(actortype != A_PLAYER) return "bot";
1295         const char *privnames[2][PRIV_MAX] = {
1296             { "none", "player account", "global supporter", "global moderator", "global administrator", "project developer", "project founder" },
1297             { "none", "player account", "local supporter", "local moderator", "local administrator", "none", "none" }
1298         };
1299         return privnames[priv&PRIV_LOCAL ? 1 : 0][clamp(priv&PRIV_TYPE, 0, int(priv&PRIV_LOCAL ? PRIV_ADMINISTRATOR : PRIV_LAST))];
1300     }
1301 
privnamex(int priv,int actortype,bool local)1302     const char *privnamex(int priv, int actortype, bool local)
1303     {
1304         if(actortype != A_PLAYER) return "bot";
1305         const char *privnames[2][PRIV_MAX] = {
1306             { "none", "player", "supporter", "moderator", "administrator", "developer", "founder" },
1307             { "none", "player", "localsupporter", "localmoderator", "localadministrator", "none", "none" }
1308         };
1309         return privnames[local && priv&PRIV_LOCAL ? 1 : 0][clamp(priv&PRIV_TYPE, 0, int(priv&PRIV_LOCAL ? PRIV_ADMINISTRATOR : PRIV_LAST))];
1310     }
1311 
colourname(clientinfo * ci,char * name=NULL,bool icon=true,bool dupname=true,int colour=3)1312     const char *colourname(clientinfo *ci, char *name = NULL, bool icon = true, bool dupname = true, int colour = 3)
1313     {
1314         if(!name) name = ci->name;
1315         static string colored; colored[0] = '\0'; string colortmp;
1316         if(colour) concatstring(colored, "\fs");
1317         if(icon)
1318         {
1319             if(colour&1)
1320             {
1321                 formatstring(colortmp, "\f[%d]", findcolour(ci));
1322                 concatstring(colored, colortmp);
1323             }
1324             formatstring(colortmp, "\f($priv%stex)", privnamex(ci->privilege, ci->actortype, true));
1325             concatstring(colored, colortmp);
1326         }
1327         if(colour&2)
1328         {
1329             formatstring(colortmp, "\f[%d]", TEAM(ci->team, colour));
1330             concatstring(colored, colortmp);
1331         }
1332         concatstring(colored, name);
1333         if(!name[0] || (ci->actortype < A_ENEMY && dupname && duplicatename(ci, name)))
1334         {
1335             formatstring(colortmp, "%s[%d]", name[0] ? " " : "", ci->clientnum);
1336             concatstring(colored, colortmp);
1337         }
1338         if(colour) concatstring(colored, "\fS");
1339         return colored;
1340     }
1341 
teamtexnamex(int team)1342     const char *teamtexnamex(int team)
1343     {
1344         const char *teamtexs[T_MAX] = { "teamneutraltex", "teamalphatex", "teamomegatex", "teamenemytex" };
1345         return teamtexs[clamp(team, 0, T_MAX-1)];
1346     }
1347 
colourteam(int team,const char * icon="")1348     const char *colourteam(int team, const char *icon = "")
1349     {
1350         if(team < 0 || team > T_MAX) team = T_NEUTRAL;
1351         static string teamed; teamed[0] = '\0'; string teamtmp;
1352         concatstring(teamed, "\fs");
1353         formatstring(teamtmp, "\f[%d]", TEAM(team, colour));
1354         concatstring(teamed, teamtmp);
1355         if(icon != NULL)
1356         {
1357             formatstring(teamtmp, "\f($%s)", *icon ? icon : teamtexnamex(team));
1358             concatstring(teamed, teamtmp);
1359         }
1360         concatstring(teamed, TEAM(team, name));
1361         concatstring(teamed, "\fS");
1362         return teamed;
1363     }
1364 
haspriv(clientinfo * ci,int flag,const char * msg=NULL)1365     bool haspriv(clientinfo *ci, int flag, const char *msg = NULL)
1366     {
1367         if((ci->local && flag <= PRIV_MAX) || (ci->privilege&PRIV_TYPE) >= flag) return true;
1368         else if(mastermask()&MM_AUTOAPPROVE && flag <= PRIV_ELEVATED && !numclients(ci->clientnum)) return true;
1369         else if(msg && *msg)
1370             srvmsgft(ci->clientnum, CON_MESG, "\frAccess denied, you need to be \fs\fc%s\fS to \fs\fc%s\fS", privnamex(flag), msg);
1371         return false;
1372     }
1373 
cmppriv(clientinfo * ci,clientinfo * cp,const char * msg=NULL)1374     bool cmppriv(clientinfo *ci, clientinfo *cp, const char *msg = NULL)
1375     {
1376         stringz(str);
1377         if(msg && *msg) formatstring(str, "%s %s", msg, colourname(cp));
1378         if(haspriv(ci, cp->local ? PRIV_ADMINISTRATOR : cp->privilege&PRIV_TYPE, str)) return true;
1379         return false;
1380     }
1381 
gameid()1382     const char *gameid() { return VERSION_GAMEID; }
1383     ICOMMAND(0, gameid, "", (), result(gameid()));
1384 
getver(int n)1385     int getver(int n)
1386     {
1387         switch(n)
1388         {
1389             case 0: return CUR_VERSION;
1390             case 1: return VERSION_GAME;
1391             case 2: case 3: return sversion[n%2];
1392             case 4: return CUR_ARCH;
1393             default: break;
1394         }
1395         return 0;
1396     }
1397     ICOMMAND(0, getversion, "i", (int *a), intret(getver(*a)));
1398 
gamename(int mode,int muts,int compact,int limit,char separator)1399     const char *gamename(int mode, int muts, int compact, int limit, char separator)
1400     {
1401         if(!m_game(mode)) mode = G_DEATHMATCH;
1402         if(gametype[mode].implied) muts |= gametype[mode].implied;
1403         static string gname; gname[0] = '\0';
1404         int start = clamp(compact, 0, 3), lps = clamp(4-start, 1, 4);
1405         loopk(lps)
1406         {
1407             int iter = start+k;
1408             if(muts)
1409             {
1410                 int implied = gametype[mode].implied;
1411                 loopi(G_M_NUM) if(muts&(1<<mutstype[i].type)) implied |= mutstype[i].implied&~(1<<mutstype[i].type);
1412                 loopi(G_M_NUM) if(muts&(1<<mutstype[i].type) && (!implied || !(implied&(1<<mutstype[i].type))))
1413                 {
1414                     const char *mut = i < G_M_GSP ? mutstype[i].name : gametype[mode].gsp[i-G_M_GSP];
1415                     if(mut && *mut)
1416                     {
1417                         string name;
1418                         switch(iter)
1419                         {
1420                             case 2: case 3: formatstring(name, "%s%s%c", *gname ? gname : "", *gname ? "-" : "", mut[0]); break;
1421                             case 1: formatstring(name, "%s%s%c%c", *gname ? gname : "", *gname ? "-" : "", mut[0], mut[1]); break;
1422                             case 0: default: formatstring(name, "%s%s%s", *gname ? gname : "", *gname ? "-" : "", mut); break;
1423                         }
1424                         copystring(gname, name);
1425                     }
1426                 }
1427             }
1428             defformatstring(mname, "%s%s%s", *gname ? gname : "", *gname ? "-" : "", k < 3 ? gametype[mode].name : gametype[mode].sname);
1429             if(k < 3 && limit > 0 && int(strlen(mname)) >= limit)
1430             {
1431                 gname[0] = '\0';
1432                 continue; // let's try again
1433             }
1434             copystring(gname, mname);
1435             if(separator != ' ') for(int n = strlen(mname); mname[n]; n++) if(mname[n] == ' ') mname[n] = separator;
1436             break;
1437         }
1438         return gname;
1439     }
1440     ICOMMAND(0, gamename, "iiiis", (int *g, int *m, int *c, int *t, char *s), result(gamename(*g, *m, *c, *t, *s)));
1441 
getgamename(int compact,int limit,char separator)1442     const char *getgamename(int compact, int limit, char separator) { return gamename(gamemode, mutators, compact, limit, separator); }
getmapname()1443     const char *getmapname() { return smapname; }
getserverdesc()1444     const char *getserverdesc() { return G(serverdesc); }
1445 
modedesc(int mode,int muts,int type)1446     const char *modedesc(int mode, int muts, int type)
1447     {
1448         if(!m_game(mode)) mode = G_DEATHMATCH;
1449         if(gametype[mode].implied) muts |= gametype[mode].implied;
1450         static string mdname; mdname[0] = '\0';
1451         if(type == 1 || type == 3 || type == 4) concatstring(mdname, gametype[mode].name);
1452         if(type == 3 || type == 4) concatstring(mdname, ": ");
1453         if(type == 2 || type == 3 || type == 4 || type == 5)
1454         {
1455             if((type == 4 || type == 5) && m_ctf_protect(mode, muts)) concatstring(mdname, gametype[mode].gsd[2]);
1456             else if((type == 4 || type == 5) && m_dac_king(mode, muts)) concatstring(mdname, gametype[mode].gsd[1]);
1457             else if((type == 4 || type == 5) && m_bb_hold(mode, muts)) concatstring(mdname, gametype[mode].gsd[0]);
1458             else if((type == 4 || type == 5) && m_bb_assault(mode, muts)) concatstring(mdname, gametype[mode].gsd[2]);
1459             else if((type == 4 || type == 5) && m_ra_lapped(mode, muts)) concatstring(mdname, gametype[mode].gsd[0]);
1460             else if((type == 4 || type == 5) && m_ra_gauntlet(mode, muts)) concatstring(mdname, gametype[mode].gsd[2]);
1461             else concatstring(mdname, gametype[mode].desc);
1462         }
1463         return mdname;
1464     }
1465     ICOMMAND(0, modedesc, "iii", (int *g, int *m, int *c), result(modedesc(*g, *m, *c)));
1466 
mutsdesc(int mode,int muts,int type)1467     const char *mutsdesc(int mode, int muts, int type)
1468     {
1469         if(!m_game(mode)) mode = G_DEATHMATCH;
1470         static string mtname; mtname[0] = '\0';
1471         int mutid = -1;
1472         loopi(G_M_NUM) if(muts == (1<<mutstype[i].type)) mutid = i;
1473         if(mutid < 0) return "";
1474         if(type == 4 || type == 5)
1475         {
1476             if(m_ctf_protect(mode, muts)) return "";
1477             else if(m_dac_king(mode, muts)) return "";
1478             else if(m_bb_hold(mode, muts) || m_bb_assault(mode, muts)) return "";
1479             else if(m_ra_lapped(mode, muts) || m_ra_gauntlet(mode, muts)) return "";
1480         }
1481         if(type == 1 || type == 3 || type == 4)
1482         {
1483             const char *n = mutid >= G_M_GSP ? gametype[mode].gsp[mutid-G_M_GSP] : mutstype[mutid].name;
1484             if(!n || !*n) return "";
1485             concatstring(mtname, n);
1486         }
1487         if(type == 3 || type == 4) concatstring(mtname, ": ");
1488         if(type == 2 || type == 3 || type == 4 || type == 5)
1489         {
1490             const char *n = mutid >= G_M_GSP ? gametype[mode].gsd[mutid-G_M_GSP] : mutstype[mutid].desc;
1491             if(!n || !*n) return "";
1492             concatstring(mtname, n);
1493         }
1494         return mtname;
1495     }
1496     ICOMMAND(0, mutsdesc, "iii", (int *g, int *m, int *c), result(mutsdesc(*g, *m, *c)));
1497 
changemode(int & mode,int & muts)1498     void changemode(int &mode, int &muts)
1499     {
1500         if(mode < 0)
1501         {
1502             mode = G(defaultmode);
1503             if(G(rotatemode))
1504             {
1505                 int num = 0;
1506                 loopi(G_MAX) if(G(rotatemodefilter)&(1<<i)) num++;
1507                 if(!num) mode = rnd(G_RAND)+G_PLAY;
1508                 else
1509                 {
1510                     int r = rnd(num), n = 0;
1511                     loopi(G_MAX) if(G(rotatemodefilter)&(1<<i))
1512                     {
1513                         if(n != r) n++;
1514                         else { mode = i; break; }
1515                     }
1516                 }
1517                 if(!mode || !(G(rotatemodefilter)&(1<<mode))) mode = rnd(G_RAND)+G_PLAY;
1518             }
1519         }
1520         if(muts < 0)
1521         {
1522             muts = G(defaultmuts);
1523             if(G(rotatemuts))
1524             {
1525                 int num = rnd(G_M_NUM+1);
1526                 if(num) loopi(num) if(G(rotatemuts) == 1 || !rnd(G(rotatemuts)))
1527                 {
1528                     int rmut = 1<<rnd(G_M_NUM);
1529                     if(G(rotatemutsfilter) && !(G(rotatemutsfilter)&rmut)) continue;
1530                     muts |= rmut;
1531                     modecheck(mode, muts, rmut);
1532                 }
1533             }
1534         }
1535         modecheck(mode, muts);
1536     }
1537 
choosemap(const char * suggest,int mode,int muts,int force,bool notry)1538     const char *choosemap(const char *suggest, int mode, int muts, int force, bool notry)
1539     {
1540         static string chosen;
1541         if(suggest && *suggest)
1542         {
1543             if(!strncasecmp(suggest, "maps/", 5) || !strncasecmp(suggest, "maps\\", 5))
1544                 copystring(chosen, suggest+5);
1545             else copystring(chosen, suggest);
1546         }
1547         else *chosen = 0;
1548         int rotate = force ? force : G(rotatemaps);
1549         if(rotate) loopj(2)
1550         {
1551             char *list = NULL;
1552             maplist(list, mode, muts, numclients(), G(rotatemapsfilter), j!=0);
1553             if(list)
1554             {
1555                 bool found = false;
1556                 int n = listlen(list), c = n ? rnd(n) : 0;
1557                 if(c >= 0)
1558                 {
1559                     int len = 0;
1560                     const char *elem = indexlist(list, c, len);
1561                     if(len > 0)
1562                     {
1563                         copystring(chosen, elem, len+1);
1564                         found = true;
1565                     }
1566                 }
1567                 DELETEA(list);
1568                 if(found) break;
1569             }
1570         }
1571         return *chosen ? chosen : pickmap(suggest, mode, muts, notry);
1572     }
1573 
canload(const char * type)1574     bool canload(const char *type)
1575     {
1576         if(!strcmp(type, gameid())) return true;
1577         return false;
1578     }
1579 
timeleft()1580     int timeleft()
1581     {
1582         switch(gamestate)
1583         {
1584             case G_S_PLAYING: case G_S_OVERTIME: return timeremaining;
1585             default: return gamewaittime ? max(gamewaittime-totalmillis, 0)/1000 : 0;
1586         }
1587         return 0;
1588     }
1589 
sendtick()1590     void sendtick()
1591     {
1592         sendf(-1, 1, "ri3", N_TICK, gamestate, timeleft());
1593     }
1594 
1595     bool checkvotes(bool force = false);
1596     void sendstats(bool fromintermission = false);
startintermission(bool req=false)1597     void startintermission(bool req = false)
1598     {
1599         if(gs_playing(gamestate))
1600         {
1601             sendstats(true);
1602             setpause(false);
1603             timeremaining = 0;
1604             gamelimit = min(gamelimit, gamemillis);
1605             if(smode) smode->intermission();
1606             mutate(smuts, mut->intermission());
1607         }
1608         if(req || !G(intermlimit))
1609         {
1610             checkdemorecord(true);
1611             if(gamestate != G_S_VOTING && G(votelimit))
1612             {
1613                 gamestate = G_S_VOTING;
1614                 gamewaittime = totalmillis+G(votelimit);
1615                 sendtick();
1616             }
1617             else checkvotes(true);
1618         }
1619         else
1620         {
1621             gamestate = G_S_INTERMISSION;
1622             gamewaittime = totalmillis+G(intermlimit);
1623             sendtick();
1624         }
1625     }
1626 
wantsovertime()1627     bool wantsovertime()
1628     {
1629         if(smode && smode->wantsovertime()) return true;
1630         mutate(smuts, if(mut->wantsovertime()) return true);
1631         if(!m_mmvar(gamemode, mutators, overtimeallow) || m_balance(gamemode, mutators, teamspawns)) return false;
1632         bool result = false;
1633         if(m_team(gamemode, mutators))
1634         {
1635             int best = -1;
1636             loopi(numteams(gamemode, mutators))
1637             {
1638                 score &cs = teamscore(i+T_FIRST);
1639                 if(best < 0 || cs.total > teamscore(best).total)
1640                 {
1641                     best = i+T_FIRST;
1642                     result = false;
1643                 }
1644                 else if(cs.total == teamscore(best).total) result = true;
1645             }
1646         }
1647         else
1648         {
1649             int best = -1;
1650             loopv(clients) if(clients[i]->actortype < A_ENEMY && clients[i]->state != CS_SPECTATOR)
1651             {
1652                 if(best < 0 || (m_ra_timed(gamemode, mutators) ? (clients[best]->cptime <= 0 || (clients[i]->cptime > 0 && clients[i]->cptime < clients[best]->cptime)) : clients[i]->points > clients[best]->points))
1653                 {
1654                     best = i;
1655                     result = false;
1656                 }
1657                 else if(m_ra_timed(gamemode, mutators) ? clients[i]->cptime == clients[best]->cptime : clients[i]->points == clients[best]->points) result = true;
1658             }
1659         }
1660         return result;
1661     }
1662 
balancecmp(clientinfo * a,clientinfo * b)1663     bool balancecmp(clientinfo *a, clientinfo *b)
1664     {
1665         return (a->balancescore() > b->balancescore());
1666     }
1667 
doteambalance(bool init)1668     void doteambalance(bool init)
1669     {
1670         vector<clientinfo *> tc[T_NUM];
1671         int numplaying = 0;
1672         loopv(clients)
1673         {
1674             clientinfo *cp = clients[i];
1675             if(!cp->team || cp->state == CS_SPECTATOR || cp->actortype > A_PLAYER) continue;
1676             cp->updatetimeplayed();
1677             tc[cp->team-T_FIRST].add(cp);
1678             numplaying++;
1679         }
1680         if((G(teambalancestyle) || m_swapteam(gamemode, mutators)) && numplaying >= G(teambalanceplaying))
1681         {
1682             int nt = numteams(gamemode, mutators), mid = numplaying/nt, pmax = -1, pmin = -1;
1683             loopi(nt)
1684             {
1685                 int cl = tc[i].length();
1686                 if(pmax < 0 || cl > pmax) pmax = cl;
1687                 if(pmin < 0 || cl < pmin) pmin = cl;
1688             }
1689             int offset = pmax-pmin;
1690             if(offset >= G(teambalanceamt))
1691             {
1692                 if(!init && !nextteambalance)
1693                 {
1694                     int secs = G(teambalancedelay)/1000;
1695                     nextteambalance = gamemillis+G(teambalancedelay);
1696                     ancmsgft(-1, S_V_BALWARN, CON_EVENT, "\fy\fs\fzoyWARNING:\fS \fs\fcteams\fS will be \fs\fcbalanced\fS in \fs\fc%d\fS %s", secs, secs != 1 ? "seconds" : "second");
1697                 }
1698                 else if(init)
1699                 {
1700                     vector <clientinfo *> pool;
1701                     loopvj(clients)
1702                     {
1703                         clientinfo *cp = clients[j];
1704                         if(!cp->team || cp->state == CS_SPECTATOR || cp->actortype > A_PLAYER) continue;
1705                         pool.add(cp);
1706                         setteam(cp, T_NEUTRAL, 0, false);
1707                     }
1708                     pool.sort(balancecmp);
1709                     loopvj(pool)
1710                     {
1711                         clientinfo *cp = pool[j];
1712                         cp->swapteam = T_NEUTRAL;
1713                         int t = chooseteam(cp, -1, true);
1714                         if(t != cp->team)
1715                         {
1716                             setteam(cp, t, (m_balreset(gamemode, mutators) ? TT_RESET : 0)|TT_INFOSM, false);
1717                             cp->lastdeath = 0;
1718                         }
1719                     }
1720                 }
1721                 else if(canbalancenow())
1722                 {
1723                     int moved = 0;
1724                     loopi(nt) for(int team = i+T_FIRST, iters = tc[i].length(); iters > 0 && tc[i].length() > mid; iters--)
1725                     {
1726                         int id = -1;
1727                         loopvj(tc[i])
1728                         {
1729                             clientinfo *cp = tc[i][j];
1730                             if(m_swapteam(gamemode, mutators) && cp->swapteam && cp->swapteam == team) { id = j; break; }
1731                             if(G(teambalancestyle) == 0)
1732                             {
1733                                 if(id < 0) id = j;
1734                             }
1735                             else if(G(teambalancehighest))
1736                             {
1737                                 if(id < 0 || tc[i][id]->balancescore() < cp->balancescore()) id = j;
1738                             }
1739                             else
1740                             {
1741                                 if(id < 0 || tc[i][id]->balancescore() > cp->balancescore()) id = j;
1742                             }
1743                         }
1744                         if(id >= 0)
1745                         {
1746                             clientinfo *cp = tc[i][id];
1747                             cp->swapteam = T_NEUTRAL; // make them rechoose if necessary
1748                             int t = chooseteam(cp, -1, true);
1749                             if(t != cp->team)
1750                             {
1751                                 setteam(cp, t, (m_balreset(gamemode, mutators) ? TT_RESET : 0)|TT_INFOSM, false);
1752                                 cp->lastdeath = 0;
1753                                 tc[i].removeobj(cp);
1754                                 tc[t-T_FIRST].add(cp);
1755                                 moved++;
1756                             }
1757                         }
1758                         else break; // won't get any more
1759                     }
1760                     if(!init)
1761                     {
1762                         if(moved) ancmsgft(-1, S_V_BALALERT, CON_EVENT, "\fy\fs\fzoyALERT:\fS \fs\fcteams\fS have now been \fs\fcbalanced\fS");
1763                         else ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fy\fs\fzoyALERT:\fS \fs\fcteams\fS failed to be \fs\fcbalanced\fS");
1764                     }
1765                     lastteambalance = gamemillis+G(teambalancewait);
1766                     nextteambalance = 0;
1767                 }
1768             }
1769             else
1770             {
1771                 if(!init && nextteambalance) ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fy\fs\fzoyALERT:\fS \fs\fcteams\fS no longer need to be \fs\fcbalanced\fS");
1772                 lastteambalance = gamemillis+(nextteambalance ? G(teambalancewait) : G(teambalancedelay));
1773                 nextteambalance = 0;
1774             }
1775         }
1776         else
1777         {
1778             if(!init && nextteambalance) ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fy\fs\fzoyALERT:\fS \fs\fcteams\fS are no longer able to be \fs\fcbalanced\fS");
1779             lastteambalance = gamemillis+(nextteambalance ? G(teambalancewait) : G(teambalancedelay));
1780             nextteambalance = 0;
1781         }
1782     }
1783 
checklimits()1784     void checklimits()
1785     {
1786         if(!m_play(gamemode)) return;
1787         bool wasinovertime = gamestate == G_S_OVERTIME;
1788         int limit = wasinovertime ? m_mmvar(gamemode, mutators, overtimelimit) : m_mmvar(gamemode, mutators, timelimit), numt = numteams(gamemode, mutators);
1789         bool newlimit = limit != oldtimelimit, newtimer = gamemillis-curtime > 0 && gamemillis/1000 != (gamemillis-curtime)/1000,
1790              iterate = newlimit || newtimer;
1791         if(iterate)
1792         {
1793             if(newlimit)
1794             {
1795                 if(limit && oldtimelimit) gamelimit += (limit-oldtimelimit)*60000;
1796                 else if(limit) gamelimit = max(gamemillis, limit*60000);
1797                 oldtimelimit = limit;
1798             }
1799             if(timeremaining)
1800             {
1801                 if(limit)
1802                 {
1803                     if(gamemillis >= gamelimit) timeremaining = 0;
1804                     else timeremaining = (gamelimit-gamemillis+999)/1000;
1805                 }
1806                 else timeremaining = -1;
1807                 bool wantsoneminute = true;
1808                 if(!timeremaining)
1809                 {
1810                     if(gamestate != G_S_OVERTIME && wantsovertime())
1811                     {
1812                         limit = oldtimelimit = m_mmvar(gamemode, mutators, overtimelimit);
1813                         if(limit)
1814                         {
1815                             timeremaining = limit*60;
1816                             gamelimit += timeremaining*1000;
1817                             ancmsgft(-1, S_V_OVERTIME, CON_EVENT, "\fyOvertime, match extended by \fs\fc%d\fS %s", limit, limit > 1 ? "minutes" : "minute");
1818                         }
1819                         else
1820                         {
1821                             timeremaining = -1;
1822                             gamelimit = 0;
1823                             ancmsgft(-1, S_V_OVERTIME, CON_EVENT, "\fyOvertime, match extended until someone wins");
1824                         }
1825                         gamestate = G_S_OVERTIME;
1826                         wantsoneminute = false;
1827                     }
1828                     else
1829                     {
1830                         ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fyTime limit has been reached");
1831                         startintermission();
1832                         return; // bail
1833                     }
1834                 }
1835                 if(gs_playing(gamestate) && timeremaining != 0)
1836                 {
1837                     if(wantsoneminute && timeremaining == 60) ancmsgft(-1, S_V_ONEMINUTE, CON_EVENT, "\fzYgone minute remains");
1838                     sendtick();
1839                 }
1840             }
1841         }
1842         if(wasinovertime && !wantsovertime())
1843         {
1844             ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fyOvertime has ended, a winner has been chosen");
1845             startintermission();
1846             return; // bail
1847         }
1848         if(!m_balance(gamemode, mutators, teamspawns))
1849         {
1850             int plimit = 0;
1851             if(m_dm(gamemode)) plimit = m_dm_oldschool(gamemode, mutators) ? G(fraglimit) : G(pointlimit);
1852             else if(m_capture(gamemode)) plimit = G(capturelimit);
1853             else if(m_defend(gamemode)) plimit = G(defendlimit) ? G(defendlimit) : INT_MAX-1;
1854             else if(m_bomber(gamemode)) plimit = m_bb_hold(gamemode, mutators) ? G(bomberholdlimit) : G(bomberlimit);
1855             else if(m_race(gamemode) && m_ra_lapped(gamemode, mutators) && !m_ra_gauntlet(gamemode, mutators)) plimit = G(racelimit);
1856             if(plimit)
1857             {
1858                 if(m_team(gamemode, mutators))
1859                 {
1860                     int best = -1;
1861                     loopi(numt) if(best < 0 || teamscore(i+T_FIRST).total > teamscore(best).total)
1862                         best = i+T_FIRST;
1863                     if(best >= 0 && teamscore(best).total >= plimit)
1864                     {
1865                         ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fyScore limit has been reached");
1866                         startintermission();
1867                         return; // bail
1868                     }
1869                 }
1870                 else
1871                 {
1872                     int best = -1;
1873                     loopv(clients) if(clients[i]->actortype < A_ENEMY && (best < 0 || clients[i]->points > clients[best]->points))
1874                         best = i;
1875                     if(best >= 0 && clients[best]->points >= plimit)
1876                     {
1877                         ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fyScore limit has been reached");
1878                         startintermission();
1879                         return; // bail
1880                     }
1881                 }
1882             }
1883         }
1884         else if(gamelimit > 0 && curbalance < (numt-1))
1885         {
1886             int delpart = min(gamelimit/(numt*2), G(balancedelay)), balpart = (gamelimit/numt*(curbalance+1))-delpart;
1887             if(gamemillis >= balpart)
1888             {
1889                 if(!nextbalance)
1890                 {
1891                     nextbalance = gamemillis+delpart;
1892                     if(delpart >= 1000)
1893                     {
1894                         int secs = delpart/1000;
1895                         ancmsgft(-1, S_V_BALWARN, CON_EVENT, "\fy\fs\fzoyWARNING:\fS \fs\fcteams\fS will be \fs\fcreassigned\fS in \fs\fc%d\fS %s %s", secs, secs != 1 ? "seconds" : "second", m_forcebal(gamemode, mutators) ? "to switch roles" : "for map symmetry");
1896                     }
1897                 }
1898                 if(gamemillis >= nextbalance && canbalancenow())
1899                 {
1900                     int oldbalance = curbalance;
1901                     if(++curbalance >= numt) curbalance = 0; // safety first
1902                     static vector<clientinfo *> assign[T_NUM];
1903                     loopk(T_NUM) assign[k].setsize(0);
1904                     loopv(clients) if(isteam(gamemode, mutators, clients[i]->team, T_FIRST))
1905                         assign[clients[i]->team-T_FIRST].add(clients[i]);
1906                     int scores[T_NUM] = {0};
1907                     loopk(numt) scores[k] = teamscore(k+T_FIRST).total;
1908                     loopk(numt)
1909                     {
1910                         int from = mapbals[oldbalance][k], fromt = from-T_FIRST,
1911                             to = mapbals[curbalance][k], tot = to-T_FIRST;
1912                         loopv(assign[fromt])
1913                         {
1914                             clientinfo *cp = assign[fromt][i];
1915                             if(cp->swapteam)
1916                             {
1917                                 loopj(numt) if(mapbals[oldbalance][j] == cp->swapteam)
1918                                 {
1919                                     cp->swapteam = mapbals[curbalance][j];
1920                                     break;
1921                                 }
1922                             }
1923                             if(m_race(gamemode))
1924                             {
1925                                 cp->cpmillis = 0;
1926                                 cp->cpnodes.shrink(0);
1927                                 sendf(-1, 1, "ri3", N_CHECKPOINT, cp->clientnum, -1);
1928                             }
1929                             setteam(cp, to, (m_balreset(gamemode, mutators) ? TT_RESET : 0)|TT_INFO, false);
1930                             cp->lastdeath = 0;
1931                         }
1932                         score &cs = teamscore(from);
1933                         cs.total = scores[tot];
1934                         sendf(-1, 1, "ri3", N_SCORE, cs.team, cs.total);
1935                     }
1936                     ancmsgft(-1, S_V_BALALERT, CON_EVENT, "\fy\fs\fzoyALERT:\fS \fs\fcteams\fS have %sbeen \fs\fcreassigned\fS %s", delpart > 0 ? "now " : "", m_forcebal(gamemode, mutators) ? "to switch roles" : "for map symmetry");
1937                     if(smode) smode->balance(oldbalance);
1938                     mutate(smuts, mut->balance(oldbalance));
1939                     if(smode) smode->layout();
1940                     mutate(smuts, mut->layout());
1941                     nextbalance = 0;
1942                 }
1943             }
1944         }
1945         if(m_balteam(gamemode, mutators, 4) && gamestate != G_S_OVERTIME && gamemillis >= G(teambalancewait) && (!lastteambalance || gamemillis >= lastteambalance) && (!nextteambalance || gamemillis >= nextteambalance))
1946             doteambalance(false);
1947     }
1948 
hasitem(int i,bool item=true)1949     bool hasitem(int i, bool item = true)
1950     {
1951         if((m_race(gamemode) && !m_ra_gauntlet(gamemode, mutators)) || !sents.inrange(i) || sents[i].type != WEAPON) return false;
1952         if(!checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr]) || (sents[i].attrs[4] && sents[i].attrs[4] != triggerid) || !m_check(sents[i].attrs[2], sents[i].attrs[3], gamemode, mutators)) return false;
1953         int attr = m_attr(sents[i].type, sents[i].attrs[0]);
1954         if(!isweap(attr) || !m_check(W(attr, modes), W(attr, muts), gamemode, mutators) || W(attr, disabled)) return false;
1955         if(item && m_loadout(gamemode, mutators) && !W2(attr, ammosub, false) && !W2(attr, ammosub, true)) return false;
1956         return true;
1957     }
1958 
finditem(int i,bool spawned=false)1959     bool finditem(int i, bool spawned = false)
1960     {
1961         if(sents[i].spawned) return true;
1962         if(spawned && gamemillis < sents[i].millis) return true;
1963         return false;
1964     }
1965 
1966     template<class T>
sortrandomly(vector<T> & src)1967     void sortrandomly(vector<T> &src)
1968     {
1969         vector<T> dst;
1970         dst.reserve(src.length());
1971         while(src.length()) dst.add(src.removeunordered(rnd(src.length())));
1972         src.move(dst);
1973     }
1974 
setupitems(bool update)1975     void setupitems(bool update)
1976     {
1977         vector<int> items, enemies;
1978         bool dospawn = true;
1979         if(smode && !smode->spawnitems()) dospawn = false;
1980         mutate(smuts, if(!mut->spawnitems()) dospawn = false);
1981         loopv(sents)
1982         {
1983             if(sents[i].type == ACTOR)
1984             {
1985                 if(!checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr])) continue;
1986                 if(sents[i].attrs[0] < 0 || sents[i].attrs[0] >= A_TOTAL) continue;
1987                 if(sents[i].attrs[5] && sents[i].attrs[5] != triggerid) continue;
1988                 if(!m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators)) continue;
1989                 if(sents[i].attrs[0] == A_TURRET && m_insta(gamemode, mutators)) continue;
1990                 sents[i].millis = gamemillis+G(enemyspawndelay);
1991                 switch(G(enemyspawnstyle) == 3 ? rnd(2)+1 : G(enemyspawnstyle))
1992                 {
1993                     case 1: enemies.add(i); break;
1994                     case 2: sents[i].millis += (G(enemyspawntime)+rnd(G(enemyspawntime)))/2; break;
1995                     default: break;
1996                 }
1997             }
1998             else if(enttype[sents[i].type].usetype == EU_ITEM && hasitem(i))
1999             {
2000                 sents[i].millis = gamemillis;
2001                 if(dospawn)
2002                 {
2003                     sents[i].millis += G(itemspawndelay);
2004                     switch(G(itemspawnstyle) == 3 ? rnd(2)+1 : G(itemspawnstyle))
2005                     {
2006                         case 1: items.add(i); break;
2007                         case 2:
2008                         {
2009                             int attr = m_attr(sents[i].type, sents[i].attrs[0]), delay = sents[i].type == WEAPON && isweap(attr) ? W(attr, spawntime) : G(itemspawntime);
2010                             if(delay > 1) sents[i].millis += (delay+rnd(delay))/2;
2011                             break;
2012                         }
2013                         default: break;
2014                     }
2015                 }
2016                 else sents[i].millis += 60000;
2017             }
2018         }
2019         if(dospawn && !items.empty())
2020         {
2021             sortrandomly(items);
2022             loopv(items) sents[items[i]].millis += G(itemspawndelay)*i;
2023         }
2024         if(!enemies.empty())
2025         {
2026             sortrandomly(enemies);
2027             loopv(enemies) sents[enemies[i]].millis += G(enemyspawndelay)*i;
2028         }
2029     }
2030 
setuptriggers(bool update)2031     void setuptriggers(bool update)
2032     {
2033         triggerid = 0;
2034         loopi(TRIGGERIDS+1) triggers[i].reset(i);
2035         if(!update) return;
2036 
2037         loopv(sents) if(enttype[sents[i].type].idattr >= 0 && sents[i].attrs[enttype[sents[i].type].idattr] >= 0 && sents[i].attrs[enttype[sents[i].type].idattr] <= TRIGGERIDS)
2038         {
2039             if(enttype[sents[i].type].mvattr >= 0 && !checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr])) continue;
2040             if(enttype[sents[i].type].modesattr >= 0 && !m_check(sents[i].attrs[enttype[sents[i].type].modesattr], sents[i].attrs[enttype[sents[i].type].modesattr+1], gamemode, mutators)) continue;
2041             triggers[sents[i].attrs[enttype[sents[i].type].idattr]].ents.add(i);
2042         }
2043 
2044         vector<int> valid;
2045         loopi(TRIGGERIDS) if(!triggers[i+1].ents.empty()) valid.add(triggers[i+1].id);
2046         if(!valid.empty()) triggerid = valid[rnd(valid.length())];
2047 
2048         loopi(TRIGGERIDS) if(triggers[i+1].id != triggerid) loopvk(triggers[i+1].ents)
2049         {
2050             if(sents[triggers[i+1].ents[k]].type != TRIGGER) continue;
2051             bool spawn = sents[triggers[i+1].ents[k]].attrs[4]%2;
2052             if(spawn != sents[triggers[i+1].ents[k]].spawned)
2053             {
2054                 sents[triggers[i+1].ents[k]].spawned = spawn;
2055                 sents[triggers[i+1].ents[k]].millis = gamemillis;
2056             }
2057             sendf(-1, 1, "ri3", N_TRIGGER, triggers[i+1].ents[k], 1+(spawn ? 2 : 1));
2058             loopvj(sents[triggers[i+1].ents[k]].kin) if(sents.inrange(sents[triggers[i+1].ents[k]].kin[j]))
2059             {
2060                 sents[sents[triggers[i+1].ents[k]].kin[j]].spawned = sents[triggers[i+1].ents[k]].spawned;
2061                 sents[sents[triggers[i+1].ents[k]].kin[j]].millis = sents[triggers[i+1].ents[k]].millis;
2062             }
2063         }
2064     }
2065 
2066     struct spawn
2067     {
2068         int current, iteration;
2069         vector<int> ents;
2070         vector<int> cycle;
2071 
spawnserver::spawn2072         spawn() { reset(); }
~spawnserver::spawn2073         ~spawn() {}
2074 
resetserver::spawn2075         void reset()
2076         {
2077             ents.shrink(0);
2078             cycle.shrink(0);
2079             iteration = 0;
2080             current = -1;
2081         }
addserver::spawn2082         void add(int n)
2083         {
2084             ents.add(n);
2085             cycle.add(0);
2086         }
2087     } spawns[T_COUNT];
2088 
setupspawns(bool update)2089     void setupspawns(bool update)
2090     {
2091         totalspawns = 0;
2092         teamspawns = m_teamspawn(gamemode, mutators);
2093         loopi(T_COUNT) spawns[i].reset();
2094         if(update)
2095         {
2096             int numt = numteams(gamemode, mutators), cplayers = 0;
2097             if(m_race(gamemode))
2098             {
2099                 loopv(sents) if(sents[i].type == PLAYERSTART && checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr]) && sents[i].attrs[0] == T_NEUTRAL && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators))
2100                 {
2101                     spawns[m_ra_gauntlet(gamemode, mutators) ? T_ALPHA : T_NEUTRAL].add(i);
2102                     totalspawns++;
2103                 }
2104                 if(!totalspawns) loopv(sents) if(sents[i].type == CHECKPOINT && checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr]) && sents[i].attrs[6] == CP_START && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators))
2105                 {
2106                     spawns[m_ra_gauntlet(gamemode, mutators) ? T_ALPHA : T_NEUTRAL].add(i);
2107                     totalspawns++;
2108                 }
2109                 if(m_ra_gauntlet(gamemode, mutators))
2110                 {
2111                     int enemyspawns = 0;
2112                     loopv(sents) if(sents[i].type == PLAYERSTART && checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr]) && sents[i].attrs[0] >= T_OMEGA && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators))
2113                     {
2114                         loopk(numt-1) spawns[T_OMEGA+k].add(i);
2115                         totalspawns++;
2116                         enemyspawns++;
2117                     }
2118                     if(!enemyspawns) loopv(sents) if(sents[i].type == CHECKPOINT && checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr]) && sents[i].attrs[6] == CP_RESPAWN && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators))
2119                     {
2120                         loopk(numt-1) spawns[T_OMEGA+k].add(i);
2121                         totalspawns++;
2122                         enemyspawns++;
2123                     }
2124                 }
2125                 setmod(sv_numplayers, 0);
2126                 setmod(sv_maxplayers, 0);
2127                 return;
2128             }
2129             if(m_duel(gamemode, mutators))
2130             { // iterate through teams so players spawn on opposite sides in duel
2131                 teamspawns = true;
2132                 numt = T_NUM;
2133             }
2134             if(teamspawns)
2135             {
2136                 loopk(3)
2137                 {
2138                     loopv(sents) if(sents[i].type == PLAYERSTART && checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr]) && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators))
2139                     {
2140                         if(!k && (m_team(gamemode, mutators) ? !isteam(gamemode, mutators, sents[i].attrs[0], T_FIRST) : (sents[i].attrs[0] == T_ALPHA || sents[i].attrs[0] == T_OMEGA)))
2141                             continue;
2142                         else if(k == 1 && sents[i].attrs[0] == T_NEUTRAL) continue;
2143                         else if(k == 2 && sents[i].attrs[0] != T_NEUTRAL) continue;
2144                         spawns[k ? T_NEUTRAL : sents[i].attrs[0]].add(i);
2145                         totalspawns++;
2146                     }
2147                     if(totalspawns && teamspawns)
2148                     {
2149                         loopi(numt) if(spawns[i+T_FIRST].ents.empty())
2150                         {
2151                             loopj(T_COUNT) spawns[j].reset();
2152                             totalspawns = 0;
2153                             break;
2154                         }
2155                     }
2156                     if(totalspawns) break;
2157                     teamspawns = false;
2158                 }
2159                 if(totalspawns && teamspawns)
2160                 {
2161                     int actt = numteams(gamemode, mutators), off = numt-actt;
2162                     if(off > 0) loopk(off)
2163                     {
2164                         int t = T_ALPHA+k*2, v = t+2;
2165                         if(isteam(gamemode, mutators, t, T_FIRST) && isteam(gamemode, mutators, v, T_FIRST))
2166                             loopv(spawns[t].ents) spawns[v].add(spawns[t].ents[i]);
2167                     }
2168                 }
2169             }
2170             if(!totalspawns)
2171             { // use all neutral spawns
2172                 teamspawns = false;
2173                 loopv(sents) if(sents[i].type == PLAYERSTART && checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr]) && sents[i].attrs[0] == T_NEUTRAL && (sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators))
2174                 {
2175                     spawns[T_NEUTRAL].add(i);
2176                     totalspawns++;
2177                 }
2178             }
2179             if(!totalspawns)
2180             { // use all spawns
2181                 teamspawns = false;
2182                 loopk(2)
2183                 {
2184                     loopv(sents) if(sents[i].type == PLAYERSTART && checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr]) && (k || ((sents[i].attrs[5] == triggerid || !sents[i].attrs[5]) && m_check(sents[i].attrs[3], sents[i].attrs[4], gamemode, mutators))))
2185                     {
2186                         spawns[T_NEUTRAL].add(i);
2187                         totalspawns++;
2188                     }
2189                     if(totalspawns) break;
2190                 }
2191             }
2192 
2193             if(totalspawns) cplayers = totalspawns/2;
2194             else
2195             { // we can cheat and use weapons for spawns
2196                 teamspawns = false;
2197                 loopv(sents) if(sents[i].type == WEAPON)
2198                 {
2199                     spawns[T_NEUTRAL].add(i);
2200                     totalspawns++;
2201                 }
2202                 cplayers = totalspawns/3;
2203             }
2204             if(!m_edit(gamemode))
2205             {
2206                 if(!cplayers) cplayers = totalspawns ? totalspawns : 1;
2207                 int np = G(numplayers) ? G(numplayers) : cplayers, mp = G(maxplayers) ? G(maxplayers) : np*3;
2208                 if(m_team(gamemode, mutators))
2209                 {
2210                     int offt = np%numt, offq = mp%numt;
2211                     if(offt) np += numt-offt;
2212                     if(offq) mp += numt-offq;
2213                 }
2214                 if(mp < np) mp = np;
2215                 setmod(sv_numplayers, np);
2216                 setmod(sv_maxplayers, mp);
2217             }
2218         }
2219     }
2220 
pickspawn(clientinfo * ci)2221     int pickspawn(clientinfo *ci)
2222     {
2223         if(ci->actortype >= A_ENEMY) return ci->spawnpoint;
2224         if(m_race(gamemode) && !ci->cpnodes.empty() && !m_ra_endurance(gamemode, mutators) && (!m_ra_gauntlet(gamemode, mutators) || ci->team == T_ALPHA))
2225         {
2226             int checkpoint = ci->cpnodes.last();
2227             if(sents.inrange(checkpoint)) return checkpoint;
2228         }
2229         if(totalspawns)
2230         {
2231             int rot = G(spawnrotate), team = teamspawns && m_teamspawn(gamemode, mutators) ? ci->team : T_NEUTRAL;
2232             if(m_duke(gamemode, mutators))
2233             {
2234                 if(m_duel(gamemode, mutators) && !m_team(gamemode, mutators)) // only use the opposing teams, multi spawns are usually diagonally arranged
2235                     team = spawns[T_ALPHA].iteration <= spawns[T_OMEGA].iteration ? T_ALPHA : T_OMEGA;
2236                 if(!rot) rot = 2; // letting the client decide would be bad in duel/survivor
2237             }
2238             if(team != T_NEUTRAL && spawns[team].ents.empty()) team = T_NEUTRAL; // not that this should happen
2239             switch(rot)
2240             {
2241                 case 2:
2242                 { // random
2243                     static vector<int> lowest;
2244                     lowest.setsize(0);
2245                     loopv(spawns[team].cycle) if(lowest.empty() || spawns[team].cycle[i] <= spawns[team].cycle[lowest[0]])
2246                     {
2247                         if(spawns[team].cycle.length() >= 2 && spawns[team].current == i) continue; // avoid using this one again straight away
2248                         if(!lowest.empty() && spawns[team].cycle[i] < spawns[team].cycle[lowest[0]]) lowest.setsize(0);
2249                         lowest.add(i);
2250                     }
2251                     if(!lowest.empty())
2252                     {
2253                         spawns[team].current = lowest[lowest.length() >= 2 ? rnd(lowest.length()) : 0];
2254                         break;
2255                     }
2256                     // fall through if this fails..
2257                 }
2258                 case 1:
2259                 { // sequential
2260                     if(++spawns[team].current >= spawns[team].ents.length()) spawns[team].current = 0;
2261                     break;
2262                 }
2263                 case 0: default: return -1; // let the client decide..
2264             }
2265             if(spawns[team].ents.inrange(spawns[team].current))
2266             {
2267                 spawns[team].iteration++;
2268                 spawns[team].cycle[spawns[team].current]++;
2269                 return spawns[team].ents[spawns[team].current];
2270             }
2271         }
2272         return -1;
2273     }
2274 
setupgameinfo(bool update=false)2275     void setupgameinfo(bool update = false)
2276     {
2277         setuptriggers(true);
2278         setupitems(true);
2279         setupspawns(true);
2280         if(!update)
2281         {
2282             mapgameinfo = -1;
2283             hasgameinfo = true;
2284         }
2285         aiman::poke();
2286     }
2287 
changemapvariant(int variant)2288     void changemapvariant(int variant)
2289     {
2290         if(variant != smapvariant)
2291         {
2292             smapvariant = variant;
2293             setupgameinfo(true);
2294         }
2295     }
2296 
sendspawn(clientinfo * ci)2297     void sendspawn(clientinfo *ci)
2298     {
2299         int weap = -1, health = ci->gethealth(gamemode, mutators);
2300         if(ci->actortype >= A_ENEMY)
2301         {
2302             bool hasent = sents.inrange(ci->spawnpoint) && sents[ci->spawnpoint].type == ACTOR;
2303             if(m_sweaps(gamemode, mutators)) weap = m_weapon(ci->actortype, gamemode, mutators);
2304             else weap = hasent && sents[ci->spawnpoint].attrs[6] > 0 ? sents[ci->spawnpoint].attrs[6]-1 : m_weapon(ci->actortype, gamemode, mutators);
2305             if(!m_insta(gamemode, mutators) && hasent && sents[ci->spawnpoint].attrs[7] > 0) health = max(sents[ci->spawnpoint].attrs[7], 1);
2306         }
2307         int spawn = pickspawn(ci);
2308         ci->spawnstate(gamemode, mutators, weap, health);
2309         ci->updatetimeplayed();
2310         loopv(sents) if(sents[i].type == WEAPON && isweap(sents[i].attrs[0]) && hasitem(i, false) && ci->weapent[sents[i].attrs[0]] < 0) ci->weapent[sents[i].attrs[0]] = i;
2311         sendf(ci->clientnum, 1, "ri9ifi4vv", N_SPAWNSTATE, ci->clientnum, spawn, ci->state, ci->points, ci->frags, ci->deaths, ci->totalpoints, ci->totalfrags, ci->totaldeaths, ci->totalavgpos, ci->timeplayed, ci->health, ci->cptime, ci->weapselect, W_MAX*W_A_MAX, &ci->weapammo[0][0], W_MAX, &ci->weapent[0]);
2312         ci->lastspawn = gamemillis;
2313     }
2314 
2315     template<class T>
sendstate(clientinfo * ci,T & p)2316     void sendstate(clientinfo *ci, T &p)
2317     {
2318         ci->updatetimeplayed();
2319         putint(p, ci->state);
2320         putint(p, ci->points);
2321         putint(p, ci->frags);
2322         putint(p, ci->deaths);
2323         putint(p, ci->totalpoints);
2324         putint(p, ci->totalfrags);
2325         putint(p, ci->totaldeaths);
2326         putfloat(p, ci->totalavgpos);
2327         putint(p, ci->timeplayed);
2328         putint(p, ci->health);
2329         putint(p, ci->cptime);
2330         putint(p, ci->weapselect);
2331         loopi(W_MAX) loopj(W_A_MAX) putint(p, ci->weapammo[i][j]);
2332         loopi(W_MAX) putint(p, ci->weapent[i]);
2333     }
2334 
relayf(int r,const char * s,...)2335     void relayf(int r, const char *s, ...)
2336     {
2337         defvformatbigstring(str, s, s);
2338         ircoutf(r, "%s", str);
2339 #ifdef STANDALONE
2340         bigstring ft;
2341         filterstring(ft, str);
2342         logoutf("%s", ft);
2343 #endif
2344     }
2345 
ancmsgft(int cn,int snd,int conlevel,const char * s,...)2346     void ancmsgft(int cn, int snd, int conlevel, const char *s, ...)
2347     {
2348         defvformatbigstring(str, s, s);
2349         if(cn < 0 || allowbroadcast(cn)) sendf(cn, 1, "ri3s", N_ANNOUNCE, snd, conlevel, str);
2350     }
2351 
srvmsgft(int cn,int conlevel,const char * s,...)2352     void srvmsgft(int cn, int conlevel, const char *s, ...)
2353     {
2354         defvformatbigstring(str, s, s);
2355         if(cn < 0 || allowbroadcast(cn)) sendf(cn, 1, "ri2s", N_SERVMSG, conlevel, str);
2356     }
2357 
srvmsgftforce(int cn,int conlevel,const char * s,...)2358     void srvmsgftforce(int cn, int conlevel, const char *s, ...)
2359     {
2360         defvformatbigstring(str, s, s);
2361         sendf(cn, 1, "ri2s", N_SERVMSG, conlevel, str);
2362     }
2363 
srvmsgf(int cn,const char * s,...)2364     void srvmsgf(int cn, const char *s, ...)
2365     {
2366         defvformatbigstring(str, s, s);
2367         if(cn < 0 || allowbroadcast(cn))
2368         {
2369             int conlevel = CON_EVENT;
2370             switch(cn)
2371             {
2372                 case -3: conlevel = CON_DEBUG; cn = -1; break;
2373                 case -2: conlevel = CON_MESG; cn = -1; break;
2374                 default: break;
2375             }
2376             sendf(cn, 1, "ri2s", N_SERVMSG, conlevel, str);
2377         }
2378     }
2379 
srvoutf(int r,const char * s,...)2380     void srvoutf(int r, const char *s, ...)
2381     {
2382         defvformatbigstring(str, s, s);
2383         srvmsgf(r >= 0 ? -1 : -2, "%s", str);
2384         relayf(abs(r), "%s", str);
2385     }
2386 
listdemos(int cn)2387     void listdemos(int cn)
2388     {
2389         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2390         putint(p, N_SENDDEMOLIST);
2391         putint(p, demos.length());
2392         loopv(demos)
2393         {
2394             sendstring(demos[i].info, p);
2395             putint(p, demos[i].len);
2396             putint(p, demos[i].ctime);
2397         }
2398         sendpacket(cn, 1, p.finalize());
2399     }
2400 
cleardemos(int n)2401     void cleardemos(int n)
2402     {
2403         if(!n)
2404         {
2405             loopv(demos) delete[] demos[i].data;
2406             demos.shrink(0);
2407             srvoutf(4, "\fyCleared all demos");
2408         }
2409         else if(demos.inrange(n-1))
2410         {
2411             delete[] demos[n-1].data;
2412             demos.remove(n-1);
2413             srvoutf(4, "\fyCleared demo \fs\fc%d\fS", n);
2414         }
2415     }
2416 
senddemo(int cn,int num,int dni)2417     void senddemo(int cn, int num, int dni)
2418     {
2419         if(!num) num = demos.length();
2420         if(!demos.inrange(num-1)) return;
2421         demofile &d = demos[num-1];
2422         sendf(cn, 2, "ri3m", N_SENDDEMO, d.ctime, dni, d.len, d.data);
2423     }
2424 
2425     void sendwelcome(clientinfo *ci);
2426     int welcomepacket(packetbuf &p, clientinfo *ci);
2427 
enddemoplayback()2428     void enddemoplayback()
2429     {
2430         if(!demoplayback) return;
2431         DELETEP(demoplayback);
2432         loopv(clients) sendf(clients[i]->clientnum, 1, "ri3", N_DEMOPLAYBACK, 0, clients[i]->clientnum);
2433         srvoutf(4, "\fyDemo playback finished");
2434         loopv(clients) sendwelcome(clients[i]);
2435         startintermission(true);
2436         resetgamevars(true);
2437     }
2438 
setupdemoplayback()2439     void setupdemoplayback()
2440     {
2441         demoheader hdr;
2442         stringz(msg);
2443         defformatstring(file, strstr(smapname, "maps/") == smapname || strstr(smapname, "maps\\") == smapname ? "%s.dmo" : "demos/%s.dmo", smapname);
2444         demoplayback = opengzfile(file, "rb");
2445         if(!demoplayback) formatstring(msg, "\frCould not read demo \fs\fc%s\fS", file);
2446         else if(demoplayback->read(&hdr, sizeof(demoheader)) != sizeof(demoheader) || memcmp(hdr.magic, VERSION_DEMOMAGIC, sizeof(hdr.magic)))
2447             formatstring(msg, "\frSorry, \fs\fc%s\fS is not a demo file", file);
2448         else
2449         {
2450             lilswap(&hdr.gamever, 4);
2451             if(hdr.gamever != VERSION_GAME)
2452                 formatstring(msg, "\frDemo \fs\fc%s\fS requires %s version of %s (with protocol version %d)", file, hdr.gamever < VERSION_GAME ? "an older" : "a newer", versionname, hdr.gamever);
2453         }
2454         if(msg[0])
2455         {
2456             DELETEP(demoplayback);
2457             srvoutf(4, "%s", msg);
2458             return;
2459         }
2460 
2461         srvoutf(4, "\fyPlaying demo \fs\fc%s\fS", file);
2462         sendf(-1, 1, "ri3", N_DEMOPLAYBACK, 1, -1);
2463 
2464         if(demoplayback->read(&nextplayback, sizeof(nextplayback)) != sizeof(nextplayback))
2465         {
2466             enddemoplayback();
2467             return;
2468         }
2469         lilswap(&nextplayback, 1);
2470     }
2471 
readdemo()2472     void readdemo()
2473     {
2474         if(!demoplayback || paused) return;
2475         while(gamemillis >= nextplayback)
2476         {
2477             int chan, len;
2478             if(demoplayback->read(&chan, sizeof(chan)) != sizeof(chan) ||
2479                 demoplayback->read(&len, sizeof(len)) != sizeof(len))
2480             {
2481                 enddemoplayback();
2482                 return;
2483             }
2484             lilswap(&chan, 1);
2485             lilswap(&len, 1);
2486             ENetPacket *packet = enet_packet_create(NULL, len, 0);
2487             if(!packet || demoplayback->read(packet->data, len) != size_t(len))
2488             {
2489                 if(packet) enet_packet_destroy(packet);
2490                 enddemoplayback();
2491                 return;
2492             }
2493             sendpacket(-1, chan, packet);
2494             if(!packet->referenceCount) enet_packet_destroy(packet);
2495             if(!demoplayback) break;
2496             if(demoplayback->read(&nextplayback, sizeof(nextplayback)) != sizeof(nextplayback))
2497             {
2498                 enddemoplayback();
2499                 return;
2500             }
2501             lilswap(&nextplayback, 1);
2502         }
2503     }
2504 
prunedemos(int extra=0)2505     void prunedemos(int extra = 0)
2506     {
2507         int n = clamp(demos.length()+extra-G(democount), 0, demos.length());
2508         if(n <= 0) return;
2509         loopi(n) delete[] demos[i].data;
2510         demos.remove(0, n);
2511     }
2512 
2513     struct demoinfo
2514     {
2515         demoheader hdr;
2516         string file;
2517     };
2518     vector<demoinfo> demoinfos;
2519     vector<char *> faildemos;
2520 
scandemo(const char * name)2521     int scandemo(const char *name)
2522     {
2523         if(!name || !*name) return -1;
2524         loopv(demoinfos) if(!strcmp(demoinfos[i].file, name)) return i;
2525         loopv(faildemos) if(!strcmp(faildemos[i], name)) return -1;
2526         stream *f = opengzfile(name, "rb");
2527         if(!f)
2528         {
2529             faildemos.add(newstring(name));
2530             return -1;
2531         }
2532         int num = demoinfos.length();
2533         demoinfo &d = demoinfos.add();
2534         copystring(d.file, name);
2535         stringz(msg);
2536         if(f->read(&d.hdr, sizeof(demoheader)) != sizeof(demoheader) || memcmp(d.hdr.magic, VERSION_DEMOMAGIC, sizeof(d.hdr.magic)))
2537             formatstring(msg, "\fs\fc%s\fS is not a demo file", name);
2538         else
2539         {
2540             lilswap(&d.hdr.gamever, 4);
2541         }
2542         delete f;
2543         if(msg[0])
2544         {
2545             conoutf("%s", msg);
2546             demoinfos.pop();
2547             faildemos.add(newstring(name));
2548             return -1;
2549         }
2550         return num;
2551     }
2552 
adddemo()2553     void adddemo()
2554     {
2555         if(!demotmp) return;
2556         int len = (int)min(demotmp->size(), stream::offset(G(demomaxsize) + 0x10000));
2557         demofile &d = demos.add();
2558         d.ctime = clocktime;
2559         d.data = new uchar[len];
2560         d.len = len;
2561         formatstring(d.info, "%s on %s", gamename(gamemode, mutators, 0, 32), smapname);
2562         relayf(4, "\fyDemo \fs\fc%s\fS recorded \fs\fc%s UTC\fS [\fs\fw%.2f%s\fS]", d.info, gettime(d.ctime, "%Y-%m-%d %H:%M.%S"), d.len > 1024*1024 ? d.len/(1024*1024.f) : d.len/1024.0f, d.len > 1024*1024 ? "MB" : "kB");
2563         sendf(-1, 1, "ri4s", N_DEMOREADY, demos.length(), d.ctime, d.len, d.info);
2564         demotmp->seek(0, SEEK_SET);
2565         demotmp->read(d.data, len);
2566         DELETEP(demotmp);
2567         if(G(demoautoserversave))
2568         {
2569             stringz(dafilepath);
2570             if(*filetimeformat) formatstring(dafilepath, "demos/sv_%s_%s-%s.dmo", gettime(d.ctime, filetimeformat), gamename(gamemode, mutators, 1, 32, '_'), smapname);
2571             else formatstring(dafilepath, "demos/sv_%u_%s-%s.dmo", uint(d.ctime), gamename(gamemode, mutators, 1, 32, '_'), smapname);
2572             stream *dafile = openrawfile(dafilepath, "w");
2573             dafile->write(d.data, d.len);
2574             dafile->close();
2575             DELETEP(dafile);
2576         }
2577         if(G(demoserverkeeptime))
2578         {
2579             vector<char *> files;
2580             listfiles("demos", "dmo", files);
2581             loopvrev(files) if(!strncmp(files[i], "sv_", 3))
2582             {
2583                 defformatstring(dirfile, "demos/%s.dmo", files[i]);
2584                 int q = scandemo(dirfile);
2585                 if(q >= 0 && (clocktime-demoinfos[q].hdr.starttime) >= G(demoserverkeeptime))
2586                 {
2587                     const char *fullfile = findfile(dirfile, "r");
2588                     if(fullfile && *fullfile && !unlink(fullfile))
2589                     {
2590                         conoutf("Deleted old demo: %s", files[i]);
2591                         demoinfos.remove(q);
2592                     }
2593                 }
2594             }
2595         }
2596     }
2597 
enddemorecord(bool full)2598     void enddemorecord(bool full)
2599     {
2600         if(!demorecord) return;
2601         DELETEP(demorecord);
2602         if(!demotmp) return;
2603         if(!full && !G(demokeep)) { DELETEP(demotmp); }
2604         else
2605         {
2606             prunedemos(1);
2607             adddemo();
2608         }
2609     }
2610 
writedemo(int chan,void * data,int len)2611     void writedemo(int chan, void *data, int len)
2612     {
2613         if(!demorecord) return;
2614         int stamp[3] = { gamemillis, chan, len };
2615         lilswap(stamp, 3);
2616         demorecord->write(stamp, sizeof(stamp));
2617         demorecord->write(data, len);
2618         if(demorecord->rawtell() >= G(demomaxsize)) enddemorecord(!gs_playing(gamestate));
2619     }
2620 
recordpacket(int chan,void * data,int len)2621     void recordpacket(int chan, void *data, int len)
2622     {
2623         writedemo(chan, data, len);
2624     }
2625 
setupdemorecord()2626     void setupdemorecord()
2627     {
2628         if(demorecord) enddemorecord(false);
2629         if(m_demo(gamemode) || m_edit(gamemode)) return;
2630         demonextmatch = false;
2631 
2632         demotmp = opentempfile("backups/demorecord", "w+b");
2633         stream *f = opengzfile(NULL, "wb", demotmp);
2634         if(!f) { DELETEP(demotmp); return; }
2635 
2636         demorecord = f;
2637 
2638         demoheader hdr;
2639         memcpy(hdr.magic, VERSION_DEMOMAGIC, sizeof(hdr.magic));
2640         hdr.gamever = VERSION_GAME;
2641         hdr.gamemode = gamemode;
2642         hdr.mutators = mutators;
2643         hdr.starttime = clocktime;
2644         lilswap(&hdr.gamever, 4);
2645         copystring(hdr.mapname, smapname);
2646         demorecord->write(&hdr, sizeof(demoheader));
2647 
2648         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2649         welcomepacket(p, NULL);
2650         writedemo(1, p.buf, p.len);
2651     }
2652 
endmatch()2653     void endmatch()
2654     {
2655         setpause(false);
2656         checkdemorecord(true);
2657         setmod(sv_botoffset, 0);
2658         if(G(resetmmonend) >= 2) mastermode = MM_OPEN;
2659         if(G(resetvarsonend) >= 2) resetgamevars(false);
2660         if(G(resetallowsonend) >= 2) resetcontrols(ipinfo::ALLOW);
2661         if(G(resetbansonend) >= 2) resetcontrols(ipinfo::BAN);
2662         if(G(resetmutesonend) >= 2) resetcontrols(ipinfo::MUTE);
2663         if(G(resetlimitsonend) >= 2) resetcontrols(ipinfo::LIMIT);
2664         if(G(resetexceptsonend) >= 2) resetcontrols(ipinfo::EXCEPT);
2665     }
2666 
checkvotes(bool force)2667     bool checkvotes(bool force)
2668     {
2669         shouldcheckvotes = false;
2670         int style = gamestate == G_S_VOTING ? G(voteinterm) : G(votestyle);
2671         if(style == 3 && !force) return false;
2672         vector<votecount> votes;
2673         int maxvotes = 0;
2674         loopv(clients)
2675         {
2676             clientinfo *oi = clients[i];
2677             if(oi->actortype > A_PLAYER) continue;
2678             if(G(votefilter) && !gs_waiting(gamestate) && oi->state == CS_SPECTATOR && !*oi->mapvote) continue; // filter out spectators who haven't voted
2679             maxvotes++;
2680             if(!*oi->mapvote) continue;
2681             if(style == 3) votes.add(votecount(oi->mapvote, oi->modevote, oi->mutsvote));
2682             else
2683             {
2684                 votecount *vc = NULL;
2685                 loopvj(votes) if(!strcmp(oi->mapvote, votes[j].map) && oi->modevote == votes[j].mode && oi->mutsvote == votes[j].muts)
2686                 {
2687                     vc = &votes[j];
2688                     break;
2689                 }
2690                 if(!vc) vc = &votes.add(votecount(oi->mapvote, oi->modevote, oi->mutsvote));
2691                 vc->count++;
2692             }
2693         }
2694 
2695         votecount *best = NULL;
2696         bool passed = force;
2697         if(style == 3) best = !votes.empty() ? &votes[rnd(votes.length())] : NULL;
2698         else
2699         {
2700             int morethanone = 0;
2701             loopv(votes) if(!best || votes[i].count >= best->count)
2702             {
2703                 if(best && votes[i].count == best->count) morethanone++;
2704                 else morethanone = 0;
2705                 best = &votes[i];
2706             }
2707             if(force && morethanone)
2708             {
2709                 int r = rnd(morethanone+1), n = 0;
2710                 loopv(votes) if(votes[i].count == best->count)
2711                 {
2712                     if(n != r) n++;
2713                     else { best = &votes[i]; break; }
2714                 }
2715             }
2716             if(!passed && best) switch(style)
2717             {
2718                 case 2: passed = best->count >= maxvotes; break;
2719                 case 1: passed = best->count >= maxvotes*G(votethreshold); break;
2720                 case 0: default: break;
2721             }
2722         }
2723         if(passed)
2724         {
2725             sendstats();
2726             endmatch();
2727             if(best)
2728             {
2729                 srvoutf(-3, "vote passed: \fs\fy%s\fS on \fs\fo%s\fS", gamename(best->mode, best->muts), best->map);
2730                 changemap(best->map, best->mode, best->muts);
2731             }
2732             else
2733             {
2734                 int mode = G(rotatemode) ? -1 : gamemode, muts = G(rotatemuts) ? -1 : mutators;
2735                 changemode(mode, muts);
2736                 const char *map = choosemap(smapname, mode, muts);
2737                 srvoutf(-3, "server chooses: \fs\fy%s\fS on \fs\fo%s\fS", gamename(mode, muts), map);
2738                 changemap(map, mode, muts);
2739             }
2740             return true;
2741         }
2742         return false;
2743     }
2744 
mutscmp(int req,int limit)2745     bool mutscmp(int req, int limit)
2746     {
2747         if(req)
2748         {
2749             if(!limit) return false;
2750             loopi(G_M_NUM) if(req&(1<<i) && !(limit&(1<<i))) return false;
2751         }
2752         return true;
2753     }
2754 
vote(const char * reqmap,int & reqmode,int & reqmuts,int sender)2755     void vote(const char *reqmap, int &reqmode, int &reqmuts, int sender)
2756     {
2757         clientinfo *ci = (clientinfo *)getinfo(sender);
2758         if(!ci) return;
2759         reqmuts |= G(mutslockforce);
2760         modecheck(reqmode, reqmuts);
2761         if(!m_game(reqmode)) return;
2762         if(!reqmap || !*reqmap) reqmap = "<random>";
2763         bool israndom = !strcmp(reqmap, "<random>");
2764         if(m_local(reqmode) && !ci->local)
2765         {
2766             srvmsgft(ci->clientnum, CON_EVENT, "\frAccess denied, you must be a local client to start a %s game", gametype[reqmode].name);
2767             return;
2768         }
2769         bool hasvote = false, hasveto = (mastermode == MM_VETO && haspriv(ci, G(vetolock))) || !numclients(ci->clientnum);
2770         if(!hasveto)
2771         {
2772             if(ci->lastvote && totalmillis-ci->lastvote <= G(votewait)) return;
2773             if(ci->modevote == reqmode && ci->mutsvote == reqmuts && !strcmp(ci->mapvote, reqmap)) return;
2774         }
2775         loopv(clients)
2776         {
2777             clientinfo *oi = clients[i];
2778             if(oi->actortype > A_PLAYER || !*oi->mapvote || ci == oi) continue;
2779             if(!strcmp(oi->mapvote, reqmap) && oi->modevote == reqmode && oi->mutsvote == reqmuts)
2780             {
2781                 hasvote = true;
2782                 break;
2783             }
2784         }
2785         if(!hasvote)
2786         {
2787             if(G(votelock)) switch(G(votelocktype))
2788             {
2789                 case 1: if(!haspriv(ci, G(votelock), "vote for a new game")) return; break;
2790                 case 2:
2791                     if(!israndom && !m_edit(reqmode))
2792                     {
2793                         int n = listincludes(sv_previousmaps, reqmap, strlen(reqmap));
2794                         if(n >= 0 && n < G(maphistory) && !haspriv(ci, G(votelock), "vote for a recently played map")) return;
2795                     }
2796                     break;
2797                 case 0: default: break;
2798             }
2799             if(G(modelock)) switch(G(modelocktype))
2800             {
2801                 case 1: if(!haspriv(ci, G(modelock), "change game modes")) return; break;
2802                 case 2: if((!((1<<reqmode)&G(modelockfilter)) || !mutscmp(reqmuts, G(mutslockfilter))) && !haspriv(ci, G(modelock), "change to a locked game mode")) return; break;
2803                 case 0: default: break;
2804             }
2805             if(!m_edit(reqmode) && G(mapslock))
2806             {
2807                 char *list = NULL;
2808                 switch(G(mapslocktype))
2809                 {
2810                     case 1:
2811                     {
2812                         list = newstring(G(allowmaps));
2813                         mapcull(list, reqmode, reqmuts, numclients(), G(mapsfilter), true);
2814                         break;
2815                     }
2816                     case 2:
2817                     {
2818                         maplist(list, reqmode, reqmuts, numclients(), G(mapsfilter), true);
2819                         break;
2820                     }
2821                     case 0: default: break;
2822                 }
2823                 if(list)
2824                 {
2825                     if(!israndom && listincludes(list, reqmap, strlen(reqmap)) < 0 && !haspriv(ci, G(modelock), "select maps not in the rotation"))
2826                     {
2827                         DELETEA(list);
2828                         return;
2829                     }
2830                     DELETEA(list);
2831                 }
2832             }
2833         }
2834         copystring(ci->mapvote, reqmap);
2835         ci->modevote = reqmode;
2836         ci->mutsvote = reqmuts;
2837         ci->lastvote = totalmillis ? totalmillis : 1;
2838         if(hasveto)
2839         {
2840             sendstats();
2841             endmatch();
2842             srvoutf(-3, "%s forced: \fs\fy%s\fS on \fs\fo%s\fS", colourname(ci), gamename(ci->modevote, ci->mutsvote), ci->mapvote);
2843             changemap(ci->mapvote, ci->modevote, ci->mutsvote);
2844             return;
2845         }
2846         sendf(-1, 1, "ri2si2", N_MAPVOTE, ci->clientnum, ci->mapvote, ci->modevote, ci->mutsvote);
2847         relayf(3, "%s suggests: \fs\fy%s\fS on \fs\fo%s\fS", colourname(ci), gamename(ci->modevote, ci->mutsvote), ci->mapvote);
2848         checkvotes();
2849     }
2850 
scorecmp(clientinfo * ci,uint ip,const char * name,const char * handle,const char * steamid,uint clientip)2851     bool scorecmp(clientinfo *ci, uint ip, const char *name, const char *handle, const char *steamid, uint clientip)
2852     {
2853         if(ci->handle[0] && !strcmp(handle, ci->handle)) return true;
2854         if(ci->steamid[0] && !strcmp(steamid, ci->steamid)) return true;
2855         if(!ci->handle[0] && ip && clientip == ip && !strcmp(name, ci->name)) return true;
2856         return false;
2857     }
2858 
findscore(vector<savedscore> & scores,clientinfo * ci,bool insert)2859     savedscore *findscore(vector<savedscore> &scores, clientinfo *ci, bool insert)
2860     {
2861         uint ip = getclientip(ci->clientnum);
2862         if(!insert) loopv(clients)
2863         {
2864             clientinfo *oi = clients[i];
2865             if(oi->clientnum != ci->clientnum && scorecmp(ci, ip, oi->name, oi->handle, oi->steamid, getclientip(oi->clientnum)))
2866             {
2867                 oi->updatetimeplayed();
2868                 static savedscore curscore;
2869                 curscore.save(oi);
2870                 return &curscore;
2871             }
2872         }
2873         loopv(scores)
2874         {
2875             savedscore &sc = scores[i];
2876             if(scorecmp(ci, ip, sc.name, sc.handle, sc.steamid, sc.ip)) return &sc;
2877         }
2878         if(!insert) return NULL;
2879         savedscore &sc = scores.add();
2880         copystring(sc.name, ci->name);
2881         copystring(sc.handle, ci->handle);
2882         copystring(sc.steamid, ci->steamid);
2883         sc.ip = ip;
2884         return &sc;
2885     }
2886 
givepoints(clientinfo * ci,int points,bool give,bool team=true)2887     void givepoints(clientinfo *ci, int points, bool give, bool team = true)
2888     {
2889         ci->totalpoints += points;
2890         ci->localtotalpoints += points;
2891         if(give)
2892         {
2893             ci->points += points;
2894             sendf(-1, 1, "ri5", N_POINTS, ci->clientnum, points, ci->points, ci->totalpoints);
2895             if(team && m_team(gamemode, mutators) && m_dm(gamemode))
2896             {
2897                 score &ts = teamscore(ci->team);
2898                 ts.total += points;
2899                 sendf(-1, 1, "ri3", N_SCORE, ts.team, ts.total);
2900             }
2901         }
2902         else if(points) sendf(-1, 1, "ri5", N_POINTS, ci->clientnum, points, ci->points, ci->totalpoints);
2903     }
2904 
savescore(clientinfo * ci)2905     void savescore(clientinfo *ci)
2906     {
2907         ci->updatetimeplayed();
2908         savedscore *sc = findscore(savedscores, ci, true);
2909         if(sc)
2910         {
2911             if(ci->actortype == A_PLAYER && m_dm(gamemode) && m_team(gamemode, mutators) && !m_nopoints(gamemode, mutators) && G(teamkillrestore) && canplay())
2912             {
2913                 int restorepoints[T_MAX] = {0};
2914                 loopv(ci->teamkills) restorepoints[ci->teamkills[i].team] += ci->teamkills[i].points;
2915                 loopi(T_MAX) if(restorepoints[i] >= G(teamkillrestore))
2916                 {
2917                     score &ts = teamscore(i);
2918                     ts.total += restorepoints[i];
2919                     sendf(-1, 1, "ri3", N_SCORE, ts.team, ts.total);
2920                 }
2921             }
2922             sc->save(ci);
2923         }
2924     }
2925 
savestatsscore(clientinfo * ci)2926     void savestatsscore(clientinfo *ci)
2927     {
2928         ci->updatetimeplayed();
2929         savedscore *sc = findscore(savedstatsscores, ci, true);
2930         if(sc) sc->save(ci);
2931     }
2932 
swapteam(clientinfo * ci,int oldteam,int newteam=T_NEUTRAL,bool swaps=true)2933     void swapteam(clientinfo *ci, int oldteam, int newteam = T_NEUTRAL, bool swaps = true)
2934     {
2935         if(ci->swapteam && (!newteam || ci->swapteam == newteam)) ci->swapteam = T_NEUTRAL;
2936         if(!swaps || ci->actortype != A_PLAYER || !oldteam || oldteam == newteam || !m_swapteam(gamemode, mutators)) return;
2937         loopv(clients) if(clients[i] && clients[i] != ci)
2938         {
2939             clientinfo *cp = clients[i];
2940             if(cp->actortype != A_PLAYER || (newteam && cp->team != newteam) || !cp->swapteam || cp->swapteam != oldteam) continue;
2941             setteam(cp, oldteam, TT_RESET|TT_INFOSM, false);
2942             cp->lastdeath = 0;
2943             ancmsgft(cp->clientnum, S_V_BALALERT, CON_EVENT, "\fyYou have been moved to %s as previously requested", colourteam(oldteam));
2944             return;
2945         }
2946         if(haspriv(ci, G(teambalancelock)))
2947         {
2948             int worst = -1;
2949             float csk = 0, wsk = 0;
2950             csk = ci->balancescore();
2951             loopv(clients) if(clients[i] && clients[i] != ci)
2952             {
2953                 clientinfo *cp = clients[i];
2954                 if(cp->actortype != A_PLAYER || (newteam && cp->team != newteam)) continue;
2955                 float psk = 0;
2956                 psk = cp->balancescore();
2957                 if(psk > csk || psk > wsk) continue;
2958                 worst = i;
2959                 wsk = psk;
2960             }
2961             if(worst >= 0)
2962             {
2963                 clientinfo *cp = clients[worst];
2964                 setteam(cp, oldteam, TT_RESET|TT_INFOSM, false);
2965                 cp->lastdeath = 0;
2966                 ancmsgft(cp->clientnum, S_V_BALALERT, CON_EVENT, "\fyYou have been moved to %s by higher skilled %s %s", colourteam(oldteam), privname(G(teambalancelock)), colourname(ci));
2967                 return;
2968             }
2969         }
2970     }
2971 
setteam(clientinfo * ci,int team,int flags,bool swaps)2972     void setteam(clientinfo *ci, int team, int flags, bool swaps)
2973     {
2974         swapteam(ci, ci->team, team, swaps);
2975         if(ci->team != team)
2976         {
2977             bool reenter = false;
2978             if(flags&TT_RESET) waiting(ci, DROP_WEAPONS, false);
2979             else if(flags&TT_SMODE && ci->state == CS_ALIVE)
2980             {
2981                 if(smode) smode->leavegame(ci);
2982                 mutate(smuts, mut->leavegame(ci));
2983                 reenter = true;
2984             }
2985             ci->lastteam = ci->team;
2986             ci->team = team;
2987             if(reenter)
2988             {
2989                 if(smode) smode->entergame(ci);
2990                 mutate(smuts, mut->entergame(ci));
2991             }
2992             if(ci->isready()) aiman::poke();
2993         }
2994         if(flags&TT_INFO) sendf(-1, 1, "ri3", N_SETTEAM, ci->clientnum, ci->team);
2995     }
2996 
2997     struct teamcheck
2998     {
2999         int team;
3000         float score;
3001         int clients;
3002 
teamcheckserver::teamcheck3003         teamcheck() : team(T_NEUTRAL), score(0.f), clients(0) {}
teamcheckserver::teamcheck3004         teamcheck(int n) : team(n), score(0.f), clients(0) {}
teamcheckserver::teamcheck3005         teamcheck(int n, float r) : team(n), score(r), clients(0) {}
teamcheckserver::teamcheck3006         teamcheck(int n, int s) : team(n), score(s), clients(0) {}
3007 
~teamcheckserver::teamcheck3008         ~teamcheck() {}
3009     };
3010 
allowteam(clientinfo * ci,int team,int first=T_FIRST,bool check=true)3011     bool allowteam(clientinfo *ci, int team, int first = T_FIRST, bool check = true)
3012     {
3013         if(isteam(gamemode, mutators, team, first))
3014         {
3015             if(!m_coop(gamemode, mutators))
3016             {
3017                 if(check && m_balteam(gamemode, mutators, 3) && team != chooseteam(ci, team)) return false;
3018                 return true;
3019             }
3020             else if(ci->actortype >= A_BOT) return team != mapbals[curbalance][0];
3021             else return team == mapbals[curbalance][0];
3022         }
3023         return false;
3024     }
3025 
chooseteam(clientinfo * ci,int suggest,bool wantbal)3026     int chooseteam(clientinfo *ci, int suggest, bool wantbal)
3027     {
3028         if(ci->actortype >= A_ENEMY) return T_ENEMY;
3029         else if(m_team(gamemode, mutators) && ci->state != CS_SPECTATOR && ci->state != CS_EDITING)
3030         {
3031             bool human = ci->actortype == A_PLAYER;
3032             int team = -1, bal = human && !wantbal && (G(teambalance) != 6 || !gs_playing(gamestate)) ? G(teambalance) : 1;
3033             if(human)
3034             {
3035                 if(m_coop(gamemode, mutators)) return mapbals[curbalance][0];
3036                 int teams[3][3] = {
3037                     { suggest, ci->team, -1 },
3038                     { suggest, ci->team, ci->lastteam },
3039                     { suggest, ci->lastteam, ci->team }
3040                 };
3041                 loopi(3) if(allowteam(ci, teams[G(teampersist)][i], T_FIRST, false))
3042                 {
3043                     team = teams[G(teampersist)][i];
3044                     if(bal <= 2 && G(teampersist) == 2) return team;
3045                     break;
3046                 }
3047             }
3048             teamcheck teamchecks[T_NUM];
3049             loopk(T_NUM) teamchecks[k].team = T_FIRST+k;
3050             loopv(clients) if(clients[i] != ci)
3051             {
3052                 clientinfo *cp = clients[i];
3053                 if(!cp->team || cp->state == CS_SPECTATOR) continue;
3054                 if((cp->actortype > A_PLAYER && cp->ownernum < 0) || cp->actortype >= A_ENEMY) continue;
3055                 teamcheck &ts = teamchecks[cp->team-T_FIRST];
3056                 if(team > 0 && m_swapteam(gamemode, mutators) && ci->actortype == A_PLAYER && cp->actortype == A_PLAYER && cp->swapteam && ci->team == cp->swapteam && cp->team == team)
3057                     return team; // swapteam
3058                 if(ci->actortype > A_PLAYER || (ci->actortype == A_PLAYER && cp->actortype == A_PLAYER))
3059                 { // remember: ai just balance teams
3060                     ts.score += cp->balancescore(1);
3061                     ts.clients++;
3062                 }
3063             }
3064             if(bal || team <= 0) loopj(team > 0 ? 2 : 1)
3065             {
3066                 teamcheck *worst = NULL;
3067                 loopi(numteams(gamemode, mutators)) if(allowteam(ci, teamchecks[i].team, T_FIRST, false))
3068                 {
3069                     teamcheck &ts = teamchecks[i];
3070                     switch(bal)
3071                     {
3072                         case 2: case 5: case 6:
3073                         {
3074                             if(!worst || (team > 0 && ts.team == team && ts.score <= worst->score) || ts.score < worst->score || ((team <= 0 || worst->team != team) && ts.score == worst->score && ts.clients < worst->clients))
3075                                 worst = &ts;
3076                             break;
3077                         }
3078                         case 1: case 3: case 4: default:
3079                         {
3080                             if(!worst || (team > 0 && ts.team == team && ts.clients <= worst->clients) || ts.clients < worst->clients || ((team <= 0 || worst->team != team) && ts.clients == worst->clients && ts.score < worst->score))
3081                                 worst = &ts;
3082                             break;
3083                         }
3084                     }
3085                 }
3086                 if(worst)
3087                 {
3088                     vector <int> possibleteams;
3089                     loopi(numteams(gamemode, mutators)) if(allowteam(ci, teamchecks[i].team, T_FIRST, false))
3090                     {
3091                         teamcheck &ts = teamchecks[i];
3092                         if(ts.score == worst->score && ts.clients == worst->clients)
3093                         {
3094                             possibleteams.add(ts.team);
3095                         }
3096                     }
3097                     team = possibleteams[rnd(possibleteams.length())];
3098                     break;
3099                 }
3100                 team = -1;
3101             }
3102             return allowteam(ci, team, T_FIRST, false) ? team : T_ALPHA;
3103         }
3104         return T_NEUTRAL;
3105     }
3106 
stopdemo()3107     void stopdemo()
3108     {
3109         if(m_demo(gamemode)) enddemoplayback();
3110         else checkdemorecord(!gs_playing(gamestate));
3111     }
3112 
3113     void connected(clientinfo *ci);
3114     void welcomeinitclient(clientinfo *ci, packetbuf &p, int exclude = -1, bool nobots = false);
3115 
3116     #include "auth.h"
3117 
3118     enum { ALST_TRY = 0, ALST_SPAWN, ALST_SPEC, ALST_EDIT, ALST_WALK, ALST_MAX };
3119 
3120     bool getmap(clientinfo *ci = NULL, bool force = false);
3121 
crclocked(clientinfo * ci,bool msg=false)3122     bool crclocked(clientinfo *ci, bool msg = false)
3123     {
3124         if(m_play(gamemode) && G(crclock) && ci->actortype == A_PLAYER && (smapcrc ? ci->clientcrc != smapcrc : !ci->clientcrc) && !haspriv(ci, G(crclock)))
3125         {
3126             if(msg) srvmsgft(ci->clientnum, CON_EVENT, "\fyYou are \fs\fccrc locked\fS, please wait for the correct map version..");
3127             return true;
3128         }
3129         return false;
3130     }
3131 
spectator(clientinfo * ci,bool quarantine=false,int sender=-1)3132     void spectator(clientinfo *ci, bool quarantine = false, int sender = -1)
3133     {
3134         if(!ci || ci->actortype > A_PLAYER) return;
3135         ci->state = CS_SPECTATOR;
3136         ci->quarantine = quarantine;
3137         sendf(sender, 1, "ri3", N_SPECTATOR, ci->clientnum, quarantine ? 2 : 1);
3138         setteam(ci, T_NEUTRAL, TT_INFOSM);
3139     }
3140 
spectate(clientinfo * ci,bool val,bool quarantine=false)3141     bool spectate(clientinfo *ci, bool val, bool quarantine = false)
3142     {
3143         if(ci->state != CS_SPECTATOR && val)
3144         {
3145             if(ci->state == CS_ALIVE)
3146             {
3147                 suicideevent ev;
3148                 ev.flags = HIT(SPEC);
3149                 ev.process(ci); // process death immediately
3150             }
3151             if(smode) smode->leavegame(ci);
3152             mutate(smuts, mut->leavegame(ci));
3153             sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, quarantine ? 2 : 1);
3154             ci->state = CS_SPECTATOR;
3155             ci->quarantine = quarantine;
3156             ci->updatetimeplayed();
3157             setteam(ci, T_NEUTRAL, TT_INFO);
3158             if(ci->isready()) aiman::poke();
3159         }
3160         else if(ci->state == CS_SPECTATOR && !val)
3161         {
3162             if(crclocked(ci, true))
3163             {
3164                 getmap(ci);
3165                 return false;
3166             }
3167             int nospawn = 0;
3168             if(numclients(ci->clientnum, true) >= G(serverclients)) nospawn++;
3169             if(smode && !smode->canspawn(ci, true)) { nospawn++; }
3170             mutate(smuts, if(!mut->canspawn(ci, true)) { nospawn++; });
3171             ci->state = CS_DEAD;
3172             if(nospawn)
3173             {
3174                 spectate(ci, true);
3175                 return false;
3176             }
3177             ci->lasttimeplayed = totalmillis ? totalmillis : 1;
3178             ci->lasttimealive = totalmillis ? totalmillis : 1;
3179             ci->lasttimeactive = totalmillis ? totalmillis : 1;
3180             ci->lasttimewielded = totalmillis ? totalmillis : 1;
3181             loopi(W_MAX) ci->lasttimeloadout[i] = totalmillis ? totalmillis : 1;
3182             ci->quarantine = false;
3183             waiting(ci, DROP_RESET);
3184             if(smode) smode->entergame(ci);
3185             mutate(smuts, mut->entergame(ci));
3186             if(ci->isready()) aiman::poke();
3187         }
3188         return true;
3189     }
3190 
3191     struct clientcrcs
3192     {
3193         int id;
3194         vector<clientinfo *> clients;
clientcrcsserver::clientcrcs3195         clientcrcs() {}
clientcrcsserver::clientcrcs3196         clientcrcs(int n, clientinfo *m) { id = n; clients.add(m); }
~clientcrcsserver::clientcrcs3197         ~clientcrcs() { clients.setsize(0); }
3198     };
3199 
resetmapdata(bool get=false)3200     void resetmapdata(bool get = false)
3201     {
3202         smapcrc = 0;
3203         mapsending = -1;
3204         loopi(SENDMAP_MAX) if(mapdata[i]) DELETEP(mapdata[i]);
3205         if(get) getmap();
3206     }
3207 
hasmapdata()3208     bool hasmapdata()
3209     {
3210         if(m_edit(gamemode))
3211         {
3212             loopi(SENDMAP_EDIT) if(!mapdata[i]) return false;
3213             return true;
3214         }
3215         if(!smapcrc) return false;
3216         loopi(SENDMAP_HAS) if(!mapdata[i]) return false;
3217         return true;
3218     }
3219 
getmap(clientinfo * ci,bool force)3220     bool getmap(clientinfo *ci, bool force)
3221     {
3222         if(gs_intermission(gamestate)) return false; // pointless
3223         if(ci && !numclients(ci->clientnum) && !hasmapdata())
3224         {
3225             ci->wantsmap = false;
3226             sendf(ci->clientnum, 1, "ri", N_FAILMAP);
3227             return false;
3228         }
3229         if(ci)
3230         {
3231             ci->clientcrc = 0;
3232             ci->wantsmap = true;
3233             if(mapsending == ci->clientnum)
3234             {
3235                 resetmapdata();
3236                 return false;
3237             }
3238             if(mapsending >= 0)
3239             {
3240                 srvmsgft(ci->clientnum, CON_EVENT, "\fyThe map is being uploaded, please wait..");
3241                 return true;
3242             }
3243             if(hasmapdata())
3244             {
3245                 if(ci->gettingmap) return true;
3246                 ci->gettingmap = true;
3247                 srvmsgft(ci->clientnum, CON_EVENT, "\fySending you the map, please wait..");
3248                 loopi(SENDMAP_MAX) if(mapdata[i]) sendfile(ci->clientnum, 2, mapdata[i], "ri3s", N_SENDMAPFILE, i, smapcrc, smapname);
3249                 sendwelcome(ci);
3250                 ci->needclipboard = 0;
3251                 return true;
3252             }
3253             else srvmsgft(ci->clientnum, CON_EVENT, "\fyAttempting to download the map, please wait..");
3254         }
3255         if((!force && gs_waiting(gamestate)) || mapsending >= 0 || hasmapdata()) return false;
3256         clientinfo *best = NULL;
3257         if(!m_edit(gamemode) || force)
3258         {
3259             vector<clientcrcs> crcs;
3260             loopv(clients)
3261             {
3262                 clientinfo *cs = clients[i];
3263                 if(cs->actortype > A_PLAYER || !cs->name[0] || !cs->online || cs->wantsmap || !cs->clientcrc || !cs->ready) continue;
3264                 bool found = false;
3265                 loopvj(crcs) if(crcs[j].id == cs->clientcrc)
3266                 {
3267                     crcs[j].clients.add(cs);
3268                     found = true;
3269                     break;
3270                 }
3271                 if(!found) crcs.add(clientcrcs(cs->clientcrc, cs));
3272             }
3273             int n = -1;
3274             loopv(crcs) if(n < 0 || crcs[n].clients.length() < crcs[i].clients.length()) n = i;
3275             if(n > 0) loopv(crcs[n].clients)
3276             {
3277                 clientinfo *cs = crcs[n].clients[i];
3278                 cs->updatetimeplayed();
3279                 if(!best || cs->timeplayed > best->timeplayed) best = cs;
3280             }
3281         }
3282         if(!best) loopv(clients)
3283         {
3284             clientinfo *cs = clients[i];
3285             if(cs->actortype > A_PLAYER || !cs->name[0] || !cs->online || cs->wantsmap || !cs->ready) continue;
3286             cs->updatetimeplayed();
3287             if(!best || cs->timeplayed > best->timeplayed) best = cs;
3288         }
3289         if(best)
3290         {
3291             mapsending = best->clientnum;
3292             if(m_edit(gamemode))
3293             {
3294                 smapcrc = 0;
3295                 srvoutf(4, "\fyThe map is being requested from %s..", colourname(best));
3296             }
3297             else
3298             {
3299                 smapcrc = best->clientcrc;
3300                 srvoutf(4, "\fyThe map crc \fs\fc0x%.8x\fS is being requested from %s..", smapcrc, colourname(best));
3301             }
3302             sendf(best->clientnum, 1, "ri", N_GETMAP);
3303             loopv(clients)
3304             {
3305                 clientinfo *cs = clients[i];
3306                 if(cs->actortype > A_PLAYER || !cs->name[0] || !cs->online || !cs->ready) continue;
3307                 if(cs->wantsmap || crclocked(cs, true))
3308                 {
3309                     cs->clientcrc = 0;
3310                     cs->wantsmap = true;
3311                     spectate(cs, true);
3312                 }
3313             }
3314             return true;
3315         }
3316         if(ci) srvmsgft(ci->clientnum, CON_EVENT, "\fySorry, unable to get a map..");
3317         sendf(-1, 1, "ri", N_FAILMAP);
3318         return false;
3319     }
3320 
allowstate(clientinfo * ci,int n,int lock=-1)3321     bool allowstate(clientinfo *ci, int n, int lock = -1)
3322     {
3323         if(!ci) return false;
3324         uint ip = getclientip(ci->clientnum);
3325         switch(n)
3326         {
3327             case ALST_TRY: // try spawn
3328             {
3329                 if(ci->quarantine || (ci->state == CS_SPECTATOR && numclients(ci->clientnum, true) >= G(serverclients))) return false;
3330                 if(ci->actortype == A_PLAYER)
3331                     if(mastermode >= MM_LOCKED && ip && !checkipinfo(control, ipinfo::ALLOW, ip) && !haspriv(ci, lock, "spawn"))
3332                         return false;
3333                 if(ci->state == CS_ALIVE || ci->state == CS_WAITING) return false;
3334                 if(ci->lastdeath && gamemillis-ci->lastdeath <= DEATHMILLIS) return false;
3335                 if(crclocked(ci, true))
3336                 {
3337                     getmap(ci);
3338                     return false;
3339                 }
3340                 break;
3341             }
3342             case ALST_SPAWN: // spawn
3343             {
3344                 if(ci->quarantine || (ci->state == CS_SPECTATOR && numclients(ci->clientnum, true) >= G(serverclients))) return false;
3345                 if(ci->state != CS_DEAD && ci->state != CS_WAITING) return false;
3346                 if(ci->lastdeath && gamemillis-ci->lastdeath <= DEATHMILLIS) return false;
3347                 if(crclocked(ci, true))
3348                 {
3349                     getmap(ci);
3350                     return false;
3351                 }
3352                 break;
3353             }
3354             case ALST_SPEC: return ci->actortype == A_PLAYER; // spec
3355             case ALST_WALK: if(ci->state != CS_EDITING) return false;
3356             case ALST_EDIT: // edit on/off
3357             {
3358                 if(ci->quarantine || (ci->state == CS_SPECTATOR && numclients(ci->clientnum, true) >= G(serverclients)) || ci->actortype != A_PLAYER || !m_edit(gamemode)) return false;
3359                 if(mastermode >= MM_LOCKED && ip && !checkipinfo(control, ipinfo::ALLOW, ip) && !haspriv(ci, lock, "edit")) return false;
3360                 break;
3361             }
3362             default: break;
3363         }
3364         return true;
3365     }
3366 
sendstats(bool fromintermission)3367     void sendstats(bool fromintermission)
3368     {
3369         if(G(serverstats) && auth::hasstats && !sentstats && gamemillis)
3370         {
3371             loopv(clients) if(clients[i]->actortype == A_PLAYER) savestatsscore(clients[i]);
3372             bool worthy = false;
3373             if(fromintermission) worthy = true;
3374             else if(m_ra_timed(gamemode, mutators))
3375             {
3376                 loopv(savedstatsscores) if(savedstatsscores[i].actortype == A_PLAYER) if(savedstatsscores[i].cptime > 0)
3377                 {
3378                     worthy = true;
3379                     break;
3380                 }
3381             }
3382             if(!worthy) return;
3383 
3384             loopv(clients)
3385             {
3386                 clients[i]->localtotalpoints -= clients[i]->points;
3387                 clients[i]->localtotalfrags -= clients[i]->frags;
3388                 clients[i]->localtotaldeaths -= clients[i]->deaths;
3389             }
3390 
3391             sentstats = true;
3392             requestmasterf("stats begin\n");
3393             int unique = 0;
3394             vector<uint> seen;
3395             loopv(savedstatsscores) if(savedstatsscores[i].actortype == A_PLAYER)
3396             {
3397                 if((gamemillis / 1000 / 25) >= savedstatsscores[i].timeactive) continue;
3398                 if(savedstatsscores[i].handle[0])
3399                 {
3400                     seen.add(savedstatsscores[i].ip);
3401                     unique += 1;
3402                 }
3403                 else
3404                 {
3405                     bool inseen = false;
3406                     loopvj(seen) if(seen[j] == savedstatsscores[i].ip) inseen = true;
3407                     if(!inseen)
3408                     {
3409                         seen.add(savedstatsscores[i].ip);
3410                         unique += 1;
3411                     }
3412                 }
3413             }
3414             requestmasterf("stats game %s %d %d %d %d %d\n", escapestring(smapname), gamemode, mutators, gamemillis/1000, unique, m_normweaps(gamemode, mutators) ? 1 : 0);
3415             flushmasteroutput();
3416             requestmasterf("stats server %s %s %d\n", escapestring(limitstring(G(serverdesc), MAXSDESCLEN+1)), versionstring, serverport);
3417             flushmasteroutput();
3418             loopi(numteams(gamemode, mutators))
3419             {
3420                 int tp = m_team(gamemode, mutators) ? T_FIRST : T_NEUTRAL;
3421                 requestmasterf("stats team %d %d %s\n", i + tp, teamscore(i + tp).total, escapestring(TEAM(i + tp, name)));
3422                 flushmasteroutput();
3423             }
3424             loopv(savedstatsscores) if(savedstatsscores[i].actortype == A_PLAYER && (savedstatsscores[i].timealive > 0 || savedstatsscores[i].timeactive > 0))
3425             {
3426                 requestmasterf("stats player %s %s %d %d %d %d %d %d\n",
3427                     escapestring(savedstatsscores[i].name), escapestring(savedstatsscores[i].handle),
3428                     m_ra_timed(gamemode, mutators) ? savedstatsscores[i].cptime : savedstatsscores[i].points,
3429                     savedstatsscores[i].timealive, savedstatsscores[i].frags, savedstatsscores[i].deaths, i,
3430                     savedstatsscores[i].timeactive
3431                 );
3432                 flushmasteroutput();
3433                 loopj(W_MAX)
3434                 {
3435                     weaponstats w = savedstatsscores[i].weapstats[j];
3436                     if (w.timewielded == 0 && w.timeloadout == 0) continue;
3437                     requestmasterf("stats weapon %d %s %s %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
3438                         i, escapestring(savedstatsscores[i].handle), weaptype[j].name, w.timewielded, w.timeloadout,
3439                         w.damage1, w.frags1, w.hits1, w.flakhits1, w.shots1, w.flakshots1,
3440                         w.damage2, w.frags2, w.hits2, w.flakhits2, w.shots2, w.flakshots2
3441                     );
3442                     flushmasteroutput();
3443                 }
3444                 loopvj(savedstatsscores[i].captures)
3445                 {
3446                     requestmasterf("stats capture %d %s %d %d\n",
3447                         i, escapestring(savedstatsscores[i].handle),
3448                         savedstatsscores[i].captures[j].capturing, savedstatsscores[i].captures[j].captured);
3449                     flushmasteroutput();
3450                 }
3451                 loopvj(savedstatsscores[i].bombings)
3452                 {
3453                     requestmasterf("stats bombing %d %s %d %d\n",
3454                         i, escapestring(savedstatsscores[i].handle),
3455                         savedstatsscores[i].bombings[j].bombing, savedstatsscores[i].bombings[j].bombed);
3456                     flushmasteroutput();
3457                 }
3458                 loopvj(savedstatsscores[i].ffarounds)
3459                 {
3460                     requestmasterf("stats ffaround %d %s %d %d\n",
3461                         i, escapestring(savedstatsscores[i].handle),
3462                         savedstatsscores[i].ffarounds[j].round, (int)savedstatsscores[i].ffarounds[j].winner);
3463                     flushmasteroutput();
3464                 }
3465             }
3466             requestmasterf("stats end\n");
3467             flushmasteroutput();
3468         }
3469     }
3470 
3471     #include "capturemode.h"
3472     #include "defendmode.h"
3473     #include "bombermode.h"
3474     #include "duelmut.h"
3475     #include "aiman.h"
3476 
changemap(const char * name,int mode,int muts)3477     void changemap(const char *name, int mode, int muts)
3478     {
3479         hasgameinfo = shouldcheckvotes = firstblood = sentstats = false;
3480         mapgameinfo = -1;
3481         smapvariant = G(forcemapvariant) ? G(forcemapvariant) : (m_edit(mode) ? MPV_DEF : 1+rnd(MPV_MAX-1));
3482         stopdemo();
3483         resetmapdata();
3484         changemode(gamemode = mode, mutators = muts);
3485         curbalance = nextbalance = lastteambalance = nextteambalance = lastavgposcalc = gamemillis = 0;
3486         gamestate = G_S_WAITING;
3487         gamewaittime = 0;
3488         bool hastime = m_play(gamemode) && m_mmvar(gamemode, mutators, timelimit);
3489         oldtimelimit = hastime ? m_mmvar(gamemode, mutators, timelimit) : -1;
3490         timeremaining = hastime ? m_mmvar(gamemode, mutators, timelimit)*60 : -1;
3491         gamelimit = hastime ? timeremaining*1000 : 0;
3492         loopv(savedscores) savedscores[i].mapchange();
3493         loopv(savedstatsscores) savedstatsscores[i].mapchange();
3494         setuptriggers(false);
3495         setupspawns(false);
3496         if(smode) smode->reset();
3497         mutate(smuts, mut->reset());
3498         smode = NULL;
3499         smuts.shrink(0);
3500         sents.shrink(0);
3501         scores.shrink(0);
3502         aiman::clearai();
3503         aiman::poke();
3504         const char *reqmap = name && *name && strcmp(name, "<random>") ? name : pickmap(NULL, gamemode, mutators);
3505         if(servercheck(reqmap && *reqmap))
3506         {
3507             loopi(SENDMAP_MAX)
3508             {
3509                 defformatstring(reqfile, strstr(reqmap, "maps/") == reqmap || strstr(reqmap, "maps\\") == reqmap ? "%s.%s" : "maps/%s.%s", reqmap, sendmaptypes[i]);
3510                 if(i == SENDMAP_MPZ) smapcrc = crcfile(reqfile);
3511                 mapdata[i] = openfile(reqfile, "rb");
3512             }
3513             if(!hasmapdata()) resetmapdata();
3514         }
3515         copystring(smapname, reqmap);
3516         sendf(-1, 1, "risi4", N_MAPCHANGE, smapname, gamemode, mutators, hasmapdata() ? smapcrc : -1, smapvariant);
3517 
3518         // server modes
3519         if(m_capture(gamemode)) smode = &capturemode;
3520         else if(m_defend(gamemode)) smode = &defendmode;
3521         else if(m_bomber(gamemode)) smode = &bombermode;
3522         smuts.add(&spawnmutator);
3523         if(m_duke(gamemode, mutators)) smuts.add(&duelmutator);
3524         if(m_vampire(gamemode, mutators)) smuts.add(&vampiremutator);
3525         if(smode) smode->reset();
3526         mutate(smuts, mut->reset());
3527 
3528         if(m_local(gamemode)) kicknonlocalclients(DISC_PRIVATE);
3529 
3530         loopv(clients)
3531         {
3532             clients[i]->mapchange(true);
3533             spectator(clients[i]);
3534         }
3535 
3536         if(!demoplayback && m_play(gamemode) && numclients())
3537         {
3538             vector<char> buf;
3539             buf.put(smapname, strlen(smapname));
3540             if(*sv_previousmaps && G(maphistory))
3541             {
3542                 vector<char *> prev;
3543                 explodelist(sv_previousmaps, prev);
3544                 loopvrev(prev) if(!strcmp(prev[i], smapname))
3545                 {
3546                     delete[] prev[i];
3547                     prev.remove(i);
3548                 }
3549                 while(prev.length() >= G(maphistory))
3550                 {
3551                     int last = prev.length()-1;
3552                     delete[] prev[last];
3553                     prev.remove(last);
3554                 }
3555                 loopv(prev)
3556                 {
3557                     buf.add(' ');
3558                     buf.put(prev[i], strlen(prev[i]));
3559                 }
3560                 prev.deletearrays();
3561             }
3562             buf.add(0);
3563             const char *str = buf.getbuf();
3564             if(*str) setmods(sv_previousmaps, str);
3565         }
3566         else setmods(sv_previousmaps, "");
3567 
3568         setupattrmap();
3569         if(numclients())
3570         {
3571             sendtick();
3572             if(m_demo(gamemode)) setupdemoplayback();
3573             else if(demonextmatch) setupdemorecord();
3574         }
3575     }
3576 
checkvar(ident * id,const char * arg)3577     void checkvar(ident *id, const char *arg)
3578     {
3579         if(id && id->flags&IDF_SERVER && id->flags&IDF_GAMEMOD) switch(id->type)
3580         {
3581             case ID_VAR:
3582             {
3583                 int ret = parseint(arg);
3584                 if(*id->storage.i == id->bin.i) { if(ret != id->bin.i) numgamemods++; }
3585                 else if(ret == id->bin.i) numgamemods--;
3586                 break;
3587             }
3588             case ID_FVAR:
3589             {
3590                 int ret = parsefloat(arg);
3591                 if(*id->storage.f == id->bin.f) { if(ret != id->bin.f) numgamemods++; }
3592                 else if(ret == id->bin.f) numgamemods--;
3593                 break;
3594             }
3595             case ID_SVAR:
3596             {
3597                 if(!strcmp(*id->storage.s, id->bin.s)) { if(strcmp(arg, id->bin.s)) numgamemods++; }
3598                 else if(!strcmp(arg, id->bin.s)) numgamemods--;
3599                 break;
3600             }
3601             default: break;
3602         }
3603     }
3604 
servcmd(int nargs,const char * cmd,const char * arg)3605     bool servcmd(int nargs, const char *cmd, const char *arg)
3606     { // incoming commands
3607 #ifndef STANDALONE
3608         if(::connected(false, false)) return false;
3609 #endif
3610         ident *id = idents.access(cmd);
3611         if(id && id->flags&IDF_SERVER)
3612         {
3613             const char *val = NULL;
3614             switch(id->type)
3615             {
3616                 case ID_COMMAND:
3617                 {
3618                     int slen = strlen(id->name);
3619                     if(arg && nargs > 1) slen += strlen(arg)+1;
3620                     char *s = newstring(slen);
3621                     if(nargs <= 1 || !arg) nformatstring(s, slen+1, "%s", id->name);
3622                     else nformatstring(s, slen+1, "%s %s", id->name, arg);
3623                     char *ret = executestr(s);
3624                     conoutft(CON_DEBUG, "\fy\fs\fc%s\fS returned \fs\fc%s\fS", id->name, ret && *ret ? ret : "failed");
3625                     delete[] s;
3626                     delete[] ret;
3627                     return true;
3628                 }
3629                 case ID_VAR:
3630                 {
3631                     if(nargs <= 1 || !arg)
3632                     {
3633                         conoutft(CON_DEBUG, id->flags&IDF_HEX && *id->storage.i >= 0 ? (id->maxval == 0xFFFFFF ? "\fy%s = 0x%.6X" : (uint(id->maxval) == 0xFFFFFFFFU ? "\fy%s = 0x%.8X" : "\fy%s = 0x%X")) : "\fy%s = %d", id->name, id->flags&IDF_HEX && uint(id->maxval) == 0xFFFFFFFFU ? uint(*id->storage.i) : *id->storage.i);
3634                         return true;
3635                     }
3636                     if(id->flags&IDF_READONLY)
3637                     {
3638                         conoutft(CON_DEBUG, "\frCannot override variable: %s", id->name);
3639                         return true;
3640                     }
3641                     int ret = parseint(arg);
3642                     if(id->flags&IDF_HEX && uint(id->maxval) == 0xFFFFFFFFU)
3643                     {
3644                         if(uint(ret) < uint(id->minval) || uint(ret) > uint(id->maxval))
3645                         {
3646                             conoutft(CON_DEBUG, "\frValid range for %s is 0x%X..0x%X", id->name, uint(id->minval), uint(id->maxval));
3647                             return true;
3648                         }
3649                     }
3650                     else if(ret < id->minval || ret > id->maxval)
3651                     {
3652                         conoutft(CON_DEBUG,
3653                             id->flags&IDF_HEX ?
3654                                     (id->minval <= 255 ? "\frValid range for %s is %d..0x%X" : "\frValid range for %s is 0x%X..0x%X") :
3655                                     "\frValid range for %s is %d..%d", id->name, id->minval, id->maxval);
3656                         return true;
3657                     }
3658                     if(versioning)
3659                     {
3660                         id->def.i = ret;
3661                         if(versioning == 2) id->bin.i = ret;
3662                     }
3663                     checkvar(id, arg);
3664                     *id->storage.i = ret;
3665                     id->changed();
3666 #ifndef STANDALONE
3667                     if(versioning) setvar(&id->name[3], ret, true);
3668 #endif
3669                     val = intstr(id);
3670                     break;
3671                 }
3672                 case ID_FVAR:
3673                 {
3674                     if(nargs <= 1 || !arg)
3675                     {
3676                         conoutft(CON_DEBUG, "\fy%s = %s", id->name, floatstr(*id->storage.f));
3677                         return true;
3678                     }
3679                     if(id->maxvalf < id->minvalf || id->flags&IDF_READONLY)
3680                     {
3681                         conoutft(CON_DEBUG, "\frCannot override variable: %s", id->name);
3682                         return true;
3683                     }
3684                     float ret = parsefloat(arg);
3685                     if(ret < id->minvalf || ret > id->maxvalf)
3686                     {
3687                         conoutft(CON_DEBUG, "\frValid range for %s is %s..%s", id->name, floatstr(id->minvalf), floatstr(id->maxvalf));
3688                         return true;
3689                     }
3690                     if(versioning)
3691                     {
3692                         id->def.f = ret;
3693                         if(versioning == 2) id->bin.f = ret;
3694                     }
3695                     checkvar(id, arg);
3696                     *id->storage.f = ret;
3697                     id->changed();
3698 #ifndef STANDALONE
3699                     if(versioning) setfvar(&id->name[3], ret, true);
3700 #endif
3701                     val = floatstr(*id->storage.f);
3702                     break;
3703                 }
3704                 case ID_SVAR:
3705                 {
3706                     if(nargs <= 1 || !arg)
3707                     {
3708                         conoutft(CON_DEBUG, strchr(*id->storage.s, '"') ? "\fy%s = [%s]" : "\fy%s = \"%s\"", id->name, *id->storage.s);
3709                         return true;
3710                     }
3711                     if(id->flags&IDF_READONLY)
3712                     {
3713                         conoutft(CON_DEBUG, "\frCannot override variable: %s", id->name);
3714                         return true;
3715                     }
3716                     if(versioning)
3717                     {
3718                         delete[] id->def.s;
3719                         id->def.s = newstring(arg);
3720                         if(versioning == 2)
3721                         {
3722                             delete[] id->bin.s;
3723                             id->bin.s = newstring(arg);
3724                         }
3725                     }
3726                     checkvar(id, arg);
3727                     delete[] *id->storage.s;
3728                     *id->storage.s = newstring(arg);
3729                     id->changed();
3730 #ifndef STANDALONE
3731                     if(versioning) setsvar(&id->name[3], arg, true);
3732 #endif
3733                     val = *id->storage.s;
3734                     break;
3735                 }
3736                 default: return false;
3737             }
3738             if(val)
3739             {
3740                 sendf(-1, 1, "ri2sis", N_COMMAND, -1, &id->name[3], strlen(val), val);
3741                 arg = val;
3742             }
3743             return true;
3744         }
3745         return false; // parse will spit out "unknown command" in this case
3746     }
3747 
parsecommand(clientinfo * ci,int nargs,const char * cmd,const char * arg)3748     void parsecommand(clientinfo *ci, int nargs, const char *cmd, const char *arg)
3749     { // incoming commands from clients
3750         defformatstring(cmdname, "sv_%s", cmd);
3751         ident *id = idents.access(cmdname);
3752         if(id && id->flags&IDF_SERVER)
3753         {
3754             const char *name = &id->name[3], *val = NULL, *oldval = NULL;
3755             bool needfreeoldval = false;
3756             int locked = clamp(id->level, max(G(varslock), 0), int(PRIV_CREATOR));
3757             if(id->type == ID_VAR)
3758             {
3759                 int len = strlen(id->name);
3760                 if(len > 4 && !strcmp(&id->name[len-4], "lock"))
3761                     locked = min(max(max(*id->storage.i, parseint(arg)), locked), int(PRIV_CREATOR));
3762             }
3763 #ifndef STANDALONE
3764             if(servertype < 3 && (!strcmp(id->name, "sv_gamespeed") || !strcmp(id->name, "sv_gamepaused"))) locked = PRIV_MAX;
3765 #endif
3766             switch(id->type)
3767             {
3768                 case ID_COMMAND:
3769                 {
3770                     if(locked && !haspriv(ci, locked, "execute that command")) return;
3771                     int slen = strlen(id->name);
3772                     if(arg && nargs > 1) slen += strlen(arg)+1;
3773                     char *s = newstring(slen);
3774                     if(nargs <= 1 || !arg) nformatstring(s, slen+1, "%s", id->name);
3775                     else nformatstring(s, slen+1, "%s %s", id->name, arg);
3776                     char *ret = executestr(s);
3777                     srvoutf(3, "\fy%s executed \fs\fc%s\fS (returned: \fs\fc%s\fS)", colourname(ci), name, ret && * ret ? ret : "failed");
3778                     delete[] s;
3779                     delete[] ret;
3780                     return;
3781                 }
3782                 case ID_VAR:
3783                 {
3784                     if(nargs <= 1 || !arg)
3785                     {
3786                         srvmsgft(ci->clientnum, CON_DEBUG, id->flags&IDF_HEX && *id->storage.i >= 0 ? (id->maxval == 0xFFFFFF ? "\fy%s = 0x%.6X" : (uint(id->maxval) == 0xFFFFFFFFU ? "\fy%s = 0x%.8X" : "\fy%s = 0x%X")) : "\fy%s = %d", name, id->flags&IDF_HEX && uint(id->maxval) == 0xFFFFFFFFU ? uint(*id->storage.i) : *id->storage.i);
3787                         return;
3788                     }
3789                     else if(locked && !haspriv(ci, locked, "change that variable"))
3790                     {
3791                         val = intstr(id);
3792                         sendf(ci->clientnum, 1, "ri2sis", N_COMMAND, -1, name, strlen(val), val);
3793                         return;
3794                     }
3795                     if(id->flags&IDF_READONLY)
3796                     {
3797                         srvmsgft(ci->clientnum, CON_DEBUG, "\frCannot override variable: %s", name);
3798                         return;
3799                     }
3800                     int ret = parseint(arg);
3801                     if(id->flags&IDF_HEX && uint(id->maxval) == 0xFFFFFFFFU)
3802                     {
3803                         if(uint(ret) < uint(id->minval) || uint(ret) > uint(id->maxval))
3804                         {
3805                             srvmsgf(ci->clientnum, "\frValid range for %s is 0x%X..0x%X", id->name, uint(id->minval), uint(id->maxval));
3806                             return;
3807                         }
3808                     }
3809                     else if(ret < id->minval || ret > id->maxval)
3810                     {
3811                         srvmsgf(ci->clientnum,
3812                             id->flags&IDF_HEX ?
3813                                 (id->minval <= 255 ? "\frValid range for %s is %d..0x%X" : "\frValid range for %s is 0x%X..0x%X") :
3814                                 "\frValid range for %s is %d..%d", name, id->minval, id->maxval);
3815                         return;
3816                     }
3817                     checkvar(id, arg);
3818                     oldval = intstr(id);
3819                     *id->storage.i = ret;
3820                     id->changed();
3821                     val = intstr(id);
3822                     break;
3823                 }
3824                 case ID_FVAR:
3825                 {
3826                     if(nargs <= 1 || !arg)
3827                     {
3828                         srvmsgft(ci->clientnum, CON_DEBUG, "\fy%s = %s", name, floatstr(*id->storage.f));
3829                         return;
3830                     }
3831                     else if(locked && !haspriv(ci, locked, "change that variable"))
3832                     {
3833                         val = floatstr(*id->storage.f);
3834                         sendf(ci->clientnum, 1, "ri2sis", N_COMMAND, -1, name, strlen(val), val);
3835                         return;
3836                     }
3837                     if(id->maxvalf < id->minvalf || id->flags&IDF_READONLY)
3838                     {
3839                         srvmsgft(ci->clientnum, CON_DEBUG, "\frCannot override variable: %s", name);
3840                         return;
3841                     }
3842                     float ret = parsefloat(arg);
3843                     if(ret < id->minvalf || ret > id->maxvalf)
3844                     {
3845                         srvmsgft(ci->clientnum, CON_DEBUG, "\frValid range for %s is %s..%s", name, floatstr(id->minvalf), floatstr(id->maxvalf));
3846                         return;
3847                     }
3848                     checkvar(id, arg);
3849                     oldval = floatstr(*id->storage.f);
3850                     *id->storage.f = ret;
3851                     id->changed();
3852                     val = floatstr(*id->storage.f);
3853                     break;
3854                 }
3855                 case ID_SVAR:
3856                 {
3857                     if(nargs <= 1 || !arg)
3858                     {
3859                         srvmsgft(ci->clientnum, CON_DEBUG, strchr(*id->storage.s, '"') ? "\fy%s = [%s]" : "\fy%s = \"%s\"", name, *id->storage.s);
3860                         return;
3861                     }
3862                     else if(locked && !haspriv(ci, locked, "change that variable"))
3863                     {
3864                         val = *id->storage.s;
3865                         sendf(ci->clientnum, 1, "ri2sis", N_COMMAND, -1, name, strlen(val), val);
3866                         return;
3867                     }
3868                     if(id->flags&IDF_READONLY)
3869                     {
3870                         srvmsgft(ci->clientnum, CON_DEBUG, "\frCannot override variable: %s", name);
3871                         return;
3872                     }
3873                     checkvar(id, arg);
3874                     oldval = newstring(*id->storage.s);
3875                     needfreeoldval = true;
3876                     delete[] *id->storage.s;
3877                     *id->storage.s = newstring(arg);
3878                     id->changed();
3879                     val = *id->storage.s;
3880                     break;
3881                 }
3882                 default: return;
3883             }
3884             if(val)
3885             {
3886                 sendf(-1, 1, "ri2sis", N_COMMAND, ci->clientnum, name, strlen(val), val);
3887                 if(oldval)
3888                 {
3889                     relayf(3, "\fy%s set %s to %s (was: %s)", colourname(ci), name, val, oldval);
3890                     if(needfreeoldval) delete[] oldval;
3891                 }
3892                 else relayf(3, "\fy%s set %s to %s", colourname(ci), name, val);
3893             }
3894         }
3895         else srvmsgft(ci->clientnum, CON_DEBUG, "\frUnknown command: %s", cmd);
3896     }
3897 
rewritecommand(ident * id,tagval * args,int numargs)3898     bool rewritecommand(ident *id, tagval *args, int numargs)
3899     {
3900         bool found = false;
3901         const char *argstr = numargs > 2 ? conc(&args[1], numargs-1, true) : (numargs > 1 ? args[1].getstr() : "");
3902         if(id && id->flags&IDF_WORLD && identflags&IDF_WORLD) found = true;
3903         else if(id && id->flags&IDF_SERVER && id->type != ID_COMMAND) found = servcmd(numargs, args[0].getstr(), argstr);
3904 #ifndef STANDALONE
3905         else if(!id || id->flags&IDF_CLIENT) found = client::sendcmd(numargs, args[0].getstr(), argstr);
3906 #endif
3907         if(numargs > 2) delete[] (char *)argstr;
3908         return found;
3909     }
3910 
sendservinit(clientinfo * ci)3911     void sendservinit(clientinfo *ci)
3912     {
3913         int flags = 0;
3914         if(cdpi::steam::serverauthmode()) flags |= SS_F_STEAMAUTH;
3915         sendf(ci->clientnum, 1, "ri3si2", N_SERVERINIT, ci->clientnum, VERSION_GAME, gethostip(ci->clientnum), ci->sessionid, flags);
3916     }
3917 
restorescore(clientinfo * ci)3918     bool restorescore(clientinfo *ci)
3919     {
3920         savedscore *sc = findscore(savedscores, ci, false);
3921         if(sc)
3922         {
3923             sc->restore(ci);
3924             if(ci->actortype == A_PLAYER && m_dm(gamemode) && m_team(gamemode, mutators) && !m_nopoints(gamemode, mutators) && G(teamkillrestore) && canplay())
3925             {
3926                 int restorepoints[T_MAX] = {0};
3927                 loopv(ci->teamkills) restorepoints[ci->teamkills[i].team] += ci->teamkills[i].points;
3928                 loopi(T_MAX) if(restorepoints[i] >= G(teamkillrestore))
3929                 {
3930                     score &ts = teamscore(i);
3931                     ts.total -= restorepoints[i];
3932                     sendf(-1, 1, "ri3", N_SCORE, ts.team, ts.total);
3933                 }
3934             }
3935             return true;
3936         }
3937         return false;
3938     }
3939 
sendresume(clientinfo * ci,bool reset=false)3940     void sendresume(clientinfo *ci, bool reset = false)
3941     {
3942         int target = -1, state = ci->state;
3943         if(reset)
3944         {
3945             if(ci->state != CS_ALIVE || ci->needsresume) return; // useless / waiting for ack
3946             target = ci->clientnum;
3947             state = -1;
3948             ci->needsresume = true;
3949             ci->weapreset(false);
3950         }
3951         ci->updatetimeplayed();
3952         sendf(target, 1, "ri9fi4vvi", N_RESUME, ci->clientnum, state, ci->points, ci->frags, ci->deaths, ci->totalpoints, ci->totalfrags, ci->totaldeaths, ci->totalavgpos, ci->timeplayed, ci->health, ci->cptime, ci->weapselect, W_MAX*W_A_MAX, &ci->weapammo[0][0], W_MAX, &ci->weapent[0], -1);
3953     }
3954 
putinitclient(clientinfo * ci,packetbuf & p,bool allow)3955     void putinitclient(clientinfo *ci, packetbuf &p, bool allow)
3956     {
3957         if(ci->actortype > A_PLAYER)
3958         {
3959             if(ci->ownernum >= 0)
3960             {
3961                 putint(p, N_INITAI);
3962                 putint(p, ci->clientnum);
3963                 putint(p, ci->ownernum);
3964                 putint(p, ci->actortype);
3965                 putint(p, ci->spawnpoint);
3966                 putint(p, ci->skill);
3967                 sendstring(ci->name, p);
3968                 putint(p, ci->team);
3969                 putint(p, ci->colour);
3970                 putint(p, ci->model);
3971                 putint(p, ci->pattern);
3972                 sendstring(ci->vanity, p);
3973                 putint(p, ci->loadweap.length());
3974                 loopv(ci->loadweap) putint(p, ci->loadweap[i]);
3975             }
3976         }
3977         else
3978         {
3979             putint(p, N_CLIENTINIT);
3980             putint(p, ci->clientnum);
3981             putint(p, ci->colour);
3982             putint(p, ci->model);
3983             putint(p, ci->pattern);
3984             putint(p, ci->checkpointspawn);
3985             putint(p, ci->team);
3986             putint(p, ci->privilege);
3987             sendstring(ci->name, p);
3988             sendstring(ci->vanity, p);
3989             putint(p, ci->loadweap.length());
3990             loopv(ci->loadweap) putint(p, ci->loadweap[i]);
3991             putint(p, ci->randweap.length());
3992             loopv(ci->randweap) putint(p, ci->randweap[i]);
3993             sendstring(ci->handle, p);
3994             sendstring(ci->steamid, p);
3995             sendstring(allow ? gethostip(ci->clientnum) : "*", p);
3996             ci->version.put(p);
3997         }
3998     }
3999 
sendinitclient(clientinfo * ci)4000     void sendinitclient(clientinfo *ci)
4001     {
4002         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE), q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
4003         putinitclient(ci, p, true);
4004         p.finalize();
4005         putinitclient(ci, q, false);
4006         q.finalize();
4007         loopv(clients) if(clients[i] != ci && allowbroadcast(clients[i]->clientnum))
4008             sendpacket(clients[i]->clientnum, 1, haspriv(clients[i], G(iphostlock)) ? p.packet : q.packet);
4009         sendpacket(-1, -1, q.packet); // anonymous packet just for recording
4010     }
4011 
sendinitclientself(clientinfo * ci)4012     void sendinitclientself(clientinfo *ci)
4013     {
4014         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
4015         putinitclient(ci, p, true);
4016         sendpacket(ci->clientnum, 1, p.finalize());
4017     }
4018 
welcomeinitclient(clientinfo * ci,packetbuf & p,int exclude,bool nobots)4019     void welcomeinitclient(clientinfo *ci, packetbuf &p, int exclude, bool nobots)
4020     {
4021         bool iph = ci ? haspriv(ci, G(iphostlock)) : false;
4022         loopv(clients)
4023         {
4024             clientinfo *cp = clients[i];
4025             if(!cp->connected || cp->clientnum == exclude || (nobots && cp->actortype != A_PLAYER)) continue;
4026             putinitclient(cp, p, iph);
4027         }
4028     }
4029 
sendwelcome(clientinfo * ci)4030     void sendwelcome(clientinfo *ci)
4031     {
4032         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
4033         int chan = welcomepacket(p, ci);
4034         sendpacket(ci->clientnum, chan, p.finalize());
4035     }
4036 
welcomepacket(packetbuf & p,clientinfo * ci)4037     int welcomepacket(packetbuf &p, clientinfo *ci)
4038     {
4039         putint(p, N_WELCOME);
4040         putint(p, mastermode);
4041         putint(p, N_MAPCHANGE);
4042         sendstring(smapname, p);
4043         putint(p, gamemode);
4044         putint(p, mutators);
4045         if(ci && !ci->online && m_edit(gamemode))
4046         {
4047             if(numclients(ci->clientnum))
4048             {
4049                 if(mapsending < 0) resetmapdata();
4050                 getmap(ci);
4051                 putint(p, -2); // start with an empty map and wait for it
4052             }
4053             else putint(p, -1); // start with an empty map and use it
4054         }
4055         else putint(p, smapcrc);
4056         putint(p, smapvariant);
4057 
4058         enumerate(idents, ident, id,
4059         {
4060             if(id.flags&IDF_SERVER && !(id.flags&IDF_WORLD)) // reset vars
4061             {
4062                 const char *val = NULL;
4063                 switch(id.type)
4064                 {
4065                     case ID_VAR:
4066                     {
4067                         val = intstr(&id);
4068                         break;
4069                     }
4070                     case ID_FVAR:
4071                     {
4072                         val = floatstr(*id.storage.f);
4073                         break;
4074                     }
4075                     case ID_SVAR:
4076                     {
4077                         val = *id.storage.s;
4078                         break;
4079                     }
4080                     default: break;
4081                 }
4082                 if(val)
4083                 {
4084                     putint(p, N_COMMAND);
4085                     putint(p, -1);
4086                     sendstring(&id.name[3], p);
4087                     putint(p, strlen(val));
4088                     sendstring(val, p);
4089                 }
4090             }
4091         });
4092 
4093         if(!ci || numclients())
4094         {
4095             putint(p, N_TICK);
4096             putint(p, gamestate);
4097             putint(p, timeleft());
4098         }
4099 
4100         if(hasgameinfo)
4101         {
4102             putint(p, N_GAMEINFO);
4103             loopv(sents) if(enttype[sents[i].type].resyncs)
4104             {
4105                 putint(p, i);
4106                 if(enttype[sents[i].type].usetype == EU_ITEM) putint(p, finditem(i) ? 1 : 0);
4107                 else putint(p, sents[i].spawned ? 1 : 0);
4108             }
4109             putint(p, -1);
4110         }
4111         putint(p, N_ATTRMAP);
4112         loopi(W_MAX) putint(p, attrmap[i]);
4113 
4114         if(ci)
4115         {
4116             ci->state = CS_SPECTATOR;
4117             ci->team = T_NEUTRAL;
4118             putint(p, N_SPECTATOR);
4119             putint(p, ci->clientnum);
4120             putint(p, ci->quarantine ? 2 : 1);
4121             sendf(-1, 1, "ri3x", N_SPECTATOR, ci->clientnum, ci->quarantine ? 2 : 1, ci->clientnum);
4122             putint(p, N_SETTEAM);
4123             putint(p, ci->clientnum);
4124             putint(p, ci->team);
4125         }
4126         if(!ci || clients.length() > 1)
4127         {
4128             putint(p, N_RESUME);
4129             loopv(clients)
4130             {
4131                 clientinfo *oi = clients[i];
4132                 if(ci && oi->clientnum == ci->clientnum) continue;
4133                 putint(p, oi->clientnum);
4134                 sendstate(oi, p);
4135             }
4136             putint(p, -1);
4137             welcomeinitclient(ci, p, ci ? ci->clientnum : -1);
4138             loopv(clients)
4139             {
4140                 clientinfo *oi = clients[i];
4141                 if(oi->actortype > A_PLAYER || (ci && oi->clientnum == ci->clientnum)) continue;
4142                 if(*oi->mapvote)
4143                 {
4144                     putint(p, N_MAPVOTE);
4145                     putint(p, oi->clientnum);
4146                     sendstring(oi->mapvote, p);
4147                     putint(p, oi->modevote);
4148                     putint(p, oi->mutsvote);
4149                 }
4150             }
4151         }
4152 
4153         if(m_team(gamemode, mutators)) loopv(scores)
4154         {
4155             score &cs = scores[i];
4156             putint(p, N_SCORE);
4157             putint(p, cs.team);
4158             putint(p, cs.total);
4159         }
4160 
4161         if(smode) smode->initclient(ci, p, true);
4162         mutate(smuts, mut->initclient(ci, p, true));
4163 
4164         if(ci && !ci->online && *G(servermotd))
4165         {
4166             putint(p, N_ANNOUNCE);
4167             putint(p, S_ACTION);
4168             putint(p, CON_MESG);
4169             sendstring(G(servermotd), p);
4170         }
4171 
4172         if(ci) ci->online = true;
4173         return 1;
4174     }
4175 
clearevent(clientinfo * ci)4176     void clearevent(clientinfo *ci) { delete ci->events.remove(0); }
4177 
addhistory(clientinfo * m,clientinfo * v,int millis)4178     void addhistory(clientinfo *m, clientinfo *v, int millis)
4179     {
4180         bool found = false;
4181         loopv(m->damagelog) if (m->damagelog[i].clientnum == v->clientnum)
4182         {
4183             m->damagelog[i].millis = millis;
4184             found = true;
4185             break;
4186         }
4187         if(!found) m->damagelog.add(dmghist(v->clientnum, millis));
4188     }
4189 
gethistory(clientinfo * m,clientinfo * v,int millis,vector<int> & log,bool clear=false,int points=0,int lastpoints=0)4190     void gethistory(clientinfo *m, clientinfo *v, int millis, vector<int> &log, bool clear = false, int points = 0, int lastpoints = 0)
4191     {
4192         int last = -1;
4193         if(lastpoints)
4194         {
4195             loopv(m->damagelog) if(m->damagelog[i].clientnum != v->clientnum && millis-m->damagelog[i].millis <= G(lasthitdelay))
4196             {
4197                 if(last < 0 || m->damagelog[i].millis > m->damagelog[last].millis) last = i;
4198             }
4199         }
4200         loopv(m->damagelog) if(m->damagelog[i].clientnum != v->clientnum)
4201         {
4202             clientinfo *assist = (clientinfo *)getinfo(m->damagelog[i].clientnum);
4203             if(assist)
4204             {
4205                 if(millis-m->damagelog[i].millis <= G(assistkilldelay)) log.add(assist->clientnum);
4206                 if(points)
4207                 {
4208                     if(!lastpoints || i != last)
4209                     {
4210                         if(millis-m->damagelog[i].millis <= G(assistkilldelay)) givepoints(assist, points, m_points(gamemode, mutators), true);
4211                     }
4212                     else givepoints(assist, lastpoints, m_points(gamemode, mutators), true);
4213                 }
4214             }
4215         }
4216         if(clear) m->damagelog.shrink(0);
4217     }
4218 
isghost(clientinfo * d,clientinfo * e)4219     bool isghost(clientinfo *d, clientinfo *e)
4220     {
4221         if(!e) return false;
4222         if(d->actortype < A_ENEMY && e->actortype < A_ENEMY && m_ghost(gamemode, mutators)) return true;
4223         switch(d->actortype)
4224         {
4225             case A_PLAYER: if(!(AA(e->actortype, collide)&(1<<A_C_PLAYERS))) return true; break;
4226             case A_BOT: if(!(AA(e->actortype, collide)&(1<<A_C_BOTS))) return true; break;
4227             default: if(!(AA(e->actortype, collide)&(1<<A_C_ENEMIES))) return true; break;
4228         }
4229         if(m_team(gamemode, mutators) && d->team == e->team) switch(d->actortype)
4230         {
4231             case A_PLAYER: if(!(AA(e->actortype, teamdamage)&(1<<A_T_PLAYERS))) return true; break;
4232             case A_BOT: if(!(AA(e->actortype, teamdamage)&(1<<A_T_BOTS))) return true; break;
4233             default: if(!(AA(e->actortype, teamdamage)&(1<<A_T_ENEMIES))) return true; break;
4234         }
4235         return false;
4236     }
4237 
dodamage(clientinfo * m,clientinfo * v,int damage,int weap,int fromweap,int fromflags,int flags,int material,const ivec & hitpush=ivec (0,0,0),const ivec & hitvel=ivec (0,0,0),float dist=0,bool first=true)4238     void dodamage(clientinfo *m, clientinfo *v, int damage, int weap, int fromweap, int fromflags, int flags, int material, const ivec &hitpush = ivec(0, 0, 0), const ivec &hitvel = ivec(0, 0, 0), float dist = 0, bool first = true)
4239     {
4240         int realdamage = damage, realflags = flags, nodamage = 0, hurt = 0, statweap = fromweap, statalt = WS(fromflags);
4241         realflags &= ~HIT_SFLAGS;
4242         if(realflags&HIT(MATERIAL) && (material&MATF_VOLUME) == MAT_LAVA)
4243         {
4244             realflags |= HIT(BURN);
4245             m->burntime = G(lavaburntime);
4246             m->burndelay = G(lavaburndelay);
4247             m->sendburn();
4248         }
4249 
4250         if(smode && !smode->damage(m, v, realdamage, weap, realflags, material, hitpush, hitvel, dist)) { nodamage++; }
4251         mutate(smuts, if(!mut->damage(m, v, realdamage, weap, realflags, material, hitpush, hitvel, dist)) { nodamage++; });
4252         if(!(realflags&HIT(MATERIAL)) && v->actortype < A_ENEMY)
4253         {
4254             if(v == m && !G(damageself)) nodamage++;
4255             else if(isghost(m, v)) nodamage++;
4256         }
4257 
4258         if(isweap(weap) && WF(WK(flags), weap, residualundo, WS(flags)) != 0)
4259         {
4260             if(WF(WK(flags), weap, residualundo, WS(flags))&WR(BURN) && m->burning(gamemillis, m->burntime))
4261             {
4262                 m->lastres[W_R_BURN] = m->lastrestime[W_R_BURN] = 0;
4263                 sendf(-1, 1, "ri3", N_SPHY, m->clientnum, SPHY_EXTINGUISH);
4264             }
4265             if(WF(WK(flags), weap, residualundo, WS(flags))&WR(BLEED) && m->bleeding(gamemillis, m->bleedtime))
4266                 m->lastres[W_R_BLEED] = m->lastrestime[W_R_BLEED] = 0;
4267             if(WF(WK(flags), weap, residualundo, WS(flags))&WR(SHOCK) && m->shocking(gamemillis, m->shocktime))
4268                 m->lastres[W_R_SHOCK] = m->lastrestime[W_R_SHOCK] = 0;
4269         }
4270 
4271         if(nodamage || !hitdealt(realflags))
4272         {
4273             realflags &= ~HIT_CLEAR;
4274             realflags |= HIT(WAVE);
4275         }
4276         else
4277         {
4278             m->health = min(m->health-realdamage, m->gethealth(gamemode, mutators, true));
4279             if(realdamage > 0)
4280             {
4281                 hurt = min(m->health, realdamage);
4282                 m->lastregen = m->lastregenamt = 0;
4283                 m->lastpain = gamemillis;
4284                 v->damage += realdamage;
4285                 if(m != v && (!m_team(gamemode, mutators) || m->team != v->team))
4286                 {
4287                     if(weap == -1)
4288                     {
4289                         if(flags&HIT(BURN))
4290                         {
4291                             statalt = m->lastresalt[W_R_BURN];
4292                             statweap = m->lastresweapon[W_R_BURN];
4293                             if(isweap(statweap))
4294                             {
4295                                 if(statalt) v->weapstats[statweap].damage2 += realdamage;
4296                                 else v->weapstats[statweap].damage1 += realdamage;
4297                             }
4298                         }
4299                         if(flags&HIT(BLEED))
4300                         {
4301                             statalt = m->lastresalt[W_R_BLEED];
4302                             statweap = m->lastresweapon[W_R_BLEED];
4303                             if(isweap(statweap))
4304                             {
4305                                 if(statalt) v->weapstats[statweap].damage2 += realdamage;
4306                                 else v->weapstats[statweap].damage1 += realdamage;
4307                             }
4308                         }
4309                         if(flags&HIT(SHOCK))
4310                         {
4311                             statalt = m->lastresalt[W_R_SHOCK];
4312                             statweap = m->lastresweapon[W_R_SHOCK];
4313                             if(isweap(statweap))
4314                             {
4315                                 if(statalt) v->weapstats[statweap].damage2 += realdamage;
4316                                 else v->weapstats[statweap].damage1 += realdamage;
4317                             }
4318                         }
4319                     }
4320                     else if(isweap(statweap))
4321                     {
4322                         if(statalt) v->weapstats[statweap].damage2 += realdamage;
4323                         else v->weapstats[statweap].damage1 += realdamage;
4324                     }
4325                 }
4326                 if(m->health <= 0) realflags |= HIT(KILL);
4327                 if(weap >= 0)
4328                 {
4329                     if(wr_burning(weap, flags))
4330                     {
4331                         m->burntime = WF(WK(flags), weap, burntime, WS(flags));
4332                         m->burndelay = WF(WK(flags), weap, burndelay, WS(flags));
4333                         m->burndamage = WF(WK(flags), weap, burndamage, WS(flags));
4334                         m->sendburn();
4335                     }
4336                     if(wr_bleeding(weap, flags))
4337                     {
4338                         m->bleedtime = WF(WK(flags), weap, bleedtime, WS(flags));
4339                         m->bleeddelay = WF(WK(flags), weap, bleeddelay, WS(flags));
4340                         m->bleeddamage = WF(WK(flags), weap, bleeddamage, WS(flags));
4341                         m->sendbleed();
4342                     }
4343                     if(wr_shocking(weap, flags))
4344                     {
4345                         m->shocktime = WF(WK(flags), weap, shocktime, WS(flags));
4346                         m->shockdelay = WF(WK(flags), weap, shockdelay, WS(flags));
4347                         m->shockdamage = WF(WK(flags), weap, shockdamage, WS(flags));
4348                         m->shockstun = WF(WK(flags), weap, shockstun, WS(flags));
4349                         m->shockstunscale = WF(WK(flags), weap, shockstunscale, WS(flags));
4350                         m->shockstunfall = WF(WK(flags), weap, shockstunfall, WS(flags));
4351                         m->shockstuntime = WF(WK(flags), weap, shockstuntime, WS(flags));
4352                         m->sendshock();
4353                     }
4354                 }
4355                 if(wr_burning(weap, flags) && (m->submerged < G(liquidextinguish) || (m->inmaterial&MATF_VOLUME) != MAT_WATER))
4356                 {
4357                     m->lastres[W_R_BURN] = m->lastrestime[W_R_BURN] = gamemillis;
4358                     m->lastresowner[W_R_BURN] = v->clientnum;
4359                     m->lastresweapon[W_R_BURN] = fromweap;
4360                     m->lastresalt[W_R_BURN] = statalt;
4361                 }
4362                 if(wr_bleeding(weap, flags))
4363                 {
4364                     m->lastres[W_R_BLEED] = m->lastrestime[W_R_BLEED] = gamemillis;
4365                     m->lastresowner[W_R_BLEED] = v->clientnum;
4366                     m->lastresweapon[W_R_BLEED] = fromweap;
4367                     m->lastresalt[W_R_BLEED] = statalt;
4368                 }
4369                 if(wr_shocking(weap, flags))
4370                 {
4371                     m->lastres[W_R_SHOCK] = m->lastrestime[W_R_SHOCK] = gamemillis;
4372                     m->lastresowner[W_R_SHOCK] = v->clientnum;
4373                     m->lastresweapon[W_R_SHOCK] = fromweap;
4374                     m->lastresalt[W_R_SHOCK] = statalt;
4375                 }
4376                 if(isweap(statweap) && m != v && (!m_team(gamemode, mutators) || m->team != v->team) && first)
4377                 {
4378                     if(WK(flags))
4379                     {
4380                         if(statalt) v->weapstats[statweap].flakhits2++;
4381                         else v->weapstats[statweap].flakhits1++;
4382                     }
4383                     else
4384                     {
4385                         if(statalt) v->weapstats[statweap].hits2++;
4386                         else v->weapstats[statweap].hits1++;
4387                     }
4388                 }
4389             }
4390         }
4391         if(smode) smode->dodamage(m, v, realdamage, hurt, weap, realflags, material, hitpush, hitvel, dist);
4392         mutate(smuts, mut->dodamage(m, v, realdamage, hurt, weap, realflags, material, hitpush, hitvel, dist));
4393         if(realdamage >= 0 && m != v && (!m_team(gamemode, mutators) || m->team != v->team))
4394             addhistory(m, v, gamemillis);
4395         sendf(-1, 1, "ri9i5", N_DAMAGE, m->clientnum, v->clientnum, weap, realflags, realdamage, m->health, hitpush.x, hitpush.y, hitpush.z, hitvel.x, hitvel.y, hitvel.z, int(dist*DNF));
4396         if(realflags&HIT(KILL))
4397         {
4398             int fragvalue = 1;
4399             if(m != v && (!m_team(gamemode, mutators) || m->team != v->team))
4400             {
4401                 v->frags++;
4402                 v->totalfrags++;
4403                 v->localtotalfrags++;
4404                 if(isweap(statweap))
4405                 {
4406                     if(statalt) v->weapstats[statweap].frags2++;
4407                     else v->weapstats[statweap].frags1++;
4408                 }
4409             }
4410             else fragvalue = -fragvalue;
4411             bool isai = m->actortype >= A_ENEMY, isteamkill = false;
4412             int pointvalue = fragvalue, style = FRAG_NONE;
4413             if(!m_dm_oldschool(gamemode, mutators))
4414                 pointvalue = (smode && !isai ? smode->points(m, v) : fragvalue)*(isai ? G(enemybonus) : G(fragbonus));
4415             if(realdamage >= (realflags&HIT(EXPLODE) ? max(m->gethealth(gamemode, mutators)/2, 1) : m->gethealth(gamemode, mutators)))
4416                 style = FRAG_OBLITERATE;
4417             m->spree = 0;
4418             if(m_team(gamemode, mutators) && v->team == m->team)
4419             {
4420                 v->spree = 0;
4421                 if(isweap(weap) && (v == m || WF(WK(flags), weap, damagepenalty, WS(flags)) != 0))
4422                 {
4423                     if(!m_dm_oldschool(gamemode, mutators)) pointvalue *= G(teamkillpenalty);
4424                     if(v != m) isteamkill = true;
4425                 }
4426                 else pointvalue = 0; // no penalty
4427             }
4428             else if(v != m && v->actortype < A_ENEMY)
4429             {
4430                 if(!firstblood && !m_duel(gamemode, mutators) && ((v->actortype == A_PLAYER && m->actortype < A_ENEMY) || (v->actortype < A_ENEMY && m->actortype == A_PLAYER)))
4431                 {
4432                     firstblood = true;
4433                     style |= FRAG_FIRSTBLOOD;
4434                     if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(firstbloodpoints);
4435                 }
4436                 if(flags&HIT(HEAD)) // NOT HZONE
4437                 {
4438                     style |= FRAG_HEADSHOT;
4439                     if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(headshotpoints);
4440                 }
4441                 if(m->actortype < A_ENEMY)
4442                 {
4443                     int logs = 0;
4444                     v->spree++;
4445                     v->fraglog.add(m->clientnum);
4446                     if(G(multikilldelay))
4447                     {
4448                         logs = 0;
4449                         loopv(v->fragmillis)
4450                         {
4451                             if(gamemillis-v->fragmillis[i] > G(multikilldelay)) v->fragmillis.remove(i--);
4452                             else logs++;
4453                         }
4454                         if(!logs) v->rewards[0] &= ~FRAG_MULTI;
4455                         v->fragmillis.add(gamemillis);
4456                         logs++;
4457                         if(logs >= 2)
4458                         {
4459                             int offset = clamp(logs-2, 0, 2), type = 1<<(FRAG_MKILL+offset); // double, triple, multi..
4460                             if(!(v->rewards[0]&type))
4461                             {
4462                                 style |= type;
4463                                 v->rewards[0] |= type;
4464                                 if(!m_dm_oldschool(gamemode, mutators)) pointvalue += (G(multikillbonus) ? offset+1 : 1)*G(multikillpoints);
4465                             }
4466                         }
4467                     }
4468                     loopj(FRAG_SPREES) if(m->rewards[1]&(1<<(FRAG_SPREE+j)))
4469                     {
4470                         style |= FRAG_BREAKER;
4471                         if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(spreebreaker);
4472                         break;
4473                     }
4474                     if(v->spree <= G(spreecount)*FRAG_SPREES && !(v->spree%G(spreecount)))
4475                     {
4476                         int offset = clamp((v->spree/G(spreecount)), 1, int(FRAG_SPREES))-1, type = 1<<(FRAG_SPREE+offset);
4477                         if(!(v->rewards[0]&type))
4478                         {
4479                             style |= type;
4480                             loopj(2) v->rewards[j] |= type;
4481                             if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(spreepoints);
4482                         }
4483                     }
4484                     logs = 0;
4485                     loopv(m->fraglog) if(m->fraglog[i] == v->clientnum) { logs++; m->fraglog.remove(i--); }
4486                     if(logs >= G(dominatecount))
4487                     {
4488                         style |= FRAG_REVENGE;
4489                         if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(revengepoints);
4490                     }
4491                     logs = 0;
4492                     loopv(v->fraglog) if(v->fraglog[i] == m->clientnum) logs++;
4493                     if(logs == G(dominatecount))
4494                     {
4495                         style |= FRAG_DOMINATE;
4496                         if(!m_dm_oldschool(gamemode, mutators)) pointvalue += G(dominatepoints);
4497                     }
4498                 }
4499             }
4500             if(m->actortype < A_ENEMY && m_race(gamemode) && (!m_ra_gauntlet(gamemode, mutators) || m->team == T_ALPHA) && m->cpnodes.length() == 1)
4501             {  // reset if hasn't reached another checkpoint yet
4502                 m->cpmillis = 0;
4503                 m->cpnodes.shrink(0);
4504                 sendf(-1, 1, "ri3", N_CHECKPOINT, m->clientnum, -1);
4505             }
4506             if(pointvalue)
4507             {
4508                 if(v != m && v->actortype >= A_ENEMY && m->actortype < A_ENEMY)
4509                 {
4510                     pointvalue = -pointvalue;
4511                     givepoints(m, pointvalue, m_points(gamemode, mutators) || m_dm_oldschool(gamemode, mutators), true);
4512                 }
4513                 else if(v->actortype < A_ENEMY) givepoints(v, pointvalue, m_points(gamemode, mutators) || m_dm_oldschool(gamemode, mutators), true);
4514             }
4515             m->deaths++;
4516             m->totaldeaths++;
4517             m->localtotaldeaths++;
4518             m->rewards[1] = 0;
4519             dropitems(m, DROP_DEATH);
4520             static vector<int> dmglog;
4521             dmglog.setsize(0);
4522             gethistory(m, v, gamemillis, dmglog, true, m_dm_oldschool(gamemode, mutators) ? 0 : 1);
4523             sendf(-1, 1, "ri9i5v", N_DIED, m->clientnum, m->deaths, m->totaldeaths, v->clientnum, v->frags, v->totalfrags, v->spree, style, weap, realflags, realdamage, material, dmglog.length(), dmglog.length(), dmglog.getbuf());
4524             m->position.setsize(0);
4525             if(smode) smode->died(m, v);
4526             mutate(smuts, mut->died(m, v));
4527             m->updatetimeplayed();
4528             m->state = CS_DEAD; // don't issue respawn yet until DEATHMILLIS has elapsed
4529             m->lastdeath = gamemillis;
4530             if(m->actortype == A_BOT) aiman::setskill(m);
4531             if(m != v && v->actortype == A_BOT) aiman::setskill(v);
4532             if(isteamkill && v->actortype == A_PLAYER) // don't punish the idiot bots
4533             {
4534                 v->teamkills.add(teamkill(totalmillis, v->team, 0-pointvalue));
4535                 if(G(teamkilllock) && !haspriv(v, G(teamkilllock)))
4536                 {
4537                     int numkills = 0;
4538                     if(!G(teamkilltime)) numkills = v->teamkills.length();
4539                     else loopv(v->teamkills)
4540                         if(totalmillis-v->teamkills[i].millis <= G(teamkilltime)*1000*60) numkills++;
4541                     if(numkills >= G(teamkillwarn) && numkills%G(teamkillwarn) == 0)
4542                     {
4543                         uint ip = getclientip(v->clientnum);
4544                         v->warnings[WARN_TEAMKILL][0]++;
4545                         v->warnings[WARN_TEAMKILL][1] = totalmillis ? totalmillis : 1;
4546                         if(ip && G(teamkillban) && v->warnings[WARN_TEAMKILL][0] >= G(teamkillban) && !haspriv(v, PRIV_MODERATOR) && !checkipinfo(control, ipinfo::EXCEPT, ip))
4547                         {
4548                             ipinfo &c = control.add();
4549                             c.ip = ip;
4550                             c.mask = 0xFFFFFFFFU;
4551                             c.type = ipinfo::BAN;
4552                             c.flag = ipinfo::INTERNAL;
4553                             c.time = totalmillis ? totalmillis : 1;
4554                             c.reason = newstring("team killing is not permitted");
4555                             srvoutf(3, "\fs\fcbanned\fS %s: %s", colourname(v), c.reason);
4556                             updatecontrols = true;
4557                         }
4558                         else if(G(teamkillkick) && v->warnings[WARN_TEAMKILL][0] >= G(teamkillkick))
4559                         {
4560                             srvoutf(3, "\fs\fckicked\fS %s: team killing is not permitted", colourname(v));
4561                             v->kicked = updatecontrols = true;
4562                         }
4563                         else srvmsgft(v->clientnum, CON_MESG, "\fy\fs\fzoyWARNING:\fS team killing is not permitted, action will be taken if you continue");
4564                     }
4565                 }
4566             }
4567         }
4568     }
4569 
process(clientinfo * ci)4570     void suicideevent::process(clientinfo *ci)
4571     {
4572         if(ci->state != CS_ALIVE) return;
4573         if(flags&HIT(MATERIAL) && (material&MATF_VOLUME) == MAT_LAVA)
4574         {
4575             flags |= HIT(BURN);
4576             ci->burntime = G(lavaburntime);
4577             ci->burndelay = G(lavaburndelay);
4578             ci->sendburn();
4579         }
4580         if(!(flags&HIT(MATERIAL)) && !(flags&HIT(LOST)) && !(flags&HIT(SPEC)))
4581         {
4582             if(smode && !smode->damage(ci, ci, ci->health, -1, flags, material)) { return; }
4583             mutate(smuts, if(!mut->damage(ci, ci, ci->health, -1, flags, material)) { return; });
4584         }
4585         ci->spree = 0;
4586         ci->deaths++;
4587         ci->totaldeaths++;
4588         bool kamikaze = dropitems(ci, DROP_DEATH);
4589         if(ci->actortype < A_ENEMY && m_race(gamemode) && (!m_ra_gauntlet(gamemode, mutators) || ci->team == T_ALPHA) && !(flags&HIT(SPEC)) && (!flags || ci->cpnodes.length() == 1 || !ci->checkpointspawn))
4590         { // reset if suicided, hasn't reached another checkpoint yet
4591             ci->cpmillis = 0;
4592             ci->cpnodes.shrink(0);
4593             sendf(-1, 1, "ri3", N_CHECKPOINT, ci->clientnum, -1);
4594         }
4595         else if(!(flags&HIT(LOST)) && !(flags&HIT(SPEC)))
4596         {
4597             int pointvalue = -1;
4598             if(!m_dm_oldschool(gamemode, mutators))
4599             {
4600                 pointvalue = (smode ? smode->points(ci, ci) : -1)*G(fragbonus);
4601                 if(kamikaze) pointvalue *= G(teamkillpenalty);
4602             }
4603             givepoints(ci, pointvalue, m_points(gamemode, mutators) || m_dm_oldschool(gamemode, mutators), true);
4604         }
4605         if(ci->burntime && flags&HIT(BURN))
4606         {
4607             ci->lastres[W_R_BURN] = ci->lastrestime[W_R_BURN] = gamemillis;
4608             ci->lastresowner[W_R_BURN] = ci->clientnum;
4609         }
4610         if(ci->bleedtime && flags&HIT(BLEED))
4611         {
4612             ci->lastres[W_R_BLEED] = ci->lastrestime[W_R_BLEED] = gamemillis;
4613             ci->lastresowner[W_R_BLEED] = ci->clientnum;
4614         }
4615         if(ci->shocktime && flags&HIT(SHOCK))
4616         {
4617             ci->lastres[W_R_SHOCK] = ci->lastrestime[W_R_SHOCK] = gamemillis;
4618             ci->lastresowner[W_R_SHOCK] = ci->clientnum;
4619         }
4620         static vector<int> dmglog; dmglog.setsize(0);
4621         gethistory(ci, ci, gamemillis, dmglog, true, m_dm_oldschool(gamemode, mutators) ? 0 : 1, m_lasthit(gamemode, mutators) ? G(lasthitbonus) : 0);
4622         sendf(-1, 1, "ri9i5v", N_DIED, ci->clientnum, ci->deaths, ci->totaldeaths, ci->clientnum, ci->frags, ci->totalfrags, 0, 0, -1, flags, ci->health*2, material, dmglog.length(), dmglog.length(), dmglog.getbuf());
4623         ci->position.setsize(0);
4624         if(smode) smode->died(ci, NULL);
4625         mutate(smuts, mut->died(ci, NULL));
4626         ci->updatetimeplayed();
4627         ci->state = CS_DEAD;
4628         ci->lastdeath = gamemillis;
4629         if(ci->actortype == A_BOT) aiman::setskill(ci);
4630     }
4631 
calcdamage(clientinfo * v,clientinfo * m,int weap,int & flags,float radial,float size,float dist,float scale,bool self)4632     int calcdamage(clientinfo *v, clientinfo *m, int weap, int &flags, float radial, float size, float dist, float scale, bool self)
4633     {
4634         flags &= ~HIT_SFLAGS;
4635         if(!hitdealt(flags))
4636         {
4637             flags &= ~HIT_CLEAR;
4638             flags |= HIT(WAVE);
4639         }
4640 
4641         float skew = clamp(scale, 0.f, 1.f)*G(damagescale);
4642 
4643         if(flags&HIT(WHIPLASH)) skew *= WF(WK(flags), weap, damagewhiplash, WS(flags));
4644         else if(flags&HIT(HEAD)) skew *= WF(WK(flags), weap, damagehead, WS(flags));
4645         else if(flags&HIT(TORSO)) skew *= WF(WK(flags), weap, damagetorso, WS(flags));
4646         else if(flags&HIT(LIMB)) skew *= WF(WK(flags), weap, damagelimb, WS(flags));
4647         else return 0;
4648 
4649         if(radial > 0) skew *= clamp(1.f-dist/size, 1e-6f, 1.f);
4650         else if(WF(WK(flags), weap, taper, WS(flags)) != 0)
4651             skew *= clamp(dist, WF(WK(flags), weap, tapermin, WS(flags)), WF(WK(flags), weap, tapermax, WS(flags)));
4652 
4653         if(!m_insta(gamemode, mutators))
4654         {
4655             if(m_capture(gamemode) && G(capturebuffdelay))
4656             {
4657                 if(v->lastbuff) skew *= G(capturebuffdamage);
4658                 if(m->lastbuff) skew /= G(capturebuffshield);
4659             }
4660             else if(m_defend(gamemode) && G(defendbuffdelay))
4661             {
4662                 if(v->lastbuff) skew *= G(defendbuffdamage);
4663                 if(m->lastbuff) skew /= G(defendbuffshield);
4664             }
4665             else if(m_bomber(gamemode) && G(bomberbuffdelay))
4666             {
4667                 if(v->lastbuff) skew *= G(bomberbuffdamage);
4668                 if(m->lastbuff) skew /= G(bomberbuffshield);
4669             }
4670         }
4671 
4672         if(self)
4673         {
4674             float modify = WF(WK(flags), weap, damageself, WS(flags))*G(damageselfscale);
4675             if(modify != 0) skew *= modify;
4676             else
4677             {
4678                 flags &= ~HIT_CLEAR;
4679                 flags |= HIT(WAVE);
4680             }
4681         }
4682         else if(m_team(gamemode, mutators) && v->team == m->team)
4683         {
4684             float modify = WF(WK(flags), weap, damageteam, WS(flags))*G(damageteamscale);
4685             if(modify != 0) skew *= modify;
4686             else
4687             {
4688                 flags &= ~HIT_CLEAR;
4689                 flags |= HIT(WAVE);
4690             }
4691         }
4692 
4693         return int(ceilf(WF(WK(flags), weap, damage, WS(flags))*skew));
4694     }
4695 
process(clientinfo * ci)4696     void stickyevent::process(clientinfo *ci)
4697     {
4698         if(isweap(weap))
4699         {
4700             if(!ci->weapshots[weap][WS(flags) ? 1 : 0].find(id))
4701             {
4702                 srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s sticky [%d (%d)] failed - not found", colourname(ci), weap, id);
4703                 return;
4704             }
4705             clientinfo *m = target >= 0 ? (clientinfo *)getinfo(target) : NULL;
4706             if(target < 0 || (m && m->state == CS_ALIVE && !m->protect(gamemillis, m_protect(gamemode, mutators))))
4707                 sendf(-1, 1, "ri9ix", N_STICKY, ci->clientnum, target, id, norm.x, norm.y, norm.z, pos.x, pos.y, pos.z, ci->clientnum);
4708         }
4709     }
4710 
process(clientinfo * ci)4711     void destroyevent::process(clientinfo *ci)
4712     {
4713         switch(type)
4714         {
4715             case PRJ_ENT:
4716             {
4717                 if(ci->dropped.remove(id))
4718                     sendf(-1, 1, "ri5x", N_DESTROY, ci->clientnum, PRJ_ENT, 1, id, ci->clientnum);
4719                 break;
4720             }
4721             case PRJ_SHOT:
4722             {
4723                 if(!isweap(weap)) break;
4724                 if(!ci->weapshots[weap][WS(flags) ? 1 : 0].find(id))
4725                 {
4726                     srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s destroy [%d:%d (%d)] failed - not found", colourname(ci), weap, WS(flags) ? 1 : 0, id);
4727                     return;
4728                 }
4729                 vector<clientinfo *> hitclients;
4730                 if(hits.empty())
4731                 {
4732                     ci->weapshots[weap][WS(flags) ? 1 : 0].remove(id);
4733                     if(id >= 0 && !m_insta(gamemode, mutators))
4734                     {
4735                         int f = W2(weap, fragweap, WS(flags));
4736                         if(f >= 0)
4737                         {
4738                             int w = f%W_MAX, r = min(W2(weap, fragrays, WS(flags)), MAXPARAMS);
4739                             loopi(r) ci->weapshots[w][f >= W_MAX ? 1 : 0].add(-id);
4740                             if(WS(flags)) ci->weapstats[weap].flakshots2 += r;
4741                             else ci->weapstats[weap].flakshots1 += r;
4742                         }
4743                     }
4744                     sendf(-1, 1, "ri5x", N_DESTROY, ci->clientnum, PRJ_SHOT, 1, id, ci->clientnum);
4745                 }
4746                 else loopv(hits)
4747                 {
4748                     hitset &h = hits[i];
4749                     clientinfo *m = (clientinfo *)getinfo(h.target);
4750                     if(!m) continue;
4751                     bool first = true;
4752                     loopvj(hitclients) if(hitclients[j] == m) first = false;
4753                     hitclients.add(m);
4754                     if(h.proj)
4755                     {
4756                         loopj(W_MAX) loopk(2) if(m->weapshots[j][k].find(h.proj))
4757                         {
4758                             sendf(m->clientnum, 1, "ri5", N_DESTROY, m->clientnum, PRJ_SHOT, 1, h.proj);
4759                             break;
4760                         }
4761                     }
4762                     else
4763                     {
4764                         int hflags = flags|h.flags;
4765                         float skew = float(scale)/DNF, rad = radial > 0 ? clamp(radial/DNF, 0.f, WX(WK(flags), weap, radial, WS(flags), gamemode, mutators, skew)) : 0.f,
4766                               size = rad > 0 ? (hflags&HIT(WAVE) ? rad*WF(WK(flags), weap, wavepush, WS(flags)) : rad) : 0.f, dist = float(h.dist)/DNF;
4767                         if(m->state == CS_ALIVE && !m->protect(gamemillis, m_protect(gamemode, mutators)))
4768                         {
4769                             int damage = calcdamage(ci, m, weap, hflags, rad, size, dist, skew, ci == m);
4770                             if(damage) dodamage(m, ci, damage, weap, fromweap, fromflags, hflags, 0, h.dir, h.vel, dist, first);
4771                         }
4772                     }
4773                 }
4774                 break;
4775             }
4776         }
4777     }
4778 
checkweapload(clientinfo * ci,int weap)4779     void checkweapload(clientinfo *ci, int weap)
4780     {
4781         if(ci->weapload[ci->weapselect][W_A_CLIP] <= 0) return;
4782         takeammo(ci, ci->weapselect, ci->weapload[ci->weapselect][W_A_CLIP]);
4783         if(W(ci->weapselect, ammostore) > 0)
4784             ci->weapammo[ci->weapselect][W_A_STORE] = clamp(ci->weapammo[ci->weapselect][W_A_STORE]+ci->weapload[ci->weapselect][W_A_CLIP], 0, W(ci->weapselect, ammostore));
4785         ci->weapload[ci->weapselect][W_A_CLIP] = -ci->weapload[ci->weapselect][W_A_CLIP]; // the client should already do this for themself
4786         sendf(-1, 1, "ri6x", N_RELOAD, ci->clientnum, ci->weapselect, ci->weapload[ci->weapselect][W_A_CLIP], ci->weapammo[ci->weapselect][W_A_CLIP], ci->weapammo[ci->weapselect][W_A_STORE], ci->clientnum);
4787     }
4788 
process(clientinfo * ci)4789     void shotevent::process(clientinfo *ci)
4790     {
4791         if(!ci->isalive(gamemillis) || !isweap(weap))
4792         {
4793             srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s shoot [%d] failed - unexpected message", colourname(ci), weap);
4794             return;
4795         }
4796         int sweap = m_weapon(ci->actortype, gamemode, mutators), sub = W2(weap, ammosub, WS(flags));
4797         if(sub > 1 && W2(weap, cooktime, WS(flags)))
4798         {
4799             if(ci->weapammo[weap][W_A_CLIP] < sub)
4800             {
4801                 int maxscale = int(ci->weapammo[weap][W_A_CLIP]/float(sub)*W2(weap, cooktime, WS(flags)));
4802                 if(scale > maxscale) scale = maxscale;
4803             }
4804             sub = clamp(int(ceilf(sub*scale/float(W2(weap, cooktime, WS(flags))))), 1, W2(weap, ammosub, WS(flags)));
4805         }
4806         if(!ci->canshoot(weap, flags, sweap, millis))
4807         {
4808             if(!ci->canshoot(weap, flags, sweap, millis, (1<<W_S_RELOAD)))
4809             {
4810                 if(sub && W(weap, ammoclip)) ci->weapammo[weap][W_A_CLIP] = max(ci->weapammo[weap][W_A_CLIP]-sub, 0);
4811                 srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s shoot [%d] failed - current state disallows it", colourname(ci), weap);
4812                 sendresume(ci, true);
4813                 return;
4814             }
4815             checkweapload(ci, ci->weapselect);
4816         }
4817         takeammo(ci, weap, sub);
4818         ci->setweapstate(weap, WS(flags) ? W_S_SECONDARY : W_S_PRIMARY, W2(weap, delayattack, WS(flags)), millis);
4819         sendf(-1, 1, "ri9i4vx", N_SHOTFX, ci->clientnum, weap, flags, scale, target, from.x, from.y, from.z, dest.x, dest.y, dest.z, shots.length(), shots.length()*sizeof(shotmsg)/sizeof(int), shots.getbuf(), ci->clientnum);
4820         ci->weapshot[weap] = sub;
4821         ci->shotdamage += W2(weap, damage, WS(flags))*shots.length();
4822         loopv(shots) ci->weapshots[weap][WS(flags) ? 1 : 0].add(shots[i].id);
4823         if(WS(flags)) ci->weapstats[weap].shots2++;
4824         else ci->weapstats[weap].shots1++;
4825         if(W2(weap, ammosub, WS(flags)))
4826         {
4827             if(ci->state != CS_ALIVE)
4828             {
4829                 if(sents.inrange(ci->weapent[weap])) loopv(ci->dropped.projs)
4830                 {
4831                     if(ci->dropped.projs[i].id != ci->weapent[weap]) continue;
4832                     ci->dropped.projs[i].ammo -= sub;
4833                     break;
4834                 }
4835             }
4836             else if(!ci->hasweap(weap, sweap, m_classic(gamemode, mutators) ? 5 : 6))
4837             {
4838                 sendf(-1, 1, "ri7", N_WEAPDROP, ci->clientnum, -1, 1, weap, -1, 0);
4839                 ci->weapammo[weap][W_A_CLIP] = -1;
4840                 ci->weapammo[weap][W_A_STORE] = 0;
4841             }
4842         }
4843     }
4844 
process(clientinfo * ci)4845     void switchevent::process(clientinfo *ci)
4846     {
4847         if(!ci->isalive(gamemillis) || !isweap(weap))
4848         {
4849             srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s switch [%d] failed - unexpected message", colourname(ci), weap);
4850             return;
4851         }
4852         if(!ci->canswitch(weap, m_weapon(ci->actortype, gamemode, mutators), millis, (1<<W_S_SWITCH)))
4853         {
4854             if(!ci->canswitch(weap, m_weapon(ci->actortype, gamemode, mutators), millis, (1<<W_S_SWITCH)|(1<<W_S_RELOAD)))
4855             {
4856                 srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s switch [%d] failed - current state disallows it", colourname(ci), weap);
4857                 sendresume(ci, true);
4858                 return;
4859             }
4860             checkweapload(ci, ci->weapselect);
4861         }
4862         ci->updateweaptime();
4863         ci->weapswitch(weap, millis, W(weap, delayswitch));
4864         sendf(-1, 1, "ri3x", N_WEAPSELECT, ci->clientnum, weap, ci->clientnum);
4865     }
4866 
process(clientinfo * ci)4867     void cookevent::process(clientinfo *ci)
4868     {
4869         if(!ci->isalive(gamemillis) || !isweap(weap) || etype < -1 || etype > 2)
4870         {
4871             srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s cook [%d] failed - unexpected message", colourname(ci), weap);
4872             return;
4873         }
4874         if(ci->weapstate[weap] == W_S_RELOAD && !ci->weapwaited(weap, gamemillis))
4875         {
4876             if(!ci->weapwaited(weap, gamemillis, (1<<W_S_RELOAD)))
4877             {
4878                 srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s cook [%d] failed - current state disallows it", colourname(ci), weap);
4879                 sendresume(ci, true);
4880                 return;
4881             }
4882             checkweapload(ci, weap);
4883         }
4884         if(etype >= 0)
4885         {
4886             float maxscale = 1;
4887             int sub = W2(weap, ammosub, etype >= 1);
4888             if(sub > 1 && ci->weapammo[weap][W_A_CLIP] < sub) maxscale = ci->weapammo[weap][W_A_CLIP]/float(sub);
4889             ci->setweapstate(weap, etype >= 2 ? W_S_ZOOM : W_S_POWER, max(int(W2(weap, cooktime, etype >= 1)*maxscale), 1), millis, offtime);
4890         }
4891         else ci->setweapstate(weap, W_S_IDLE, 0, millis, 0, true);
4892         ci->lastcook = millis;
4893         sendf(-1, 1, "ri5x", N_WEAPCOOK, ci->clientnum, weap, etype, offtime, ci->clientnum);
4894     }
4895 
process(clientinfo * ci)4896     void dropevent::process(clientinfo *ci)
4897     {
4898         if(!ci->isalive(gamemillis) || !isweap(weap))
4899         {
4900             srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s drop [%d] failed - unexpected message", colourname(ci), weap);
4901             return;
4902         }
4903         int sweap = m_weapon(ci->actortype, gamemode, mutators);
4904         if(!ci->candrop(weap, sweap, millis, m_classic(gamemode, mutators), (1<<W_S_SWITCH)))
4905         {
4906             if(!ci->candrop(weap, sweap, millis, m_classic(gamemode, mutators), (1<<W_S_SWITCH)|(1<<W_S_RELOAD)))
4907             {
4908                 srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s drop [%d] failed - current state disallows it", colourname(ci), weap);
4909                 sendresume(ci, true);
4910                 return;
4911             }
4912             checkweapload(ci, ci->weapselect);
4913         }
4914         int dropped = -1, ammo = -1, nweap = ci->bestweap(sweap, false, true, weap); // switch to best weapon
4915         if(sents.inrange(ci->weapent[weap]))
4916         {
4917             dropped = ci->weapent[weap];
4918             ammo = ci->getammo(weap, 0, true);
4919             if(ammo > 0) ci->dropped.add(dropped, ammo);
4920         }
4921         ci->weapammo[weap][W_A_CLIP] = -1;
4922         ci->weapammo[weap][W_A_STORE] = 0;
4923         ci->weapswitch(nweap, millis, W(nweap, delayswitch));
4924         sendf(-1, 1, "ri7", N_WEAPDROP, ci->clientnum, nweap, 1, weap, dropped, ammo);
4925     }
4926 
process(clientinfo * ci)4927     void reloadevent::process(clientinfo *ci)
4928     {
4929         if(!ci->isalive(gamemillis) || !isweap(weap))
4930         {
4931             srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s reload [%d] failed - unexpected message", colourname(ci), weap);
4932             return;
4933         }
4934         if(!ci->canreload(weap, m_weapon(ci->actortype, gamemode, mutators), true, millis))
4935         {
4936             srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s reload [%d] failed - current state disallows it", colourname(ci), weap);
4937             sendresume(ci, true);
4938             return;
4939         }
4940         int oldammo = max(ci->weapammo[weap][W_A_CLIP], 0), ammoadd = W(weap, ammoadd);
4941         if(ci->actortype < A_ENEMY && W(weap, ammostore) > 0) ammoadd = min(ci->weapammo[weap][W_A_STORE], ammoadd);
4942         if(!ammoadd)
4943         {
4944             srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s reload [%d] failed - no ammo available", colourname(ci), weap);
4945             sendresume(ci, true);
4946             return;
4947         }
4948         ci->setweapstate(weap, W_S_RELOAD, W(weap, delayreload), millis);
4949         ci->weapammo[weap][W_A_CLIP] = min(oldammo+ammoadd, W(weap, ammoclip));
4950         int diff = ci->weapammo[weap][W_A_CLIP]-oldammo;
4951         if(W(weap, ammostore) > 0) ci->weapammo[weap][W_A_STORE] = clamp(ci->weapammo[weap][W_A_STORE]-diff, 0, W(weap, ammostore));
4952         ci->weapload[weap][W_A_CLIP] = diff;
4953         sendf(-1, 1, "ri6x", N_RELOAD, ci->clientnum, weap, ci->weapload[weap][W_A_CLIP], ci->weapammo[weap][W_A_CLIP], ci->weapammo[weap][W_A_STORE], ci->clientnum);
4954     }
4955 
process(clientinfo * ci)4956     void useevent::process(clientinfo *ci)
4957     {
4958         if(ci->state != CS_ALIVE || !sents.inrange(ent) || sents[ent].type != WEAPON)
4959         {
4960             srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s use [%d] failed - unexpected message", colourname(ci), ent);
4961             return;
4962         }
4963         clientinfo *cp = NULL;
4964         if(cn >= 0)
4965         {
4966             cp = (clientinfo *)getinfo(cn);
4967             if(!cp || !cp->dropped.find(ent))
4968             {
4969                 srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s use [%d] failed - doesn't seem to be dropped anywhere", colourname(ci), ent);
4970                 return;
4971             }
4972         }
4973         else if(!finditem(ent))
4974         {
4975             srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s use [%d] failed - doesn't seem to be spawned anywhere", colourname(ci), ent);
4976             return;
4977         }
4978         int sweap = m_weapon(ci->actortype, gamemode, mutators), attr = m_attr(sents[ent].type, sents[ent].attrs[0]);
4979         if(!isweap(attr)) return;
4980         ci->updateweaptime();
4981         if(!ci->canuse(gamemode, mutators, sents[ent].type, attr, sents[ent].attrs, sweap, millis, (1<<W_S_SWITCH)))
4982         {
4983             if(!ci->canuse(gamemode, mutators, sents[ent].type, attr, sents[ent].attrs, sweap, millis, (1<<W_S_SWITCH)|(1<<W_S_RELOAD)))
4984             {
4985                 srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s use [%d] failed - current state disallows it", colourname(ci), ent);
4986                 sendresume(ci, true);
4987                 return;
4988             }
4989             checkweapload(ci, ci->weapselect);
4990         }
4991         int weap = -1, ammoamt = W(attr, ammoitem), dropped = -1, ammo = -1;
4992         if(m_classic(gamemode, mutators) && !ci->hasweap(attr, sweap) && w_carry(attr, sweap) && ci->carry(sweap) >= m_maxcarry(ci->actortype, gamemode, mutators))
4993             weap = ci->drop(sweap);
4994         if(isweap(weap))
4995         {
4996             if(sents.inrange(ci->weapent[weap]))
4997             {
4998                 dropped = ci->weapent[weap];
4999                 ammo = ci->getammo(weap, 0, true);
5000                 ci->setweapstate(weap, W_S_SWITCH, W(weap, delayswitch), millis);
5001                 if(ammo > 0) ci->dropped.add(dropped, ammo);
5002             }
5003             ci->weapammo[weap][W_A_CLIP] = -1;
5004             ci->weapammo[weap][W_A_STORE] = 0;
5005         }
5006         if(cn >= 0)
5007         {
5008             cp->dropped.values(ent, ammoamt);
5009             cp->dropped.remove(ent);
5010         }
5011         else setspawn(ent, false);
5012         ci->useitem(ent, sents[ent].type, attr, ammoamt, sweap, millis, W(attr, delayitem));
5013         sendf(-1, 1, "ri9", N_ITEMACC, ci->clientnum, cn, ent, ammoamt, cn < 0 && sents[ent].spawned ? 1 : 0, weap, dropped, ammo);
5014     }
5015 
flush(clientinfo * ci,int fmillis)5016     bool gameevent::flush(clientinfo *ci, int fmillis)
5017     {
5018         process(ci);
5019         return true;
5020     }
5021 
flush(clientinfo * ci,int fmillis)5022     bool timedevent::flush(clientinfo *ci, int fmillis)
5023     {
5024         if(millis > fmillis) return false;
5025         else if(millis >= ci->lastevent)
5026         {
5027             ci->lastevent = millis;
5028             process(ci);
5029         }
5030         return true;
5031     }
5032 
flushevents(clientinfo * ci,int millis)5033     void flushevents(clientinfo *ci, int millis)
5034     {
5035         while(ci->events.length())
5036         {
5037             gameevent *ev = ci->events[0];
5038             if(ev->flush(ci, millis)) clearevent(ci);
5039             else break;
5040         }
5041     }
5042 
processevents()5043     void processevents()
5044     {
5045         loopv(clients)
5046         {
5047             clientinfo *ci = clients[i];
5048             flushevents(ci, gamemillis);
5049         }
5050     }
5051 
cleartimedevents(clientinfo * ci)5052     void cleartimedevents(clientinfo *ci)
5053     {
5054         int keep = 0;
5055         loopv(ci->events)
5056         {
5057             if(ci->events[i]->keepable())
5058             {
5059                 if(keep < i)
5060                 {
5061                     for(int j = keep; j < i; j++) delete ci->events[j];
5062                     ci->events.remove(keep, i - keep);
5063                     i = keep;
5064                 }
5065                 keep = i+1;
5066                 continue;
5067             }
5068         }
5069         while(ci->events.length() > keep) delete ci->events.pop();
5070     }
5071 
requestswap(clientinfo * ci,int team)5072     int requestswap(clientinfo *ci, int team)
5073     {
5074         if(!allowteam(ci, team, T_FIRST, numclients() > 1))
5075         {
5076             if(team && m_swapteam(gamemode, mutators) && ci->team != team && ci->actortype == A_PLAYER && ci->swapteam != team && canplay())
5077             {
5078                 ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fy%s requests swap to team %s, change teams to accept", colourname(ci), colourteam(team));
5079                 ci->swapteam = team;
5080             }
5081             team = chooseteam(ci);
5082         }
5083         return team;
5084     }
5085 
waiting(clientinfo * ci,int drop,bool doteam,bool exclude)5086     void waiting(clientinfo *ci, int drop, bool doteam, bool exclude)
5087     {
5088         ci->updatetimeplayed();
5089         if(ci->state == CS_ALIVE)
5090         {
5091             if(drop) dropitems(ci, drop);
5092             if(smode) smode->died(ci);
5093             mutate(smuts, mut->died(ci));
5094             ci->lastdeath = gamemillis;
5095         }
5096         if(exclude) sendf(-1, 1, "ri2x", N_WAITING, ci->clientnum, ci->clientnum);
5097         else sendf(-1, 1, "ri2", N_WAITING, ci->clientnum);
5098         ci->state = CS_WAITING;
5099         ci->weapreset(true);
5100         if(doteam && !allowteam(ci, ci->team, T_FIRST, false)) setteam(ci, chooseteam(ci), TT_INFO);
5101     }
5102 
triggertime(bool delay=false)5103     int triggertime(bool delay = false)
5104     {
5105         return delay ? TRIGGERDELAY : TRIGGERTIME;
5106     }
5107 
checkents()5108     void checkents()
5109     {
5110         loopv(sents) switch(sents[i].type)
5111         {
5112             case TRIGGER:
5113             {
5114                 if(sents[i].attrs[1] != TR_LINK || !checkmapvariant(sents[i].attrs[enttype[sents[i].type].mvattr])) continue;
5115                 bool spawn = sents[i].attrs[4]%2;
5116                 if(spawn != sents[i].spawned && gamemillis >= sents[i].millis && (sents[i].attrs[0] == triggerid || !sents[i].attrs[0]) && m_check(sents[i].attrs[5], sents[i].attrs[6], gamemode, mutators))
5117                 {
5118                     sents[i].spawned = spawn;
5119                     sents[i].millis = gamemillis+(triggertime()*2);
5120                     sendf(-1, 1, "ri3", N_TRIGGER, i, 0);
5121                     loopvj(sents[i].kin) if(sents.inrange(sents[i].kin[j]))
5122                     {
5123                         if(sents[sents[i].kin[j]].type == TRIGGER && !m_check(sents[sents[i].kin[j]].attrs[5], sents[sents[i].kin[j]].attrs[6], gamemode, mutators))
5124                             continue;
5125                         sents[sents[i].kin[j]].spawned = sents[i].spawned;
5126                         sents[sents[i].kin[j]].millis = sents[i].millis;
5127                     }
5128                 }
5129                 break;
5130             }
5131             default:
5132             {
5133                 if(enttype[sents[i].type].usetype != EU_ITEM) break;
5134                 bool allowed = hasitem(i);
5135                 if((allowed && !sents[i].spawned && !finditem(i, true)) || (!allowed && sents[i].spawned))
5136                     setspawn(i, allowed, false, true);
5137                 break;
5138             }
5139         }
5140     }
5141 
checkclients()5142     void checkclients()
5143     {
5144         bool avgposcalc = (m_normweaps(gamemode, mutators) && gamemillis-lastavgposcalc >= G(teambalanceavgposdelay));
5145         int maxpoints = 0;
5146         if(avgposcalc)
5147         {
5148             lastavgposcalc = gamemillis;
5149             loopv(clients) if(clients[i]->state == CS_ALIVE || clients[i]->state == CS_DEAD) maxpoints = max(maxpoints, clients[i]->points);
5150             if(maxpoints) loopv(clients) if(clients[i]->state == CS_ALIVE || clients[i]->state == CS_DEAD)
5151             {
5152                 clientinfo *ci = clients[i];
5153                 ci->localtotalavgpossum += (float)max(ci->points, 0) / maxpoints;
5154                 ci->localtotalavgposnum++;
5155                 ci->updateavgpos();
5156                 sendf(-1, 1, "ri2f", N_AVGPOS, ci->clientnum, ci->totalavgpos);
5157             }
5158         }
5159         loopv(clients) if(clients[i]->name[0] && clients[i]->online)
5160         {
5161             clientinfo *ci = clients[i];
5162             if(smode) smode->checkclient(ci);
5163             mutate(smuts, mut->checkclient(ci));
5164             if(ci->state == CS_ALIVE)
5165             {
5166                 // hurt material
5167                 if((ci->inmaterial&MATF_FLAGS)&MAT_HURT)
5168                 {
5169                     if(!ci->lasthurt || gamemillis-ci->lasthurt >= G(hurtdelay))
5170                     {
5171                         int flags = HIT(MATERIAL);
5172                         if(G(hurtresidual)&WR(BURN))
5173                         {
5174                             flags |= HIT(BURN);
5175                             ci->burntime = G(hurtburntime);
5176                             ci->burndelay = G(hurtburndelay);
5177                             ci->burndamage = G(hurtburndamage);
5178                             ci->sendburn();
5179                         }
5180                         if(G(hurtresidual)&WR(BLEED))
5181                         {
5182                             flags |= HIT(BLEED);
5183                             ci->bleedtime = G(hurtbleedtime);
5184                             ci->bleeddelay = G(hurtbleeddelay);
5185                             ci->bleeddamage = G(hurtbleeddamage);
5186                             ci->sendbleed();
5187                         }
5188                         if(G(hurtresidual)&WR(SHOCK))
5189                         {
5190                             flags |= HIT(SHOCK);
5191                             ci->shocktime = G(hurtshocktime);
5192                             ci->shockdelay = G(hurtshockdelay);
5193                             ci->shockdamage = G(hurtshockdamage);
5194                             ci->shockstun = G(hurtshockstun);
5195                             ci->shockstunscale = G(hurtshockstunscale);
5196                             ci->shockstunfall = G(hurtshockstunfall);
5197                             ci->shockstuntime = G(hurtshockstuntime);
5198                             ci->sendshock();
5199                         }
5200                         dodamage(ci, ci, G(hurtdamage), -1, -1, 0, flags, ci->inmaterial);
5201                         if(!ci->lasthurt) ci->lasthurt = gamemillis;
5202                         else ci->lasthurt += G(hurtdelay);
5203                         if(ci->state != CS_ALIVE) continue;
5204                     }
5205                 }
5206                 else if(ci->lasthurt && gamemillis-ci->lasthurt >= G(hurtdelay)) ci->lasthurt = 0;
5207                 // burning residual
5208                 if(ci->burning(gamemillis, ci->burntime))
5209                 {
5210                     if(gamemillis-ci->lastrestime[W_R_BURN] >= ci->burndelay)
5211                     {
5212                         clientinfo *co = (clientinfo *)getinfo(ci->lastresowner[W_R_BURN]);
5213                         dodamage(ci, co ? co : ci, ci->burndamage, -1, -1, 0, HIT(BURN), 0);
5214                         ci->lastrestime[W_R_BURN] += ci->burndelay;
5215                         if(ci->state != CS_ALIVE) continue;
5216                     }
5217                 }
5218                 else if(ci->lastres[W_R_BURN]) ci->lastres[W_R_BURN] = ci->lastrestime[W_R_BURN] = 0;
5219                 // bleeding residual
5220                 if(ci->bleeding(gamemillis, ci->bleedtime))
5221                 {
5222                     if(gamemillis-ci->lastrestime[W_R_BLEED] >= ci->bleeddelay)
5223                     {
5224                         clientinfo *co = (clientinfo *)getinfo(ci->lastresowner[W_R_BLEED]);
5225                         dodamage(ci, co ? co : ci, ci->bleeddamage, -1, -1, 0, HIT(BLEED), 0);
5226                         ci->lastrestime[W_R_BLEED] += ci->bleeddelay;
5227                         if(ci->state != CS_ALIVE) continue;
5228                     }
5229                 }
5230                 else if(ci->lastres[W_R_BLEED]) ci->lastres[W_R_BLEED] = ci->lastrestime[W_R_BLEED] = 0;
5231                 // shocking residual
5232                 if(ci->shocking(gamemillis, ci->shocktime))
5233                 {
5234                     if(gamemillis-ci->lastrestime[W_R_SHOCK] >= ci->shockdelay)
5235                     {
5236                         clientinfo *co = (clientinfo *)getinfo(ci->lastresowner[W_R_SHOCK]);
5237                         dodamage(ci, co ? co : ci, ci->shockdamage, -1, -1, 0, HIT(SHOCK), 0);
5238                         ci->lastrestime[W_R_SHOCK] += ci->shockdelay;
5239                         if(ci->state != CS_ALIVE) continue;
5240                     }
5241                 }
5242                 else if(ci->lastres[W_R_SHOCK]) ci->lastres[W_R_SHOCK] = ci->lastrestime[W_R_SHOCK] = 0;
5243                 // regen wear-off
5244                 if(m_regen(gamemode, mutators) && AA(ci->actortype, abilities)&(1<<A_A_REGEN))
5245                 {
5246                     int total = ci->gethealth(gamemode, mutators), amt = G(regenhealth),
5247                         delay = ci->lastregen ? G(regentime) : G(regendelay);
5248                     if(smode) smode->regen(ci, total, amt, delay);
5249                     if(delay && ci->health != total)
5250                     {
5251                         int millis = gamemillis-(ci->lastregen ? ci->lastregen : ci->lastpain);
5252                         if(millis >= delay)
5253                         {
5254                             int low = 0;
5255                             if(ci->health > total)
5256                             {
5257                                 amt = -G(regendecay);
5258                                 total = ci->health;
5259                                 low = ci->gethealth(gamemode, mutators);
5260                             }
5261                             int heal = clamp(ci->health+amt, low, total), eff = heal-ci->health;
5262                             if(eff)
5263                             {
5264                                 ci->health = heal;
5265                                 ci->lastregen = gamemillis;
5266                                 ci->lastregenamt = eff;
5267                                 sendf(-1, 1, "ri4", N_REGEN, ci->clientnum, ci->health, ci->lastregenamt);
5268                             }
5269                         }
5270                     }
5271                 }
5272             }
5273             else if(ci->state == CS_WAITING)
5274             {
5275                 int nospawn = 0;
5276                 if(smode && !smode->canspawn(ci, false)) { nospawn++; }
5277                 mutate(smuts, if(!mut->canspawn(ci, false)) { nospawn++; });
5278                 if(!nospawn)
5279                 {
5280                     if(ci->lastdeath) flushevents(ci, ci->lastdeath + DEATHMILLIS);
5281                     cleartimedevents(ci);
5282                     ci->state = CS_DEAD; // safety
5283                     ci->respawn(gamemillis);
5284                     sendspawn(ci);
5285                 }
5286             }
5287             if(G(autospectate) && !m_duke(gamemode, mutators) && ci->state == CS_DEAD && ci->lastdeath && gamemillis-ci->lastdeath >= G(autospecdelay))
5288             {
5289                 if(ci->actortype > A_PLAYER) waiting(ci, DROP_RESET);
5290                 else spectate(ci, true);
5291             }
5292         }
5293     }
5294 
serverupdate()5295     void serverupdate()
5296     {
5297         loopvrev(connects) if(totalmillis-connects[i]->connectmillis >= G(connecttimeout))
5298         {
5299             clientinfo *ci = connects[i];
5300             if(ci->connectauth)
5301             { // auth might have stalled
5302                 ci->connectauth = false;
5303                 ci->authreq = ci->authname[0] = ci->handle[0] = '\0';
5304                 srvmsgftforce(ci->clientnum, CON_EVENT, "\foUnable to verify, authority request timed out");
5305                 int disc = auth::allowconnect(ci);
5306                 if(disc) disconnect_client(ci->clientnum, disc);
5307                 else
5308                 {
5309                     ci->connectmillis = totalmillis ? totalmillis : 1; // in case it doesn't work
5310                     if(!ci->connectsteam) connected(ci);
5311                 }
5312             }
5313             else disconnect_client(ci->clientnum, DISC_TIMEOUT);
5314         }
5315         loopvrev(control) if(control[i].flag <= ipinfo::INTERNAL)
5316         {
5317             int timeout = 0;
5318             switch(control[i].type)
5319             {
5320                 case ipinfo::ALLOW: timeout = G(allowtimeout); break;
5321                 case ipinfo::BAN: timeout = G(bantimeout); break;
5322                 case ipinfo::MUTE: timeout = G(mutetimeout); break;
5323                 case ipinfo::LIMIT: timeout = G(limittimeout); break;
5324                 case ipinfo::EXCEPT: timeout = G(excepttimeout); break;
5325                 default: break;
5326             }
5327             if(timeout && totalmillis-control[i].time >= timeout) control.remove(i);
5328         }
5329         if(updatecontrols)
5330         {
5331             loopvrev(clients)
5332             {
5333                 uint ip = getclientip(clients[i]->clientnum);
5334                 if(ip && !haspriv(clients[i], G(banlock)) && checkipinfo(control, ipinfo::BAN, ip) && !checkipinfo(control, ipinfo::EXCEPT, ip))
5335                 {
5336                     disconnect_client(clients[i]->clientnum, DISC_IPBAN);
5337                     continue;
5338                 }
5339                 if(clients[i]->kicked)
5340                 {
5341                     disconnect_client(clients[i]->clientnum, DISC_KICK);
5342                     continue;
5343                 }
5344             }
5345             updatecontrols = false;
5346         }
5347         if(numclients())
5348         {
5349             if(servercheck(shutdownwait))
5350             {
5351                 int waituntil = maxshutdownwait*(gs_playing(gamestate) ? 2000 : 1000);
5352                 if(totalmillis >= shutdownwait+waituntil)
5353                 {
5354                     srvoutf(3, "Waited \fs\fc%s\fS to shutdown, overriding and exiting...", timestr(totalmillis-shutdownwait, 4));
5355 #ifdef STANDALONE
5356                     cleanupserver();
5357                     exit(EXIT_SUCCESS);
5358 #else
5359                     quit();
5360 #endif
5361                     return;
5362                 }
5363             }
5364             if(gs_waiting(gamestate))
5365             {
5366                 int numwait = 0, numgetmap = 0, numnotready = 0;
5367                 loopv(clients)
5368                 {
5369                     clientinfo *cs = clients[i];
5370                     if(cs->actortype > A_PLAYER) continue;
5371                     if(m_play(gamemode) && (!cs->ready || (G(waitforplayers) == 2 && cs->state == CS_SPECTATOR))) numwait++;
5372                     if(cs->wantsmap || cs->gettingmap) numgetmap++;
5373                     if(!cs->ready) numnotready++;
5374                 }
5375                 switch(gamestate)
5376                 {
5377                     case G_S_WAITING: // start check
5378                     {
5379                         if(!G(waitforplayermaps))
5380                         {
5381                             gamewaittime = totalmillis+G(waitforplayertime);
5382                             gamestate = G_S_READYING;
5383                             sendtick();
5384                             break;
5385                         }
5386                         if(!gamewaittime)
5387                         {
5388                             gamewaittime = totalmillis+max(G(waitforplayerload), 1);
5389                             sendtick();
5390                         }
5391                         if(numnotready && gamewaittime > totalmillis) break;
5392                         if(!hasmapdata())
5393                         {
5394                             if(mapsending < 0) getmap(NULL, true);
5395                             if(mapsending >= 0)
5396                             {
5397                                 srvoutf(4, "\fyPlease wait while the server downloads the map..");
5398                                 gamewaittime = totalmillis+G(waitforplayermaps);
5399                                 gamestate = G_S_GETMAP;
5400                                 sendtick();
5401                                 break;
5402                             }
5403                             gamewaittime = totalmillis+G(waitforplayertime);
5404                             gamestate = G_S_READYING;
5405                             sendtick();
5406                             break;
5407                         }
5408                         // fall through
5409                     }
5410                     case G_S_GETMAP: // waiting for server
5411                     {
5412                         if(!gamewaittime)
5413                         {
5414                             gamewaittime = totalmillis+G(waitforplayermaps);
5415                             sendtick();
5416                         }
5417                         if(!hasmapdata() && mapsending >= 0 && gamewaittime > totalmillis) break;
5418                         if(numgetmap && hasmapdata())
5419                         {
5420                             srvoutf(4, "\fyPlease wait for \fs\fc%d\fS %s to download the map..", numgetmap, numgetmap != 1 ? "players" : "player");
5421                             gamewaittime = totalmillis+G(waitforplayermaps);
5422                             gamestate = G_S_SENDMAP;
5423                             sendtick();
5424                             break;
5425                         }
5426                         gamewaittime = totalmillis+G(waitforplayertime);
5427                         gamestate = G_S_READYING;
5428                         sendtick();
5429                         break;
5430                     }
5431                     case G_S_SENDMAP: // waiting for players
5432                     {
5433                         if(!gamewaittime)
5434                         {
5435                             gamewaittime = totalmillis+G(waitforplayermaps);
5436                             sendtick();
5437                         }
5438                         if(numgetmap && gamewaittime > totalmillis && hasmapdata()) break;
5439                         gamewaittime = totalmillis+G(waitforplayertime);
5440                         gamestate = G_S_READYING;
5441                         sendtick();
5442                         // fall through
5443                     }
5444                     case G_S_READYING: // waiting for ready
5445                     {
5446                         if(!gamewaittime)
5447                         {
5448                             gamewaittime = totalmillis+G(waitforplayertime);
5449                             sendtick();
5450                         }
5451                         if(numwait && gamewaittime > totalmillis) break;
5452                         if(!hasgameinfo)
5453                         {
5454                             clientinfo *best = NULL;
5455                             loopv(clients)
5456                             {
5457                                 clientinfo *cs = clients[i];
5458                                 if(cs->actortype > A_PLAYER || !cs->name[0] || !cs->online || cs->wantsmap || !cs->ready) continue;
5459                                 cs->updatetimeplayed();
5460                                 if(!best || cs->timeplayed > best->timeplayed) best = cs;
5461                             }
5462                             if(best)
5463                             {
5464                                 mapgameinfo = best->clientnum;
5465                                 srvoutf(4, "\fyRequesting game information from %s..", colourname(best));
5466                                 sendf(best->clientnum, 1, "ri", N_GETGAMEINFO);
5467                                 gamewaittime = totalmillis+G(waitforplayerinfo);
5468                                 gamestate = G_S_GAMEINFO;
5469                                 sendtick();
5470                                 break;
5471                             }
5472                         }
5473                         gamestate = G_S_PLAYING;
5474                         break;
5475                     }
5476                     case G_S_GAMEINFO:
5477                     {
5478                         if(!gamewaittime)
5479                         {
5480                             gamewaittime = totalmillis+G(waitforplayerinfo);
5481                             sendtick();
5482                         }
5483                         if(!hasgameinfo && gamewaittime > totalmillis) break;
5484                         if(hasgameinfo) srvoutf(4, "\fyGame information received, starting..");
5485                         else
5486                         {
5487                             if(mapgameinfo != -2)
5488                             {
5489                                 int asked = 0;
5490                                 mapgameinfo = -2;
5491                                 loopv(clients)
5492                                 {
5493                                     clientinfo *cs = clients[i];
5494                                     if(cs->actortype > A_PLAYER || !cs->name[0] || !cs->online || cs->wantsmap || !cs->ready || cs->clientnum == mapgameinfo) continue;
5495                                     sendf(cs->clientnum, 1, "ri", N_GETGAMEINFO);
5496                                     asked++;
5497                                 }
5498                                 if(!asked) srvoutf(4, "\fyNo game information response, and nobody to ask, giving up..");
5499                                 else
5500                                 {
5501                                     srvoutf(4, "\fyNo game information response, broadcasting..");
5502                                     gamewaittime = totalmillis+G(waitforplayerinfo);
5503                                     sendtick();
5504                                     break;
5505                                 }
5506                             }
5507                             else srvoutf(4, "\fyNo broadcast game information response, giving up..");
5508                         }
5509                         mapgameinfo = -1;
5510                     }
5511                     default: gamestate = G_S_PLAYING; break;
5512                 }
5513                 if(gamestate == G_S_PLAYING)
5514                 {
5515                     gamewaittime = 0;
5516                     if(m_team(gamemode, mutators)) doteambalance(true);
5517                     if(m_play(gamemode) && !m_bomber(gamemode) && !m_duke(gamemode, mutators)) // they do their own "fight"
5518                         sendf(-1, 1, "ri3s", N_ANNOUNCE, S_V_FIGHT, CON_EVENT, "Match start, fight!");
5519                     sendtick();
5520                 }
5521             }
5522             if(canplay() && !paused) gamemillis += curtime;
5523             if(m_demo(gamemode)) readdemo();
5524             else if(canplay() && !paused)
5525             {
5526                 processevents();
5527                 checkents();
5528                 checklimits();
5529                 checkclients();
5530                 if(smode) smode->update();
5531                 mutate(smuts, mut->update());
5532             }
5533             if(gs_intermission(gamestate) && gamewaittime <= totalmillis) startintermission(true); // wait then call for next map
5534             if(shouldcheckvotes) checkvotes();
5535         }
5536         else
5537         {
5538             if(servercheck(shutdownwait))
5539             {
5540                 srvoutf(4, "Server empty, shutting down as scheduled");
5541                 #ifdef STANDALONE
5542                 cleanupserver();
5543                 exit(EXIT_SUCCESS);
5544                 #else
5545                 quit();
5546                 #endif
5547                 return;
5548             }
5549             if(G(rotatecycle) && clocktime-lastrotatecycle >= G(rotatecycle)*60) cleanup();
5550         }
5551         aiman::checkai();
5552         auth::update();
5553     }
5554 
5555     int lastquerysort = 0;
querysort(const clientinfo * a,const clientinfo * b)5556     bool querysort(const clientinfo *a, const clientinfo *b)
5557     {
5558         if(a->points > b->points) return true;
5559         if(a->points < b->points) return false;
5560         return strcmp(a->name, b->name) < 0;
5561     }
5562     vector<clientinfo *> queryplayers;
5563 
clientconnect(int n,uint ip,bool local)5564     int clientconnect(int n, uint ip, bool local)
5565     {
5566         clientinfo *ci = (clientinfo *)getinfo(n);
5567         ci->clientnum = n;
5568         ci->connectmillis = totalmillis ? totalmillis : 1;
5569         ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF;
5570         ci->local = local;
5571         connects.add(ci);
5572         conoutf("%s peer connection attempt from %s [%d]", ci->local ? "Local" : "Remote", gethostip(ci->clientnum), ci->clientnum);
5573         if(!local && (m_local(gamemode) || servertype <= 0)) return DISC_PRIVATE;
5574         sendservinit(ci);
5575         return DISC_NONE;
5576     }
5577 
clientdisconnect(int n,bool local,int reason)5578     void clientdisconnect(int n, bool local, int reason)
5579     {
5580         clientinfo *ci = (clientinfo *)getinfo(n);
5581         bool complete = !numclients(n);
5582         if(local)
5583         {
5584             if(m_demo(gamemode)) enddemoplayback();
5585         }
5586         if(complete && ci->connected) sendstats();
5587         if(ci->steamid[0]) cdpi::steam::servercancelticket(ci->steamid);
5588         if(ci->connected)
5589         {
5590             if(reason != DISC_SHUTDOWN)
5591             {
5592                 aiman::removeai(ci, complete);
5593                 if(!complete)
5594                 {
5595                     aiman::poke();
5596                     swapteam(ci, ci->team);
5597                 }
5598                 savestatsscore(ci);
5599                 loopv(clients) if(clients[i] != ci)
5600                 {
5601                     loopvk(clients[i]->fraglog) if(clients[i]->fraglog[k] == ci->clientnum)
5602                         clients[i]->fraglog.remove(k--);
5603                 }
5604                 if(ci->privilege) auth::setprivilege(ci, -1);
5605                 if(smode) smode->leavegame(ci, true);
5606                 mutate(smuts, mut->leavegame(ci, true));
5607                 savescore(ci);
5608             }
5609             sendf(-1, 1, "ri3", N_DISCONNECT, n, reason);
5610             ci->connected = false;
5611             if(ci->name[0])
5612             {
5613                 int amt = numclients(ci->clientnum);
5614                 relayf(2, "\fo%s has left the game (%s, %d %s)", colourname(ci), reason >= 0 ? disc_reasons[reason] : "normal", amt, amt != 1 ? "players" : "player");
5615             }
5616             clients.removeobj(ci);
5617             queryplayers.removeobj(ci);
5618         }
5619         else connects.removeobj(ci);
5620         if(complete) cleanup();
5621         else shouldcheckvotes = true;
5622         if(n == mapsending)
5623         {
5624             if(hasmapdata()) mapsending = -1;
5625             else resetmapdata(true);
5626         }
5627         if(n == mapgameinfo) mapgameinfo = -1;
5628     }
5629 
clientsteamticket(const char * id,bool result)5630     void clientsteamticket(const char *id, bool result)
5631     {
5632         loopv(connects) if(connects[i]->clientnum >= 0)
5633         {
5634             clientinfo *ci = connects[i];
5635             if(!ci->connectsteam || strcmp(ci->authsteam, id)) continue;
5636             ci->connectsteam = false;
5637             if(result)
5638             {
5639                 copystring(ci->steamid, id);
5640                 srvmsgftforce(ci->clientnum, CON_EVENT, "\fgSteam identity confirmed (%s)", ci->steamid);
5641                 int disc = auth::allowconnect(ci);
5642                 if(disc) { disconnect_client(ci->clientnum, disc); return; }
5643                 if(!ci->connectauth) connected(ci);
5644                 break;
5645             }
5646             srvmsgftforce(ci->clientnum, CON_EVENT, "\frSteam identity could not be confirmed (%s)", id);
5647             if(cdpi::steam::serverauthmode() >= 2) disconnect_client(ci->clientnum, DISC_AUTH);
5648             else copystring(ci->steamid, "0");
5649             break;
5650         }
5651     }
5652 
queryreply(ucharbuf & req,ucharbuf & p)5653     void queryreply(ucharbuf &req, ucharbuf &p)
5654     {
5655         if(!getint(req)) return;
5656         if(!lastquerysort || totalmillis-lastquerysort >= G(queryinterval))
5657         {
5658             queryplayers.setsize(0);
5659             loopv(clients) if(clients[i]->clientnum >= 0 && clients[i]->name[0] && clients[i]->actortype == A_PLAYER) queryplayers.add(clients[i]);
5660             queryplayers.sort(querysort);
5661             lastquerysort = totalmillis ? totalmillis : 1;
5662         }
5663         putint(p, queryplayers.length());
5664         putint(p, 15); // number of attrs following
5665         putint(p, VERSION_GAME); // 1
5666         putint(p, gamemode); // 2
5667         putint(p, mutators); // 3
5668         putint(p, timeremaining); // 4
5669         putint(p, maxslots()); // 5
5670         putint(p, serverpass[0] || G(connectlock) ? MM_PASSWORD : (m_local(gamemode) ? MM_PRIVATE : mastermode)); // 6
5671         putint(p, numgamevars); // 7
5672         putint(p, numgamemods); // 8
5673         putint(p, versionmajor); // 9
5674         putint(p, versionminor); // 10
5675         putint(p, versionpatch); // 11
5676         putint(p, versionplatform); // 12
5677         putint(p, versionarch); // 13
5678         putint(p, gamestate); // 14
5679         putint(p, timeleft()); // 15
5680         sendstring(smapname, p);
5681         if(*G(serverdesc)) sendstring(limitstring(G(serverdesc), MAXSDESCLEN+1), p);
5682         else
5683         {
5684             #ifdef STANDALONE
5685             sendstring("", p);
5686             #else
5687             const char *cname = client::getname();
5688             if(!cname || !cname[0]) cname = "";
5689             sendstring(cname, p);
5690             #endif
5691         }
5692         sendstring(versionbranch, p);
5693         if(!queryplayers.empty())
5694         {
5695             loopv(queryplayers) sendstring(colourname(queryplayers[i]), p);
5696             loopv(queryplayers) sendstring(queryplayers[i]->handle, p);
5697         }
5698         sendqueryreply(p);
5699     }
5700 
receivefile(int sender,uchar * data,int len)5701     int receivefile(int sender, uchar *data, int len)
5702     {
5703         clientinfo *ci = (clientinfo *)getinfo(sender);
5704         ucharbuf p(data, len);
5705         int type = getint(p), n = getint(p), crc = getint(p);
5706         data += p.length();
5707         len -= p.length();
5708         if(type != N_SENDMAPFILE) return -1;
5709         if(n < 0 || n >= SENDMAP_MAX) return -1;
5710         if(ci->clientnum != mapsending) return -1;
5711         if(mapdata[n]) DELETEP(mapdata[n]);
5712         if(!len) return n; // zero len is no file
5713         defformatstring(fname, "backups/tempfile.%s", sendmaptypes[n]);
5714         mapdata[n] = opentempfile(fname, "w+b");
5715         if(!mapdata[n])
5716         {
5717             srvmsgf(-1, "Failed to open temporary file for map");
5718             return n;
5719         }
5720         mapdata[n]->write(data, len);
5721         if(n == SENDMAP_MPZ)
5722         {
5723             smapcrc = crcstream(mapdata[n]);
5724             if(crc != smapcrc)
5725             {
5726                 if(m_edit(gamemode)) ci->clientcrc = smapcrc;
5727                 else srvmsgf(-1, "Warning: new crc 0x%.8x doesn't match client 0x%.8x [0x%.8x]", smapcrc, crc, ci->clientcrc);
5728             }
5729         }
5730         return n;
5731     }
5732 
5733     static struct msgfilter
5734     {
5735         uchar msgmask[NUMMSG];
5736 
msgfilterserver::msgfilter5737         msgfilter(int msg, ...)
5738         {
5739             memset(msgmask, 0, sizeof(msgmask));
5740             va_list msgs;
5741             va_start(msgs, msg);
5742             for(uchar val = 1; msg < NUMMSG; msg = va_arg(msgs, int))
5743             {
5744                 if(msg < 0) val = uchar(-msg);
5745                 else msgmask[msg] = val;
5746             }
5747             va_end(msgs);
5748         }
5749 
operator []server::msgfilter5750         uchar operator[](int msg) const { return msg >= 0 && msg < NUMMSG ? msgmask[msg] : 0; }
5751     } msgfilter(-1, N_CONNECT, N_SERVERINIT, N_CLIENTINIT, N_WELCOME, N_MAPCHANGE, N_SERVMSG, N_DAMAGE, N_SHOTFX, N_LOADOUT, N_DIED, N_POINTS, N_SPAWNSTATE, N_ITEMACC, N_ITEMSPAWN, N_TICK, N_DISCONNECT, N_CURRENTPRIV, N_PONG, N_SCOREAFFIN, N_SCORE, N_ANNOUNCE, N_SENDDEMOLIST, N_SENDDEMO, N_DEMOPLAYBACK, N_SENDMAP, N_REGEN, N_CLIENT, N_AUTHCHAL, N_QUEUEPOS, N_STEAMCHAL, -2, N_REMIP, N_NEWMAP, N_CLIPBOARD, -3, N_EDITENT, N_EDITLINK, N_EDITVAR, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, N_DELCUBE, N_EDITVSLOT, N_UNDO, N_REDO, -4, N_POS, N_SPAWN, N_DESTROY, NUMMSG),
5752       connectfilter(-1, N_CONNECT, -2, N_AUTHANS, N_STEAMANS, N_STEAMFAIL, -3, N_PING, NUMMSG);
5753 
checktype(int type,clientinfo * ci)5754     int checktype(int type, clientinfo *ci)
5755     {
5756         if(ci)
5757         {
5758             if(!ci->connected) switch(connectfilter[type])
5759             {
5760                 // allow only before authconnect
5761                 case 1: return !ci->connectauth && !ci->connectsteam ? type : -1;
5762                 // allow only during authconnect
5763                 case 2: return ci->connectauth || ci->connectsteam ? type : -1;
5764                 // always allow
5765                 case 3: return type;
5766                 // never allow
5767                 default: return -1;
5768             }
5769             if(ci->local) return type;
5770         }
5771         switch(msgfilter[type])
5772         {
5773             // server-only messages
5774             case 1: return ci ? -1 : type;
5775             // only allowed in coop-edit
5776             case 2: if(m_edit(gamemode) && ci && ci->state == CS_EDITING) break; return -1;
5777             // only allowed in coop-edit, no overflow check
5778             case 3: return m_edit(gamemode) && ci && ci->state == CS_EDITING ? type : -1;
5779             // no overflow check
5780             case 4: return type;
5781         }
5782         if(ci && !haspriv(ci, G(overflowlock)) && ++ci->overflow >= G(overflowsize)) return -2;
5783         return type;
5784     }
5785 
5786     struct worldstate
5787     {
5788         int uses, len;
5789         uchar *data;
5790 
worldstateserver::worldstate5791         worldstate() : uses(0), len(0), data(NULL) {}
5792 
setupserver::worldstate5793         void setup(int n) { len = n; data = new uchar[n]; }
cleanupserver::worldstate5794         void cleanup() { DELETEA(data); len = 0; }
containsserver::worldstate5795         bool contains(const uchar *p) const { return p >= data && p < &data[len]; }
5796     };
5797     vector<worldstate> worldstates;
5798     bool reliablemessages = false;
5799 
cleanworldstate(ENetPacket * packet)5800     void cleanworldstate(ENetPacket *packet)
5801     {
5802         loopv(worldstates)
5803         {
5804             worldstate &ws = worldstates[i];
5805             if(!ws.contains(packet->data)) continue;
5806             ws.uses--;
5807             if(ws.uses <= 0)
5808             {
5809                 ws.cleanup();
5810                 worldstates.removeunordered(i);
5811             }
5812             break;
5813         }
5814     }
5815 
sendpositions(worldstate & ws,ucharbuf & wsbuf)5816     static void sendpositions(worldstate &ws, ucharbuf &wsbuf)
5817     {
5818         if(wsbuf.empty()) return;
5819         int wslen = wsbuf.length();
5820         recordpacket(0, wsbuf.buf, wslen);
5821         wsbuf.put(wsbuf.buf, wslen);
5822         loopv(clients)
5823         {
5824             clientinfo &ci = *clients[i];
5825             if(ci.actortype != A_PLAYER) continue;
5826             uchar *data = wsbuf.buf;
5827             int size = wslen;
5828             if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; }
5829             if(size <= 0) continue;
5830             ENetPacket *packet = enet_packet_create(data, size, ENET_PACKET_FLAG_NO_ALLOCATE);
5831             sendpacket(ci.clientnum, 0, packet);
5832             if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; }
5833             else enet_packet_destroy(packet);
5834         }
5835         wsbuf.offset(wsbuf.length());
5836     }
5837 
addposition(worldstate & ws,ucharbuf & wsbuf,int mtu,clientinfo & bi,clientinfo & ci)5838     static inline void addposition(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci)
5839     {
5840         if(bi.position.empty()) return;
5841         if(wsbuf.length() + bi.position.length() > mtu) sendpositions(ws, wsbuf);
5842         int offset = wsbuf.length();
5843         wsbuf.put(bi.position.getbuf(), bi.position.length());
5844         bi.position.setsize(0);
5845         int len = wsbuf.length() - offset;
5846         if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; }
5847         else ci.wslen += len;
5848     }
5849 
sendmessages(worldstate & ws,ucharbuf & wsbuf)5850     static void sendmessages(worldstate &ws, ucharbuf &wsbuf)
5851     {
5852         if(wsbuf.empty()) return;
5853         int wslen = wsbuf.length();
5854         recordpacket(1, wsbuf.buf, wslen);
5855         wsbuf.put(wsbuf.buf, wslen);
5856         loopv(clients)
5857         {
5858             clientinfo &ci = *clients[i];
5859             if(ci.actortype != A_PLAYER) continue;
5860             uchar *data = wsbuf.buf;
5861             int size = wslen;
5862             if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; }
5863             if(size <= 0) continue;
5864             ENetPacket *packet = enet_packet_create(data, size, (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE);
5865             sendpacket(ci.clientnum, 1, packet);
5866             if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; }
5867             else enet_packet_destroy(packet);
5868         }
5869         wsbuf.offset(wsbuf.length());
5870     }
5871 
addmessages(worldstate & ws,ucharbuf & wsbuf,int mtu,clientinfo & bi,clientinfo & ci)5872     static inline void addmessages(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci)
5873     {
5874         if(bi.messages.empty()) return;
5875         if(wsbuf.length() + 10 + bi.messages.length() > mtu) sendmessages(ws, wsbuf);
5876         int offset = wsbuf.length();
5877         putint(wsbuf, N_CLIENT);
5878         putint(wsbuf, bi.clientnum);
5879         putuint(wsbuf, bi.messages.length());
5880         wsbuf.put(bi.messages.getbuf(), bi.messages.length());
5881         bi.messages.setsize(0);
5882         int len = wsbuf.length() - offset;
5883         if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; }
5884         else ci.wslen += len;
5885     }
5886 
buildworldstate()5887     bool buildworldstate()
5888     {
5889         int wsmax = 0;
5890         loopv(clients)
5891         {
5892             clientinfo &ci = *clients[i];
5893             ci.overflow = 0;
5894             ci.wsdata = NULL;
5895             wsmax += ci.position.length();
5896             if(ci.messages.length()) wsmax += 10 + ci.messages.length();
5897         }
5898         if(wsmax <= 0)
5899         {
5900             reliablemessages = false;
5901             return false;
5902         }
5903         worldstate &ws = worldstates.add();
5904         ws.setup(2*wsmax);
5905         int mtu = getservermtu() - 100;
5906         if(mtu <= 0) mtu = ws.len;
5907         ucharbuf wsbuf(ws.data, ws.len);
5908         loopv(clients)
5909         {
5910             clientinfo &ci = *clients[i];
5911             if(ci.actortype != A_PLAYER) continue;
5912             addposition(ws, wsbuf, mtu, ci, ci);
5913             loopvj(ci.bots) addposition(ws, wsbuf, mtu, *ci.bots[j], ci);
5914         }
5915         sendpositions(ws, wsbuf);
5916         loopv(clients)
5917         {
5918             clientinfo &ci = *clients[i];
5919             if(ci.actortype != A_PLAYER) continue;
5920             addmessages(ws, wsbuf, mtu, ci, ci);
5921             loopvj(ci.bots) addmessages(ws, wsbuf, mtu, *ci.bots[j], ci);
5922         }
5923         sendmessages(ws, wsbuf);
5924         reliablemessages = false;
5925         if(ws.uses) return true;
5926         ws.cleanup();
5927         worldstates.drop();
5928         return false;
5929     }
5930 
sendpackets(bool force)5931     bool sendpackets(bool force)
5932     {
5933         if(clients.empty() || (!hasnonlocalclients() && !demorecord)) return false;
5934         enet_uint32 millis = enet_time_get()-lastsend;
5935         if(millis < 40 && !force) return false;
5936         bool flush = buildworldstate();
5937         lastsend += millis - (millis%40);
5938         return flush;
5939     }
5940 
sendclipboard(clientinfo * ci)5941     void sendclipboard(clientinfo *ci)
5942     {
5943         if(!ci->lastclipboard || !ci->clipboard) return;
5944         bool flushed = false;
5945         loopv(clients)
5946         {
5947             clientinfo &e = *clients[i];
5948             if(e.clientnum != ci->clientnum && e.needclipboard < ci->lastclipboard)
5949             {
5950                 if(!flushed) { flushserver(true); flushed = true; }
5951                 sendpacket(e.clientnum, 1, ci->clipboard);
5952                 e.needclipboard = ci->lastclipboard;
5953             }
5954         }
5955     }
5956 
connected(clientinfo * ci)5957     void connected(clientinfo *ci)
5958     {
5959         if(!m_demo(gamemode) && !numclients() && demonextmatch) setupdemorecord();
5960 
5961         connects.removeobj(ci);
5962         clients.add(ci);
5963 
5964         ci->connected = true;
5965         ci->needclipboard = 0;
5966         ci->lasttimeplayed = totalmillis ? totalmillis : 1;
5967         ci->lasttimealive = totalmillis ? totalmillis : 1;
5968         ci->lasttimeactive = totalmillis ? totalmillis : 1;
5969         ci->lasttimewielded = totalmillis ? totalmillis : 1;
5970         loopi(W_MAX) ci->lasttimeloadout[i] = totalmillis ? totalmillis : 1;
5971 
5972         if(ci->handle[0]) // kick old logins
5973         {
5974             loopvrev(clients) if(clients[i] != ci && clients[i]->handle[0] && !strcmp(clients[i]->handle, ci->handle))
5975                 disconnect_client(clients[i]->clientnum, DISC_AUTH);
5976         }
5977         if(ci->steamid[0])
5978         {
5979             loopvrev(clients) if(clients[i] != ci && clients[i]->steamid[0] && !strcmp(clients[i]->steamid, ci->steamid))
5980                 disconnect_client(clients[i]->clientnum, DISC_AUTH);
5981         }
5982         sendwelcome(ci);
5983         if(restorescore(ci)) sendresume(ci);
5984         sendinitclient(ci);
5985         int amt = numclients();
5986         if((ci->privilege&PRIV_TYPE) > PRIV_NONE)
5987         {
5988             if(ci->handle[0]) relayf(2, "\fg%s has joined the game (\fs\fy%s\fS: \fs\fc%s\fS) [%d.%d.%d-%s%d-%s] (%d %s)", colourname(ci), privname(ci->privilege), ci->handle, ci->version.major, ci->version.minor, ci->version.patch, plat_name(ci->version.platform), ci->version.arch, ci->version.branch, amt, amt != 1 ? "players" : "player");
5989             else relayf(2, "\fg%s has joined the game (\fs\fy%s\fS) [%d.%d.%d-%s%d-%s] (%d %s)", colourname(ci), privname(ci->privilege), ci->version.major, ci->version.minor, ci->version.patch, plat_name(ci->version.platform), ci->version.arch, ci->version.branch, amt, amt != 1 ? "players" : "player");
5990         }
5991         else relayf(2, "\fg%s has joined the game [%d.%d.%d-%s%d-%s] (%d %s)", colourname(ci), ci->version.major, ci->version.minor, ci->version.patch, plat_name(ci->version.platform), ci->version.arch, ci->version.branch, amt, amt != 1 ? "players" : "player");
5992 
5993         if(m_demo(gamemode)) setupdemoplayback();
5994         else if(m_edit(gamemode))
5995         {
5996             ci->ready = true;
5997             aiman::poke();
5998         }
5999     }
6000 
parsepacket(int sender,int chan,packetbuf & p)6001     void parsepacket(int sender, int chan, packetbuf &p)     // has to parse exactly each byte of the packet
6002     {
6003         if(sender < 0 || p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED || chan > 2) return;
6004         char text[MAXTRANS];
6005         int type = -1, prevtype = -1;
6006         clientinfo *ci = sender >= 0 ? (clientinfo *)getinfo(sender) : NULL;
6007         if(ci && !ci->connected)
6008         {
6009             if(chan == 0) return;
6010             else if(chan != 1)
6011             {
6012                 conoutf("\fy[msg error] from: %d, chan: %d while connecting", sender, chan);
6013                 disconnect_client(sender, DISC_MSGERR);
6014                 return;
6015             }
6016             else while(p.length() < p.maxlen)
6017             {
6018                 int curtype = getint(p);
6019                 prevtype = type;
6020                 switch(type = checktype(curtype, ci))
6021                 {
6022                     case N_CONNECT:
6023                     {
6024                         getstring(text, p);
6025                         stringz(namestr);
6026                         filterstring(namestr, text, true, true, true, true, MAXNAMELEN);
6027                         if(!*namestr) copystring(namestr, "unnamed");
6028                         copystring(ci->name, namestr, MAXNAMELEN+1);
6029                         ci->colour = max(getint(p), 0);
6030                         ci->model = max(getint(p), 0);
6031                         ci->pattern = max(getint(p), 0);
6032                         getstring(text, p);
6033                         ci->setvanity(text);
6034                         int lw = getint(p);
6035                         ci->loadweap.shrink(0);
6036                         loopk(lw)
6037                         {
6038                             if(k >= W_LOADOUT) getint(p);
6039                             else ci->loadweap.add(getint(p));
6040                         }
6041                         int rw = getint(p);
6042                         ci->randweap.shrink(0);
6043                         loopk(rw)
6044                         {
6045                             if(k >= W_LOADOUT) getint(p);
6046                             else ci->randweap.add(getint(p));
6047                         }
6048 
6049                         stringz(password);
6050                         stringz(authname);
6051                         getstring(password, p);
6052                         getstring(text, p);
6053                         filterstring(authname, text, true, true, true, true, 100);
6054 
6055                         ci->version.get(p);
6056 
6057                         int disc = auth::allowconnect(ci, authname, password);
6058                         if(disc)
6059                         {
6060                             disconnect_client(sender, disc);
6061                             return;
6062                         }
6063 
6064                         if(!ci->connectauth && !ci->connectsteam) connected(ci);
6065 
6066                         break;
6067                     }
6068 
6069                     case N_AUTHANS:
6070                     {
6071                         uint id = (uint)getint(p);
6072                         getstring(text, p);
6073                         if(!auth::answerchallenge(ci, id, text)) auth::authfailed(ci->authreq);
6074                         break;
6075                     }
6076 
6077                     case N_STEAMANS:
6078                     {
6079                         getstring(text, p);
6080                         uint tokenlen = getuint(p);
6081                         if(tokenlen <= 0 || tokenlen > (1<<16))
6082                         {
6083                             if(tokenlen > 0) p.subbuf(tokenlen);
6084                             tokenlen = 0;
6085                         }
6086                         if(tokenlen < 0 || !ci->connectsteam) break;
6087                         const uchar *token = p.subbuf(tokenlen).buf;
6088                         if(cdpi::steam::serverparseticket(text, token, tokenlen))
6089                         {
6090                             srvmsgftforce(ci->clientnum, CON_EVENT, "\fySteam identity in progress (%s [%u])", text, tokenlen);
6091                             copystring(ci->authsteam, text);
6092                         }
6093                         else srvmsgftforce(ci->clientnum, CON_EVENT, "\foSteam identity could not be verified! (%s [%u])", text, tokenlen);
6094                         break;
6095                     }
6096 
6097                     case N_STEAMFAIL:
6098                     {
6099                         if(!ci->connectsteam) break;
6100                         copystring(ci->steamid, "0");
6101                         srvmsgftforce(ci->clientnum, CON_EVENT, "\foSteam identity could not be verified!");
6102                         ci->connectsteam = false;
6103                         int disc = auth::allowconnect(ci);
6104                         if(disc) { disconnect_client(ci->clientnum, disc); return; }
6105                         if(!ci->connectauth) connected(ci);
6106                         break;
6107                     }
6108 
6109                     case N_PING:
6110                         getint(p);
6111                         break;
6112 
6113                     default:
6114                         conoutf("\fy[msg error] from: %d, cur: %d, msg: %d, prev: %d", sender, curtype, type, prevtype);
6115                         disconnect_client(sender, DISC_MSGERR);
6116                         return;
6117                 }
6118             }
6119             return;
6120         }
6121         else if(chan == 2)
6122         {
6123             int ret = receivefile(sender, p.buf, p.maxlen);
6124             if(ret == SENDMAP_ALL)
6125             {
6126                 clientinfo *cs = (clientinfo *)getinfo(mapsending);
6127                 if(hasmapdata())
6128                 {
6129                     if(cs && !hasgameinfo && !gs_waiting(gamestate)) sendf(cs->clientnum, 1, "ri", N_GETGAMEINFO);
6130                     mapsending = -1;
6131                     sendf(-1, 1, "ri", N_SENDMAP);
6132                     loopv(clients)
6133                     {
6134                         clientinfo *cs = clients[i];
6135                         if(cs->actortype > A_PLAYER || !cs->online || !cs->name[0] || !cs->ready) continue;
6136                         if(cs->wantsmap || crclocked(cs, true)) getmap(cs);
6137                     }
6138                 }
6139                 else
6140                 {
6141                     if(cs) cs->wantsmap = true;
6142                     resetmapdata(true);
6143                 }
6144             }
6145             return;
6146         }
6147         if(p.packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true;
6148         #define QUEUE_MSG { if(ci && (!ci->local || demorecord || hasnonlocalclients())) while(curmsg < p.length()) ci->messages.add(p.buf[curmsg++]); }
6149         #define QUEUE_BUF(body) { \
6150             if(ci && (!ci->local || demorecord || hasnonlocalclients())) \
6151             { \
6152                 curmsg = p.length(); \
6153                 { body; } \
6154             } \
6155         }
6156         #define QUEUE_INT(n) QUEUE_BUF(putint(ci->messages, n))
6157         #define QUEUE_UINT(n) QUEUE_BUF(putuint(ci->messages, n))
6158         #define QUEUE_FLT(n) QUEUE_BUF(putfloat(ci->messages, n))
6159         #define QUEUE_STR(text) QUEUE_BUF(sendstring(text, ci->messages))
6160 
6161         int curmsg;
6162         while((curmsg = p.length()) < p.maxlen)
6163         {
6164             int curtype = getint(p);
6165             prevtype = type;
6166             switch(type = checktype(curtype, ci))
6167             {
6168                 case N_POS:
6169                 {
6170                     int lcn = getuint(p);
6171                     if(lcn < 0)
6172                     {
6173                         disconnect_client(sender, DISC_CN);
6174                         return;
6175                     }
6176 
6177                     bool havecn = true;
6178                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6179                     if(!hasclient(cp, ci)) havecn = false;
6180                     getuint(p);
6181                     getuint(p);
6182                     uint flags = getuint(p);
6183                     vec pos, floorpos, vel, falling;
6184                     float yaw, pitch, roll;
6185                     loopk(3)
6186                     {
6187                         int n = p.get();
6188                         n |= p.get()<<8;
6189                         if(flags&(1<<k))
6190                         {
6191                             n |= p.get()<<16;
6192                             if(n&0x800000) n |= ~0U<<24;
6193                         }
6194                         pos[k] = n/DMF;
6195                     }
6196                     loopk(3)
6197                     {
6198                         int n = p.get();
6199                         n |= p.get()<<8;
6200                         if(flags&(1<<(k+3)))
6201                         {
6202                             n |= p.get()<<16;
6203                             if(n&0x800000) n |= ~0U<<24;
6204                         }
6205                         floorpos[k] = n/DMF;
6206                     }
6207                     int dir = p.get();
6208                     dir |= p.get()<<8;
6209                     yaw = dir%360;
6210                     pitch = clamp(dir/360, 0, 180)-90;
6211                     roll = clamp(int(p.get()), 0, 180)-90;
6212                     int mag = p.get();
6213                     if(flags&(1<<6)) mag |= p.get()<<8;
6214                     dir = p.get();
6215                     dir |= p.get()<<8;
6216                     vecfromyawpitch(dir%360, clamp(dir/360, 0, 180)-90, 1, 0, vel);
6217                     vel.mul(mag/DVELF);
6218                     if(flags&(1<<7))
6219                     {
6220                         mag = p.get();
6221                         if(flags&(1<<8)) mag |= p.get()<<8;
6222                         if(flags&(1<<9))
6223                         {
6224                             dir = p.get();
6225                             dir |= p.get()<<8;
6226                             vecfromyawpitch(dir%360, clamp(dir/360, 0, 180)-90, 1, 0, falling);
6227                         }
6228                         else falling = vec(0, 0, -1);
6229                         falling.mul(mag/DVELF);
6230                     }
6231                     else falling = vec(0, 0, 0);
6232                     if(havecn)
6233                     {
6234                         vec oldpos = cp->o;
6235                         cp->o = pos;
6236                         cp->floorpos = floorpos;
6237                         cp->vel = vel;
6238                         cp->falling = falling;
6239                         cp->yaw = yaw;
6240                         cp->pitch = pitch;
6241                         cp->roll = roll;
6242                         if((!ci->local || demorecord || hasnonlocalclients()) && (cp->state == CS_ALIVE || cp->state == CS_EDITING))
6243                         {
6244                             cp->position.setsize(0);
6245                             while(curmsg < p.length()) cp->position.add(p.buf[curmsg++]);
6246                         }
6247                         if(cp->state == CS_ALIVE)
6248                         {
6249                             if(smode) smode->moved(cp, oldpos, cp->o);
6250                             mutate(smuts, mut->moved(cp, oldpos, cp->o));
6251                         }
6252                     }
6253                     break;
6254                 }
6255 
6256                 case N_SPHY:
6257                 {
6258                     int lcn = getint(p), idx = getint(p);
6259                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6260                     bool proceed = hasclient(cp, ci), qmsg = false;
6261                     switch(idx)
6262                     {
6263                         case SPHY_BOOST: case SPHY_POUND: case SPHY_SLIDE: case SPHY_MELEE: case SPHY_KICK: case SPHY_GRAB: case SPHY_PARKOUR: case SPHY_AFTER:
6264                         {
6265                             if(!proceed || cp->state != CS_ALIVE) break;
6266                             qmsg = true;
6267                             break;
6268                         }
6269                         case SPHY_MATERIAL:
6270                         {
6271                             int inmaterial = getint(p);
6272                             float submerged = getfloat(p);
6273                             if(!proceed) break;
6274                             cp->inmaterial = inmaterial;
6275                             cp->submerged = submerged;
6276                             if((cp->inmaterial&MATF_VOLUME) == MAT_WATER && cp->burning(gamemillis, ci->burntime) && cp->submerged >= G(liquidextinguish))
6277                             {
6278                                 cp->lastres[W_R_BURN] = cp->lastrestime[W_R_BURN] = 0;
6279                                 sendf(-1, 1, "ri3", N_SPHY, cp->clientnum, SPHY_EXTINGUISH);
6280                             }
6281                             if(cp->state == CS_ALIVE && (cp->inmaterial&MATF_FLAGS)&MAT_DEATH)
6282                             {
6283                                 suicideevent ev;
6284                                 ev.flags = HIT(MATERIAL);
6285                                 ev.material = cp->inmaterial;
6286                                 ev.process(cp); // process death immediately
6287                             }
6288                             break; // does not get sent to clients
6289                         }
6290                         default: break;
6291                     }
6292                     if(qmsg) QUEUE_MSG;
6293                     break;
6294                 }
6295 
6296                 case N_EDITMODE:
6297                 {
6298                     int val = getint(p);
6299                     if(!ci || ci->actortype > A_PLAYER) break;
6300                     if(!allowstate(ci, val ? ALST_EDIT : ALST_WALK, G(editlock)))
6301                     {
6302                         srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s unable to switch state - %d [%d, %d]", colourname(ci), ci->state, ci->lastdeath, gamemillis);
6303                         spectator(ci);
6304                         break;
6305                     }
6306                     ci->editspawn(gamemode, mutators);
6307                     if(val)
6308                     {
6309                         if(smode) smode->leavegame(ci);
6310                         mutate(smuts, mut->leavegame(ci));
6311                         ci->state = CS_EDITING;
6312                         ci->events.deletecontents();
6313                     }
6314                     else
6315                     {
6316                         ci->state = CS_ALIVE;
6317                         if(smode) smode->entergame(ci);
6318                         mutate(smuts, mut->entergame(ci));
6319                     }
6320                     QUEUE_MSG;
6321                     break;
6322                 }
6323 
6324                 case N_MAPCRC:
6325                 {
6326                     getstring(text, p);
6327                     int crc = getint(p);
6328                     if(!ci) break;
6329                     copystring(ci->clientmap, text);
6330                     ci->clientcrc = crc;
6331                     ci->ready = true;
6332                     ci->wantsmap = ci->gettingmap = false;
6333                     if(!m_edit(gamemode))
6334                     {
6335                         if(hasmapdata()) srvoutf(4, "\fy%s has map crc: \fs\fc0x%.8x\fS (server: \fs\fc0x%.8x\fS)", colourname(ci), ci->clientcrc, smapcrc);
6336                         else srvoutf(4, "\fy%s has map crc: \fs\fc0x%.8x\fS", colourname(ci), ci->clientcrc);
6337                     }
6338                     if(crclocked(ci, true)) getmap(ci);
6339                     if(ci->isready()) aiman::poke();
6340                     break;
6341                 }
6342 
6343                 case N_TRYSPAWN:
6344                 {
6345                     int lcn = getint(p);
6346                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6347                     if(!hasclient(cp, ci)) break;
6348                     if(!allowstate(cp, ALST_TRY, m_edit(gamemode) ? G(spawneditlock) : G(spawnlock)))
6349                     {
6350                         srvmsgftforce(cp->clientnum, CON_DEBUG, "Sync error: %s unable to spawn - %d [%d, %d]", colourname(cp), cp->state, cp->lastdeath, gamemillis);
6351                         break;
6352                     }
6353                     int nospawn = 0;
6354                     if(smode && !smode->canspawn(cp, true)) { nospawn++; }
6355                     mutate(smuts, if(!mut->canspawn(cp, true)) { nospawn++; });
6356                     if(!nospawn)
6357                     {
6358                         cp->state = CS_DEAD;
6359                         waiting(cp, DROP_RESET);
6360                     }
6361                     break;
6362                 }
6363 
6364                 case N_WEAPSELECT:
6365                 {
6366                     int lcn = getint(p), id = getint(p), weap = getint(p);
6367                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6368                     if(!hasclient(cp, ci) || !isweap(weap) || weap >= W_ALL || cp->needsresume) break;
6369                     switchevent *ev = new switchevent;
6370                     ev->id = id;
6371                     ev->weap = weap;
6372                     ev->millis = cp->getmillis(gamemillis, ev->id);
6373                     cp->addevent(ev);
6374                     break;
6375                 }
6376 
6377                 case N_WEAPCOOK:
6378                 {
6379                     int lcn = getint(p), id = getint(p), weap = getint(p), etype = getint(p), offtime = getint(p);
6380                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6381                     if(!hasclient(cp, ci) || !isweap(weap) || weap >= W_ALL || cp->needsresume) break;
6382                     cookevent *ev = new cookevent;
6383                     ev->id = id;
6384                     ev->weap = weap;
6385                     ev->etype = etype;
6386                     ev->offtime = offtime;
6387                     ev->millis = cp->getmillis(gamemillis, ev->id);
6388                     cp->addevent(ev);
6389                     break;
6390                 }
6391 
6392                 case N_SPAWN:
6393                 {
6394                     int lcn = getint(p);
6395                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6396                     if(!hasclient(cp, ci)) break;
6397                     if(!allowstate(cp, ALST_SPAWN))
6398                     {
6399                         srvmsgftforce(cp->clientnum, CON_DEBUG, "Sync error: %s unable to spawn - %d [%d, %d]", colourname(cp), cp->state, cp->lastdeath, gamemillis);
6400                         break;
6401                     }
6402                     cp->updatetimeplayed();
6403                     cp->state = CS_ALIVE;
6404                     if(smode) smode->spawned(cp);
6405                     mutate(smuts, mut->spawned(cp););
6406                     QUEUE_BUF({
6407                         putint(ci->messages, N_SPAWN);
6408                         putint(ci->messages, cp->clientnum);
6409                         sendstate(cp, ci->messages);
6410                     });
6411                     break;
6412                 }
6413 
6414                 case N_RESUME:
6415                 {
6416                     int lcn = getint(p);
6417                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6418                     if(!hasclient(cp, ci) || !cp->needsresume) break;
6419                     cp->needsresume = false;
6420                     break;
6421                 }
6422 
6423                 case N_SUICIDE:
6424                 {
6425                     int lcn = getint(p), flags = getint(p), material = getint(p);
6426                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6427                     if(!hasclient(cp, ci)) break;
6428                     suicideevent ev;
6429                     ev.flags = flags;
6430                     cp->inmaterial = ev.material = material;
6431                     ev.process(cp); // process death immediately
6432                     break;
6433                 }
6434 
6435                 case N_SHOOT:
6436                 {
6437                     int lcn = getint(p);
6438                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6439                     bool havecn = hasclient(cp, ci) && !cp->needsresume;
6440                     shotevent *ev = new shotevent;
6441                     ev->id = getint(p);
6442                     ev->weap = getint(p);
6443                     ev->flags = getint(p);
6444                     ev->scale = getint(p);
6445                     ev->target = getint(p);
6446                     if(!isweap(ev->weap)) havecn = false;
6447                     else
6448                     {
6449                         ev->scale = clamp(ev->scale, 0, W2(ev->weap, cooktime, WS(ev->flags)));
6450                         if(havecn) ev->millis = cp->getmillis(gamemillis, ev->id);
6451                     }
6452                     loopk(3) ev->from[k] = getint(p);
6453                     loopk(3) ev->dest[k] = getint(p);
6454                     ev->num = getint(p);
6455                     loopj(ev->num)
6456                     {
6457                         if(p.overread()) break;
6458                         if(j >= MAXPARAMS || !havecn)
6459                         {
6460                             loopk(4) getint(p);
6461                             continue;
6462                         }
6463                         shotmsg &s = ev->shots.add();
6464                         s.id = getint(p);
6465                         loopk(3) s.pos[k] = getint(p);
6466                     }
6467                     if(havecn)
6468                     {
6469                         int rays = min(W2(ev->weap, rays, WS(ev->flags)), MAXPARAMS);
6470                         if(rays > 1 && W2(ev->weap, cooktime, WS(ev->flags))) rays = int(ceilf(rays*ev->scale/float(W2(ev->weap, cooktime, WS(ev->flags)))));
6471                         while(ev->shots.length() > rays) ev->shots.remove(rnd(ev->shots.length()));
6472                         cp->addevent(ev);
6473                         cp->lastshoot = gamemillis;
6474                     }
6475                     else delete ev;
6476                     break;
6477                 }
6478 
6479                 case N_WEAPDROP:
6480                 {
6481                     int lcn = getint(p), id = getint(p), weap = getint(p);
6482                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6483                     if(!hasclient(cp, ci) || cp->needsresume) break;
6484                     dropevent *ev = new dropevent;
6485                     ev->id = id;
6486                     ev->weap = weap;
6487                     ev->millis = cp->getmillis(gamemillis, ev->id);
6488                     cp->events.add(ev);
6489                     break;
6490                 }
6491 
6492                 case N_RELOAD:
6493                 {
6494                     int lcn = getint(p), id = getint(p), weap = getint(p);
6495                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6496                     if(!hasclient(cp, ci) || cp->needsresume) break;
6497                     reloadevent *ev = new reloadevent;
6498                     ev->id = id;
6499                     ev->weap = weap;
6500                     ev->millis = cp->getmillis(gamemillis, ev->id);
6501                     cp->events.add(ev);
6502                     break;
6503                 }
6504 
6505                 case N_DESTROY:
6506                 {
6507                     int lcn = getint(p), millis = getint(p);
6508                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6509                     bool havecn = hasclient(cp, ci);
6510                     destroyevent *ev = new destroyevent;
6511                     ev->type = getint(p);
6512                     if(ev->type != PRJ_SHOT && ev->type != PRJ_ENT) havecn = false;
6513                     ev->weap = getint(p);
6514                     ev->fromweap = getint(p);
6515                     ev->fromflags = getint(p);
6516                     ev->flags = getint(p);
6517                     if(havecn) ev->millis = cp->getmillis(gamemillis, millis);
6518                     ev->id = getint(p);
6519                     ev->radial = getint(p);
6520                     ev->scale = getint(p);
6521                     int hits = getint(p);
6522                     loopj(hits)
6523                     {
6524                         if(p.overread()) break;
6525                         static hitset dummy;
6526                         hitset &hit = havecn && j < 100 ? ev->hits.add() : dummy;
6527                         hit.flags = getint(p);
6528                         hit.proj = getint(p);
6529                         hit.target = getint(p);
6530                         hit.dist = max(getint(p), 0);
6531                         loopk(3) hit.dir[k] = getint(p);
6532                         loopk(3) hit.vel[k] = getint(p);
6533                     }
6534                     if(havecn) cp->events.add(ev);
6535                     else delete ev;
6536                     break;
6537                 }
6538 
6539                 case N_STICKY:
6540                 {
6541                     int lcn = getint(p), millis = getint(p);
6542                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6543                     bool havecn = hasclient(cp, ci);
6544                     stickyevent *ev = new stickyevent;
6545                     ev->weap = getint(p);
6546                     ev->flags = getint(p);
6547                     if(havecn) ev->millis = cp->getmillis(gamemillis, millis);
6548                     ev->id = getint(p);
6549                     ev->target = getint(p);
6550                     loopk(3) ev->norm[k] = getint(p);
6551                     loopk(3) ev->pos[k] = getint(p);
6552                     if(havecn) cp->events.add(ev);
6553                     else delete ev;
6554                     break;
6555                 }
6556 
6557                 case N_ITEMUSE:
6558                 {
6559                     int lcn = getint(p), id = getint(p), cn = getint(p), ent = getint(p);
6560                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6561                     if(!hasclient(cp, ci) || cp->needsresume) break;
6562                     useevent *ev = new useevent;
6563                     ev->id = id;
6564                     ev->cn = cn;
6565                     ev->ent = ent;
6566                     ev->millis = cp->getmillis(gamemillis, ev->id);
6567                     cp->events.add(ev);
6568                     break;
6569                 }
6570 
6571                 case N_TRIGGER:
6572                 {
6573                     int lcn = getint(p), ent = getint(p);
6574                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6575                     if(!hasclient(cp, ci) || cp->state != CS_ALIVE) break;
6576                     if(!sents.inrange(ent))
6577                     {
6578                         srvmsgftforce(cp->clientnum, CON_DEBUG, "Sync error: %s cannot trigger %d - entity does not exist (max: %d)", colourname(cp), ent, sents.length());
6579                         break;
6580                     }
6581                     if(sents[ent].type == CHECKPOINT)
6582                     {
6583                         if(sents[ent].attrs[5] && sents[ent].attrs[5] != triggerid) break;
6584                         if(!checkmapvariant(sents[ent].attrs[enttype[sents[ent].type].mvattr])) break;
6585                         if(!m_check(sents[ent].attrs[3], sents[ent].attrs[4], gamemode, mutators)) break;
6586                         if(!m_race(gamemode) || (m_ra_gauntlet(gamemode, mutators) && cp->team != T_ALPHA)) break;
6587                         if(cp->cpnodes.find(ent) >= 0) break;
6588                         switch(sents[ent].attrs[6])
6589                         {
6590                             case CP_LAST: case CP_FINISH:
6591                             {
6592                                 if(cp->cpmillis)
6593                                 {
6594                                     int laptime = gamemillis-cp->cpmillis, total = 0;
6595                                     if(cp->cptime <= 0 || laptime < cp->cptime) cp->cptime = laptime;
6596                                     cp->points++;
6597                                     sendf(-1, 1, "ri6", N_CHECKPOINT, cp->clientnum, ent, laptime, cp->cptime, cp->points);
6598                                     if(m_team(gamemode, mutators))
6599                                     {
6600                                         if(m_ra_timed(gamemode, mutators))
6601                                         {
6602                                             score &ts = teamscore(cp->team);
6603                                             if(!ts.total || ts.total > cp->cptime)
6604                                             {
6605                                                 total = ts.total = cp->cptime;
6606                                                 sendf(-1, 1, "ri3", N_SCORE, ts.team, ts.total);
6607                                             }
6608                                         }
6609                                         else
6610                                         {
6611                                             score &ts = teamscore(cp->team);
6612                                             total = ++ts.total;
6613                                             sendf(-1, 1, "ri3", N_SCORE, ts.team, ts.total);
6614                                         }
6615                                         if(total && m_ra_gauntlet(gamemode, mutators) && G(racegauntletwinner))
6616                                         {
6617                                             int numt = numteams(gamemode, mutators);
6618                                             if(curbalance == numt-1)
6619                                             {
6620                                                 bool found = false;
6621                                                 loopi(numt)
6622                                                 {
6623                                                     int t = i+T_FIRST, s = teamscore(t).total;
6624                                                     if(t != T_OMEGA && (m_ra_timed(gamemode, mutators) ? s <= total : s >= total))
6625                                                     {
6626                                                         found = true;
6627                                                         break;
6628                                                     }
6629                                                 }
6630                                                 if(!found)
6631                                                 {
6632                                                     ancmsgft(-1, S_V_NOTIFY, CON_EVENT, "\fyBest score has been reached");
6633                                                     startintermission();
6634                                                 }
6635                                             }
6636                                         }
6637                                     }
6638                                 }
6639                                 else waiting(cp);
6640                                 cp->cpmillis = 0;
6641                                 cp->cpnodes.shrink(0);
6642                                 if(sents[ent].attrs[6] == CP_FINISH) waiting(cp);
6643                                 break;
6644                             }
6645                             case CP_START: case CP_RESPAWN:
6646                             {
6647                                 if(cp->cpnodes.find(ent) >= 0) break;
6648                                 if(sents[ent].attrs[6] == CP_START)
6649                                 {
6650                                     if(cp->cpmillis) break;
6651                                     cp->cpmillis = gamemillis;
6652                                 }
6653                                 else if(!cp->cpmillis)
6654                                 {
6655                                     waiting(cp);
6656                                     break;
6657                                 }
6658                                 sendf(-1, 1, "ri4", N_CHECKPOINT, cp->clientnum, ent, -1);
6659                                 cp->cpnodes.add(ent);
6660                             }
6661                             default: break;
6662                         }
6663                     }
6664                     else if(sents[ent].type == TRIGGER)
6665                     {
6666                         if(!checkmapvariant(sents[ent].attrs[enttype[sents[ent].type].mvattr])) break;
6667                         if(sents[ent].attrs[0] && sents[ent].attrs[0] != triggerid) break;
6668                         if(!m_check(sents[ent].attrs[5], sents[ent].attrs[6], gamemode, mutators)) break;
6669                         bool commit = false, kin = false, spawn = sents[ent].attrs[4]%2;
6670                         switch(sents[ent].attrs[1])
6671                         {
6672                             case TR_TOGGLE:
6673                             {
6674                                 sents[ent].millis = gamemillis+(triggertime()*2);
6675                                 sents[ent].spawned = !sents[ent].spawned;
6676                                 commit = kin = true;
6677                                 break;
6678                             }
6679                             case TR_ONCE: if(sents[ent].spawned != spawn) break;
6680                             case TR_LINK:
6681                             {
6682                                 sents[ent].millis = gamemillis+(triggertime()*2);
6683                                 kin = true;
6684                                 if(sents[ent].spawned == spawn)
6685                                 {
6686                                     sents[ent].spawned = !spawn;
6687                                     commit = true;
6688                                 }
6689                                 break;
6690                             }
6691                             case TR_EXIT:
6692                             {
6693                                 if(sents[ent].spawned) break;
6694                                 sents[ent].spawned = true;
6695                             }
6696                         }
6697                         if(commit) sendf(-1, 1, "ri3x", N_TRIGGER, ent, sents[ent].spawned ? 1 : 0, cp->clientnum);
6698                         if(kin) loopvj(sents[ent].kin) if(sents.inrange(sents[ent].kin[j]))
6699                         {
6700                             if(sents[sents[ent].kin[j]].type == TRIGGER && !checkmapvariant(sents[sents[ent].kin[j]].attrs[enttype[sents[sents[ent].kin[j]].type].mvattr]) && !m_check(sents[sents[ent].kin[j]].attrs[5], sents[sents[ent].kin[j]].attrs[6], gamemode, mutators))
6701                                 continue;
6702                             sents[sents[ent].kin[j]].spawned = sents[ent].spawned;
6703                             sents[sents[ent].kin[j]].millis = sents[ent].millis;
6704                         }
6705                     }
6706                     break;
6707                 }
6708 
6709                 case N_TEXT:
6710                 {
6711                     int fcn = getint(p), tcn = getint(p), flags = getint(p);
6712                     getstring(text, p);
6713                     clientinfo *fcp = (clientinfo *)getinfo(fcn);
6714                     clientinfo *tcp = (clientinfo *)getinfo(tcn);
6715                     if(!hasclient(fcp, ci)) break;
6716                     if(!haspriv(fcp, G(messagelock), "send messages on this server")) break;
6717                     uint ip = getclientip(fcp->clientnum);
6718                     if(ip && checkipinfo(control, ipinfo::MUTE, ip) && !checkipinfo(control, ipinfo::EXCEPT, ip) && !haspriv(fcp, G(mutelock), "send messages while muted")) break;
6719                     if(G(floodlock))
6720                     {
6721                         int numlines = 0;
6722                         loopvrev(fcp->chatmillis)
6723                         {
6724                             if(totalmillis-fcp->chatmillis[i] <= G(floodtime)) numlines++;
6725                             else fcp->chatmillis.remove(i);
6726                         }
6727                         if(numlines >= G(floodlines))
6728                         {
6729                             if((!fcp->warnings[WARN_CHAT][1] || totalmillis-fcp->warnings[WARN_CHAT][1] >= 1000) && !haspriv(fcp, G(floodlock), "send too many messages consecutively"))
6730                             {
6731                                 fcp->warnings[WARN_CHAT][0]++;
6732                                 fcp->warnings[WARN_CHAT][1] = totalmillis ? totalmillis : 1;
6733                                 if(ip && G(floodmute) && fcp->warnings[WARN_CHAT][0] >= G(floodmute) && !checkipinfo(control, ipinfo::EXCEPT, ip) && !haspriv(fcp, G(mutelock)))
6734                                 {
6735                                     ipinfo &c = control.add();
6736                                     c.ip = ip;
6737                                     c.mask = 0xFFFFFFFFU;
6738                                     c.type = ipinfo::MUTE;
6739                                     c.flag = ipinfo::INTERNAL;
6740                                     c.time = totalmillis ? totalmillis : 1;
6741                                     c.reason = newstring("exceeded the number of allowed flood warnings");
6742                                     srvoutf(3, "\fs\fcmute\fS added on %s: %s", colourname(fcp), c.reason);
6743                                 }
6744                             }
6745                             break;
6746                         }
6747                         fcp->chatmillis.add(totalmillis ? totalmillis : 1);
6748                     }
6749                     bigstring output;
6750                     copystring(output, text, G(messagelength));
6751                     filterstring(text, text, true, true, true, true, G(messagelength));
6752                     if(*(G(censorwords))) filterword(output, G(censorwords));
6753                     if(flags&SAY_TEAM && !m_team(gamemode, mutators)) flags &= ~SAY_TEAM;
6754                     sendf(-1, -1, "ri4s", N_TEXT, fcp->clientnum, tcp ? tcp->clientnum : -1, flags, output); // sent to negative chan for recordpacket
6755                     if(flags&SAY_WHISPER && tcp)
6756                     {
6757                         int scn = allowbroadcast(tcp->clientnum) ? tcp->clientnum : tcp->ownernum;
6758                         if(allowbroadcast(scn)) sendf(scn, 1, "ri4s", N_TEXT, fcp->clientnum, tcp->clientnum, flags, output);
6759                         if(allowbroadcast(fcp->clientnum) && scn != fcp->clientnum)
6760                             sendf(fcp->clientnum, 1, "ri4s", N_TEXT, fcp->clientnum, tcp->clientnum, flags, output);
6761                     }
6762                     else
6763                     {
6764                         static vector<int> sentto;
6765                         sentto.setsize(0);
6766                         loopv(clients)
6767                         {
6768                             clientinfo *t = clients[i];
6769                             if(flags&SAY_TEAM && fcp->team != t->team) continue;
6770                             int scn = t->clientnum;
6771                             if(!allowbroadcast(scn) && t->ownernum >= 0)
6772                             {
6773                                 if(strncmp(text, "bots", 4))
6774                                 {
6775                                     size_t len = strlen(t->name);
6776                                     if(!len || strncasecmp(text, t->name, len)) continue;
6777                                     switch(text[len])
6778                                     {
6779                                         case 0: break;
6780                                         case ':': case ',': case ';': len++; break;
6781                                         default: continue;
6782                                     }
6783                                     if(text[len] != 0) continue;
6784                                 }
6785                                 scn = t->ownernum;
6786                             }
6787                             if(!allowbroadcast(scn) || sentto.find(scn) >= 0) continue;
6788                             sendf(scn, 1, "ri4s", N_TEXT, fcp->clientnum, tcp ? tcp->clientnum : -1, flags, output);
6789                             sentto.add(scn);
6790                         }
6791                         defformatstring(m, "%s", colourname(fcp));
6792                         if(flags&SAY_TEAM)
6793                         {
6794                             defformatstring(t, " (to team %s)", colourteam(fcp->team));
6795                             concatstring(m, t);
6796                         }
6797                         if(flags&SAY_ACTION) relayf(0, "\fv* %s %s", m, output);
6798                         else relayf(0, "\fw<%s> %s", m, output);
6799                     }
6800                     break;
6801                 }
6802 
6803                 case N_COMMAND:
6804                 {
6805                     int lcn = getint(p), nargs = getint(p);
6806                     clientinfo *cp = (clientinfo *)getinfo(lcn);
6807                     getstring(text, p);
6808                     int alen = getint(p);
6809                     if(alen < 0 || alen > p.remaining()) break;
6810                     char *arg = newstring(alen);
6811                     getstring(arg, p, alen+1);
6812                     if(hasclient(cp, ci)) parsecommand(cp, nargs, text, arg);
6813                     delete[] arg;
6814                     break;
6815                 }
6816 
6817                 case N_SETPLAYERINFO: // name colour model pattern checkpoint vanity count <loadweaps> count <randweaps>
6818                 {
6819                     uint ip = getclientip(ci->clientnum);
6820                     if(ci->lastplayerinfo)
6821                     {
6822                         bool allow = true;
6823                         if(!haspriv(ci, G(setinfolock), "change player info on this server")) allow = false;
6824                         else if(ip && checkipinfo(control, ipinfo::MUTE, ip) && !checkipinfo(control, ipinfo::EXCEPT, ip) && !haspriv(ci, G(mutelock), "change player info while muted")) allow = false;
6825                         else if(totalmillis-ci->lastplayerinfo < G(setinfowait)) allow = false;
6826                         if(!allow)
6827                         {
6828                             getstring(text, p);
6829                             loopk(4) getint(p);
6830                             getstring(text, p);
6831                             int lw = getint(p);
6832                             loopk(lw) getint(p);
6833                             int rw = getint(p);
6834                             loopk(rw) getint(p);
6835                             sendinitclientself(ci);
6836                             break;
6837                         }
6838                     }
6839                     QUEUE_MSG;
6840                     defformatstring(oldname, "%s", colourname(ci));
6841                     getstring(text, p);
6842                     stringz(namestr);
6843                     filterstring(namestr, text, true, true, true, true, MAXNAMELEN);
6844                     if(!*namestr) copystring(namestr, "unnamed");
6845                     if(strcmp(ci->name, namestr))
6846                     {
6847                         copystring(ci->name, namestr, MAXNAMELEN+1);
6848                         relayf(2, "\fm* %s is now known as %s", oldname, colourname(ci));
6849                     }
6850                     ci->colour = max(getint(p), 0);
6851                     ci->model = max(getint(p), 0);
6852                     ci->pattern = max(getint(p), 0);
6853                     ci->checkpointspawn = max(getint(p), 0);
6854                     getstring(text, p);
6855                     ci->setvanity(text);
6856                     ci->loadweap.shrink(0);
6857                     int lw = getint(p);
6858                     vector<int> lweaps;
6859                     loopk(lw)
6860                     {
6861                         if(k >= W_LOADOUT) getint(p);
6862                         else ci->loadweap.add(getint(p));
6863                     }
6864                     ci->randweap.shrink(0);
6865                     int rw = getint(p);
6866                     loopk(rw)
6867                     {
6868                         if(k >= W_LOADOUT) getint(p);
6869                         else ci->randweap.add(getint(p));
6870                     }
6871                     ci->lastplayerinfo = totalmillis ? totalmillis : 1;
6872                     QUEUE_STR(ci->name);
6873                     QUEUE_INT(ci->colour);
6874                     QUEUE_INT(ci->model);
6875                     QUEUE_INT(ci->pattern);
6876                     QUEUE_INT(ci->checkpointspawn);
6877                     QUEUE_STR(ci->vanity);
6878                     QUEUE_INT(ci->loadweap.length());
6879                     loopvk(ci->loadweap) QUEUE_INT(ci->loadweap[k]);
6880                     QUEUE_INT(ci->randweap.length());
6881                     loopvk(ci->randweap) QUEUE_INT(ci->randweap[k]);
6882                     break;
6883                 }
6884 
6885                 case N_SWITCHTEAM:
6886                 {
6887                     int team = getint(p);
6888                     if(!m_team(gamemode, mutators) || ci->actortype >= A_ENEMY || !isteam(gamemode, mutators, team, T_FIRST)) break;
6889                     if(team == ci->team)
6890                     {
6891                         if(ci->swapteam)
6892                         {
6893                             if(m_swapteam(gamemode, mutators))
6894                                 srvoutf(4, "\fy%s no longer wishes to swap to team %s", colourname(ci), colourteam(ci->swapteam));
6895                             ci->swapteam = T_NEUTRAL;
6896                         }
6897                         break;
6898                     }
6899                     uint ip = getclientip(ci->clientnum);
6900                     if(ip && checkipinfo(control, ipinfo::LIMIT, ip) && !checkipinfo(control, ipinfo::EXCEPT, ip) && !haspriv(ci, G(limitlock), "change teams while limited")) break;
6901                     int newteam = requestswap(ci, team);
6902                     if(newteam != team || newteam == ci->team) break;
6903                     bool reset = true;
6904                     if(ci->state == CS_SPECTATOR)
6905                     {
6906                         if(!allowstate(ci, ALST_TRY, m_edit(gamemode) ? G(spawneditlock) : G(spawnlock)))
6907                         {
6908                             srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s unable to spawn - %d [%d, %d]", colourname(ci), ci->state, ci->lastdeath, gamemillis);
6909                             spectator(ci);
6910                             break;
6911                         }
6912                         if(!spectate(ci, false)) break;
6913                         reset = false;
6914                     }
6915                     setteam(ci, newteam, (reset ? TT_RESET : 0)|TT_INFOSM);
6916                     break;
6917                 }
6918 
6919                 case N_MAPVOTE:
6920                 {
6921                     getstring(text, p);
6922                     filterstring(text, text);
6923                     const char *s = text;
6924                     if(!strncasecmp(s, "maps/", 5) || !strncasecmp(s, "maps\\", 5)) s += 5;
6925                     int reqmode = getint(p), reqmuts = getint(p);
6926                     vote(s, reqmode, reqmuts, sender);
6927                     break;
6928                 }
6929 
6930                 case N_CLEARVOTE:
6931                 {
6932                     if(ci->mapvote[0])
6933                     {
6934                         ci->mapvote[0] = '\0';
6935                         ci->modevote = ci->mutsvote = -1;
6936                         sendf(-1, 1, "ri2", N_CLEARVOTE, ci->clientnum);
6937                     }
6938                     break;
6939                 }
6940 
6941                 case N_GAMEINFO:
6942                 {
6943                     bool skip = hasgameinfo || (mapgameinfo == -2 ? crclocked(ci) : mapgameinfo != sender);
6944                     int n;
6945                     while((n = getint(p)) != -1)
6946                     {
6947                         if(p.overread()) break;
6948                         getstring(text, p);
6949                         defformatstring(cmdname, "sv_%s", text);
6950                         ident *id = idents.access(cmdname);
6951                         if(!skip && id && id->flags&IDF_SERVER && id->flags&IDF_WORLD && n == id->type)
6952                         {
6953                             switch(id->type)
6954                             {
6955                                 case ID_VAR:
6956                                 {
6957                                     int ret = getint(p);
6958                                     if(id->flags&IDF_HEX && uint(id->maxval) == 0xFFFFFFFFU)
6959                                     {
6960                                          if(uint(ret) < uint(id->minval) || uint(ret) > uint(id->maxval)) break;
6961                                     }
6962                                     else if(ret < id->minval || ret > id->maxval) break;
6963                                     *id->storage.i = ret;
6964                                     id->changed();
6965                                     break;
6966                                 }
6967                                 case ID_FVAR:
6968                                 {
6969                                     float ret = getfloat(p);
6970                                     if(ret < id->minvalf || ret > id->maxvalf) break;
6971                                     *id->storage.f = ret;
6972                                     id->changed();
6973                                     break;
6974                                 }
6975                                 case ID_SVAR:
6976                                 {
6977                                     getstring(text, p);
6978                                     delete[] *id->storage.s;
6979                                     *id->storage.s = newstring(text);
6980                                     id->changed();
6981                                     break;
6982                                 }
6983                                 default: break;
6984                             }
6985                         }
6986                         else switch(n)
6987                         {
6988                             case ID_VAR: getint(p); break;
6989                             case ID_FVAR: getfloat(p); break;
6990                             case ID_SVAR: getstring(text, p); break;
6991                             default: break;
6992                         }
6993                     }
6994                     while((n = getint(p)) != -1)
6995                     {
6996                         int type = getint(p), numattr = getint(p);
6997                         if(p.overread() || type < 0 || type >= MAXENTTYPES || n < 0 || n >= MAXENTS) break;
6998                         if(!skip && enttype[type].syncs)
6999                         {
7000                             while(sents.length() <= n) sents.add();
7001                             sents[n].reset();
7002                             sents[n].type = type;
7003                             sents[n].spawned = false; // wait a bit then load 'em up
7004                             sents[n].millis = gamemillis;
7005                             sents[n].attrs.add(0, clamp(numattr, max(type >= 0 && type < MAXENTTYPES ? enttype[type].numattrs : 0, 5), MAXENTATTRS));
7006                             loopk(numattr)
7007                             {
7008                                 if(p.overread()) break;
7009                                 int attr = getint(p);
7010                                 if(sents[n].attrs.inrange(k)) sents[n].attrs[k] = attr;
7011                             }
7012                             if(enttype[type].syncpos) loopj(3)
7013                             {
7014                                 if(p.overread()) break;
7015                                 sents[n].o[j] = getint(p)/DMF;
7016                             }
7017                             if(enttype[type].synckin)
7018                             {
7019                                 int numkin = getint(p);
7020                                 sents[n].kin.add(0, clamp(numkin, 0, MAXENTKIN));
7021                                 loopk(numkin)
7022                                 {
7023                                     if(p.overread()) break;
7024                                     int kin = getint(p);
7025                                     if(k < MAXENTKIN && sents[n].kin.inrange(k)) sents[n].kin[k] = kin;
7026                                 }
7027                             }
7028                         }
7029                         else
7030                         {
7031                             loopk(numattr) { if(p.overread()) break; getint(p); }
7032                             if(enttype[type].syncpos) loopj(3) { if(p.overread()) break; getint(p); }
7033                             if(enttype[type].synckin)
7034                             {
7035                                 int numkin = getint(p);
7036                                 loopk(numkin) { if(p.overread()) break; getint(p); }
7037                             }
7038                         }
7039                     }
7040                     if(!skip) setupgameinfo();
7041                     break;
7042                 }
7043 
7044                 case N_SCORE:
7045                     getint(p);
7046                     getint(p);
7047                     QUEUE_MSG;
7048                     break;
7049 
7050                 case N_INFOAFFIN:
7051                     getint(p);
7052                     getint(p);
7053                     getint(p);
7054                     getint(p);
7055                     QUEUE_MSG;
7056                     break;
7057 
7058                 case N_SETUPAFFIN:
7059                     if(smode == &defendmode) defendmode.parseaffinity(p);
7060                     break;
7061 
7062                 case N_MOVEAFFIN:
7063                 {
7064                     int cn = getint(p), id = getint(p);
7065                     vec o, inertia;
7066                     loopi(3) o[i] = getint(p)/DMF;
7067                     loopi(3) inertia[i] = getint(p)/DMF;
7068                     clientinfo *cp = (clientinfo *)getinfo(cn);
7069                     if(!cp || !hasclient(cp, ci)) break;
7070                     if(smode == &capturemode) capturemode.moveaffinity(cp, cn, id, o, inertia);
7071                     else if(smode == &bombermode) bombermode.moveaffinity(cp, cn, id, o, inertia);
7072                     break;
7073                 }
7074 
7075                 case N_TAKEAFFIN:
7076                 {
7077                     int lcn = getint(p), flag = getint(p);
7078                     clientinfo *cp = (clientinfo *)getinfo(lcn);
7079                     if(!hasclient(cp, ci) || cp->state == CS_SPECTATOR) break;
7080                     cp->lastaffinity = gamemillis;
7081                     if(smode == &capturemode) capturemode.takeaffinity(cp, flag);
7082                     else if(smode == &bombermode) bombermode.takeaffinity(cp, flag);
7083                     break;
7084                 }
7085 
7086                 case N_RESETAFFIN:
7087                 {
7088                     int flag = getint(p);
7089                     if(!ci) break;
7090                     if(smode == &capturemode) capturemode.resetaffinity(ci, flag);
7091                     else if(smode == &bombermode) bombermode.resetaffinity(ci, flag);
7092                     break;
7093                 }
7094 
7095                 case N_DROPAFFIN:
7096                 {
7097                     int lcn = getint(p), otc = getint(p);
7098                     vec droploc, inertia;
7099                     loopk(3) droploc[k] = getint(p)/DMF;
7100                     loopk(3) inertia[k] = getint(p)/DMF;
7101                     clientinfo *cp = (clientinfo *)getinfo(lcn);
7102                     if(!hasclient(cp, ci) || cp->state == CS_SPECTATOR) break;
7103                     if(smode == &capturemode) capturemode.dropaffinity(cp, droploc, inertia, -1);
7104                     else if(smode == &bombermode) bombermode.dropaffinity(cp, droploc, inertia, otc);
7105                     break;
7106                 }
7107 
7108                 case N_INITAFFIN:
7109                 {
7110                     if(smode == &capturemode) capturemode.parseaffinity(p);
7111                     else if(smode == &bombermode) bombermode.parseaffinity(p);
7112                     break;
7113                 }
7114 
7115                 case N_PING:
7116                     sendf(sender, 1, "i2", N_PONG, getint(p));
7117                     break;
7118 
7119                 case N_CLIENTPING:
7120                 {
7121                     int ping = getint(p);
7122                     if(ci)
7123                     {
7124                         ci->ping = ping;
7125                         loopv(clients) if(clients[i]->ownernum == ci->clientnum) clients[i]->ping = ping;
7126                     }
7127                     QUEUE_MSG;
7128                     break;
7129                 }
7130 
7131                 case N_MASTERMODE:
7132                 {
7133                     int mm = getint(p);
7134                     if(haspriv(ci, G(masterlock), "change mastermode") && mm >= MM_OPEN && mm <= MM_PRIVATE)
7135                     {
7136                         if(haspriv(ci, PRIV_ADMINISTRATOR) || (mastermask()&(1<<mm)))
7137                         {
7138                             mastermode = mm;
7139                             resetcontrols(ipinfo::ALLOW);
7140                             if(mastermode >= MM_PRIVATE) loopv(clients)
7141                             {
7142                                 ipinfo &allow = control.add();
7143                                 allow.ip = getclientip(clients[i]->clientnum);
7144                                 allow.mask = 0xFFFFFFFFU;
7145                                 allow.type = ipinfo::ALLOW;
7146                                 allow.time = totalmillis ? totalmillis : 1;
7147                                 allow.reason = newstring("mastermode set private");
7148                             }
7149                             sendf(-1, 1, "ri3", N_MASTERMODE, ci->clientnum, mastermode);
7150                         }
7151                         else srvmsgft(ci->clientnum, CON_EVENT, "\foThe \fs\fcmastermode\fS of \fs\fc%d\fS (\fs\fc%s\fS) is disabled on this server", mm, mastermodename(mm));
7152                     }
7153                     break;
7154                 }
7155 
7156                 case N_CLRCONTROL:
7157                 {
7158                     int value = getint(p);
7159                     #define CONTROLSWITCH(x,y) \
7160                         case x: \
7161                         { \
7162                             if(haspriv(ci, G(y##lock), "clear " #y "s")) \
7163                             { \
7164                                 resetcontrols(x); \
7165                                 srvoutf(3, "%s cleared existing \fs\fc" #y "s\fS", colourname(ci)); \
7166                             } \
7167                             break; \
7168                         }
7169 
7170                     switch(value)
7171                     {
7172                         CONTROLSWITCH(ipinfo::ALLOW, allow);
7173                         CONTROLSWITCH(ipinfo::BAN, ban);
7174                         CONTROLSWITCH(ipinfo::MUTE, mute);
7175                         CONTROLSWITCH(ipinfo::LIMIT, limit);
7176                         CONTROLSWITCH(ipinfo::EXCEPT, except);
7177                         default: break;
7178                     }
7179                     #undef CONTROLSWITCH
7180                     break;
7181                 }
7182 
7183                 case N_ADDCONTROL:
7184                 {
7185                     int m = getint(p), value = getint(p);
7186                     getstring(text, p);
7187                     #define CONTROLSWITCH(x,y) \
7188                         case x: \
7189                         { \
7190                             if(haspriv(ci, G(y##lock), #y " players") && m >= 0) \
7191                             { \
7192                                 clientinfo *cp = (clientinfo *)getinfo(m); \
7193                                 if(!cp || cp->ownernum >= 0 || (value != ipinfo::EXCEPT && !cmppriv(ci, cp, #y))) break; \
7194                                 uint ip = getclientip(cp->clientnum); \
7195                                 if(!ip) break; \
7196                                 if(checkipinfo(control, ipinfo::EXCEPT, ip)) \
7197                                 { \
7198                                     if(!haspriv(ci, PRIV_ADMINISTRATOR, #y " protected players")) break; \
7199                                     else if(value >= ipinfo::BAN) loopvrev(control) \
7200                                         if(control[i].type == ipinfo::EXCEPT && (ip & control[i].mask) == control[i].ip) \
7201                                             control.remove(i); \
7202                                 } \
7203                                 string name; \
7204                                 copystring(name, colourname(ci)); \
7205                                 if(value >= 0) \
7206                                 { \
7207                                     ipinfo &c = control.add(); \
7208                                     c.ip = ip; \
7209                                     c.mask = 0xFFFFFFFFU; \
7210                                     c.type = value; \
7211                                     c.time = totalmillis ? totalmillis : 1; \
7212                                     c.reason = newstring(text); \
7213                                     if(text[0]) srvoutf(3, "%s added \fs\fc" #y "\fS on %s: %s", name, colourname(cp), text); \
7214                                     else srvoutf(3, "%s added \fs\fc" #y "\fS on %s", name, colourname(cp)); \
7215                                     if(value == ipinfo::BAN) updatecontrols = true; \
7216                                     else if(value == ipinfo::LIMIT) cp->swapteam = 0; \
7217                                 } \
7218                                 else \
7219                                 { \
7220                                     if(text[0]) srvoutf(3, "%s \fs\fckicked\fS %s: %s", name, colourname(cp), text); \
7221                                     else srvoutf(3, "%s \fs\fckicked\fS %s", name, colourname(cp)); \
7222                                     cp->kicked = updatecontrols = true; \
7223                                 } \
7224                             } \
7225                             break; \
7226                         }
7227                     switch(value)
7228                     {
7229                         CONTROLSWITCH(-1, kick);
7230                         CONTROLSWITCH(ipinfo::ALLOW, allow);
7231                         CONTROLSWITCH(ipinfo::BAN, ban);
7232                         CONTROLSWITCH(ipinfo::MUTE, mute);
7233                         CONTROLSWITCH(ipinfo::LIMIT, limit);
7234                         CONTROLSWITCH(ipinfo::EXCEPT, except);
7235                         default: break;
7236                     }
7237                     #undef CONTROLSWITCH
7238                     break;
7239                 }
7240 
7241                 case N_SPECTATOR:
7242                 {
7243                     int sn = getint(p), val = getint(p);
7244                     clientinfo *cp = (clientinfo *)getinfo(sn);
7245                     if(!cp || (val ? cp->state == CS_SPECTATOR && cp->actortype > A_PLAYER : cp->state != CS_SPECTATOR))
7246                     {
7247                         srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s unable to modify spectator - %d [%d, %d] - invalid", colourname(cp), cp->state, cp->lastdeath, gamemillis);
7248                         break;
7249                     }
7250                     if(sn != sender ? !haspriv(ci, max(m_edit(gamemode) ? G(spawneditlock) : G(spawnlock), G(speclock)), "control other players") : (!haspriv(ci, max(m_edit(gamemode) ? G(spawneditlock) : G(spawnlock), G(speclock))) && !allowstate(cp, val ? ALST_SPEC : ALST_TRY, m_edit(gamemode) ? G(spawneditlock) : G(spawnlock))))
7251                     {
7252                         srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s unable to modify spectator - %d [%d, %d] - restricted", colourname(cp), cp->state, cp->lastdeath, gamemillis);
7253                         break;
7254                     }
7255                     bool spec = val != 0, quarantine = cp != ci && val == 2, wasq = cp->quarantine;
7256                     if(quarantine && (ci->privilege&PRIV_TYPE) <= (cp->privilege&PRIV_TYPE))
7257                     {
7258                         srvmsgft(ci->clientnum, CON_EVENT, "\frAccess denied, you may not quarantine higher or equally privileged player %s", colourname(cp));
7259                         break;
7260                     }
7261                     if(!spectate(cp, spec, quarantine))
7262                     {
7263                         srvmsgftforce(ci->clientnum, CON_DEBUG, "Sync error: %s unable to modify spectator - %d [%d, %d] - failed", colourname(cp), cp->state, cp->lastdeath, gamemillis);
7264                         break;
7265                     }
7266                     if(quarantine && cp->quarantine)
7267                     {
7268                         defformatstring(name, "%s", colourname(ci));
7269                         srvoutf(3, "%s \fs\fcquarantined\fS %s", name, colourname(cp));
7270                     }
7271                     else if(wasq && !cp->quarantine)
7272                     {
7273                         defformatstring(name, "%s", colourname(ci));
7274                         srvoutf(3, "%s \fs\fcreleased\fS %s from \fs\fcquarantine\fS", name, colourname(cp));
7275                     }
7276                     break;
7277                 }
7278 
7279                 case N_SETTEAM:
7280                 {
7281                     int who = getint(p), team = getint(p);
7282                     clientinfo *cp = (clientinfo *)getinfo(who);
7283                     if(!cp || !m_team(gamemode, mutators) || m_local(gamemode) || cp->actortype >= A_ENEMY) break;
7284                     if(who < 0 || who >= getnumclients() || !haspriv(ci, G(teamlock), "change the team of others")) break;
7285                     if(cp->state == CS_SPECTATOR || !allowteam(cp, team, T_FIRST, false)) break;
7286                     setteam(cp, team, TT_RESETX);
7287                     break;
7288                 }
7289 
7290                 case N_RECORDDEMO:
7291                 {
7292                     int val = getint(p);
7293                     if(!haspriv(ci, G(demolock), "record demos")) break;
7294                     setdemorecord(val != 0, true);
7295                     break;
7296                 }
7297 
7298                 case N_STOPDEMO:
7299                 {
7300                     if(!haspriv(ci, G(demolock), "stop demos")) break;
7301                     if(m_demo(gamemode)) enddemoplayback();
7302                     else checkdemorecord(!gs_playing(gamestate));
7303                     break;
7304                 }
7305 
7306                 case N_CLEARDEMOS:
7307                 {
7308                     int demo = getint(p);
7309                     if(!haspriv(ci, G(demolock), "clear demos")) break;
7310                     cleardemos(demo);
7311                     break;
7312                 }
7313 
7314                 case N_LISTDEMOS:
7315                     listdemos(sender);
7316                     break;
7317 
7318                 case N_GETDEMO:
7319                 {
7320                     int n = getint(p);
7321                     int dni = getint(p);
7322                     senddemo(sender, n, dni);
7323                     break;
7324                 }
7325 
7326                 case N_EDITENT:
7327                 {
7328                     int n = getint(p), oldtype = NOTUSED, newtype = NOTUSED;
7329                     ivec o(0, 0, 0);
7330                     bool tweaked = false, inrange = n < MAXENTS;
7331                     loopk(3) o[k] = getint(p);
7332                     if(p.overread()) break;
7333                     if(sents.inrange(n)) oldtype = sents[n].type;
7334                     else if(inrange) while(sents.length() <= n) sents.add();
7335                     if((newtype = getint(p)) != oldtype && inrange)
7336                     {
7337                         sents[n].type = newtype;
7338                         tweaked = true;
7339                     }
7340                     int numattrs = getint(p), realattrs =  min(max(5, numattrs), MAXENTATTRS);
7341                     if(inrange) while(sents[n].attrs.length() < realattrs) sents[n].attrs.add(0);
7342                     loopk(numattrs)
7343                     {
7344                         int attr = getint(p);
7345                         if(p.overread()) break;
7346                         if(inrange && k < MAXENTATTRS) sents[n].attrs[k] = attr;
7347                     }
7348                     if(inrange)
7349                     {
7350                         hasgameinfo = true;
7351                         sents[n].o = vec(o).div(DMF);
7352                         packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
7353                         uchar s[MAXTRANS];
7354                         ucharbuf r(s, MAXTRANS);
7355                         putint(q, N_CLIENT);
7356                         putint(q, ci->clientnum);
7357                         putint(r, N_EDITENT);
7358                         putint(r, n);
7359                         putint(r, o.x);
7360                         putint(r, o.y);
7361                         putint(r, o.z);
7362                         putint(r, sents[n].type);
7363                         putint(r, sents[n].attrs.length());
7364                         loopvk(sents[n].attrs) putint(r, sents[n].attrs[k]);
7365                         putuint(q, r.length());
7366                         q.put(r.getbuf(), r.length());
7367                         sendpacket(-1, 1, q.finalize(), ci->clientnum);
7368                         if(tweaked)
7369                         {
7370                             if(enttype[sents[n].type].usetype == EU_ITEM) setspawn(n, true, false, true);
7371                             if(oldtype == PLAYERSTART || sents[n].type == PLAYERSTART) setupspawns(true);
7372                             if(oldtype == TRIGGER || sents[n].type == TRIGGER) setuptriggers(true);
7373                         }
7374                     }
7375                     break;
7376                 }
7377 
7378                 case N_EDITVAR:
7379                 {
7380                     int t = getint(p);
7381                     getstring(text, p);
7382                     if(!ci || ci->state != CS_EDITING)
7383                     {
7384                         switch(t)
7385                         {
7386                             case ID_VAR: getint(p); break;
7387                             case ID_FVAR: getfloat(p); break;
7388                             case ID_SVAR: case ID_ALIAS:
7389                             {
7390                                 int vlen = getint(p);
7391                                 if(vlen < 0 || vlen > p.remaining()) break;
7392                                 getstring(text, p, vlen+1);
7393                                 break;
7394                             }
7395                             default: break;
7396                         }
7397                         break;
7398                     }
7399                     QUEUE_INT(N_EDITVAR);
7400                     QUEUE_INT(t);
7401                     QUEUE_STR(text);
7402                     switch(t)
7403                     {
7404                         case ID_VAR:
7405                         {
7406                             int val = getint(p);
7407                             relayf(3, "\fy%s set world variable %s to %d", colourname(ci), text, val);
7408                             QUEUE_INT(val);
7409                             break;
7410                         }
7411                         case ID_FVAR:
7412                         {
7413                             float val = getfloat(p);
7414                             relayf(3, "\fy%s set world variable %s to %s", colourname(ci), text, floatstr(val));
7415                             QUEUE_FLT(val);
7416                             break;
7417                         }
7418                         case ID_SVAR:
7419                         case ID_ALIAS:
7420                         {
7421                             int vlen = getint(p);
7422                             if(vlen < 0 || vlen > p.remaining()) break;
7423                             char *val = newstring(vlen);
7424                             getstring(val, p, vlen+1);
7425                             relayf(3, "\fy%s set world %s %s to %s", colourname(ci), t == ID_ALIAS ? "alias" : "variable", text, val);
7426                             QUEUE_INT(vlen);
7427                             QUEUE_STR(val);
7428                             delete[] val;
7429                             break;
7430                         }
7431                         default: break;
7432                     }
7433                     break;
7434                 }
7435 
7436                 case N_GETMAP:
7437                 {
7438                     ci->ready = true;
7439                     getmap(ci);
7440                     break;
7441                 }
7442 
7443                 case N_NEWMAP:
7444                 {
7445                     int size = getint(p);
7446                     getstring(text, p);
7447                     if(ci->state != CS_EDITING) break;
7448                     QUEUE_INT(N_NEWMAP);
7449                     QUEUE_INT(size);
7450                     if(size >= 0)
7451                     {
7452                         if(*text) formatstring(smapname, strstr(text, "maps/") == text || strstr(text, "maps\\") == text ? "%s" : "maps/%s", text);
7453                         else copystring(smapname, "maps/untitled");
7454                         sents.shrink(0);
7455                         hasgameinfo = true;
7456                         mapgameinfo = -1;
7457                         if(smode) smode->reset();
7458                         mutate(smuts, mut->reset());
7459                         QUEUE_STR(smapname);
7460                     }
7461                     else QUEUE_STR(text);
7462                     break;
7463                 }
7464 
7465                 case N_SETPRIV:
7466                 {
7467                     int val = getint(p);
7468                     getstring(text, p);
7469                     if(val != 0)
7470                     {
7471                         if(text[0])
7472                         {
7473                             if(!adminpass[0]) srvmsgft(ci->clientnum, CON_EVENT, "\frAccess denied, no administrator password set");
7474                             else if(!checkpassword(ci, adminpass, text)) srvmsgft(ci->clientnum, CON_EVENT, "\frAccess denied, invalid administrator password");
7475                             else auth::setprivilege(ci, 1, PRIV_ADMINISTRATOR|PRIV_LOCAL);
7476                         }
7477                         else if((ci->privilege&PRIV_TYPE) < PRIV_ELEVATED)
7478                         {
7479                             bool fail = false;
7480                             if(!(mastermask()&MM_AUTOAPPROVE))
7481                             {
7482                                 srvmsgft(ci->clientnum, CON_EVENT, "\frAccess denied, you need a \fs\fcpassword/account\fS to \fs\fcelevate privileges\fS");
7483                                 fail = true;
7484                             }
7485                             else loopv(clients) if(ci != clients[i] && (clients[i]->privilege&PRIV_TYPE) >= PRIV_ELEVATED)
7486                             {
7487                                 srvmsgft(ci->clientnum, CON_EVENT, "\frAccess denied, there is already another player with elevated privileges");
7488                                 fail = true;
7489                                 break;
7490                             }
7491                             if(!fail) auth::setprivilege(ci, 1, PRIV_ELEVATED|PRIV_LOCAL);
7492                         }
7493                     }
7494                     else auth::setprivilege(ci, 0);
7495                     break; // don't broadcast the password
7496                 }
7497 
7498                 case N_AUTHTRY:
7499                 {
7500                     getstring(text, p);
7501                     stringz(authname);
7502                     filterstring(authname, text, true, true, true, true, 100);
7503                     auth::tryauth(ci, authname);
7504                     break;
7505                 }
7506 
7507                 case N_AUTHANS:
7508                 {
7509                     uint id = (uint)getint(p);
7510                     getstring(text, p);
7511                     auth::answerchallenge(ci, id, text);
7512                     break;
7513                 }
7514 
7515                 case N_COPY:
7516                     ci->cleanclipboard();
7517                     ci->lastclipboard = totalmillis ? totalmillis : 1;
7518                     goto genericmsg;
7519 
7520                 case N_PASTE:
7521                     if(ci->state == CS_EDITING) sendclipboard(ci);
7522                     goto genericmsg;
7523 
7524                 case N_CLIPBOARD:
7525                 {
7526                     int unpacklen = getint(p), packlen = getint(p);
7527                     ci->cleanclipboard();
7528                     ci->lastclipboard = totalmillis ? totalmillis : 1;
7529                     if(ci->state != CS_EDITING)
7530                     {
7531                         if(packlen > 0) p.subbuf(packlen);
7532                         break;
7533                     }
7534                     if(packlen <= 0 || packlen > (1<<16) || unpacklen <= 0)
7535                     {
7536                         if(packlen > 0) p.subbuf(packlen);
7537                         packlen = unpacklen = 0;
7538                     }
7539                     packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE);
7540                     putint(q, N_CLIPBOARD);
7541                     putint(q, ci->clientnum);
7542                     putint(q, unpacklen);
7543                     putint(q, packlen);
7544                     if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen);
7545                     ci->clipboard = q.finalize();
7546                     ci->clipboard->referenceCount++;
7547                     break;
7548                 }
7549 
7550                 case N_EDITT:
7551                 case N_REPLACE:
7552                 case N_EDITVSLOT:
7553                 {
7554                     int size = msgsizelookup(type);
7555                     if(size <= 0) { disconnect_client(sender, DISC_MSGERR); return; }
7556                     loopi(size-1) getint(p);
7557                     if(p.remaining() < 2) { disconnect_client(sender, DISC_MSGERR); return; }
7558                     int extra = lilswap(*(const ushort *)p.pad(2));
7559                     if(p.remaining() < extra) { disconnect_client(sender, DISC_MSGERR); return; }
7560                     p.pad(extra);
7561                     if(ci && ci->state != CS_SPECTATOR) QUEUE_MSG;
7562                     break;
7563                 }
7564 
7565                 case N_UNDO:
7566                 case N_REDO:
7567                 {
7568                     int unpacklen = getint(p), packlen = getint(p);
7569                     if(!ci || ci->state == CS_SPECTATOR || packlen <= 0 || packlen > (1<<16) || unpacklen <= 0)
7570                     {
7571                         if(packlen > 0) p.subbuf(packlen);
7572                         break;
7573                     }
7574                     if(p.remaining() < packlen) { disconnect_client(sender, DISC_MSGERR); return; }
7575                     packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE);
7576                     putint(q, type);
7577                     putint(q, ci->clientnum);
7578                     putint(q, unpacklen);
7579                     putint(q, packlen);
7580                     if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen);
7581                     ci->messages.put(q.buf, q.length());
7582                     curmsg += q.length();
7583                     break;
7584                 }
7585 
7586                 case N_ADDPRIV:
7587                 {
7588                     int sn = getint(p), priv = getint(p);
7589                     clientinfo *cp = (clientinfo *)getinfo(sn);
7590                     if(!cp)
7591                     {
7592                         srvmsgft(ci->clientnum, CON_EVENT, "\frThat client does not exist");
7593                         break;
7594                     }
7595                     if(priv != -1 && (priv < PRIV_SUPPORTER || priv > PRIV_ADMINISTRATOR || cp->actortype != A_PLAYER))
7596                     {
7597                         srvmsgft(ci->clientnum, CON_EVENT, "\frYou may not add that privilege");
7598                         break;
7599                     }
7600                     if(priv == -1 && ((ci->privilege&PRIV_TYPE) <= (cp->privilege&PRIV_TYPE)) && ((ci->privilege&PRIV_TYPE) < PRIV_ADMINISTRATOR))
7601                     {
7602                         srvmsgft(ci->clientnum, CON_EVENT, "\frYou must be a \fs\fc%s\fS to reset that client's privileges", privname((cp->privilege & PRIV_TYPE) + 1));
7603                         break;
7604                     }
7605                     if(!((ci->privilege&PRIV_TYPE) >= PRIV_ADMINISTRATOR) && !haspriv(ci, priv, "add that privilege")) break;
7606                     if(priv == -1)
7607                     {
7608                         if(cp->oldprivilege == -1)
7609                         {
7610                             srvmsgft(ci->clientnum, CON_EVENT, "\fr%s does not have any added privilege", colourname(cp));
7611                             break;
7612                         }
7613                         else
7614                         {
7615                             auth::setprivilege(cp, 1, cp->oldprivilege, false, ci);
7616                             cp->oldprivilege = -1;
7617                             break;
7618                         }
7619                     }
7620                     if(priv <= (cp->privilege&PRIV_TYPE))
7621                     {
7622                         srvmsgft(ci->clientnum, CON_EVENT, "\fr%s is already elevated to \fs\fc%s\fS", colourname(cp), privname(cp->privilege));
7623                         break;
7624                     }
7625                     if(cp->oldprivilege == -1) cp->oldprivilege = cp->privilege;
7626                     auth::setprivilege(cp, 1, priv|PRIV_LOCAL, false, ci);
7627                     break;
7628                 }
7629 
7630                 case -1:
7631                     conoutf("\fy[msg error] from: %d, cur: %d, msg: %d, prev: %d", sender, curtype, type, prevtype);
7632                     disconnect_client(sender, DISC_MSGERR);
7633                     return;
7634 
7635                 case -2:
7636                     disconnect_client(sender, DISC_OVERFLOW);
7637                     return;
7638 
7639                 default: genericmsg:
7640                 {
7641                     int size = msgsizelookup(type);
7642                     if(size <= 0)
7643                     {
7644                         conoutf("\fy[msg error] from: %d, cur: %d, msg: %d, prev: %d", sender, curtype, type, prevtype);
7645                         disconnect_client(sender, DISC_MSGERR);
7646                         return;
7647                     }
7648                     loopi(size-1) getint(p);
7649                     if(ci) QUEUE_MSG;
7650                     break;
7651                 }
7652             }
7653             if(verbose > 5) conoutf("\fy[server] from: %d, cur: %d, msg: %d, prev: %d", sender, curtype, type, prevtype);
7654         }
7655     }
7656 
serveroption(char * arg)7657     bool serveroption(char *arg)
7658     {
7659         if(arg[0] == '-' && arg[1] == 's') switch(arg[2])
7660         {
7661             case 'P': setsvar("adminpass", &arg[3]); return true;
7662             case 'k': setsvar("serverpass", &arg[3]); return true;
7663             default: break;
7664         }
7665         return false;
7666     }
7667 };
7668 #undef CPP_GAME_SERVER
7669