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