1 #include "game.h"
2 
3 namespace client
4 {
5     bool sendplayerinfo = false, sendgameinfo = false, sendcrcinfo = false, loadedmap = false, isready = false, remote = false, demoplayback = false;
6     int needsmap = 0, gettingmap = 0, lastping = 0, sessionid = 0, sessionver = 0, sessionflags = 0, lastplayerinfo = 0, mastermode = 0, needclipboard = -1, demonameid = 0;
7     string connectpass = "";
8     hashtable<int, const char *>demonames;
9 
10     VAR(0, debugmessages, 0, 0, 1);
11     ICOMMAND(0, getready, "", (), intret(isready ? 1 : 0));
12     ICOMMAND(0, getloadedmap, "", (), intret(maploading || loadedmap ? 1 : 0));
13 
14     SVAR(IDF_PERSIST, demolist, "");
15     VAR(0, demoendless, 0, 0, 1);
16     VAR(IDF_PERSIST, showpresence, 0, 1, 2); // 0 = never show join/leave, 1 = show only during game, 2 = show when connecting/disconnecting
17     VAR(IDF_PERSIST, showpresencehostinfo, 0, 1, 1);
18     VAR(IDF_PERSIST, showteamchange, 0, 1, 2); // 0 = never show, 1 = show only when switching between, 2 = show when entering match too
19     VAR(IDF_PERSIST, showservervariables, 0, 0, 1); // determines if variables set by the server are printed to the console
20     VAR(IDF_PERSIST, showmapvotes, 0, 1, 3); // shows map votes, 1 = only mid-game (not intermision), 2 = at all times, 3 = verbose
21 
22     VAR(IDF_PERSIST, checkpointannounce, 0, 5, 7); // 0 = never, &1 = active players, &2 = all players, &4 = all players in gauntlet
23     VAR(IDF_PERSIST, checkpointannouncefilter, 0, CP_ALL, CP_ALL); // which checkpoint types to announce for
24     VARF(0, checkpointspawn, 0, 1, 1, game::player1->checkpointspawn = checkpointspawn; sendplayerinfo = true);
25     VAR(IDF_PERSIST, demoautoclientsave, 0, 0, 1);
26     ICOMMAND(0, getdemoplayback, "", (), intret(demoplayback ? 1 : 0));
27 
state()28     int state() { return game::player1->state; }
29     ICOMMAND(0, getplayerstate, "", (), intret(state()));
30 
maxmsglen()31     int maxmsglen() { return G(messagelength); }
32 
33     ICOMMAND(0, numgameplayers, "", (), intret(game::numdynents()));
34     ICOMMAND(0, loopgameplayers, "re", (ident *id, uint *body),
35     {
36         loopstart(id, stack);
37         int numdyns = game::numdynents();
38         loopi(numdyns)
39         {
40             gameent *d = (gameent *)game::iterdynents(i);
41             if(!d) continue;
42             loopiter(id, stack, d->clientnum);
43             execute(body);
44         }
45         loopend(id, stack);
46     });
47 
otherclients(bool self,bool nospec)48     int otherclients(bool self, bool nospec)
49     {
50         int n = self ? 1 : 0;
51         loopv(game::players) if(game::players[i] && game::players[i]->actortype == A_PLAYER && (!nospec || game::players[i]->state != CS_SPECTATOR)) n++;
52         return n;
53     }
54     ICOMMAND(0, getclientcount, "ii", (int *s, int *n), intret(otherclients(*s!=0, *n!=0)));
55 
numplayers()56     int numplayers()
57     {
58         int n = 1; // count ourselves
59         loopv(game::players) if(game::players[i] && game::players[i]->actortype < A_ENEMY) n++;
60         return n;
61     }
62 
waiting(bool state)63     int waiting(bool state)
64     {
65         if(!connected(false) || !isready || game::maptime <= 0 || (state && needsmap)) // && otherclients()
66             return state && needsmap ? (gettingmap ? 3 : 2) : 1;
67         return 0;
68     }
69     ICOMMAND(0, waiting, "i", (int *n), intret(waiting(!*n)));
70 
71 //*maplist commands: see gamemode.h for related macros
72 
73 //getmaplist
74 //arg1: i/gamemode
75 //arg2: i/mutators
76 //returns: list of valid maps including previousmaps validity (cuts previousmaps entries)
makemaplist(int g,int m,int c)77     void makemaplist(int g, int m, int c)
78     {
79          char *list = NULL;
80          loopi(2)
81          {
82              maplist(list, g, m, c, mapsfilter, i!=0);
83              if(list)
84              {
85                  result(list);
86                  DELETEA(list);
87                  return;
88              }
89          }
90     }
91     ICOMMAND(0, getmaplist, "iii", (int *g, int *m, int *c), makemaplist(*g, *m, *c));
92 
93 //allowmaplist
94 //arg1: i/gamemode
95 //arg2: i/mutators
96 //returns: list of valid maps regardless of previousmaps presence
makeallowmaplist(int g,int m)97     void makeallowmaplist(int g, int m)
98     {
99          char *list = NULL;
100          loopi(2)
101          {
102              allowmaplist(list, g, m);
103              if(list)
104              {
105                  result(list);
106                  DELETEA(list);
107                  return;
108              }
109          }
110     }
111     ICOMMAND(0, allowmaplist, "ii", (int *g, int *m), makeallowmaplist(*g, *m));
112 
113     extern int sortvotes;
114     struct mapvote
115     {
116         vector<gameent *> players;
117         string map;
118         int millis, mode, muts;
119 
mapvoteclient::mapvote120         mapvote() {}
~mapvoteclient::mapvote121         ~mapvote() { players.shrink(0); }
122 
compareclient::mapvote123         static bool compare(const mapvote &a, const mapvote &b)
124         {
125             if(sortvotes)
126             {
127                 if(a.players.length() > b.players.length()) return true;
128                 if(a.players.length() < b.players.length()) return false;
129             }
130             return a.millis < b.millis;
131         }
132     };
133     vector<mapvote> mapvotes;
134 
135     VARF(IDF_PERSIST, sortvotes, 0, 0, 1, mapvotes.sort(mapvote::compare));
136     VARF(IDF_PERSIST, cleanvotes, 0, 0, 1,
137     {
138         if(cleanvotes && !mapvotes.empty()) loopvrev(mapvotes) if(mapvotes[i].players.empty()) mapvotes.remove(i);
139     });
140 
clearvotes(gameent * d,bool msg)141     void clearvotes(gameent *d, bool msg)
142     {
143         int found = 0;
144         loopvrev(mapvotes) if(mapvotes[i].players.find(d) >= 0)
145         {
146             found++;
147             mapvotes[i].players.removeobj(d);
148             if(cleanvotes && mapvotes[i].players.empty()) mapvotes.remove(i);
149         }
150         if(found)
151         {
152             if(!mapvotes.empty()) mapvotes.sort(mapvote::compare);
153             if(msg && showmapvotes >= (d == game::player1 ? 2 : 3)) conoutft(CON_EVENT, "%s cleared their previous vote", game::colourname(d));
154         }
155     }
156 
vote(gameent * d,const char * text,int mode,int muts,bool force=false)157     void vote(gameent *d, const char *text, int mode, int muts, bool force = false)
158     {
159         mapvote *m = NULL;
160         if(!text || !*text) text = "<random>";
161         if(!mapvotes.empty()) loopvrev(mapvotes)
162         {
163             if(!force && mapvotes[i].players.find(d) >= 0)
164             {
165                 if(!strcmp(text, mapvotes[i].map) && mode == mapvotes[i].mode && muts == mapvotes[i].muts) return;
166                 mapvotes[i].players.removeobj(d);
167                 if(cleanvotes && mapvotes[i].players.empty()) mapvotes.remove(i);
168             }
169             if(!strcmp(text, mapvotes[i].map) && mode == mapvotes[i].mode && muts == mapvotes[i].muts) m = &mapvotes[i];
170         }
171         if(!m)
172         {
173             m = &mapvotes.add();
174             copystring(m->map, text);
175             m->mode = mode;
176             m->muts = muts;
177             m->millis = totalmillis ? totalmillis : 1;
178         }
179         m->players.add(d);
180         mapvotes.sort(mapvote::compare);
181         if(showmapvotes >= (!gs_playing(game::gamestate) ? 2 : 1) && !isignored(d->clientnum))
182             conoutft(CON_EVENT, "%s suggests: \fs\fy%s\fS on \fs\fo%s\fS, press \f{=%s votes} to vote", game::colourname(d), server::gamename(mode, muts), mapctitle(m->map), UI::uiopencmd);
183     }
184     ICOMMAND(0, fakevote, "", (), loopi(20) vote(game::player1, "maps/bloodlust", G_DEATHMATCH, 0, true); loopi(20) vote(game::player1, "maps/dutility", G_CAPTURE, 1<<G_M_GSP1, true));
185 
getvotes(int vote,int prop,int idx)186     void getvotes(int vote, int prop, int idx)
187     {
188         if(vote < 0) intret(mapvotes.length());
189         else if(mapvotes.inrange(vote))
190         {
191             mapvote &v = mapvotes[vote];
192             if(prop < 0) intret(4);
193             else switch(prop)
194             {
195                 case 0:
196                     if(idx < 0) intret(v.players.length());
197                     else if(v.players.inrange(idx)) intret(v.players[idx]->clientnum);
198                     break;
199                 case 1: intret(v.mode); break;
200                 case 2: intret(v.muts); break;
201                 case 3: result(v.map); break;
202             }
203         }
204     }
205     ICOMMAND(0, getvote, "bbb", (int *vote, int *prop, int *idx), getvotes(*vote, *prop, *idx));
206 
207     ICOMMAND(0, loopvotes, "ree", (ident *id, uint *body, uint *none),
208     {
209         loopstart(id, stack);
210         if(mapvotes.empty())
211         {
212             loopiter(id, stack, -1);
213             execute(none);
214         }
215         else loopv(mapvotes)
216         {
217             loopiter(id, stack, i);
218             execute(body);
219         }
220         loopend(id, stack);
221     });
222 
223     struct demoinfo
224     {
225         demoheader hdr;
226         string file;
227 
compareclient::demoinfo228         static int compare(demoinfo &a, demoinfo &b)
229         {
230             return strcmp(a.file, b.file);
231         }
232     };
233     vector<demoinfo> demoinfos;
234     vector<char *> faildemos;
235 
scandemo(const char * name)236     int scandemo(const char *name)
237     {
238         if(!name || !*name) return -1;
239         loopv(demoinfos) if(!strcmp(demoinfos[i].file, name)) return i;
240         loopv(faildemos) if(!strcmp(faildemos[i], name)) return -1;
241         stream *f = opengzfile(name, "rb");
242         if(!f)
243         {
244             faildemos.add(newstring(name));
245             return -1;
246         }
247         int num = demoinfos.length();
248         demoinfo &d = demoinfos.add();
249         copystring(d.file, name);
250         stringz(msg);
251         if(f->read(&d.hdr, sizeof(demoheader)) != sizeof(demoheader) || memcmp(d.hdr.magic, VERSION_DEMOMAGIC, sizeof(d.hdr.magic)))
252             formatstring(msg, "\frSorry, \fs\fc%s\fS is not a demo file", name);
253         else
254         {
255             lilswap(&d.hdr.gamever, 4);
256             if(d.hdr.gamever != VERSION_GAME)
257                 formatstring(msg, "\frDemo \fs\fc%s\fS requires \fs\fc%s\fS version of %s (with protocol version %d)", name, d.hdr.gamever<VERSION_GAME ? "an older" : "a newer", VERSION_NAME, d.hdr.gamever);
258         }
259         delete f;
260         if(msg[0])
261         {
262             conoutft(CON_DEBUG, "%s", msg);
263             demoinfos.pop();
264             faildemos.add(newstring(name));
265             return -1;
266         }
267         return num;
268     }
269     ICOMMAND(0, demoscan, "s", (char *name), intret(scandemo(name)));
270 
resetdemos(bool all)271     void resetdemos(bool all)
272     {
273         if(all) loopvrev(demoinfos) demoinfos.remove(i);
274         loopvrev(faildemos)
275         {
276             DELETEA(faildemos[i]);
277             faildemos.remove(i);
278         }
279     }
280     ICOMMAND(0, demoreset, "i", (int *all), resetdemos(*all!=0));
281     ICOMMAND(0, demosort, "", (), demoinfos.sort(demoinfo::compare));
282 
infodemo(int idx,int prop)283     void infodemo(int idx, int prop)
284     {
285         if(idx < 0) intret(demoinfos.length());
286         else if(demoinfos.inrange(idx))
287         {
288             demoinfo &d = demoinfos[idx];
289             switch(prop)
290             {
291                 case 0: intret(d.hdr.gamever); break;
292                 case 1: result(d.hdr.mapname); break;
293                 case 2: intret(d.hdr.gamemode); break;
294                 case 3: intret(d.hdr.mutators); break;
295                 case 4: intret(d.hdr.starttime); break;
296                 default: break;
297             }
298         }
299     }
300     ICOMMAND(0, demoinfo, "bb", (int *idx, int *prop), infodemo(*idx, *prop));
301 
302     ICOMMAND(0, loopdemos, "rre", (ident *id, ident *id2, uint *body),
303     {
304         loopstart(id, stack);
305         loopstart(id2, stack2);
306         loopv(demoinfos)
307         {
308             loopiter(id, stack, i);
309             loopiter(id2, stack2, demoinfos[i].file);
310             execute(body);
311         }
312         loopend(id, stack);
313         loopend(id2, stack2);
314     });
315 
316     VAR(IDF_PERSIST, authconnect, 0, 1, 1);
317     VAR(IDF_PERSIST, noauthconfig, 0, 0, 1);
318 
319     string accountname = "", accountpass = "";
320     ICOMMAND(0, accountname, "s", (char *s), copystring(accountname, s && *s ? s : ""));
321     ICOMMAND(0, accountpass, "s", (char *s), copystring(accountpass, s && *s ? s : ""));
322     ICOMMAND(0, authkey, "ss", (char *name, char *key),
323     {
324         copystring(accountname, name && *name ? name : "");
325         copystring(accountpass, key && *key ? key : "");
326     });
327     ICOMMAND(0, hasauthkey, "i", (int *n), intret(accountname[0] && accountpass[0] && (!*n || authconnect) ? 1 : 0));
328 
writecfg()329     void writecfg()
330     {
331         if(noauthconfig || !*accountname || !*accountpass) return;
332         stream *f = openutf8file("auth.cfg", "w");
333         if(!f) return;
334         f->printf("authkey %s %s\n", accountname, accountpass);
335         delete f;
336     }
337 
writegamevars(const char * name,bool all=false,bool server=false)338     void writegamevars(const char *name, bool all = false, bool server = false)
339     {
340         if(!name || !*name) name = "vars.cfg";
341         stream *f = openfile(name, "w");
342         if(!f) return;
343         vector<ident *> ids;
344         enumerate(idents, ident, id, ids.add(&id));
345         ids.sortname();
346         loopv(ids)
347         {
348             ident &id = *ids[i];
349             if(id.flags&IDF_CLIENT && !(id.flags&IDF_READONLY) && !(id.flags&IDF_WORLD)) switch(id.type)
350             {
351                 case ID_VAR:
352                     if(*id.storage.i == id.def.i)
353                     {
354                         if(all) f->printf("// ");
355                         else break;
356                     }
357                     if(server) f->printf("sv_");
358                     f->printf("%s %s\n", escapeid(id), intstr(&id));
359                     break;
360                 case ID_FVAR:
361                     if(*id.storage.f == id.def.f)
362                     {
363                         if(all) f->printf("// ");
364                         else break;
365                     }
366                     if(server) f->printf("sv_");
367                     f->printf("%s %s\n", escapeid(id), floatstr(*id.storage.f));
368                     break;
369                 case ID_SVAR:
370                     if(!strcmp(*id.storage.s, id.def.s))
371                     {
372                         if(all) f->printf("// ");
373                         else break;
374                     }
375                     if(server) f->printf("sv_");
376                     f->printf("%s %s\n", escapeid(id), escapestring(*id.storage.s));
377                     break;
378             }
379         }
380         delete f;
381     }
382     ICOMMAND(0, writevars, "sii", (char *name, int *all, int *sv), if(!(identflags&IDF_WORLD)) writegamevars(name, *all!=0, *sv!=0));
383 
writegamevarsinfo(const char * name)384     void writegamevarsinfo(const char *name)
385     {
386         if(!name || !*name) name = "varsinfo.txt";
387         stream *f = openfile(name, "w");
388         if(!f) return;
389         f->printf("// List of vars properties, fields are separated by tabs; empty if nonexistent\n");
390         f->printf("// Fields: NAME TYPE FLAGS ARGS VALTYPE VALUE MIN MAX DESC USAGE\n");
391         vector<ident *> ids;
392         enumerate(idents, ident, id, ids.add(&id));
393         ids.sortname();
394         loopv(ids)
395         {
396             ident &id = *ids[i];
397             if(!(id.flags&IDF_SERVER)) // Exclude sv_* duplicates
398             {
399                 f->printf("%s\t%d\t%d", id.name, id.type, id.flags);
400                 switch(id.type)
401                 {
402                     case ID_VAR:
403                         f->printf("\t\t"); // empty ARGS VALTYPE
404                         if(!(id.flags&IDF_HEX))
405                             f->printf("\t%d\t%d\t%d", id.def.i, id.minval, id.maxval);
406                         else
407                         {
408                             if(id.maxval == 0xFFFFFF)
409                                 f->printf("\t0x%.6X\t0x%.6X\t0x%.6X", id.def.i, id.minval, id.maxval);
410                             else if(uint(id.maxval) == 0xFFFFFFFFU)
411                                 f->printf("\t0x%.8X\t0x%.8X\t0x%.8X", uint(id.def.i), uint(id.minval), uint(id.maxval));
412                             else
413                                 f->printf("\t0x%X\t0x%X\t0x%X", id.def.i, id.minval, id.maxval);
414                         }
415                         break;
416                     case ID_FVAR:
417                         f->printf("\t\t\t%s\t%s\t%s", floatstr(id.def.f), floatstr(id.minvalf), floatstr(id.maxvalf)); // empty ARGS VALTYPE
418                         break;
419                     case ID_SVAR:
420                         f->printf("\t\t\t%s\t\t", escapestring(id.def.s)); // empty ARGS VALTYPE MIN MAX
421                         break;
422                     case ID_ALIAS:
423                         f->printf("\t\t%d", id.valtype); // empty ARGS
424                         switch(id.valtype)
425                         {
426                             case VAL_NULL:
427                                 f->printf("\tNULL");
428                                 break;
429                             case VAL_INT:
430                                 f->printf("\t%d", id.val.i);
431                                 break;
432                             case VAL_FLOAT:
433                                 f->printf("\t%s", floatstr(id.val.f));
434                                 break;
435                             case VAL_STR:
436                                 f->printf("\t%s", escapestring(id.val.s));
437                                 break;
438                             case VAL_CSTR:
439                                 f->printf("\t%s", escapestring(id.val.cstr));
440                                 break;
441                         }
442                         f->printf("\t\t"); // empty MIN MAX
443                         break;
444                     case ID_COMMAND:
445                         f->printf("\t%s\t\t\t\t", escapestring(id.args)); // empty VALTYPE VALUE MIN MAX
446                         break;
447                 }
448                 // empty if nonexistent
449                 f->printf("\t%s", escapestring(id.desc ? id.desc : ""));
450                 stringz(fields);
451                 loopvj(id.fields) concformatstring(fields, "%s%s", j ? " " : "", id.fields[j]);
452                 f->printf("\t%s", escapestring(*fields ? fields : ""));
453                 f->printf("\n");
454             }
455         }
456         delete f;
457     }
458     ICOMMAND(0, writevarsinfo, "s", (char *name), if(!(identflags&IDF_WORLD)) writegamevarsinfo(name));
459 
460     // collect c2s messages conveniently
461     vector<uchar> messages;
462     bool messagereliable = false;
463 
464     VAR(IDF_PERSIST, colourchat, 0, 1, 1);
465     SVAR(IDF_PERSIST, filterwords, "");
466 
467     VAR(IDF_PERSIST, showlaptimes, 0, 2, 3); // 0 = off, 1 = only player, 2 = +humans, 3 = +bots
468 
defaultserversort()469     const char *defaultserversort()
470     {
471         static string vals;
472         formatstring(vals, "%d %d %d", SINFO_NUMPLRS, SINFO_PRIO, SINFO_PING);
473         return vals;
474     }
475 
resetserversort()476     void resetserversort()
477     {
478         setsvarchecked(getident("serversort"), defaultserversort());
479     }
480     ICOMMAND(0, serversortreset, "", (), resetserversort());
481 
482     vector<int> serversortstyles;
483     void updateserversort();
484     SVARF(IDF_PERSIST, serversort, defaultserversort(),
485     {
486         if(!serversort[0] || serversort[0] == '[')
487         {
488             delete[] serversort;
489             serversort = newstring(defaultserversort());
490         }
491         updateserversort();
492     });
493 
updateserversort()494     void updateserversort()
495     {
496         vector<char *> styles;
497         explodelist(serversort, styles, SINFO_MAX);
498         serversortstyles.setsize(0);
499         loopv(styles) serversortstyles.add(parseint(styles[i]));
500         styles.deletearrays();
501     }
502 
getvitem(gameent * d,int n,int v)503     void getvitem(gameent *d, int n, int v)
504     {
505         if(n < 0) intret(d->vitems.length());
506         else if(v < 0) intret(2);
507         else if(d->vitems.inrange(n)) switch(v)
508         {
509             case 0: intret(d->vitems[n]); break;
510             case 1: if(vanities.inrange(d->vitems[n])) result(vanities[d->vitems[n]].ref); break;
511             default: break;
512         }
513     }
514     ICOMMAND(0, getplayervanity, "", (), result(game::player1->vanity));
515     ICOMMAND(0, getplayervitem, "bi", (int *n, int *v), getvitem(game::player1, *n, *v));
516 
517     ICOMMAND(0, mastermode, "i", (int *val), addmsg(N_MASTERMODE, "ri", *val));
518     ICOMMAND(0, getplayername, "", (), result(game::player1->name));
519     ICOMMAND(0, getplayercolour, "bg", (int *m, int *f), intret(game::getcolour(game::player1, *m, *f >= 0 && *f <= 10 ? *f : 1.f)));
520     ICOMMAND(0, getplayermodel, "", (), intret(game::player1->model));
521     ICOMMAND(0, getplayerpattern, "", (), intret(game::player1->pattern));
522     ICOMMAND(0, getplayerteam, "i", (int *p), *p ? intret(game::player1->team) : result(TEAM(game::player1->team, name)));
523     ICOMMAND(0, getplayerteamicon, "", (), result(hud::teamtexname(game::player1->team)));
524     ICOMMAND(0, getplayerteamcolour, "", (), intret(TEAM(game::player1->team, colour)));
525     ICOMMAND(0, getplayercn, "", (), intret(game::player1->clientnum));
526 
getname()527     const char *getname() { return game::player1->name; }
528 
setplayername(const char * name)529     void setplayername(const char *name)
530     {
531         if(name && *name)
532         {
533             string namestr;
534             filterstring(namestr, name, true, true, true, true, MAXNAMELEN);
535             if(*namestr && strcmp(game::player1->name, namestr))
536             {
537                 game::player1->setname(namestr);
538                 if(initing == NOT_INITING) conoutft(CON_EVENT, "\fm* you are now known as %s", game::player1->name);
539                 sendplayerinfo = true;
540             }
541         }
542     }
543     SVARF(IDF_PERSIST, playername, "", setplayername(playername));
544 
setplayercolour(int colour)545     void setplayercolour(int colour)
546     {
547         if(colour >= 0 && colour <= 0xFFFFFF && game::player1->colour != colour)
548         {
549             game::player1->colour = colour;
550             sendplayerinfo = true;
551         }
552     }
553     VARF(IDF_PERSIST|IDF_HEX, playercolour, -1, -1, 0xFFFFFF, setplayercolour(playercolour));
554 
setplayermodel(int model)555     void setplayermodel(int model)
556     {
557         if(model >= 0 && game::player1->model != model)
558         {
559             game::player1->model = model;
560             sendplayerinfo = true;
561         }
562     }
563     VARF(IDF_PERSIST, playermodel, 0, 0, PLAYERTYPES-1, setplayermodel(playermodel));
564 
setplayerpattern(int pattern)565     void setplayerpattern(int pattern)
566     {
567         if(pattern >= 0 && game::player1->pattern != pattern)
568         {
569             game::player1->pattern = pattern;
570             sendplayerinfo = true;
571         }
572     }
573     VARF(IDF_PERSIST, playerpattern, 0, 0, PLAYERPATTERNS-1, setplayerpattern(playerpattern));
574 
575     SVARF(IDF_PERSIST, playervanity, "", if(game::player1->setvanity(playervanity)) sendplayerinfo = true;);
576 
setloadweap(const char * list)577     void setloadweap(const char *list)
578     {
579         vector<int> items;
580         if(list && *list)
581         {
582             vector<char *> chunk;
583             explodelist(list, chunk);
584             loopv(chunk)
585             {
586                 if(!chunk[i] || !*chunk[i] || !isnumeric(*chunk[i])) continue;
587                 int v = parseint(chunk[i]);
588                 items.add(v >= W_OFFSET && v < W_ITEM ? v : 0);
589             }
590             chunk.deletearrays();
591         }
592         game::player1->loadweap.shrink(0);
593         loopv(items) if(game::player1->loadweap.find(items[i]) < 0)
594         {
595             game::player1->loadweap.add(items[i]);
596             if(game::player1->loadweap.length() >= W_LOADOUT) break;
597         }
598         sendplayerinfo = true;
599     }
600     SVARF(IDF_PERSIST, playerloadweap, "", setloadweap(playerloadweap));
601 
setrandweap(const char * list)602     void setrandweap(const char *list)
603     {
604         vector<int> items;
605         if(list && *list)
606         {
607             vector<char *> chunk;
608             explodelist(list, chunk);
609             loopv(chunk)
610             {
611                 if(!chunk[i] || !*chunk[i] || !isnumeric(*chunk[i])) continue;
612                 int v = parseint(chunk[i]);
613                 items.add(v ? 1 : 0);
614             }
615             chunk.deletearrays();
616         }
617         game::player1->randweap.shrink(0);
618         loopv(items)
619         {
620             game::player1->randweap.add(items[i]);
621             if(game::player1->randweap.length() >= W_LOADOUT) break;
622         }
623         sendplayerinfo = true;
624     }
625     SVARF(IDF_PERSIST, playerrandweap, "", setrandweap(playerrandweap));
626 
627     ICOMMAND(0, getrandweap, "i", (int *n), intret(game::player1->randweap.inrange(*n) ? game::player1->randweap[*n] : 1));
628     ICOMMAND(0, getloadweap, "i", (int *n), intret(game::player1->loadweap.inrange(*n) ? game::player1->loadweap[*n] : -1));
629     ICOMMAND(0, allowedweap, "i", (int *n), intret(isweap(*n) && m_check(W(*n, modes), W(*n, muts), game::gamemode, game::mutators) && !W(*n, disabled) ? 1 : 0));
630     ICOMMAND(0, hasloadweap, "bb", (int *g, int *m), intret(m_loadout(m_game(*g) ? *g : game::gamemode, *m >= 0 ? *m : game::mutators) ? 1 : 0));
631 
teamfromname(const char * team)632     int teamfromname(const char *team)
633     {
634         if(m_team(game::gamemode, game::mutators))
635         {
636             if(team[0])
637             {
638                 int t = atoi(team);
639                 loopi(numteams(game::gamemode, game::mutators))
640                 {
641                     if((t && t == i+T_FIRST) || !strcasecmp(TEAM(i+T_FIRST, name), team))
642                     {
643                         return i+T_FIRST;
644                     }
645                 }
646             }
647             return T_FIRST;
648         }
649         return T_NEUTRAL;
650     }
651 
switchteam(const char * team)652     void switchteam(const char *team)
653     {
654         if(team[0])
655         {
656             if(m_team(game::gamemode, game::mutators))
657             {
658                 int t = teamfromname(team);
659                 if(isteam(game::gamemode, game::mutators, t, T_FIRST)) addmsg(N_SWITCHTEAM, "ri", t);
660             }
661             else conoutft(CON_DEBUG, "\frCan only change teams when actually playing in team games");
662         }
663         else conoutft(CON_DEBUG, "\fgYour team is: %s", game::colourteam(game::player1->team));
664     }
665     ICOMMAND(0, team, "s", (char *s), switchteam(s));
666 
allowedittoggle(bool edit)667     bool allowedittoggle(bool edit)
668     {
669         bool allow = edit || m_edit(game::gamemode); // && game::player1->state == CS_ALIVE);
670         if(!allow) conoutft(CON_DEBUG, "\frYou must start an editing game to edit the map");
671         return allow;
672     }
673 
edittoggled(bool edit)674     void edittoggled(bool edit)
675     {
676         if(!edit && (game::maptime <= 0 || game::player1->state != CS_EDITING)) return;
677         game::player1->editspawn(game::gamemode, game::mutators);
678         game::player1->state = edit ? CS_EDITING : (m_edit(game::gamemode) ? CS_ALIVE : CS_DEAD);
679         game::player1->o = camera1->o;
680         game::player1->yaw = camera1->yaw;
681         game::player1->pitch = camera1->pitch;
682         game::player1->resetinterp();
683         game::resetstate();
684         game::specreset();
685         physics::entinmap(game::player1, true); // find spawn closest to current floating pos
686         projs::removeplayer(game::player1);
687         if(m_edit(game::gamemode)) addmsg(N_EDITMODE, "ri", edit ? 1 : 0);
688     }
689 
690     ICOMMAND(0, getclientfocused, "", (), intret(game::focus ? game::focus->clientnum : game::player1->clientnum));
691 
getcn(physent * d)692     int getcn(physent *d)
693     {
694         if(!d || !gameent::is(d)) return -1;
695         return ((gameent *)d)->clientnum;
696     }
697 
parseplayer(const char * arg)698     int parseplayer(const char *arg)
699     {
700         if(!arg || !*arg) return game::player1->clientnum;
701         char *end;
702         int n = strtol(arg, &end, 10);
703         if(*arg && !*end)
704         {
705             if(n != game::player1->clientnum && !game::players.inrange(n)) return -1;
706             return n;
707         }
708         #define PARSEPLAYER(op,val) \
709         { \
710             gameent *o = game::player1; \
711             if(!op(arg, val)) return o->clientnum; \
712             loopv(game::players) if(game::players[i]) \
713             { \
714                 o = game::players[i]; \
715                 if(!op(arg, val)) return o->clientnum; \
716             } \
717         }
718         PARSEPLAYER(strcmp, game::colourname(o, NULL, false, true, 0));
719         PARSEPLAYER(strcasecmp, game::colourname(o, NULL, false, true, 0));
720         PARSEPLAYER(strcmp, o->name);
721         PARSEPLAYER(strcasecmp, o->name);
722         #define PARSEPLAYERN(op,val) \
723         { \
724             gameent *o = game::player1; \
725             if(!op(arg, val, len)) return o->clientnum; \
726             loopv(game::players) if(game::players[i]) \
727             { \
728                 o = game::players[i]; \
729                 if(!op(arg, val, len)) return o->clientnum; \
730             } \
731         }
732         size_t len = strlen(arg);
733         PARSEPLAYERN(strncmp, game::colourname(o, NULL, false, true, 0));
734         PARSEPLAYERN(strncasecmp, game::colourname(o, NULL, false, true, 0));
735         PARSEPLAYERN(strncmp, o->name);
736         PARSEPLAYERN(strncasecmp, o->name);
737         return -1;
738     }
739     ICOMMAND(IDF_NAMECOMPLETE, getclientnum, "s", (char *who), intret(parseplayer(who)));
740 
listclients(bool local,int noai)741     void listclients(bool local, int noai)
742     {
743         vector<char> buf;
744         string cn;
745         int numclients = 0;
746         if(local)
747         {
748             formatstring(cn, "%d", game::player1->clientnum);
749             buf.put(cn, strlen(cn));
750             numclients++;
751         }
752         loopv(game::players) if(game::players[i] && (!noai || game::players[i]->actortype > (noai >= 2 ? A_PLAYER : A_BOT)))
753         {
754             formatstring(cn, "%d", game::players[i]->clientnum);
755             if(numclients++) buf.add(' ');
756             buf.put(cn, strlen(cn));
757         }
758         buf.add('\0');
759         result(buf.getbuf());
760     }
761     ICOMMAND(0, listclients, "ii", (int *local, int *noai), listclients(*local!=0, *noai));
762 
getlastclientnum()763     void getlastclientnum()
764     {
765         int cn = game::player1->clientnum;
766         loopv(game::players) if(game::players[i] && game::players[i]->clientnum > cn) cn = game::players[i]->clientnum;
767         intret(cn);
768     }
769     ICOMMAND(0, getlastclientnum, "", (), getlastclientnum());
770 
771     #define LOOPCLIENTS(name,op,lp,nop) \
772         ICOMMAND(0, loopclients##name, "iire", (int *count, int *skip, ident *id, uint *body), \
773         { \
774             loopstart(id, stack); \
775             int amt = 1; \
776             loopv(game::players) if(game::players[i]) amt++; \
777             op(amt, *count, *skip) \
778             { \
779                 int r = -1; \
780                 int n = nop ? amt-1 : 0; \
781                 if(!i) \
782                 { \
783                     if(nop ? n <= i : n >= i) r = game::player1->clientnum; \
784                     if(nop) n--; \
785                     else n++; \
786                 } \
787                 else \
788                 { \
789                     lp(game::players) if(game::players[k]) \
790                     { \
791                         if(nop ? n <= i : n >= i) \
792                         { \
793                             r = game::players[k]->clientnum; \
794                             break; \
795                         } \
796                         if(nop) n--; \
797                         else n++; \
798                     } \
799                 } \
800                 if(r >= 0) \
801                 { \
802                     loopiter(id, stack, r); \
803                     execute(body); \
804                 } \
805             } \
806             loopend(id, stack); \
807         });
808     LOOPCLIENTS(,loopcsi,loopvk,false);
809     LOOPCLIENTS(rev,loopcsirev,loopvkrev,true);
810 
811     #define LOOPCLIENTSIF(name,op,lp,nop) \
812         ICOMMAND(0, loopclients##name##if, "iiree", (int *count, int *skip, ident *id, uint *cond, uint *body), \
813         { \
814             loopstart(id, stack); \
815             int amt = 1; \
816             loopv(game::players) if(game::players[i]) amt++; \
817             op(amt, *count, *skip) \
818             { \
819                 int r = -1; \
820                 int n = nop ? amt-1 : 0; \
821                 if(!i) \
822                 { \
823                     if(nop ? n <= i : n >= i) r = game::player1->clientnum; \
824                     if(nop) n--; \
825                     else n++; \
826                 } \
827                 else \
828                 { \
829                     lp(game::players) if(game::players[k]) \
830                     { \
831                         if(nop ? n <= i : n >= i) \
832                         { \
833                             r = game::players[k]->clientnum; \
834                             break; \
835                         } \
836                         if(nop) n--; \
837                         else n++; \
838                     } \
839                 } \
840                 if(r >= 0) \
841                 { \
842                     loopiter(id, stack, r); \
843                     if(executebool(cond)) execute(body); \
844                 } \
845             } \
846             loopend(id, stack); \
847         });
848     LOOPCLIENTSIF(,loopcsi,loopvk,false);
849     LOOPCLIENTSIF(rev,loopcsirev,loopvkrev,true);
850 
851     #define LOOPINVENTORY(name,op,lp,nop) \
852         ICOMMAND(IDF_NAMECOMPLETE, loopinventory##name, "siire", (char *who, int *count, int *skip, ident *id, uint *body), \
853         { \
854             gameent *d = game::getclient(parseplayer(who)); \
855             if(!d) return; \
856             loopstart(id, stack); \
857             int amt = d->holdweapcount(m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis); \
858             op(amt, *count, *skip) \
859             { \
860                 int r = -1; \
861                 int n = nop ? amt-1 : 0; \
862                 lp(W_ALL) if(d->holdweap(k, m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis)) \
863                 { \
864                     if(nop ? n <= i : n >= i) \
865                     { \
866                         r = k; \
867                         break; \
868                     } \
869                     if(nop) n--; \
870                     else n++; \
871                 } \
872                 if(r >= 0) \
873                 { \
874                     loopiter(id, stack, r); \
875                     execute(body); \
876                 } \
877             } \
878             loopend(id, stack); \
879         });
880     LOOPINVENTORY(,loopcsi,loopk,false);
881     LOOPINVENTORY(rev,loopcsirev,loopkrev,true);
882 
883     VAR(0, numplayertypes, 1, PLAYERTYPES, -1);
884     ICOMMAND(0, getmodelname, "ib", (int *mdl, int *idx), result(*mdl >= 0 ? playertypes[*mdl%PLAYERTYPES][*idx >= 0 ? clamp(*idx, 0, 6) : 6] : ""));
885     VAR(0, numpatterns, 1, PLAYERPATTERNS, -1);
886     ICOMMAND(0, getpattern, "ib", (int *pattern, int *idx),
887         if(*pattern >= 0)
888         {
889             const ::playerpattern &p = playerpatterns[*pattern%PLAYERPATTERNS];
890             switch(*idx)
891             {
892                 case 0: result(p.filename); break;
893                 case 1: result(p.id); break;
894                 case 2: result(p.name); break;
895                 case 3: intret(p.clamp); break;
896                 case 4: intret(p.scale); break;
897                 default: break;
898             }
899         }
900     );
901 
902     ICOMMAND(0, getcamerayaw, "", (), floatret(camera1->yaw));
903     ICOMMAND(0, getcamerapitch, "", (), floatret(camera1->pitch));
904     ICOMMAND(0, getcameraroll, "", (), floatret(camera1->roll));
905     ICOMMAND(0, getcameraoffyaw, "f", (float *yaw), floatret(*yaw-camera1->yaw));
906 
907     CLCOMMANDK(presence, intret(1), intret(0));
908     CLCOMMAND(yaw, floatret(d->yaw));
909     CLCOMMAND(pitch, floatret(d->pitch));
910     CLCOMMAND(roll, floatret(d->roll));
911 
radarallow(gameent * d,vec & dir,float & dist)912     bool radarallow(gameent *d, vec &dir, float &dist)
913     {
914         if(m_hard(game::gamemode, game::mutators) || d == game::focus || d->actortype >= A_ENEMY) return false;
915         if(d->state != CS_ALIVE && d->state != CS_EDITING && d->state != CS_DEAD && (!d->lastdeath || d->state != CS_WAITING)) return false;
916         if(m_duke(game::gamemode, game::mutators) && (!d->lastdeath || lastmillis-d->lastdeath >= 1000)) return false;
917         bool dominated = game::focus->dominated.find(d) >= 0;
918         if(!dominated && vec(d->vel).add(d->falling).magnitude() <= 0) return false;
919         dir = vec(d->center()).sub(camera1->o);
920         dist = dir.magnitude();
921         if(!dominated && hud::radarlimited(dist)) return false;
922         return true;
923     }
924     CLCOMMAND(radarallow,
925     {
926         vec dir(0, 0, 0);
927         float dist = -1;
928         intret(radarallow(d, dir, dist) ? 1 : 0);
929     });
930     CLCOMMAND(radardist,
931     {
932         vec dir(0, 0, 0);
933         float dist = -1;
934         if(!radarallow(d, dir, dist)) return;
935         floatret(dist);
936     });
937     CLCOMMAND(radardir,
938     {
939         vec dir(0, 0, 0);
940         float dist = -1;
941         if(!radarallow(d, dir, dist)) return;
942         dir.rotate_around_z(-camera1->yaw*RAD).normalize();
943         floatret(-atan2(dir.x, dir.y)/RAD);
944     });
945     CLCOMMAND(radaryaw,
946     {
947         vec dir(0, 0, 0);
948         float dist = -1;
949         if(!radarallow(d, dir, dist)) return;
950         floatret(d->yaw-camera1->yaw);
951     });
952 
953     CLCOMMANDM(name, "sbbb", (char *who, int *colour, int *icon, int *dupname), result(game::colourname(d, NULL, *icon!=0, *dupname!=0, *colour >= 0 ? *colour : 3)));
954     CLCOMMANDM(colour, "sbg", (char *who, int *m, float *f), intret(game::getcolour(d, *m, *f >= 0 && *f <= 10 ? *f : 1.f)));
955     CLCOMMANDM(vitem, "sbi", (char *who, int *n, int *v), getvitem(d, *n, *v));
956 
957     CLCOMMAND(weapselect, intret(d->weapselect));
958     CLCOMMANDM(loadweap, "si", (char *who, int *n), intret(d->loadweap.inrange(*n) ? d->loadweap[*n] : -1));
959     CLCOMMANDM(weapget, "siii", (char *who, int *n, int *a, int *b), intret(isweap(*n) ? d->getammo(*n, *a!=0 ? lastmillis : 0, *b!=0) : -1));
960     CLCOMMANDM(weapammo, "sii", (char *who, int *n, int *m), intret(isweap(*n) ? d->weapammo[*n][clamp(*m, 0, W_A_MAX-1)] : -1));
961     CLCOMMANDM(weapclip, "si", (char *who, int *n), intret(isweap(*n) ? d->weapammo[*n][W_A_CLIP] : -1));
962     CLCOMMANDM(weapstore, "si", (char *who, int *n), intret(isweap(*n) ? d->weapammo[*n][W_A_STORE] : -1));
963     CLCOMMANDM(weapstate, "si", (char *who, int *n), intret(isweap(*n) ? d->weapstate[*n] : W_S_IDLE));
964     CLCOMMANDM(weaptime, "si", (char *who, int *n), intret(isweap(*n) ? d->weaptime[*n] : 0));
965     CLCOMMANDM(weapwait, "si", (char *who, int *n), intret(isweap(*n) ? d->weapwait[*n] : 0));
966     CLCOMMANDM(weapload, "sii", (char *who, int *n, int *m), intret(isweap(*n) ? d->weapload[*n][clamp(*m, 0, W_A_MAX-1)] : 0));
967     CLCOMMANDM(weaphold, "si", (char *who, int *n), intret(isweap(*n) && d->holdweap(*n, m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis) ? 1 : 0));
968     CLCOMMAND(weapholdnum, intret(d->holdweapcount(m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis)));
969     CLCOMMANDM(action, "sb", (char *who, int *n), intret(d->action[clamp(*n, 0, int(AC_MAX-1))] ? 1 : 0));
970     CLCOMMANDM(actiontime, "sb", (char *who, int *n), intret(d->actiontime[clamp(*n, 0, int(AC_MAX-1))]));
971 
972     CLCOMMAND(move, intret(d->move));
973     CLCOMMAND(strafe, intret(d->strafe));
974     CLCOMMAND(turnside, intret(d->turnside));
975     CLCOMMAND(physstate, intret(d->physstate));
976     CLCOMMAND(lastdeath, intret(d->lastdeath));
977     CLCOMMAND(lastspawn, intret(d->lastspawn));
978     CLCOMMAND(lastbuff, intret(d->lastbuff));
979     CLCOMMAND(lastshoot, intret(d->lastshoot));
980     CLCOMMAND(airmillis, intret(d->airmillis));
981     CLCOMMAND(floormillis, intret(d->floormillis));
982     CLCOMMAND(inliquid, intret(d->inliquid ? 1 : 0));
983     CLCOMMAND(onladder, intret(d->onladder ? 1 : 0));
984     CLCOMMAND(headless, intret(d->headless ? 1 : 0));
985     CLCOMMAND(obliterated, intret(d->obliterated ? 1 : 0));
986     CLCOMMAND(actortype, intret(d->actortype));
987     CLCOMMAND(pcolour, intret(d->colour));
988     CLCOMMAND(model, intret(d->model%PLAYERTYPES));
989     CLCOMMAND(pattern, intret(d->pattern%PLAYERPATTERNS));
990     CLCOMMAND(vanity, result(d->vanity));
991     CLCOMMAND(handle, result(d->handle));
992     CLCOMMAND(steamid, result(d->steamid));
993     CLCOMMAND(host, result(d->hostip));
994     CLCOMMAND(ip, result(d->hostip));
995     CLCOMMAND(ping, intret(d->ping));
996     CLCOMMAND(pj, intret(d->plag));
997     CLCOMMAND(team, intret(d->team));
998     CLCOMMAND(state, intret(d->state));
999     CLCOMMAND(health, intret(d->health));
1000     CLCOMMAND(points, intret(d->points));
1001     CLCOMMAND(cptime, intret(d->cptime));
1002     CLCOMMAND(cplast, intret(d->cplast));
1003     CLCOMMAND(cpmillis, intret(d->cpmillis ? lastmillis-d->cpmillis : 0));
1004     CLCOMMAND(frags, intret(d->frags));
1005     CLCOMMAND(deaths, intret(d->deaths));
1006     CLCOMMAND(totalpoints, intret(d->totalpoints));
1007     CLCOMMAND(totalfrags, intret(d->totalfrags));
1008     CLCOMMAND(totaldeaths, intret(d->totaldeaths));
1009     CLCOMMAND(totalavgpos, floatret(d->totalavgpos));
1010     CLCOMMAND(balancescore, floatret(d->balancescore()));
1011     CLCOMMAND(timeplayed, intret(d->updatetimeplayed()));
1012 
1013     CLCOMMAND(speed, floatret(d->speed));
1014     CLCOMMAND(jumpspeed, floatret(d->jumpspeed));
1015     CLCOMMAND(impulsespeed, floatret(d->impulsespeed));
1016     CLCOMMAND(weight, floatret(d->weight));
1017 
1018     CLCOMMAND(scoretime, floatret(d->scoretime()));
1019     CLCOMMANDM(kdratio, "si", (char *who, int *n), intret(d->kdratio(*n!=0)));
1020 
1021     CLCOMMAND(allowimpulse, intret(physics::allowimpulse(d) ? 1 : 0));
1022     CLCOMMAND(impulsemeter, intret(d->impulse[IM_METER])); // IM_METER = 0, IM_TYPE, IM_TIME, IM_REGEN, IM_COUNT, IM_COLLECT, IM_SLIP, IM_SLIDE, IM_JUMP, IM_MAX
1023     CLCOMMAND(impulsetype, intret(d->impulse[IM_TYPE]));
1024     CLCOMMANDM(impulsetimer, "b", (char *who, int *n), intret(d->impulsetime[*n >= 0 && *n < IM_T_MAX ? *n : d->impulse[IM_TYPE]]));
1025     CLCOMMAND(impulseregen, intret(d->impulse[IM_REGEN]));
1026     CLCOMMAND(impulsecount, intret(d->impulse[IM_COUNT]));
1027     CLCOMMAND(impulsecollect, intret(d->impulse[IM_COLLECT]));
1028     CLCOMMAND(impulseslip, intret(d->impulse[IM_SLIP]));
1029     CLCOMMAND(impulsejump, intret(d->impulsetime[IM_T_JUMP]));
1030     CLCOMMAND(impulsewait, intret(d->impulsetime[IM_T_PUSHER]));
1031     CLCOMMANDM(impulse, "si", (char *who, int *n), intret(*n >= 0 && *n < IM_MAX ? d->impulse[*n] : 0));
1032 
1033     CLCOMMAND(buffing, intret(d->lastbuff));
1034     CLCOMMAND(burning, intret(d->burntime ? d->burning(lastmillis, d->burntime) : 0));
1035     CLCOMMAND(bleeding, intret(d->bleedtime ? d->bleeding(lastmillis, d->bleedtime) : 0));
1036     CLCOMMAND(shocking, intret(d->shocktime ? d->shocking(lastmillis, d->shocktime) : 0));
1037     CLCOMMAND(regen, intret(regentime ? d->lastregen : 0));
1038     CLCOMMAND(impulselast, intret(game::canregenimpulse(d) && d->impulse[IM_METER] > 0 && d->lastimpulsecollect ? (lastmillis-d->lastimpulsecollect)%1000 : 0));
1039 
1040     CLCOMMAND(spawnweap, intret(m_weapon(d->actortype, game::gamemode, game::mutators)));
1041     CLCOMMAND(spawndelay, intret(m_delay(d->actortype, game::gamemode, game::mutators, d->team)));
1042     CLCOMMAND(spawnprotect, intret(m_protect(game::gamemode, game::mutators)));
1043     CLCOMMAND(spawnhealth, intret(d->gethealth(game::gamemode, game::mutators)));
1044     CLCOMMAND(maxhealth, intret(d->gethealth(game::gamemode, game::mutators, true)));
1045 
1046     CLCOMMANDM(rescolour, "sib", (char *who, int *n, int *c), intret(game::pulsehexcol(d, *n, *c > 0 ? *c : 50)));
1047     CLCOMMANDM(velocity, "si", (char *who, int *n), floatret(vec(d->vel).add(d->falling).magnitude()*(*n!=0 ? (*n > 0 ? 3.6f/8.f : 0.125f) : 1.f)));
1048 
1049     #define CLDOMCMD(dtype) \
1050         CLCOMMANDM(dtype, "sb", (char *who, int *n), \
1051         { \
1052             if(*n < 0) intret(d->dtype.length()); \
1053             else if(d->dtype.inrange(*n)) intret(d->dtype[*n]->clientnum); \
1054         });
1055     CLDOMCMD(dominating);
1056     CLDOMCMD(dominated);
1057 
1058     #define CLISDOMCMD(dtype) \
1059         CLCOMMANDMK(is##dtype, "ss", (char *who, char *n), \
1060         { \
1061             gameent *e = game::getclient(client::parseplayer(n)); \
1062             if(!e) \
1063             { \
1064                 intret(0); \
1065                 return; \
1066             } \
1067             loopv(d->dtype) if(d->dtype[i]->clientnum == e->clientnum) \
1068             { \
1069                 intret(1); \
1070                 return; \
1071             } \
1072             intret(0); \
1073             return; \
1074         }, intret(0); return);
1075     CLISDOMCMD(dominating);
1076     CLISDOMCMD(dominated);
1077 
1078     CLCOMMAND(privilege, intret(d->privilege&PRIV_TYPE));
1079     CLCOMMAND(privlocal, intret(d->privilege&PRIV_LOCAL ? 1 : 0));
1080     CLCOMMAND(privtex, result(hud::privtex(d->privilege, d->actortype)));
haspriv(gameent * d,int priv)1081     bool haspriv(gameent *d, int priv)
1082     {
1083         if(!d) return false;
1084         if(!priv || (d == game::player1 && !remote)) return true;
1085         return (d->privilege&PRIV_TYPE) >= priv;
1086     }
1087     #define CLPRIVCMD(pname,pval) CLCOMMAND(priv##pname, intret(haspriv(d, pval) ? 1 : 0));
1088     CLPRIVCMD(none, PRIV_NONE);
1089     CLPRIVCMD(player, PRIV_PLAYER);
1090     CLPRIVCMD(supporter, PRIV_SUPPORTER);
1091     CLPRIVCMD(moderator, PRIV_MODERATOR);
1092     CLPRIVCMD(administrator, PRIV_ADMINISTRATOR);
1093     CLPRIVCMD(developer, PRIV_DEVELOPER);
1094     CLPRIVCMD(founder, PRIV_CREATOR);
1095     CLCOMMANDM(priv, "si", (char *who, int *priv), intret(haspriv(d, clamp(*priv, 0, PRIV_MAX-1)) ? 1 : 0));
1096 
getclientversion(int cn,int prop)1097     void getclientversion(int cn, int prop)
1098     {
1099         gameent *d = cn >= 0 ? game::getclient(cn) : game::player1;
1100         if(d) switch(prop)
1101         {
1102             case -3:
1103             {
1104                 defformatstring(branch, "%s", d->version.branch);
1105                 if(d->version.build > 0) concformatstring(branch, "-%d", d->version.build);
1106                 defformatstring(str, "%d.%d.%d-%s%d-%s", d->version.major, d->version.minor, d->version.patch, plat_name(d->version.platform), d->version.arch, branch);
1107                 result(str);
1108             }
1109             case -2: result(plat_name(d->version.platform)); break;
1110             case -1: intret(16);
1111             case 0: intret(d->version.major); break;
1112             case 1: intret(d->version.minor); break;
1113             case 2: intret(d->version.patch); break;
1114             case 3: intret(d->version.game); break;
1115             case 4: intret(d->version.platform); break;
1116             case 5: intret(d->version.arch); break;
1117             case 6: intret(d->version.gpuglver); break;
1118             case 7: intret(d->version.gpuglslver); break;
1119             case 8: intret(d->version.crc); break;
1120             case 9: result(d->version.gpuvendor); break;
1121             case 10: result(d->version.gpurenderer); break;
1122             case 11: result(d->version.gpuversion); break;
1123             case 13: result(d->version.branch); break;
1124             case 14: intret(d->version.build); break;
1125             case 15: result(d->version.revision); break;
1126             default: break;
1127         }
1128     }
1129     ICOMMAND(IDF_NAMECOMPLETE, getclientversion, "si", (char *who, int *prop), getclientversion(parseplayer(who), *prop));
1130 
isspectator(int cn)1131     bool isspectator(int cn)
1132     {
1133         gameent *d = game::getclient(cn);
1134         return d && d->state == CS_SPECTATOR;
1135     }
1136     ICOMMAND(IDF_NAMECOMPLETE, isspectator, "s", (char *who), intret(isspectator(parseplayer(who)) ? 1 : 0));
1137 
isquarantine(int cn)1138     bool isquarantine(int cn)
1139     {
1140         gameent *d = game::getclient(cn);
1141         return d && d->quarantine;
1142     }
1143     ICOMMAND(IDF_NAMECOMPLETE, isquarantine, "s", (char *who), intret(isquarantine(parseplayer(who)) ? 1 : 0));
1144 
isai(int cn,int type)1145     bool isai(int cn, int type)
1146     {
1147         gameent *d = game::getclient(cn);
1148         int actortype = type > 0 && type < A_MAX ? type : A_BOT;
1149         return d && d->actortype == actortype;
1150     }
1151     ICOMMAND(IDF_NAMECOMPLETE, isai, "si", (char *who, int *type), intret(isai(parseplayer(who), *type) ? 1 : 0));
1152 
mutscmp(int req,int limit)1153     bool mutscmp(int req, int limit)
1154     {
1155         if(req)
1156         {
1157             if(!limit) return false;
1158             loopi(G_M_NUM) if(req&(1<<i) && !(limit&(1<<i))) return false;
1159         }
1160         return true;
1161     }
1162 
ismodelocked(int reqmode,int reqmuts,int askmuts=0,const char * reqmap=NULL)1163     bool ismodelocked(int reqmode, int reqmuts, int askmuts = 0, const char *reqmap = NULL)
1164     {
1165         reqmuts |= mutslockforce;
1166         if(!m_game(reqmode) || (m_local(reqmode) && remote)) return true;
1167         if(!reqmap || !*reqmap) reqmap = "<random>";
1168         bool israndom = !strcmp(reqmap, "<random>");
1169         if(G(votelock)) switch(G(votelocktype))
1170         {
1171             case 1: if(!haspriv(game::player1, G(votelock))) return true; break;
1172             case 2:
1173                 if(!israndom && !m_edit(reqmode))
1174                 {
1175                     int n = listincludes(previousmaps, reqmap, strlen(reqmap));
1176                     if(n >= 0 && n < G(maphistory) && !haspriv(game::player1, G(votelock))) return true;
1177                 }
1178                 break;
1179             case 0: default: break;
1180         }
1181         modecheck(reqmode, reqmuts, askmuts);
1182         if(askmuts && !mutscmp(askmuts, reqmuts)) return true;
1183         if(G(modelock)) switch(G(modelocktype))
1184         {
1185             case 1: if(!haspriv(game::player1, G(modelock))) return true; break;
1186             case 2: if((!((1<<reqmode)&G(modelockfilter)) || !mutscmp(reqmuts, G(mutslockfilter))) && !haspriv(game::player1, G(modelock))) return true; break;
1187             case 0: default: break;
1188         }
1189         if(!israndom && !m_edit(reqmode) && G(mapslock))
1190         {
1191             char *list = NULL;
1192             switch(G(mapslocktype))
1193             {
1194                 case 1:
1195                 {
1196                     list = newstring(G(allowmaps));
1197                     mapcull(list, reqmode, reqmuts, otherclients(true), G(mapsfilter), true);
1198                     break;
1199                 }
1200                 case 2:
1201                 {
1202                     maplist(list, reqmode, reqmuts, otherclients(true), G(mapsfilter), true);
1203                     break;
1204                 }
1205                 case 0: default: break;
1206             }
1207             if(list)
1208             {
1209                 if(listincludes(list, reqmap, strlen(reqmap)) < 0 && !haspriv(game::player1, G(modelock)))
1210                 {
1211                     DELETEA(list);
1212                     return true;
1213                 }
1214                 DELETEA(list);
1215             }
1216         }
1217         return false;
1218     }
1219     ICOMMAND(0, ismodelocked, "iiis", (int *g, int *m, int *a, char *s), intret(ismodelocked(*g, *m, *a, s) ? 1 : 0));
1220 
getmastermode(int n)1221     void getmastermode(int n)
1222     {
1223         switch(n)
1224         {
1225             case 0: intret(mastermode); return;
1226             case 1: result(mastermodename(mastermode)); return;
1227             default: break;
1228         }
1229         intret(-1);
1230     }
1231     ICOMMAND(0, getmastermode, "i", (int *n), getmastermode(*n));
1232 
addcontrol(const char * arg,int type,const char * msg)1233     void addcontrol(const char *arg, int type, const char *msg)
1234     {
1235         int i = parseplayer(arg);
1236         if(i >= 0) addmsg(N_ADDCONTROL, "ri2s", i, type, msg);
1237     }
1238     ICOMMAND(IDF_NAMECOMPLETE, kick, "ss", (char *s, char *m), addcontrol(s, -1, m));
1239     ICOMMAND(IDF_NAMECOMPLETE, allow, "ss", (char *s, char *m), addcontrol(s, ipinfo::ALLOW, m));
1240     ICOMMAND(IDF_NAMECOMPLETE, ban, "ss", (char *s, char *m), addcontrol(s, ipinfo::BAN, m));
1241     ICOMMAND(IDF_NAMECOMPLETE, mute, "ss", (char *s, char *m), addcontrol(s, ipinfo::MUTE, m));
1242     ICOMMAND(IDF_NAMECOMPLETE, limit, "ss", (char *s, char *m), addcontrol(s, ipinfo::LIMIT, m));
1243     ICOMMAND(IDF_NAMECOMPLETE, except, "ss", (char *s, char *m), addcontrol(s, ipinfo::EXCEPT, m));
1244 
1245     ICOMMAND(0, clearallows, "", (), addmsg(N_CLRCONTROL, "ri", ipinfo::ALLOW));
1246     ICOMMAND(0, clearbans, "", (), addmsg(N_CLRCONTROL, "ri", ipinfo::BAN));
1247     ICOMMAND(0, clearmutes, "", (), addmsg(N_CLRCONTROL, "ri", ipinfo::MUTE));
1248     ICOMMAND(0, clearlimits, "", (), addmsg(N_CLRCONTROL, "ri", ipinfo::LIMIT));
1249     ICOMMAND(0, clearexcepts, "", (), addmsg(N_CLRCONTROL, "ri", ipinfo::EXCEPT));
1250 
1251     vector<char *> ignores;
ignore(int cn)1252     void ignore(int cn)
1253     {
1254         gameent *d = game::getclient(cn);
1255         if(!d || d == game::player1) return;
1256         if(!strcmp(d->hostip, "*"))
1257         {
1258             conoutft(CON_EVENT, "\frCannot ignore %s: host information is private", game::colourname(d));
1259             return;
1260         }
1261         if(ignores.find(d->hostip) < 0)
1262         {
1263             conoutft(CON_EVENT, "\fyIgnoring: \fs%s\fS (\fs\fc%s\fS)", game::colourname(d), d->hostip);
1264             ignores.add(d->hostip);
1265         }
1266         else
1267             conoutft(CON_EVENT, "\frAlready ignoring: \fs%s\fS (\fs\fc%s\fS)", game::colourname(d), d->hostip);
1268     }
1269 
unignore(int cn)1270     void unignore(int cn)
1271     {
1272         gameent *d = game::getclient(cn);
1273         if(!d) return;
1274         if(!strcmp(d->hostip, "*"))
1275         {
1276             conoutft(CON_EVENT, "\frCannot unignore %s: host information is private", game::colourname(d));
1277             return;
1278         }
1279         if(ignores.find(d->hostip) >= 0)
1280         {
1281             conoutft(CON_EVENT, "\fyStopped ignoring: \fs%s\fS (\fs\fc%s\fS)", game::colourname(d), d->hostip);
1282             ignores.removeobj(d->hostip);
1283         }
1284         else
1285             conoutft(CON_EVENT, "\frYou are not ignoring: \fs%s\fS (\fs\fc%s\fS)", game::colourname(d), d->hostip);
1286     }
1287 
isignored(int cn)1288     bool isignored(int cn)
1289     {
1290         gameent *d = game::getclient(cn);
1291         if(!d || !strcmp(d->hostip, "*")) return false;
1292         return ignores.find(d->hostip) >= 0;
1293     }
1294 
1295     ICOMMAND(IDF_NAMECOMPLETE, ignore, "s", (char *arg), ignore(parseplayer(arg)));
1296     ICOMMAND(IDF_NAMECOMPLETE, unignore, "s", (char *arg), unignore(parseplayer(arg)));
1297     ICOMMAND(IDF_NAMECOMPLETE, isignored, "s", (char *arg), intret(isignored(parseplayer(arg)) ? 1 : 0));
1298 
setteam(const char * arg1,const char * arg2)1299     void setteam(const char *arg1, const char *arg2)
1300     {
1301         if(m_team(game::gamemode, game::mutators))
1302         {
1303             int i = parseplayer(arg1);
1304             if(i >= 0)
1305             {
1306                 int t = teamfromname(arg2);
1307                 if(t) addmsg(N_SETTEAM, "ri2", i, t);
1308             }
1309         }
1310         else conoutft(CON_DEBUG, "\frCan only change teams in team games");
1311     }
1312     ICOMMAND(IDF_NAMECOMPLETE, setteam, "ss", (char *who, char *team), setteam(who, team));
1313 
hashpwd(const char * pwd)1314     void hashpwd(const char *pwd)
1315     {
1316         if(game::player1->clientnum < 0) return;
1317         string hash;
1318         server::hashpassword(game::player1->clientnum, sessionid, pwd, hash);
1319         result(hash);
1320     }
1321     COMMAND(0, hashpwd, "s");
1322 
setpriv(const char * arg)1323     void setpriv(const char *arg)
1324     {
1325         if(!arg[0]) return;
1326         int val = 1;
1327         stringz(hash);
1328         if(!arg[1] && isdigit(arg[0])) val = parseint(arg);
1329         else server::hashpassword(game::player1->clientnum, sessionid, arg, hash);
1330         addmsg(N_SETPRIV, "ris", val, hash);
1331     }
1332     COMMAND(0, setpriv, "s");
1333 
addpriv(int cn,int priv)1334     void addpriv(int cn, int priv)
1335     {
1336         addmsg(N_ADDPRIV, "ri2", cn, priv);
1337     }
1338     ICOMMAND(IDF_NAMECOMPLETE, addpriv, "si", (char *who, int *priv), addpriv(parseplayer(who), *priv));
1339     ICOMMAND(IDF_NAMECOMPLETE, resetpriv, "s", (char *who), addpriv(parseplayer(who), -1));
1340 
tryauth()1341     void tryauth()
1342     {
1343         if(accountname[0]) addmsg(N_AUTHTRY, "rs", accountname);
1344         else conoutft(CON_DEBUG, "\frNo account set for \fcauth");
1345     }
1346     ICOMMAND(0, auth, "", (), tryauth());
1347 
togglespectator(int cn,int val)1348     void togglespectator(int cn, int val)
1349     {
1350         if(cn >= 0) addmsg(N_SPECTATOR, "ri2", cn, val);
1351     }
1352     ICOMMAND(IDF_NAMECOMPLETE, spectator, "si", (char *who, int *val), togglespectator(parseplayer(who), *val));
1353     ICOMMAND(0, spectate, "i", (int *val), togglespectator(game::player1->clientnum, *val));
1354 
connectattempt(const char * name,int port,const char * password,const ENetAddress & address)1355     void connectattempt(const char *name, int port, const char *password, const ENetAddress &address)
1356     {
1357         if(*password) { copystring(connectpass, password); }
1358         else memset(connectpass, 0, sizeof(connectpass));
1359     }
1360 
connectfail()1361     void connectfail()
1362     {
1363         memset(connectpass, 0, sizeof(connectpass));
1364     }
1365 
gameconnect(bool _remote)1366     void gameconnect(bool _remote)
1367     {
1368         remote = _remote;
1369         if(editmode) toggleedit();
1370     }
1371 
gamedisconnect(int clean)1372     void gamedisconnect(int clean)
1373     {
1374         if(editmode) toggleedit();
1375         remote = isready = sendplayerinfo = sendgameinfo = sendcrcinfo = loadedmap = false;
1376         gettingmap = needsmap = sessionid = sessionver = lastplayerinfo = mastermode = 0;
1377         messages.shrink(0);
1378         mapvotes.shrink(0);
1379         messagereliable = false;
1380         projs::removeplayer(game::player1);
1381         removetrackedparticles(game::player1);
1382         removetrackedsounds(game::player1);
1383         game::player1->clientnum = -1;
1384         game::player1->privilege = PRIV_NONE;
1385         game::player1->handle[0] = game::player1->steamid[0] = '\0';
1386         game::gamemode = G_EDITMODE;
1387         game::mutators = game::maptime = 0;
1388         loopv(game::players) if(game::players[i]) game::clientdisconnected(i);
1389         game::waiting.setsize(0);
1390         hud::cleanup();
1391         emptymap(0, true, NULL, false);
1392         smartmusic(true);
1393         enumerate(idents, ident, id,
1394         {
1395             if(id.flags&IDF_CLIENT) switch(id.type)
1396             {
1397                 case ID_VAR: setvar(id.name, id.def.i, true); break;
1398                 case ID_FVAR: setfvar(id.name, id.def.f, true); break;
1399                 case ID_SVAR: setsvar(id.name, *id.def.s ? id.def.s : "", true); break;
1400                 default: break;
1401             }
1402         });
1403     }
1404 
addmsg(int type,const char * fmt,...)1405     bool addmsg(int type, const char *fmt, ...)
1406     {
1407         static uchar buf[MAXTRANS];
1408         ucharbuf p(buf, MAXTRANS);
1409         putint(p, type);
1410         int numi = 1, nums = 0;
1411         bool reliable = false;
1412         if(fmt)
1413         {
1414             va_list args;
1415             va_start(args, fmt);
1416             while(*fmt) switch(*fmt++)
1417             {
1418                 case 'r': reliable = true; break;
1419                 case 'v':
1420                 {
1421                     int n = va_arg(args, int);
1422                     int *v = va_arg(args, int *);
1423                     loopi(n) putint(p, v[i]);
1424                     numi += n;
1425                     break;
1426                 }
1427                 case 'i':
1428                 {
1429                     int n = isdigit(*fmt) ? *fmt++-'0' : 1;
1430                     loopi(n) putint(p, va_arg(args, int));
1431                     numi += n;
1432                     break;
1433                 }
1434                 case 'u':
1435                 {
1436                     int n = isdigit(*fmt) ? *fmt++-'0' : 1;
1437                     loopi(n) putuint(p, va_arg(args, uint));
1438                     numi += n;
1439                     break;
1440                 }
1441                 case 'f':
1442                 {
1443                     int n = isdigit(*fmt) ? *fmt++-'0' : 1;
1444                     loopi(n) putfloat(p, (float)va_arg(args, double));
1445                     numi += n;
1446                     break;
1447                 }
1448                 case 's': sendstring(va_arg(args, const char *), p); nums++; break;
1449                 case 'm':
1450                 {
1451                     int n = va_arg(args, int);
1452                     p.put(va_arg(args, uchar *), n);
1453                     numi += n;
1454                     break;
1455                 }
1456             }
1457             va_end(args);
1458         }
1459         int num = nums?0:numi, msgsize = msgsizelookup(type);
1460         if(msgsize && num != msgsize) { fatal("Inconsistent msg size for %d (%d != %d)", type, num, msgsize); }
1461         if(reliable) messagereliable = true;
1462         messages.put(buf, p.length());
1463         return true;
1464     }
1465 
saytext(gameent * f,gameent * t,int flags,char * text)1466     void saytext(gameent *f, gameent *t, int flags, char *text)
1467     {
1468         bigstring msg, line;
1469         filterstring(msg, text, true, colourchat ? false : true, true, true);
1470         if(*filterwords) filterword(msg, filterwords);
1471 
1472         defformatstring(name, "%s", game::colourname(f));
1473         if(flags&SAY_WHISPER)
1474         {
1475             if(!t) return;
1476             defformatstring(sw, " [\fs\fy%d\fS] (\fs\fcwhispers to %s\fS [\fs\fy%d\fS])", f->clientnum, t == game::player1 ? "you" : game::colourname(t), t->clientnum);
1477             concatstring(name, sw);
1478         }
1479         else if(flags&SAY_TEAM)
1480         {
1481             defformatstring(st, " (to team %s)", game::colourteam(f->team));
1482             concatstring(name, st);
1483         }
1484         if(flags&SAY_ACTION) formatstring(line, "\fv* %s %s", name, msg);
1485         else formatstring(line, "\fw<%s> %s", name, msg);
1486 
1487         int snd = S_CHAT;
1488         ident *wid = idents.access(flags&SAY_ACTION ? "on_action" : "on_text");
1489         if(wid && wid->type == ID_ALIAS && wid->getstr()[0])
1490         {
1491             defformatbigstring(act, "%s %d %d %s %s %s",
1492                 flags&SAY_ACTION ? "on_action" : "on_text", f->clientnum, flags&SAY_TEAM ? 1 : 0,
1493                 escapestring(game::colourname(f)), escapestring(text), escapestring(line));
1494             int ret = execute(act);
1495             if(ret > 0) snd = ret;
1496         }
1497         if(m_demo(game::gamemode) || ((!(flags&SAY_TEAM) || f->team == game::player1->team) && (!(flags&SAY_WHISPER) || f == game::player1 || t == game::player1)))
1498         {
1499             conoutft(CON_MESG, "%s", line);
1500             if(snd >= 0 && !issound(f->cschan)) playsound(snd, f->o, f, snd != S_CHAT ? 0 : SND_DIRECT, -1, -1, -1, &f->cschan);
1501         }
1502         ai::scanchat(f, t, flags, text);
1503     }
1504 
toserver(int flags,const char * text,const char * target)1505     void toserver(int flags, const char *text, const char *target)
1506     {
1507         if(!waiting(false) && !client::demoplayback)
1508         {
1509             bigstring output;
1510             copystring(output, text, messagelength);
1511             if(flags&SAY_WHISPER)
1512             {
1513                 gameent *e = game::getclient(parseplayer(target));
1514                 if(e && e->clientnum != game::player1->clientnum)
1515                     addmsg(N_TEXT, "ri3s", game::player1->clientnum, e->clientnum, flags, output);
1516             }
1517             else
1518             {
1519                 if(flags&SAY_TEAM && !m_team(game::gamemode, game::mutators))
1520                     flags &= ~SAY_TEAM;
1521                 addmsg(N_TEXT, "ri3s", game::player1->clientnum, -1, flags, output);
1522             }
1523         }
1524     }
1525     ICOMMAND(0, say, "C", (char *s), toserver(SAY_NONE, s));
1526     ICOMMAND(0, me, "C", (char *s), toserver(SAY_ACTION, s));
1527     ICOMMAND(0, sayteam, "C", (char *s), toserver(SAY_TEAM, s));
1528     ICOMMAND(0, meteam, "C", (char *s), toserver(SAY_ACTION|SAY_TEAM, s));
1529     ICOMMAND(IDF_NAMECOMPLETE, whisper, "ss", (char *t, char *s), toserver(SAY_WHISPER, s, t));
1530     ICOMMAND(IDF_NAMECOMPLETE, mewhisper, "ss", (char *t, char *s), toserver(SAY_ACTION|SAY_WHISPER, s, t));
1531 
parsecommand(gameent * d,const char * cmd,const char * arg)1532     void parsecommand(gameent *d, const char *cmd, const char *arg)
1533     {
1534         const char *oldval = NULL;
1535         bool needfreeoldval = false;
1536         ident *id = idents.access(cmd);
1537         if(id && id->flags&IDF_CLIENT)
1538         {
1539             const char *val = NULL;
1540             switch(id->type)
1541             {
1542                 case ID_COMMAND:
1543                 {
1544 #if 0 // these shouldn't get here
1545                     int slen = strlen(cmd)+1+strlen(arg);
1546                     char *s = newstring(slen+1);
1547                     formatstring(s, slen, "%s %s", cmd, arg);
1548                     char *ret = executestr(s);
1549                     delete[] s;
1550                     if(ret) conoutft(CON_EVENT, "\fg%s: \fc%s", cmd, ret);
1551                     delete[] ret;
1552 #endif
1553                     return;
1554                 }
1555                 case ID_VAR:
1556                 {
1557                     int ret = parseint(arg);
1558                     oldval = intstr(id);
1559                     *id->storage.i = ret;
1560                     id->changed();
1561                     val = intstr(id);
1562                     break;
1563                 }
1564                 case ID_FVAR:
1565                 {
1566                     float ret = parsefloat(arg);
1567                     oldval = floatstr(*id->storage.f);
1568                     *id->storage.f = ret;
1569                     id->changed();
1570                     val = floatstr(*id->storage.f);
1571                     break;
1572                 }
1573                 case ID_SVAR:
1574                 {
1575                     oldval = newstring(*id->storage.s);
1576                     needfreeoldval = true;
1577                     delete[] *id->storage.s;
1578                     *id->storage.s = newstring(arg);
1579                     id->changed();
1580                     val = *id->storage.s;
1581                     break;
1582                 }
1583                 default: return;
1584             }
1585             if((d || showservervariables) && val)
1586             {
1587                 if(oldval)
1588                 {
1589                     conoutft(CON_EVENT, "\fy%s set \fs\fc%s\fS to \fs\fc%s\fS (was: \fs\fc%s\fS)", d ? game::colourname(d) : (connected(false) ? "the server" : "you"), cmd, val, oldval);
1590                     if(needfreeoldval) delete[] oldval;
1591                 }
1592                 else conoutft(CON_EVENT, "\fy%s set \fs\fc%s\fS to \fs\fc%s\fS", d ? game::colourname(d) : (connected(false) ? "the server" : "you"), cmd, val);
1593             }
1594         }
1595         else if(verbose) conoutft(CON_EVENT, "\fr%s sent unknown command: \fc%s", d ? game::colourname(d) : "the server", cmd);
1596     }
1597 
sendcmd(int nargs,const char * cmd,const char * arg)1598     bool sendcmd(int nargs, const char *cmd, const char *arg)
1599     {
1600         if(connected(false))
1601         {
1602             addmsg(N_COMMAND, "ri2sis", game::player1->clientnum, nargs, cmd, strlen(arg), arg);
1603             return true;
1604         }
1605         else
1606         {
1607             defformatstring(scmd, "sv_%s", cmd);
1608             if(server::servcmd(nargs, scmd, arg))
1609             {
1610                 if(nargs > 1 && arg) parsecommand(NULL, cmd, arg);
1611                 return true;
1612             }
1613         }
1614         return false;
1615     }
1616 
changemapserv(char * name,int gamemode,int mutators,int crc,int variant)1617     void changemapserv(char *name, int gamemode, int mutators, int crc, int variant)
1618     {
1619         game::gamestate = G_S_WAITING;
1620         game::gamemode = gamemode;
1621         game::mutators = mutators;
1622         modecheck(game::gamemode, game::mutators);
1623         game::nextmode = game::gamemode;
1624         game::nextmuts = game::mutators;
1625         game::timeremaining = -1;
1626         game::maptime = 0;
1627         hud::resetscores();
1628         mapvotes.shrink(0);
1629         if(editmode) toggleedit();
1630         if(m_demo(game::gamemode))
1631         {
1632             game::maptime = 1;
1633             game::timeremaining = 0;
1634             return;
1635         }
1636         else if(demoendless) demoendless = 0;
1637         if(m_capture(game::gamemode)) capture::reset();
1638         else if(m_defend(game::gamemode)) defend::reset();
1639         else if(m_bomber(game::gamemode)) bomber::reset();
1640         needsmap = gettingmap = 0;
1641         smartmusic(true);
1642         if(crc < -1 || !name || !*name || !load_world(name, crc, variant)) switch(crc)
1643         {
1644             case -1:
1645                 if(!mapcrc) emptymap(0, true, name);
1646                 needsmap = gettingmap = 0;
1647                 loadedmap = sendcrcinfo = true; // the server wants us to start
1648                 break;
1649             case -2:
1650                 conoutf("Waiting for server to request the map..");
1651             default:
1652                 emptymap(0, true, name);
1653                 needsmap = totalmillis;
1654                 if(crc > 0) addmsg(N_GETMAP, "r");
1655                 break;
1656         }
1657         else loadedmap = sendcrcinfo = true;
1658         if(m_capture(game::gamemode)) capture::setup();
1659         else if(m_defend(game::gamemode)) defend::setup();
1660         else if(m_bomber(game::gamemode)) bomber::setup();
1661     }
1662 
receivefile(uchar * data,int len)1663     void receivefile(uchar *data, int len)
1664     {
1665         ucharbuf p(data, len);
1666         int type = getint(p);
1667         switch(type)
1668         {
1669             case N_SENDDEMO:
1670             {
1671                 int ctime = getint(p);
1672                 int nameid = getint(p);
1673                 if(filetimelocal) ctime += clockoffset;
1674                 data += p.length();
1675                 len -= p.length();
1676                 string fname;
1677                 const char *demoname = demonames.find(nameid, "");
1678                 if(*demoname)
1679                 {
1680                     formatstring(fname, "demos/%s.dmo", demoname);
1681                     DELETEA(demoname);
1682                 }
1683                 else
1684                 {
1685                     if(*filetimeformat) formatstring(fname, "demos/%s.dmo", gettime(ctime, filetimeformat));
1686                     else formatstring(fname, "demos/%u.dmo", uint(ctime));
1687                 }
1688                 stream *demo = openfile(fname, "wb");
1689                 if(!demo) return;
1690                 conoutft(CON_EVENT, "\fyReceived demo: \fc%s", fname);
1691                 demo->write(data, len);
1692                 delete demo;
1693                 break;
1694             }
1695 
1696             case N_SENDMAPFILE:
1697             {
1698                 int filetype = getint(p), filecrc = getint(p);
1699                 string fname;
1700                 gettingmap = totalmillis;
1701                 getstring(fname, p);
1702                 data += p.length();
1703                 len -= p.length();
1704                 if(filetype < 0 || filetype >= SENDMAP_MAX || len <= 0) break;
1705                 if(!*fname) copystring(fname, "maps/untitled");
1706                 // Remove any temp/ prefix from file name before testing and rebuilding.
1707                 defformatstring(nfname, "%s", (strstr(fname, "temp/") == fname || strstr(fname, "temp\\") == fname) ? fname + 5 : fname);
1708                 defformatstring(ffile, strstr(nfname, "maps/") == nfname || strstr(nfname, "maps\\") == nfname ? "temp/%s_0x%.8x" : "temp/maps/%s_0x%.8x", nfname, filecrc);
1709                 defformatstring(ffext, "%s.%s", ffile, sendmaptypes[filetype]);
1710                 stream *f = openfile(ffext, "wb");
1711                 if(!f)
1712                 {
1713                     conoutft(CON_EVENT, "\frFailed to open map file: \fc%s", ffext);
1714                     break;
1715                 }
1716                 f->write(data, len);
1717                 delete f;
1718                 conoutft(CON_EVENT, "\fyWrote map file: \fc%s (%d %s) [0x%.8x]", ffext, len, len != 1 ? "bytes" : "byte", filecrc);
1719                 break;
1720             }
1721             default: break;
1722         }
1723     }
1724     ICOMMAND(0, getmap, "", (), if(multiplayer(false)) addmsg(N_GETMAP, "r"));
1725 
stopdemo()1726     void stopdemo()
1727     {
1728         if(remote) addmsg(N_STOPDEMO, "r");
1729         else server::stopdemo();
1730     }
1731     ICOMMAND(0, stopdemo, "", (), stopdemo());
1732 
recorddemo(int val)1733     void recorddemo(int val)
1734     {
1735         addmsg(N_RECORDDEMO, "ri", val);
1736     }
1737     ICOMMAND(0, recorddemo, "i", (int *val), recorddemo(*val));
1738 
cleardemos(int val)1739     void cleardemos(int val)
1740     {
1741         addmsg(N_CLEARDEMOS, "ri", val);
1742     }
1743     ICOMMAND(0, cleardemos, "i", (int *val), cleardemos(*val));
1744 
getdemo(int i,const char * name)1745     void getdemo(int i, const char *name)
1746     {
1747         if(i <= 0) conoutft(CON_EVENT, "\fyGetting demo, please wait...");
1748         else conoutft(CON_EVENT, "\fyGetting demo \fs\fc%d\fS, please wait...", i);
1749         addmsg(N_GETDEMO, "ri2", i, demonameid);
1750         if(*name) demonames.access(demonameid, newstring(name));
1751         demonameid++;
1752     }
1753     ICOMMAND(0, getdemo, "is", (int *val, char *name), getdemo(*val, name));
1754 
listdemos()1755     void listdemos()
1756     {
1757         conoutft(CON_EVENT, "\fyListing demos...");
1758         addmsg(N_LISTDEMOS, "r");
1759     }
1760     ICOMMAND(0, listdemos, "", (), listdemos());
1761 
changemap(const char * name)1762     void changemap(const char *name) // request map change, server may ignore
1763     {
1764         int nextmode = game::nextmode, nextmuts = game::nextmuts; // in case stopdemo clobbers these
1765         if(!remote) stopdemo();
1766         if(!connected())
1767         {
1768             server::changemap(name, nextmode, nextmuts);
1769             localconnect(true);
1770         }
1771         else
1772         {
1773             stringz(reqfile);
1774             if(name && *name)
1775                 copystring(reqfile, !strncasecmp(name, "temp/", 5) || !strncasecmp(name, "temp\\", 5) ? name+5 : name);
1776             else copystring(reqfile, "<random>");
1777             addmsg(N_MAPVOTE, "rsi2", reqfile, nextmode, nextmuts);
1778         }
1779     }
1780     ICOMMAND(0, map, "s", (char *s), changemap(s));
1781     ICOMMAND(0, clearvote, "", (), addmsg(N_CLEARVOTE, "r"));
1782 
sendmap()1783     void sendmap()
1784     {
1785         conoutf("\fySending map...");
1786         const char *reqmap = mapname;
1787         if(!reqmap || !*reqmap) reqmap = "maps/untitled";
1788         bool saved = false;
1789         if(m_edit(game::gamemode))
1790         {
1791             save_world(mapname, m_edit(game::gamemode), true);
1792             ai::savewaypoints(true, mapname);
1793             reqmap = mapname;
1794             saved = true;
1795         }
1796         loopi(SENDMAP_MAX)
1797         {
1798             defformatstring(reqfext, "%s.%s", reqmap, sendmaptypes[i]);
1799             stream *f = openfile(reqfext, "rb");
1800             if(f)
1801             {
1802                 conoutf("\fyTransmitting file: \fc%s", reqfext);
1803                 sendfile(-1, 2, f, "ri3", N_SENDMAPFILE, i, mapcrc);
1804                 if(needclipboard >= 0) needclipboard++;
1805                 delete f;
1806             }
1807             else
1808             {
1809                 conoutf("\frFailed to open map file: \fc%s", reqfext);
1810                 sendfile(-1, 2, NULL, "ri3", N_SENDMAPFILE, i, mapcrc);
1811             }
1812         }
1813         if(saved) setnames(mapname);
1814     }
1815     ICOMMAND(0, sendmap, "", (), if(multiplayer(false)) sendmap());
1816 
gotoplayer(const char * arg)1817     void gotoplayer(const char *arg)
1818     {
1819         if(game::player1->state != CS_SPECTATOR && game::player1->state != CS_EDITING) return;
1820         int i = parseplayer(arg);
1821         if(i >= 0 && i != game::player1->clientnum)
1822         {
1823             gameent *d = game::getclient(i);
1824             if(!d) return;
1825             game::player1->o = d->o;
1826             vec dir(game::player1->yaw, game::player1->pitch);
1827             game::player1->o.add(dir.mul(-32));
1828             game::player1->resetinterp(true);
1829             game::resetcamera();
1830         }
1831     }
1832     ICOMMAND(IDF_NAMECOMPLETE, goto, "s", (char *s), gotoplayer(s));
1833 
editvar(ident * id,bool local)1834     void editvar(ident *id, bool local)
1835     {
1836         if(id && id->flags&IDF_WORLD && !(id->flags&IDF_SERVER) && local && m_edit(game::gamemode) && game::player1->state == CS_EDITING)
1837         {
1838             switch(id->type)
1839             {
1840                 case ID_VAR:
1841                     addmsg(N_EDITVAR, "risi", id->type, id->name, *id->storage.i);
1842                     conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(game::player1), id->name, intstr(id));
1843                     break;
1844                 case ID_FVAR:
1845                     addmsg(N_EDITVAR, "risf", id->type, id->name, *id->storage.f);
1846                     conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(game::player1), id->name, floatstr(*id->storage.f));
1847                     break;
1848                 case ID_SVAR:
1849                     addmsg(N_EDITVAR, "risis", id->type, id->name, strlen(*id->storage.s), *id->storage.s);
1850                     conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fy\fc%s\fS", game::colourname(game::player1), id->name, *id->storage.s);
1851                     break;
1852                 case ID_ALIAS:
1853                 {
1854                     const char *s = id->getstr();
1855                     addmsg(N_EDITVAR, "risis", id->type, id->name, strlen(s), s);
1856                     conoutft(CON_EVENT, "\fy%s set world alias \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(game::player1), id->name, s);
1857                     break;
1858                 }
1859                 default: break;
1860             }
1861         }
1862     }
1863 
sendclipboard()1864     void sendclipboard()
1865     {
1866         uchar *outbuf = NULL;
1867         int inlen = 0, outlen = 0;
1868         if(!packeditinfo(localedit, inlen, outbuf, outlen))
1869         {
1870             outbuf = NULL;
1871             inlen = outlen = 0;
1872             needclipboard = -1;
1873         }
1874         else needclipboard = 0;
1875         packetbuf p(16 + outlen, ENET_PACKET_FLAG_RELIABLE);
1876         putint(p, N_CLIPBOARD);
1877         putint(p, inlen);
1878         putint(p, outlen);
1879         if(outlen > 0) p.put(outbuf, outlen);
1880         sendclientpacket(p.finalize(), 1);
1881     }
1882 
edittrigger(const selinfo & sel,int op,int arg1,int arg2,int arg3,const VSlot * vs)1883     void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3, const VSlot *vs)
1884     {
1885         if(m_edit(game::gamemode) && game::player1->state == CS_EDITING) switch(op)
1886         {
1887             case EDIT_FLIP:
1888             case EDIT_COPY:
1889             case EDIT_PASTE:
1890             case EDIT_DELCUBE:
1891             {
1892                 switch(op)
1893                 {
1894                     case EDIT_COPY: needclipboard = arg1; break; // 0 - has clipboard; 1 - has clipboard with unknown geometry
1895                     case EDIT_PASTE:
1896                         if(needclipboard > 0)
1897                         {
1898                             c2sinfo(true);
1899                             sendclipboard();
1900                         }
1901                         break;
1902                 }
1903                 addmsg(N_EDITF + op, "ri9i4",
1904                     sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
1905                     sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner);
1906                 break;
1907             }
1908             case EDIT_MAT:
1909             {
1910                 addmsg(N_EDITF + op, "ri9i7",
1911                     sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
1912                     sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
1913                     arg1, arg2, arg3);
1914                 break;
1915             }
1916             case EDIT_ROTATE:
1917             {
1918                 addmsg(N_EDITF + op, "ri9i5",
1919                     sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
1920                     sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
1921                     arg1);
1922                 break;
1923             }
1924             case EDIT_FACE:
1925             {
1926                 addmsg(N_EDITF + op, "ri9i6",
1927                     sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
1928                     sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
1929                     arg1, arg2);
1930                 break;
1931             }
1932             case EDIT_TEX:
1933             {
1934                 int tex1 = shouldpacktex(arg1);
1935                 if(addmsg(N_EDITF + op, "ri9i6",
1936                     sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
1937                     sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
1938                     tex1 ? tex1 : arg1, arg2))
1939                 {
1940                     messages.pad(2);
1941                     int offset = messages.length();
1942                     if(tex1) packvslot(messages, arg1);
1943                     *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset));
1944                 }
1945                 break;
1946             }
1947             case EDIT_REPLACE:
1948             {
1949                 int tex1 = shouldpacktex(arg1), tex2 = shouldpacktex(arg2);
1950                 if(addmsg(N_EDITF + op, "ri9i7",
1951                     sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
1952                     sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
1953                     tex1 ? tex1 : arg1, tex2 ? tex2 : arg2, arg3))
1954                 {
1955                     messages.pad(2);
1956                     int offset = messages.length();
1957                     if(tex1) packvslot(messages, arg1);
1958                     if(tex2) packvslot(messages, arg2);
1959                     *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset));
1960                 }
1961                 break;
1962             }
1963             case EDIT_CALCLIGHT:
1964             case EDIT_REMIP:
1965             {
1966                 addmsg(N_EDITF + op, "r");
1967                 break;
1968             }
1969             case EDIT_VSLOT:
1970             {
1971                 if(addmsg(N_EDITF + op, "ri9i6",
1972                     sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
1973                     sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
1974                     arg1, arg2))
1975                 {
1976                     messages.pad(2);
1977                     int offset = messages.length();
1978                     packvslot(messages, vs);
1979                     *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset));
1980                 }
1981                 break;
1982             }
1983             case EDIT_UNDO:
1984             case EDIT_REDO:
1985             {
1986                 uchar *outbuf = NULL;
1987                 int inlen = 0, outlen = 0;
1988                 if(packundo(op, inlen, outbuf, outlen))
1989                 {
1990                     if(addmsg(N_EDITF + op, "ri2", inlen, outlen)) messages.put(outbuf, outlen);
1991                     delete[] outbuf;
1992                 }
1993                 break;
1994             }
1995         }
1996     }
1997 
sendintro()1998     void sendintro()
1999     {
2000         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
2001 
2002         putint(p, N_CONNECT);
2003 
2004         sendstring(game::player1->name, p);
2005         putint(p, game::player1->colour);
2006         putint(p, game::player1->model);
2007         putint(p, game::player1->pattern);
2008         sendstring(game::player1->vanity, p);
2009         putint(p, game::player1->loadweap.length());
2010         loopv(game::player1->loadweap) putint(p, game::player1->loadweap[i]);
2011         putint(p, game::player1->randweap.length());
2012         loopv(game::player1->randweap) putint(p, game::player1->randweap[i]);
2013 
2014         stringz(hash);
2015         if(connectpass[0])
2016         {
2017             server::hashpassword(game::player1->clientnum, sessionid, connectpass, hash);
2018             memset(connectpass, 0, sizeof(connectpass));
2019         }
2020         sendstring(hash, p);
2021         sendstring(authconnect ? accountname : "", p);
2022 
2023         game::player1->version.put(p);
2024 
2025         sendclientpacket(p.finalize(), 1);
2026     }
2027 
sendposition(gameent * d,packetbuf & q)2028     static void sendposition(gameent *d, packetbuf &q)
2029     {
2030         putint(q, N_POS);
2031         putuint(q, d->clientnum);
2032         // 3 bits phys state, 2 bits move, 2 bits strafe, 2 bits turnside
2033         uint physstate = d->physstate | ((d->move&3)<<3) | ((d->strafe&3)<<5) | ((d->turnside&3)<<7);
2034         putuint(q, physstate);
2035         putuint(q, d->impulse[IM_METER]);
2036         ivec o = ivec(vec(d->o.x, d->o.y, d->o.z-d->height).mul(DMF)), f = ivec(vec(d->floorpos.x, d->floorpos.y, d->floorpos.z).mul(DMF));
2037         uint vel = min(int(d->vel.magnitude()*DVELF), 0xFFFF), fall = min(int(d->falling.magnitude()*DVELF), 0xFFFF);
2038         // 3 bits position, 3 bits floor, 1 bit velocity, 3 bits falling, 1 bit conopen, X bits actions
2039         uint flags = 0;
2040         if(o.x < 0 || o.x > 0xFFFF) flags |= 1<<0;
2041         if(o.y < 0 || o.y > 0xFFFF) flags |= 1<<1;
2042         if(o.z < 0 || o.z > 0xFFFF) flags |= 1<<2;
2043         if(f.x < 0 || f.x > 0xFFFF) flags |= 1<<3;
2044         if(f.y < 0 || f.y > 0xFFFF) flags |= 1<<4;
2045         if(f.z < 0 || f.z > 0xFFFF) flags |= 1<<5;
2046         if(vel > 0xFF) flags |= 1<<6;
2047         if(fall > 0)
2048         {
2049             flags |= 1<<7;
2050             if(fall > 0xFF) flags |= 1<<8;
2051             if(d->falling.x || d->falling.y || d->falling.z > 0) flags |= 1<<9;
2052         }
2053         if(d->conopen) flags |= 1<<10;
2054         if(d->forcepos)
2055         {
2056             flags |= 1<<11;
2057             d->forcepos = false;
2058         }
2059         loopk(AC_MAX) if(d->action[k]) flags |= 1<<(12+k);
2060         putuint(q, flags);
2061         loopk(3)
2062         {
2063             q.put(o[k]&0xFF);
2064             q.put((o[k]>>8)&0xFF);
2065             if(o[k] < 0 || o[k] > 0xFFFF) q.put((o[k]>>16)&0xFF);
2066         }
2067         loopk(3)
2068         {
2069             q.put(f[k]&0xFF);
2070             q.put((f[k]>>8)&0xFF);
2071             if(f[k] < 0 || f[k] > 0xFFFF) q.put((f[k]>>16)&0xFF);
2072         }
2073         float yaw = d->yaw, pitch = d->pitch;
2074         if(d == game::player1 && game::thirdpersonview(true, d))
2075         {
2076             vectoyawpitch(vec(worldpos).sub(d->headpos()).normalize(), yaw, pitch);
2077             game::fixrange(yaw, pitch);
2078         }
2079         uint dir = (yaw < 0 ? 360 + int(yaw)%360 : int(yaw)%360) + clamp(int(pitch+90), 0, 180)*360;
2080         q.put(dir&0xFF);
2081         q.put((dir>>8)&0xFF);
2082         q.put(clamp(int(d->roll+90), 0, 180));
2083         q.put(vel&0xFF);
2084         if(vel > 0xFF) q.put((vel>>8)&0xFF);
2085         float velyaw, velpitch;
2086         vectoyawpitch(d->vel, velyaw, velpitch);
2087         uint veldir = (velyaw < 0 ? 360 + int(velyaw)%360 : int(velyaw)%360) + clamp(int(velpitch+90), 0, 180)*360;
2088         q.put(veldir&0xFF);
2089         q.put((veldir>>8)&0xFF);
2090         if(fall > 0)
2091         {
2092             q.put(fall&0xFF);
2093             if(fall > 0xFF) q.put((fall>>8)&0xFF);
2094             if(d->falling.x || d->falling.y || d->falling.z > 0)
2095             {
2096                 float fallyaw, fallpitch;
2097                 vectoyawpitch(d->falling, fallyaw, fallpitch);
2098                 uint falldir = (fallyaw < 0 ? 360 + int(fallyaw)%360 : int(fallyaw)%360) + clamp(int(fallpitch+90), 0, 180)*360;
2099                 q.put(falldir&0xFF);
2100                 q.put((falldir>>8)&0xFF);
2101             }
2102         }
2103     }
2104 
sendpositions()2105     void sendpositions()
2106     {
2107         gameent *d = NULL;
2108         int numdyns = game::numdynents();
2109         loopi(numdyns) if((d = (gameent *)game::iterdynents(i)))
2110         {
2111             if((d == game::player1 || d->ai) && (d->state == CS_ALIVE || d->state == CS_EDITING))
2112             {
2113                 packetbuf q(100);
2114                 sendposition(d, q);
2115                 for(int j = i+1; j < numdyns; j++)
2116                 {
2117                     gameent *e = (gameent *)game::iterdynents(j);
2118                     if(e && (e == game::player1 || e->ai) && (e->state == CS_ALIVE || e->state == CS_EDITING))
2119                         sendposition(e, q);
2120                 }
2121                 sendclientpacket(q.finalize(), 0);
2122                 break;
2123             }
2124         }
2125     }
2126 
sendmessages()2127     void sendmessages()
2128     {
2129         packetbuf p(MAXTRANS);
2130         if(isready)
2131         {
2132             if(sendplayerinfo && (!lastplayerinfo || totalmillis-lastplayerinfo >= setinfowait))
2133             {
2134                 p.reliable();
2135                 sendplayerinfo = false;
2136                 lastplayerinfo = totalmillis ? totalmillis : 1;
2137                 putint(p, N_SETPLAYERINFO);
2138                 sendstring(game::player1->name, p);
2139                 putint(p, game::player1->colour);
2140                 putint(p, game::player1->model);
2141                 putint(p, game::player1->pattern);
2142                 putint(p, game::player1->checkpointspawn);
2143                 sendstring(game::player1->vanity, p);
2144                 putint(p, game::player1->loadweap.length());
2145                 loopv(game::player1->loadweap) putint(p, game::player1->loadweap[i]);
2146                 putint(p, game::player1->randweap.length());
2147                 loopv(game::player1->randweap) putint(p, game::player1->randweap[i]);
2148             }
2149             if(sendcrcinfo)
2150             {
2151                 p.reliable();
2152                 putint(p, N_MAPCRC);
2153                 sendstring(mapname, p);
2154                 putint(p, mapcrc);
2155                 sendcrcinfo = false;
2156             }
2157             if(sendgameinfo)
2158             {
2159                 p.reliable();
2160                 putint(p, N_GAMEINFO);
2161                 enumerate(idents, ident, id,
2162                 {
2163                     if(id.flags&IDF_CLIENT && id.flags&IDF_WORLD) switch(id.type)
2164                     {
2165                         case ID_VAR:
2166                             putint(p, id.type);
2167                             sendstring(id.name, p);
2168                             putint(p, *id.storage.i);
2169                             break;
2170                         case ID_FVAR:
2171                             putint(p, id.type);
2172                             sendstring(id.name, p);
2173                             putfloat(p, *id.storage.f);
2174                             break;
2175                         case ID_SVAR:
2176                             putint(p, id.type);
2177                             sendstring(id.name, p);
2178                             sendstring(*id.storage.s, p);
2179                             break;
2180                         default: break;
2181                     }
2182                 });
2183                 putint(p, -1);
2184                 entities::putitems(p);
2185                 putint(p, -1);
2186                 if(m_capture(game::gamemode)) capture::sendaffinity(p);
2187                 else if(m_defend(game::gamemode)) defend::sendaffinity(p);
2188                 else if(m_bomber(game::gamemode)) bomber::sendaffinity(p);
2189                 sendgameinfo = false;
2190             }
2191             if(gs_playing(game::gamestate) && needsmap && !gettingmap && totalmillis-needsmap >= 30000)
2192             {
2193                 p.reliable();
2194                 putint(p, N_GETMAP);
2195                 needsmap = totalmillis;
2196             }
2197         }
2198         if(messages.length())
2199         {
2200             p.put(messages.getbuf(), messages.length());
2201             messages.setsize(0);
2202             if(messagereliable) p.reliable();
2203             messagereliable = false;
2204         }
2205         if(totalmillis-lastping>250)
2206         {
2207             putint(p, N_PING);
2208             putint(p, totalmillis);
2209             lastping = totalmillis ? totalmillis : 1;
2210         }
2211 
2212         sendclientpacket(p.finalize(), 1);
2213     }
2214 
c2sinfo(bool force)2215     void c2sinfo(bool force) // send update to the server
2216     {
2217         static int lastupdate = -1000;
2218         if(totalmillis-lastupdate < 40 && !force) return;    // don't update faster than 25fps
2219         lastupdate = totalmillis ? totalmillis : 1;
2220         sendpositions();
2221         sendmessages();
2222         flushclient();
2223     }
2224 
parsestate(gameent * d,ucharbuf & p,bool resume=false)2225     bool parsestate(gameent *d, ucharbuf &p, bool resume = false)
2226     {
2227         if(!d) { static gameent dummy; d = &dummy; }
2228         bool local = d == game::player1 || d->ai, reset = false;
2229         if(!local || !resume) d->respawn(lastmillis, game::gamemode, game::mutators);
2230         int state = getint(p);
2231         if(state == -1) reset = true;
2232         else if(!local) d->state = state;
2233         d->points = getint(p);
2234         d->frags = getint(p);
2235         d->deaths = getint(p);
2236         d->totalpoints = getint(p);
2237         d->totalfrags = getint(p);
2238         d->totaldeaths = getint(p);
2239         d->totalavgpos = getfloat(p);
2240         d->timeplayed = getint(p);
2241         d->lasttimeplayed = totalmillis ? totalmillis : 1;
2242         d->health = getint(p);
2243         d->cptime = getint(p);
2244         if(local && resume && !reset)
2245         {
2246             d->weapreset(false);
2247             getint(p);
2248             loopi(W_MAX) loopj(W_A_MAX) getint(p);
2249             loopi(W_MAX) getint(p);
2250         }
2251         else
2252         {
2253             d->weapreset(!resume);
2254             int weap = getint(p);
2255             d->weapselect = isweap(weap) ? weap : W_CLAW;
2256             loopi(W_MAX) loopj(W_A_MAX) d->weapammo[i][j] = getint(p);
2257             loopi(W_MAX) d->weapent[i] = getint(p);
2258         }
2259         if(resume) d->configure(lastmillis, game::gamemode, game::mutators, physics::carryaffinity(d));
2260         return reset;
2261     }
2262 
updatepos(gameent * d)2263     void updatepos(gameent *d)
2264     {
2265         // update the position of other clients in the game in our world
2266         // don't care if he's in the scenery or other players, just don't overlap with our client
2267         const float r = game::player1->radius+d->radius;
2268         const float dx = game::player1->o.x-d->o.x;
2269         const float dy = game::player1->o.y-d->o.y;
2270         const float dz = game::player1->o.z-d->o.z;
2271         const float rz = game::player1->aboveeye+game::player1->height;
2272         const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz);
2273         if(fx < r && fy < r && fz < rz && d->state != CS_SPECTATOR && d->state != CS_WAITING && d->state != CS_DEAD)
2274         {
2275             if(fx < fy) d->o.y += dy < 0 ? r-fy : -(r-fy);  // push aside
2276             else        d->o.x += dx < 0 ? r-fx : -(r-fx);
2277         }
2278         int lagtime = totalmillis-d->lastupdate;
2279         if(lagtime)
2280         {
2281             if(d->lastupdate) d->plag = (d->plag*5+lagtime)/6;
2282             d->lastupdate = totalmillis ? totalmillis : 1;
2283         }
2284     }
2285 
parsepositions(ucharbuf & p)2286     void parsepositions(ucharbuf &p)
2287     {
2288         int type;
2289         while(p.remaining()) switch(type = getint(p))
2290         {
2291             case N_POS: // position of another client
2292             {
2293                 int lcn = getuint(p), physstate = getuint(p), meter = getuint(p), flags = getuint(p);
2294                 vec o, f, vel, falling;
2295                 float yaw, pitch, roll;
2296                 loopk(3)
2297                 {
2298                     int n = p.get();
2299                     n |= p.get()<<8;
2300                     if(flags&(1<<k))
2301                     {
2302                         n |= p.get()<<16;
2303                         if(n&0x800000) n |= ~0U<<24;
2304                     }
2305                     o[k] = n/DMF;
2306                 }
2307                 loopk(3)
2308                 {
2309                     int n = p.get();
2310                     n |= p.get()<<8;
2311                     if(flags&(1<<(k+3)))
2312                     {
2313                         n |= p.get()<<16;
2314                         if(n&0x800000) n |= ~0U<<24;
2315                     }
2316                     f[k] = n/DMF;
2317                 }
2318                 int dir = p.get();
2319                 dir |= p.get()<<8;
2320                 yaw = dir%360;
2321                 pitch = clamp(dir/360, 0, 180)-90;
2322                 roll = clamp(int(p.get()), 0, 180)-90;
2323                 int mag = p.get();
2324                 if(flags&(1<<6)) mag |= p.get()<<8;
2325                 dir = p.get();
2326                 dir |= p.get()<<8;
2327                 vecfromyawpitch(dir%360, clamp(dir/360, 0, 180)-90, 1, 0, vel);
2328                 vel.mul(mag/DVELF);
2329                 if(flags&(1<<7))
2330                 {
2331                     mag = p.get();
2332                     if(flags&(1<<8)) mag |= p.get()<<8;
2333                     if(flags&(1<<9))
2334                     {
2335                         dir = p.get();
2336                         dir |= p.get()<<8;
2337                         vecfromyawpitch(dir%360, clamp(dir/360, 0, 180)-90, 1, 0, falling);
2338                     }
2339                     else falling = vec(0, 0, -1);
2340                     falling.mul(mag/DVELF);
2341                 }
2342                 else falling = vec(0, 0, 0);
2343                 gameent *d = game::getclient(lcn);
2344                 if(!d || d == game::player1 || d->ai) continue;
2345                 float oldyaw = d->yaw, oldpitch = d->pitch;
2346                 d->yaw = yaw;
2347                 d->pitch = pitch;
2348                 d->roll = roll;
2349                 d->move = (physstate>>3)&2 ? -1 : (physstate>>3)&1;
2350                 d->strafe = (physstate>>5)&2 ? -1 : (physstate>>5)&1;
2351                 d->turnside = (physstate>>7)&2 ? -1 : (physstate>>7)&1;
2352                 d->impulse[IM_METER] = meter;
2353                 d->conopen = flags&(1<<10) ? true : false;
2354                 d->forcepos = flags&(1<<11) ? true : false;
2355                 loopk(AC_MAX)
2356                 {
2357                     bool val = d->action[k];
2358                     d->action[k] = flags&(1<<(12+k)) ? true : false;
2359                     if(val != d->action[k])
2360                     {
2361                         if(d->action[k]) d->actiontime[k] = lastmillis;
2362                         else if(k == AC_CROUCH || k == AC_JUMP) d->actiontime[k] = -lastmillis;
2363                     }
2364                 }
2365                 vec oldpos(d->o);
2366                 d->o = o;
2367                 d->o.z += d->height;
2368                 d->floorpos = f;
2369                 d->vel = vel;
2370                 d->falling = falling;
2371                 d->physstate = physstate&7;
2372                 physics::updatephysstate(d);
2373                 updatepos(d);
2374                 if(d->forcepos || d->state == CS_DEAD || d->state == CS_WAITING)
2375                 {
2376                     d->resetinterp();
2377                     d->smoothmillis = 0;
2378                     d->forcepos = false;
2379                 }
2380                 else if(d->respawned < 0)
2381                 {
2382                     d->resetinterp();
2383                     d->smoothmillis = 0;
2384                     game::respawned(d, false);
2385                     d->respawned = lastmillis;
2386                 }
2387                 else if(physics::smoothmove && d->smoothmillis >= 0 && oldpos.dist(d->o) < physics::smoothdist)
2388                 {
2389                     d->newpos = d->o;
2390                     d->newpos.z -= d->height;
2391                     d->newyaw = d->yaw;
2392                     d->newpitch = d->pitch;
2393 
2394                     d->o = oldpos;
2395                     d->yaw = oldyaw;
2396                     d->pitch = oldpitch;
2397 
2398                     oldpos.z -= d->height;
2399                     (d->deltapos = oldpos).sub(d->newpos);
2400 
2401                     d->deltayaw = oldyaw - d->newyaw;
2402                     if(d->deltayaw > 180) d->deltayaw -= 360;
2403                     else if(d->deltayaw < -180) d->deltayaw += 360;
2404                     d->deltapitch = oldpitch - d->newpitch;
2405 
2406                     d->smoothmillis = lastmillis;
2407                 }
2408                 else d->smoothmillis = 0;
2409                 break;
2410             }
2411 
2412             default:
2413                 neterr("type");
2414                 return;
2415         }
2416     }
2417 
parsemessages(int cn,gameent * d,ucharbuf & p)2418     void parsemessages(int cn, gameent *d, ucharbuf &p)
2419     {
2420         static char text[MAXTRANS];
2421         int type = -1, prevtype = -1;
2422 
2423         while(p.remaining())
2424         {
2425             prevtype = type;
2426             type = getint(p);
2427             if(debugmessages) conoutf("[client] msg: %d, prev: %d", type, prevtype);
2428             switch(type)
2429             {
2430                 case N_SERVERINIT: // welcome messsage from the server
2431                 {
2432                     game::player1->clientnum = getint(p);
2433                     sessionver = getint(p);
2434                     getstring(game::player1->hostip, p);
2435                     sessionid = getint(p);
2436                     if(sessionver != VERSION_GAME)
2437                     {
2438                         conoutft(CON_EVENT, "\frError: this server is running an incompatible protocol (%d v %d)", sessionver, VERSION_GAME);
2439                         disconnect();
2440                         return;
2441                     }
2442                     sessionflags = getint(p);
2443                     conoutf("Connected, starting negotiation with server");
2444                     sendintro();
2445                     break;
2446                 }
2447                 case N_WELCOME:
2448                     mastermode = getint(p);
2449                     conoutf("Negotiation complete, \fs\fcmastermode\fS is \fs\fc%d\fS (\fs\fc%s\fS)", mastermode, mastermodename(mastermode));
2450                     isready = true;
2451                     break;
2452 
2453                 case N_CLIENT:
2454                 {
2455                     int lcn = getint(p), len = getuint(p);
2456                     ucharbuf q = p.subbuf(len);
2457                     gameent *t = game::getclient(lcn);
2458                     parsemessages(lcn, t, q);
2459                     break;
2460                 }
2461 
2462                 case N_SPHY: // simple phys events
2463                 {
2464                     int lcn = getint(p), st = getint(p);
2465                     gameent *t = game::getclient(lcn);
2466                     bool proceed = t && (st >= SPHY_SERVER || (t != game::player1 && !t->ai));
2467                     switch(st)
2468                     {
2469                         case SPHY_JUMP:
2470                         {
2471                             if(!proceed) break;
2472                             t->doimpulse(IM_T_JUMP, lastmillis);
2473                             playsound(S_JUMP, t->o, t);
2474                             createshape(PART_SMOKE, int(t->radius), 0x222222, 21, 20, 250, t->feetpos(), 1, 1, -10, 0, 10.f);
2475                             break;
2476                         }
2477                         case SPHY_BOOST: case SPHY_POUND: case SPHY_SLIDE: case SPHY_MELEE: case SPHY_KICK: case SPHY_GRAB: case SPHY_PARKOUR: case SPHY_AFTER:
2478                         {
2479                             if(!proceed) break;
2480                             t->doimpulse(IM_T_BOOST+(st-SPHY_BOOST), lastmillis);
2481                             game::impulseeffect(t);
2482                             if(st == SPHY_KICK || st == SPHY_PARKOUR || st == SPHY_MELEE) game::footstep(d);
2483                             break;
2484                         }
2485                         case SPHY_EXTINGUISH:
2486                         {
2487                             if(!proceed) break;
2488                             t->resetresidual(W_R_BURN);
2489                             playsound(S_EXTINGUISH, t->o, t);
2490                             part_create(PART_SMOKE, 500, t->feetpos(t->height/2), 0xAAAAAA, t->radius*4, 1, -10);
2491                             break;
2492                         }
2493                         case SPHY_BUFF:
2494                         {
2495                             int param = getint(p);
2496                             if(!proceed) break;
2497                             t->lastbuff = param ? lastmillis : 0;
2498                             break;
2499                         }
2500                         default: break;
2501                     }
2502                     break;
2503                 }
2504 
2505                 case N_ANNOUNCE:
2506                 {
2507                     int snd = getint(p), targ = getint(p);
2508                     getstring(text, p);
2509                     if(targ >= 0 && text[0]) game::announcef(snd, targ, NULL, false, "%s", text);
2510                     else game::announce(snd);
2511                     break;
2512                 }
2513 
2514                 case N_TEXT:
2515                 {
2516                     int fcn = getint(p), tcn = getint(p), flags = getint(p);
2517                     getstring(text, p);
2518                     gameent *fcp = game::getclient(fcn);
2519                     gameent *tcp = game::getclient(tcn);
2520                     if(!fcp || isignored(fcp->clientnum) || isignored(fcp->ownernum)) break;
2521                     saytext(fcp, tcp, flags, text);
2522                     break;
2523                 }
2524 
2525                 case N_COMMAND:
2526                 {
2527                     int lcn = getint(p);
2528                     gameent *f = lcn >= 0 ? game::getclient(lcn) : NULL;
2529                     getstring(text, p);
2530                     int alen = getint(p);
2531                     if(alen < 0 || alen > p.remaining()) break;
2532                     char *arg = newstring(alen);
2533                     getstring(arg, p, alen+1);
2534                     parsecommand(f, text, arg);
2535                     delete[] arg;
2536                     break;
2537                 }
2538 
2539                 case N_EXECLINK:
2540                 {
2541                     int tcn = getint(p), index = getint(p);
2542                     gameent *t = game::getclient(tcn);
2543                     if(!t || !d || (t->clientnum != d->clientnum && t->ownernum != d->clientnum) || t == game::player1 || t->ai) break;
2544                     entities::execlink(t, index, false);
2545                     break;
2546                 }
2547 
2548                 case N_MAPCHANGE:
2549                 {
2550                     getstring(text, p);
2551                     int mode = getint(p), muts = getint(p), crc = getint(p), variant = clamp(getint(p), int(MPV_DEF), int(MPV_MAX-1));
2552                     if(crc >= 0) conoutf("Map change: %s (%d:%d) [0x%.8x] (%s)", text, mode, muts, crc, mapvariants[variant]);
2553                     else conoutf("Map change: %s (%d:%d) [%d] (%s)", text, mode, muts, crc, mapvariants[variant]);
2554                     changemapserv(text, mode, muts, crc, variant);
2555                     break;
2556                 }
2557 
2558                 case N_GETGAMEINFO:
2559                 {
2560                     conoutf("Sending game info..");
2561                     sendgameinfo = true;
2562                     break;
2563                 }
2564 
2565                 case N_GAMEINFO:
2566                 {
2567                     int n;
2568                     sendgameinfo = false;
2569                     while(p.remaining() && (n = getint(p)) != -1) entities::setspawn(n, getint(p));
2570                     break;
2571                 }
2572 
2573                 case N_ATTRMAP:
2574                 {
2575                     loopi(W_MAX) game::attrmap[i] = getint(p);
2576                     break;
2577                 }
2578 
2579                 case N_SETPLAYERINFO: // name colour model pattern checkpoint vanity count <loadweaps> count <randweaps>
2580                 {
2581                     getstring(text, p);
2582                     int colour = getint(p), model = getint(p), pattern = getint(p), cps = getint(p);
2583                     stringz(vanity);
2584                     getstring(vanity, p);
2585                     int lw = getint(p);
2586                     vector<int> lweaps;
2587                     loopk(lw) lweaps.add(getint(p));
2588                     int rw = getint(p);
2589                     vector<int> rweaps;
2590                     loopk(rw) rweaps.add(getint(p));
2591                     if(!d) break;
2592                     stringz(namestr);
2593                     filterstring(namestr, text, true, true, true, true, MAXNAMELEN);
2594                     if(!*namestr) copystring(namestr, "unnamed");
2595                     if(strcmp(d->name, namestr))
2596                     {
2597                         string oldname, newname;
2598                         copystring(oldname, game::colourname(d));
2599                         d->setinfo(namestr, colour, model, pattern, vanity, lweaps, rweaps);
2600                         copystring(newname, game::colourname(d));
2601                         if(showpresence >= (waiting(false) ? 2 : 1) && !isignored(d->clientnum))
2602                             conoutft(CON_EVENT, "\fm%s is now known as %s", oldname, newname);
2603                     }
2604                     else d->setinfo(namestr, colour, model, pattern, vanity, lweaps, rweaps);
2605                     d->checkpointspawn = cps;
2606                     break;
2607                 }
2608 
2609                 case N_CLIENTINIT: // cn colour model pattern checkpoint team priv name vanity count <loadweaps> count <randweaps> handle steamid hostip <version>
2610                 {
2611                     int tcn = getint(p);
2612                     verinfo dummy;
2613                     gameent *d = game::newclient(tcn);
2614                     if(!d)
2615                     {
2616                         loopk(6) getint(p);
2617                         loopk(2) getstring(text, p);
2618                         loopj(2)
2619                         {
2620                             int w = getint(p);
2621                             loopk(w) getint(p);
2622                         }
2623                         loopk(3) getstring(text, p);
2624                         dummy.get(p);
2625                         break;
2626                     }
2627                     int colour = getint(p), model = getint(p), pattern = getint(p), cps = getint(p), team = clamp(getint(p), int(T_NEUTRAL), int(T_ENEMY)), priv = getint(p);
2628                     getstring(text, p);
2629                     stringz(namestr);
2630                     filterstring(namestr, text, true, true, true, true, MAXNAMELEN);
2631                     if(!*namestr) copystring(namestr, "unnamed");
2632                     stringz(vanity);
2633                     getstring(vanity, p);
2634                     int lw = getint(p);
2635                     vector<int> lweaps;
2636                     loopk(lw) lweaps.add(getint(p));
2637                     int rw = getint(p);
2638                     vector<int> rweaps;
2639                     loopk(rw) rweaps.add(getint(p));
2640                     getstring(d->handle, p);
2641                     getstring(d->steamid, p);
2642                     getstring(d->hostip, p);
2643                     if(d != game::player1) d->version.get(p);
2644                     else dummy.get(p);
2645                     d->checkpointspawn = cps;
2646                     if(d == game::focus && d->team != team) hud::lastteam = 0;
2647                     d->team = team;
2648                     d->privilege = priv;
2649                     if(d->name[0]) d->setinfo(namestr, colour, model, pattern, vanity, lweaps, rweaps); // already connected
2650                     else // new client
2651                     {
2652                         d->setinfo(namestr, colour, model, pattern, vanity, lweaps, rweaps);
2653                         if(showpresence >= (waiting(false) ? 2 : 1))
2654                         {
2655                             int amt = otherclients(true);
2656                             stringz(ipaddr);
2657                             if(showpresencehostinfo && client::haspriv(game::player1, G(iphostlock))) formatstring(ipaddr, " (%s)", d->hostip);
2658                             if(priv > PRIV_NONE)
2659                             {
2660                                 if(d->handle[0]) conoutft(CON_EVENT, "\fg%s%s joined the game (\fs\fy%s\fS: \fs\fc%s\fS) [%d.%d.%d-%s%d-%s] (%d %s)", game::colourname(d), ipaddr, server::privname(d->privilege), d->handle, d->version.major, d->version.minor, d->version.patch, plat_name(d->version.platform), d->version.arch, d->version.branch, amt, amt != 1 ? "players" : "player");
2661                                 else conoutft(CON_EVENT, "\fg%s%s joined the game (\fs\fy%s\fS) [%d.%d.%d-%s%d-%s] (%d %s)", game::colourname(d), ipaddr, server::privname(d->privilege), d->version.major, d->version.minor, d->version.patch, plat_name(d->version.platform), d->version.arch, d->version.branch, amt, amt != 1 ? "players" : "player");
2662                             }
2663                             else conoutft(CON_EVENT, "\fg%s%s joined the game [%d.%d.%d-%s%d-%s] (%d %s)", game::colourname(d), ipaddr, d->version.major, d->version.minor, d->version.patch, plat_name(d->version.platform), d->version.arch, d->version.branch, amt, amt != 1 ? "players" : "player");
2664                         }
2665                         if(needclipboard >= 0) needclipboard++;
2666                         game::specreset(d);
2667                     }
2668                     break;
2669                 }
2670 
2671                 case N_DISCONNECT:
2672                 {
2673                     int lcn = getint(p), reason = getint(p);
2674                     game::clientdisconnected(lcn, reason);
2675                     break;
2676                 }
2677 
2678                 case N_LOADOUT:
2679                 {
2680                     hud::showscores(false);
2681                     if(!UI::hasmenu()) UI::openui("profile");
2682                     lastplayerinfo = 0;
2683                     break;
2684                 }
2685 
2686                 case N_SPAWN:
2687                 {
2688                     int lcn = getint(p);
2689                     gameent *f = game::newclient(lcn);
2690                     if(!f || f == game::player1 || f->ai) f = NULL;
2691                     parsestate(f, p);
2692                     break;
2693                 }
2694 
2695                 case N_SPAWNSTATE:
2696                 {
2697                     int lcn = getint(p), ent = getint(p);
2698                     gameent *f = game::newclient(lcn);
2699                     if(!f || (f != game::player1 && !f->ai))
2700                     {
2701                         parsestate(NULL, p);
2702                         break;
2703                     }
2704                     if(f == game::player1 && editmode) toggleedit();
2705                     parsestate(f, p);
2706                     game::respawned(f, true, ent);
2707                     break;
2708                 }
2709 
2710                 case N_SHOTFX:
2711                 {
2712                     int scn = getint(p), weap = getint(p), flags = getint(p), len = getint(p), target = getint(p);
2713                     vec from, dest;
2714                     loopk(3) from[k] = getint(p)/DMF;
2715                     loopk(3) dest[k] = getint(p)/DMF;
2716                     int ls = getint(p);
2717                     vector<shotmsg> shots;
2718                     loopj(ls)
2719                     {
2720                         shotmsg &s = shots.add();
2721                         s.id = getint(p);
2722                         loopk(3) s.pos[k] = getint(p);
2723                     }
2724                     gameent *t = game::getclient(scn), *v = game::getclient(target);
2725                     if(!t || !isweap(weap) || t == game::player1 || t->ai) break;
2726                     if(weap != t->weapselect) t->weapswitch(weap, lastmillis);
2727                     float scale = 1;
2728                     int sub = W2(weap, ammosub, WS(flags));
2729                     if(W2(weap, cooktime, WS(flags)))
2730                     {
2731                         scale = len/float(W2(weap, cooktime, WS(flags)));
2732                         if(sub > 1) sub = int(ceilf(sub*scale));
2733                     }
2734                     projs::shootv(weap, flags, sub, 0, scale, from, dest, shots, t, false, v);
2735                     break;
2736                 }
2737 
2738                 case N_DESTROY:
2739                 {
2740                     int scn = getint(p), targ = getint(p), idx = getint(p), num = idx < 0 ? 0-idx : idx;
2741                     gameent *t = game::getclient(scn);
2742                     loopi(num)
2743                     {
2744                         int id = getint(p);
2745                         if(t) projs::destruct(t, targ, id, idx < 0);
2746                     }
2747                     break;
2748                 }
2749 
2750                 case N_STICKY: // cn target id norm pos
2751                 {
2752                     int scn = getint(p), tcn = getint(p), id = getint(p);
2753                     vec norm(0, 0, 0), pos(0, 0, 0);
2754                     loopk(3) norm[k] = getint(p)/DNF;
2755                     loopk(3) pos[k] = getint(p)/DMF;
2756                     gameent *t = game::getclient(scn), *v = tcn >= 0 ? game::getclient(tcn) : NULL;
2757                     if(t && (tcn < 0 || v)) projs::sticky(t, id, norm, pos, v);
2758                     break;
2759                 }
2760 
2761                 case N_DAMAGE:
2762                 {
2763                     int tcn = getint(p), acn = getint(p), weap = getint(p), flags = getint(p), damage = getint(p), health = getint(p);
2764                     vec dir, vel;
2765                     loopk(3) dir[k] = getint(p)/DNF;
2766                     loopk(3) vel[k] = getint(p)/DNF;
2767                     dir.normalize();
2768                     float dist = getint(p)/DNF;
2769                     gameent *m = game::getclient(tcn), *v = game::getclient(acn);
2770                     if(!m || !v) break;
2771                     game::damaged(weap, flags, damage, health, m, v, lastmillis, dir, vel, dist);
2772                     break;
2773                 }
2774 
2775                 case N_BURNRES:
2776                 {
2777                     int cn = getint(p);
2778                     gameent *m = game::getclient(cn);
2779                     if(!m) break;
2780                     m->burntime = getint(p);
2781                     m->burndelay = getint(p);
2782                     m->burndamage = getint(p);
2783                     break;
2784                 }
2785 
2786                 case N_BLEEDRES:
2787                 {
2788                     int cn = getint(p);
2789                     gameent *m = game::getclient(cn);
2790                     if(!m) break;
2791                     m->bleedtime = getint(p);
2792                     m->bleeddelay = getint(p);
2793                     m->bleeddamage = getint(p);
2794                     break;
2795                 }
2796 
2797                 case N_SHOCKRES:
2798                 {
2799                     int cn = getint(p);
2800                     gameent *m = game::getclient(cn);
2801                     if(!m) break;
2802                     m->shocktime = getint(p);
2803                     m->shockdelay = getint(p);
2804                     m->shockdamage = getint(p);
2805                     m->shockstun = getint(p);
2806                     m->shockstunscale = getfloat(p);
2807                     m->shockstunfall = getfloat(p);
2808                     m->shockstuntime = getint(p);
2809                     break;
2810                 }
2811 
2812                 case N_RELOAD:
2813                 {
2814                     int trg = getint(p), weap = getint(p), amt = getint(p), ammo = getint(p), store = getint(p);
2815                     gameent *m = game::getclient(trg);
2816                     if(!m || !isweap(weap)) break;
2817                     weapons::weapreload(m, weap, amt, ammo, store, false);
2818                     break;
2819                 }
2820 
2821                 case N_REGEN:
2822                 {
2823                     int trg = getint(p), heal = getint(p), amt = getint(p);
2824                     gameent *f = game::getclient(trg);
2825                     if(!f) break;
2826                     if(!amt)
2827                     {
2828                         f->impulse[IM_METER] = 0;
2829                         f->resetresidual();
2830                     }
2831                     else if(amt > 0 && (!f->lastregen || lastmillis-f->lastregen >= 500)) playsound(S_REGEN, f->o, f);
2832                     f->health = heal;
2833                     f->lastregen = lastmillis;
2834                     f->lastregenamt = amt;
2835                     break;
2836                 }
2837 
2838                 case N_DIED:
2839                 {
2840                     int vcn = getint(p), deaths = getint(p), tdeaths = getint(p), acn = getint(p), frags = getint(p), tfrags = getint(p), spree = getint(p), style = getint(p), weap = getint(p), flags = getint(p), damage = getint(p), material = getint(p);
2841                     gameent *m = game::getclient(vcn), *v = game::getclient(acn);
2842                     static vector<gameent *> assist; assist.setsize(0);
2843                     int count = getint(p);
2844                     loopi(count)
2845                     {
2846                         int lcn = getint(p);
2847                         gameent *log = game::getclient(lcn);
2848                         if(log) assist.add(log);
2849                     }
2850                     if(!v || !m) break;
2851                     m->deaths = deaths;
2852                     m->totaldeaths = tdeaths;
2853                     v->frags = frags;
2854                     v->totalfrags = tfrags;
2855                     v->spree = spree;
2856                     game::killed(weap, flags, damage, m, v, assist, style, material);
2857                     m->lastdeath = lastmillis;
2858                     m->weapreset(true);
2859                     break;
2860                 }
2861 
2862                 case N_POINTS:
2863                 {
2864                     int acn = getint(p), add = getint(p), points = getint(p), total = getint(p);
2865                     gameent *v = game::getclient(acn);
2866                     if(!v) break;
2867                     v->lastpoints = add;
2868                     v->points = points;
2869                     v->totalpoints = total;
2870                     break;
2871                 }
2872 
2873                 case N_TOTALS:
2874                 {
2875                     int acn = getint(p), totalp = getint(p), totalf = getint(p), totald = getint(p);
2876                     float totalap = getfloat(p);
2877                     gameent *v = game::getclient(acn);
2878                     if(!v) break;
2879                     v->totalpoints = totalp;
2880                     v->totalfrags = totalf;
2881                     v->totaldeaths = totald;
2882                     v->totalavgpos = totalap;
2883                     break;
2884                 }
2885 
2886                 case N_AVGPOS:
2887                 {
2888                     int acn = getint(p);
2889                     float totalap = getfloat(p);
2890                     gameent *v = game::getclient(acn);
2891                     if(!v) break;
2892                     v->totalavgpos = totalap;
2893                     break;
2894                 }
2895 
2896                 case N_WEAPDROP:
2897                 {
2898                     int trg = getint(p), weap = getint(p), ds = getint(p);
2899                     gameent *m = game::getclient(trg);
2900                     bool local = m && (m == game::player1 || m->ai);
2901                     if(ds) loopj(ds)
2902                     {
2903                         int gs = getint(p), drop = getint(p), ammo = getint(p);
2904                         if(m) projs::drop(m, gs, drop, ammo, local, j, weap);
2905                     }
2906                     if(isweap(weap) && m)
2907                     {
2908                         if(m->weapswitch(weap, lastmillis, W(weap, delayswitch)))
2909                             playsound(WSND(weap, S_W_SWITCH), m->o, m, 0, -1, -1, -1, &m->wschan[WS_MAIN_CHAN]);
2910                     }
2911                     break;
2912                 }
2913 
2914                 case N_WEAPSELECT:
2915                 {
2916                     int trg = getint(p), weap = getint(p);
2917                     gameent *m = game::getclient(trg);
2918                     if(!m || !isweap(weap)) break;
2919                     weapons::weapselect(m, weap, (1<<W_S_SWITCH)|(1<<W_S_RELOAD), false);
2920                     break;
2921                 }
2922 
2923                 case N_WEAPCOOK:
2924                 {
2925                     int trg = getint(p), weap = getint(p), etype = getint(p), offtime = getint(p);
2926                     gameent *m = game::getclient(trg);
2927                     if(!m || !isweap(weap)) break;
2928                     if(etype >= 0)
2929                     {
2930                         float maxscale = 1;
2931                         int sub = W2(weap, ammosub, etype >= 1);
2932                         if(sub > 1 && m->weapammo[weap][W_A_CLIP] < sub) maxscale = m->weapammo[weap][W_A_CLIP]/float(sub);
2933                         m->setweapstate(weap, etype >= 2 ? W_S_ZOOM : W_S_POWER, max(int(W2(weap, cooktime, etype >= 1)*maxscale), 1), lastmillis, offtime);
2934                     }
2935                     else m->setweapstate(weap, W_S_IDLE, 0, lastmillis, 0, true);
2936                     break;
2937                 }
2938 
2939                 case N_RESUME:
2940                 {
2941                     for(;;)
2942                     {
2943                         int lcn = getint(p);
2944                         if(p.overread() || lcn < 0) break;
2945                         gameent *f = game::newclient(lcn);
2946                         if(!f) parsestate(NULL, p);
2947                         else if(parsestate(f, p, true))
2948                             addmsg(N_RESUME, "ri", f->clientnum);
2949                     }
2950                     break;
2951                 }
2952 
2953                 case N_ITEMSPAWN:
2954                 {
2955                     int ent = getint(p), value = getint(p);
2956                     if(!entities::ents.inrange(ent)) break;
2957                     gameentity &e = *(gameentity *)entities::ents[ent];
2958                     entities::setspawn(ent, value);
2959                     ai::itemspawned(ent, value!=0);
2960                     if(e.spawned())
2961                     {
2962                         int attr = m_attr(e.type, e.attrs[0]), colour = e.type == WEAPON && isweap(attr) ? W(attr, colour) : colourwhite;
2963                         playsound(e.type == WEAPON && attr >= W_OFFSET && attr < W_ALL ? WSND(attr, S_W_SPAWN) : S_ITEMSPAWN, e.o, NULL, 0, -1, -1, -1, &e.schan);
2964                         if(entities::showentdescs)
2965                         {
2966                             vec pos = vec(e.o).add(vec(0, 0, 4));
2967                             const char *texname = entities::showentdescs >= 2 ? hud::itemtex(e.type, attr) : NULL;
2968                             if(texname && *texname) part_icon(pos, textureload(texname, 3), game::aboveitemiconsize, 1, -10, 0, game::eventiconfade, colour);
2969                             else
2970                             {
2971                                 const char *item = entities::entinfo(e.type, e.attrs, 0);
2972                                 if(item && *item)
2973                                 {
2974                                     defformatstring(ds, "<emphasis>%s", item);
2975                                     part_textcopy(pos, ds, PART_TEXT, game::eventiconfade, colourwhite, 2, 1, -10);
2976                                 }
2977                             }
2978                         }
2979                         game::spawneffect(PART_SPARK, e.o, enttype[e.type].radius*0.25f, colour, 1);
2980                         if(game::dynlighteffects) adddynlight(e.o, enttype[e.type].radius, vec::fromcolor(colour).mul(2.f), 250, 250);
2981                     }
2982                     break;
2983                 }
2984 
2985                 case N_TRIGGER:
2986                 {
2987                     int ent = getint(p), st = getint(p);
2988                     entities::setspawn(ent, st);
2989                     break;
2990                 }
2991 
2992                 case N_ITEMACC:
2993                 { // uses a specific drop so the client knows what to replace
2994                     int lcn = getint(p), fcn = getint(p), ent = getint(p), ammoamt = getint(p), spawn = getint(p),
2995                         weap = getint(p), drop = getint(p), ammo = getint(p);
2996                     gameent *m = game::getclient(lcn);
2997                     if(!m) break;
2998                     if(entities::ents.inrange(ent) && enttype[entities::ents[ent]->type].usetype == EU_ITEM)
2999                         entities::useeffects(m, fcn, ent, ammoamt, spawn, weap, drop, ammo);
3000                     break;
3001                 }
3002 
3003                 case N_EDITVAR:
3004                 {
3005                     int t = getint(p);
3006                     bool commit = true;
3007                     getstring(text, p);
3008                     ident *id = idents.access(text);
3009                     if(!d || !id || !(id->flags&IDF_WORLD) || id->type != t) commit = false;
3010                     switch(t)
3011                     {
3012                         case ID_VAR:
3013                         {
3014                             int val = getint(p);
3015                             if(commit)
3016                             {
3017                                 if(id->flags&IDF_HEX && uint(id->maxval) == 0xFFFFFFFFU)
3018                                 {
3019                                     if(uint(val) > uint(id->maxval)) val = uint(id->maxval);
3020                                     else if(uint(val) < uint(id->minval)) val = uint(id->minval);
3021                                 }
3022                                 else if(val > id->maxval) val = id->maxval;
3023                                 else if(val < id->minval) val = id->minval;
3024                                 setvar(text, val, true);
3025                                 conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(d), id->name, intstr(id));
3026                             }
3027                             break;
3028                         }
3029                         case ID_FVAR:
3030                         {
3031                             float val = getfloat(p);
3032                             if(commit)
3033                             {
3034                                 if(val > id->maxvalf) val = id->maxvalf;
3035                                 else if(val < id->minvalf) val = id->minvalf;
3036                                 setfvar(text, val, true);
3037                                 conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(d), id->name, floatstr(*id->storage.f));
3038                             }
3039                             break;
3040                         }
3041                         case ID_SVAR:
3042                         {
3043                             int vlen = getint(p);
3044                             if(vlen < 0 || vlen > p.remaining()) break;
3045                             char *val = newstring(vlen);
3046                             getstring(val, p, vlen+1);
3047                             if(commit)
3048                             {
3049                                 setsvar(text, val, true);
3050                                 conoutft(CON_EVENT, "\fy%s set world variable \fs\fc%s\fS to \fy\fc%s\fS", game::colourname(d), id->name, *id->storage.s);
3051                             }
3052                             delete[] val;
3053                             break;
3054                         }
3055                         case ID_ALIAS:
3056                         {
3057                             int vlen = getint(p);
3058                             if(vlen < 0 || vlen > p.remaining()) break;
3059                             char *val = newstring(vlen);
3060                             getstring(val, p, vlen+1);
3061                             if(commit || !id) // set aliases anyway
3062                             {
3063                                 worldalias(text, val);
3064                                 conoutft(CON_EVENT, "\fy%s set world alias \fs\fc%s\fS to \fs\fc%s\fS", game::colourname(d), text, val);
3065                             }
3066                             delete[] val;
3067                             break;
3068                         }
3069                         default: break;
3070                     }
3071                     break;
3072                 }
3073 
3074                 case N_CLIPBOARD:
3075                 {
3076                     int tcn = getint(p), unpacklen = getint(p), packlen = getint(p);
3077                     gameent *d = game::getclient(tcn);
3078                     ucharbuf q = p.subbuf(max(packlen, 0));
3079                     if(d) unpackeditinfo(d->edit, q.buf, q.maxlen, unpacklen);
3080                     break;
3081                 }
3082 
3083                 case N_UNDO:
3084                 case N_REDO:
3085                 {
3086                     int cn = getint(p), unpacklen = getint(p), packlen = getint(p);
3087                     gameent *d = game::getclient(cn);
3088                     ucharbuf q = p.subbuf(max(packlen, 0));
3089                     if(d) unpackundo(q.buf, q.maxlen, unpacklen);
3090                     break;
3091                 }
3092 
3093                 case N_EDITF:            // coop editing messages
3094                 case N_EDITT:
3095                 case N_EDITM:
3096                 case N_FLIP:
3097                 case N_COPY:
3098                 case N_PASTE:
3099                 case N_ROTATE:
3100                 case N_REPLACE:
3101                 case N_DELCUBE:
3102                 case N_EDITVSLOT:
3103                 {
3104                     if(!d) return;
3105                     selinfo s;
3106                     s.o.x = getint(p); s.o.y = getint(p); s.o.z = getint(p);
3107                     s.s.x = getint(p); s.s.y = getint(p); s.s.z = getint(p);
3108                     s.grid = getint(p); s.orient = getint(p);
3109                     s.cx = getint(p); s.cxs = getint(p); s.cy = getint(p), s.cys = getint(p);
3110                     s.corner = getint(p);
3111                     switch(type)
3112                     {
3113                         case N_EDITF: { int dir = getint(p), mode = getint(p); if(s.validate()) mpeditface(dir, mode, s, false); break; }
3114                         case N_EDITT:
3115                         {
3116                             int tex = getint(p),
3117                                 allfaces = getint(p);
3118                             if(p.remaining() < 2) return;
3119                             int extra = lilswap(*(const ushort *)p.pad(2));
3120                             if(p.remaining() < extra) return;
3121                             ucharbuf ebuf = p.subbuf(extra);
3122                             if(s.validate()) mpedittex(tex, allfaces, s, ebuf);
3123                             break;
3124                         }
3125                         case N_EDITM: { int mat = getint(p), filter = getint(p), style = getint(p); if(s.validate()) mpeditmat(mat, filter, style, s, false); break; }
3126                         case N_FLIP: if(s.validate()) mpflip(s, false); break;
3127                         case N_COPY: if(d && s.validate()) mpcopy(d->edit, s, false); break;
3128                         case N_PASTE: if(d && s.validate()) mppaste(d->edit, s, false); break;
3129                         case N_ROTATE: { int dir = getint(p); if(s.validate()) mprotate(dir, s, false); break; }
3130                         case N_REPLACE:
3131                         {
3132                             int oldtex = getint(p),
3133                                 newtex = getint(p),
3134                                 insel = getint(p);
3135                             if(p.remaining() < 2) return;
3136                             int extra = lilswap(*(const ushort *)p.pad(2));
3137                             if(p.remaining() < extra) return;
3138                             ucharbuf ebuf = p.subbuf(extra);
3139                             if(s.validate()) mpreplacetex(oldtex, newtex, insel>0, s, ebuf);
3140                             break;
3141                         }
3142                         case N_DELCUBE: if(s.validate()) mpdelcube(s, false); break;
3143                         case N_EDITVSLOT:
3144                         {
3145                             int delta = getint(p),
3146                                 allfaces = getint(p);
3147                             if(p.remaining() < 2) return;
3148                             int extra = lilswap(*(const ushort *)p.pad(2));
3149                             if(p.remaining() < extra) return;
3150                             ucharbuf ebuf = p.subbuf(extra);
3151                             if(s.validate()) mpeditvslot(delta, allfaces, s, ebuf);
3152                             break;
3153                         }
3154                     }
3155                     break;
3156                 }
3157                 case N_REMIP:
3158                 {
3159                     if(!d) return;
3160                     conoutft(CON_EVENT, "\fy%s remipped", game::colourname(d));
3161                     mpremip(false);
3162                     break;
3163                 }
3164                 case N_CALCLIGHT:
3165                     if(!d) return;
3166                     conoutf("\fy%s calced lights", game::colourname(d));
3167                     mpcalclight(false);
3168                     break;
3169                 case N_EDITENT:            // coop edit of ent
3170                 {
3171                     if(!d) return;
3172                     int i = getint(p);
3173                     float x = getint(p)/DMF, y = getint(p)/DMF, z = getint(p)/DMF;
3174                     int type = getint(p), numattrs = getint(p);
3175                     attrvector attrs;
3176                     attrs.add(0, clamp(numattrs, entities::numattrs(type), MAXENTATTRS));
3177                     loopk(numattrs)
3178                     {
3179                         int val = getint(p);
3180                         if(attrs.inrange(k)) attrs[k] = val;
3181                     }
3182                     mpeditent(i, vec(x, y, z), type, attrs, false);
3183                     entities::setspawn(i, 0);
3184                     break;
3185                 }
3186 
3187                 case N_EDITLINK:
3188                 {
3189                     if(!d) return;
3190                     int b = getint(p), index = getint(p), node = getint(p);
3191                     entities::linkents(index, node, b!=0, false, false);
3192                     break;
3193                 }
3194 
3195                 case N_PONG:
3196                     addmsg(N_CLIENTPING, "i", game::player1->ping = (game::player1->ping*5+totalmillis-getint(p))/6);
3197                     break;
3198 
3199                 case N_CLIENTPING:
3200                     if(!d) return;
3201                     d->ping = getint(p);
3202                     loopv(game::players) if(game::players[i] && game::players[i]->ownernum == d->clientnum)
3203                         game::players[i]->ping = d->ping;
3204                     break;
3205 
3206                 case N_MASTERMODE:
3207                 {
3208                     int tcn = getint(p);
3209                     mastermode = getint(p);
3210                     if(tcn >= 0)
3211                     {
3212                         gameent *e = game::getclient(tcn);
3213                         if(e)
3214                         {
3215                             conoutft(CON_EVENT, "\fy%s set \fs\fcmastermode\fS to \fs\fc%d\fS (\fs\fc%s\fS)", game::colourname(e), mastermode, mastermodename(mastermode));
3216                             break;
3217                         }
3218                     }
3219                     conoutft(CON_EVENT, "\fyThe server set \fs\fcmastermode\fS to \fs\fc%d\fS (\fs\fc%s\fS)", mastermode, mastermodename(mastermode));
3220                     break;
3221                 }
3222 
3223                 case N_TICK:
3224                 {
3225                     int state = getint(p), remain = getint(p);
3226                     game::timeupdate(state, remain);
3227                     break;
3228                 }
3229 
3230                 case N_SERVMSG:
3231                 {
3232                     int lev = getint(p);
3233                     getstring(text, p);
3234                     conoutft(lev >= 0 && lev < CON_MAX ? lev : CON_DEBUG, "%s", text);
3235                     break;
3236                 }
3237 
3238                 case N_SENDDEMOLIST:
3239                 {
3240                     int demos = getint(p);
3241                     if(demos <= 0) conoutft(CON_EVENT, "\foNo demos available");
3242                     else loopi(demos)
3243                     {
3244                         getstring(text, p);
3245                         int len = getint(p), ctime = getint(p);
3246                         if(p.overread()) break;
3247                         conoutft(CON_EVENT, "\fyDemo: %2d. \fs\fc%s\fS recorded \fs\fc%s UTC\fS [\fs\fw%.2f%s\fS]", i+1, text, gettime(ctime, "%Y-%m-%d %H:%M.%S"), len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB");
3248                     }
3249                     break;
3250                 }
3251 
3252                 case N_DEMOPLAYBACK:
3253                 {
3254                     bool wasdemopb = demoplayback;
3255                     demoplayback = getint(p)!=0;
3256                     if(demoplayback) game::player1->state = CS_SPECTATOR;
3257                     else loopv(game::players) if(game::players[i]) game::clientdisconnected(i);
3258                     game::player1->clientnum = getint(p);
3259                     if(!demoplayback && wasdemopb && demoendless)
3260                     {
3261                         stringz(demofile);
3262                         if(*demolist)
3263                         {
3264                             int r = rnd(listlen(demolist)), len = 0;
3265                             const char *elem = indexlist(demolist, r, len);
3266                             if(len > 0) copystring(demofile, elem, len+1);
3267                         }
3268                         if(!*demofile)
3269                         {
3270                             vector<char *> files;
3271                             listfiles("demos", "dmo", files);
3272                             while(!files.empty())
3273                             {
3274                                 int r = rnd(files.length());
3275                                 if(files[r][0] != '.')
3276                                 {
3277                                     copystring(demofile, files[r]);
3278                                     break;
3279                                 }
3280                                 else files.remove(r);
3281                             }
3282                         }
3283                         if(*demofile) addmsg(N_MAPVOTE, "rsi2", demofile, G_DEMO, 0);
3284                     }
3285                     break;
3286                 }
3287 
3288                 case N_DEMOREADY:
3289                 {
3290                     int num = getint(p), ctime = getint(p), len = getint(p);
3291                     getstring(text, p);
3292                     conoutft(CON_EVENT, "\fyDemo \fs\fc%s\fS recorded \fs\fc%s UTC\fS [\fs\fw%.2f%s\fS]", text, gettime(ctime, "%Y-%m-%d %H:%M.%S"), len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB");
3293                     if(demoautoclientsave) getdemo(num, "");
3294                     break;
3295                 }
3296 
3297                 case N_CURRENTPRIV:
3298                 {
3299                     int mn = getint(p), priv = getint(p);
3300                     getstring(text, p);
3301                     gameent *m = game::getclient(mn);
3302                     if(m)
3303                     {
3304                         m->privilege = priv;
3305                         copystring(m->handle, text);
3306                     }
3307                     break;
3308                 }
3309 
3310                 case N_EDITMODE:
3311                 {
3312                     int val = getint(p);
3313                     if(!d) break;
3314                     if(val) d->state = CS_EDITING;
3315                     else
3316                     {
3317                         d->state = CS_ALIVE;
3318                         d->editspawn(game::gamemode, game::mutators);
3319                     }
3320                     d->resetinterp();
3321                     projs::removeplayer(d);
3322                     break;
3323                 }
3324 
3325                 case N_SPECTATOR:
3326                 {
3327                     int sn = getint(p), val = getint(p);
3328                     gameent *s = game::newclient(sn);
3329                     if(!s) break;
3330                     if(s == game::player1)
3331                     {
3332                         game::specreset();
3333                         if(m_race(game::gamemode)) game::specmode = 0;
3334                     }
3335                     if(val != 0)
3336                     {
3337                         if(s == game::player1 && editmode) toggleedit();
3338                         s->state = CS_SPECTATOR;
3339                         s->quarantine = val == 2;
3340                         s->respawned = -1;
3341                     }
3342                     else
3343                     {
3344                         if(s->state == CS_SPECTATOR)
3345                         {
3346                             s->state = CS_WAITING;
3347                             if(s != game::player1 && !s->ai) s->resetinterp();
3348                             game::waiting.removeobj(s);
3349                         }
3350                         s->quarantine = false;
3351                     }
3352                     break;
3353                 }
3354 
3355                 case N_WAITING:
3356                 {
3357                     int sn = getint(p);
3358                     gameent *s = game::newclient(sn);
3359                     if(!s) break;
3360                     if(s == game::player1)
3361                     {
3362                         if(editmode) toggleedit();
3363                         hud::showscores(false);
3364                         s->stopmoving(true);
3365                         game::waiting.setsize(0);
3366                         gameent *d;
3367                         loopv(game::players) if((d = game::players[i]) && d->actortype == A_PLAYER && d->state == CS_WAITING)
3368                             game::waiting.add(d);
3369                     }
3370                     else if(!s->ai) s->resetinterp();
3371                     game::waiting.removeobj(s);
3372                     if(s->state == CS_ALIVE) s->lastdeath = lastmillis; // so spawn delay shows properly
3373                     else entities::spawnplayer(s); // so they're not nowhere
3374                     s->state = CS_WAITING;
3375                     s->quarantine = false;
3376                     s->weapreset(true);
3377                     break;
3378                 }
3379 
3380                 case N_SETTEAM:
3381                 {
3382                     int wn = getint(p), tn = getint(p);
3383                     gameent *w = game::getclient(wn);
3384                     if(!w) return;
3385                     if(w->team != tn)
3386                     {
3387                         if(m_team(game::gamemode, game::mutators) && w->actortype == A_PLAYER && showteamchange >= (w->team != T_NEUTRAL && tn != T_NEUTRAL ? 1 : 2))
3388                             conoutft(CON_EVENT, "\fa%s is now on team %s", game::colourname(w), game::colourteam(tn));
3389                         w->team = tn;
3390                         if(w == game::focus) hud::lastteam = 0;
3391                     }
3392                     break;
3393                 }
3394 
3395                 case N_INFOAFFIN:
3396                 {
3397                     int flag = getint(p), converted = getint(p),
3398                             owner = getint(p), enemy = getint(p);
3399                     if(m_defend(game::gamemode)) defend::updateaffinity(flag, owner, enemy, converted);
3400                     break;
3401                 }
3402 
3403                 case N_SETUPAFFIN:
3404                 {
3405                     if(m_defend(game::gamemode)) defend::parseaffinity(p);
3406                     break;
3407                 }
3408 
3409                 case N_MAPVOTE:
3410                 {
3411                     int vn = getint(p);
3412                     gameent *v = game::getclient(vn);
3413                     getstring(text, p);
3414                     filterstring(text, text);
3415                     int reqmode = getint(p), reqmuts = getint(p);
3416                     if(!v) break;
3417                     vote(v, text, reqmode, reqmuts);
3418                     break;
3419                 }
3420 
3421                 case N_CLEARVOTE:
3422                 {
3423                     int vn = getint(p);
3424                     gameent *v = game::getclient(vn);
3425                     if(!v) break;
3426                     clearvotes(v, true);
3427                     break;
3428                 }
3429 
3430                 case N_CHECKPOINT:
3431                 {
3432                     int tn = getint(p), ent = getint(p);
3433                     gameent *t = game::getclient(tn);
3434                     if(!t || !m_race(game::gamemode))
3435                     {
3436                         if(ent < 0) break;
3437                         if(getint(p) < 0) break;
3438                         loopi(2) getint(p);
3439                         break;
3440                     }
3441                     if(ent >= 0)
3442                     {
3443                         if(entities::ents.inrange(ent) && entities::ents[ent]->type == CHECKPOINT)
3444                         {
3445                             if(t != game::player1 && !t->ai && (!t->cpmillis || entities::ents[ent]->attrs[6] == CP_START)) t->cpmillis = lastmillis;
3446                             if((checkpointannounce&(t != game::focus ? 2 : 1) || (m_ra_gauntlet(game::gamemode, game::mutators) && checkpointannounce&4)) && checkpointannouncefilter&(1<<entities::ents[ent]->attrs[6]))
3447                             {
3448                                 switch(entities::ents[ent]->attrs[6])
3449                                 {
3450                                     case CP_START: game::announce(S_V_START, t); break;
3451                                     case CP_FINISH: case CP_LAST: game::announce(S_V_COMPLETE, t); break;
3452                                     default: game::announce(S_V_CHECKPOINT, t); break;
3453                                 }
3454                             }
3455                             entities::execlink(t, ent, false);
3456                         }
3457                         int laptime = getint(p);
3458                         if(laptime >= 0)
3459                         {
3460                             t->cplast = laptime;
3461                             t->cptime = getint(p);
3462                             t->points = getint(p);
3463                             t->cpmillis = t->impulse[IM_METER] = 0;
3464                             if(showlaptimes >= (t != game::focus ? (t->actortype > A_PLAYER ? 3 : 2) : 1))
3465                             {
3466                                 defformatstring(best, "%s", timestr(t->cptime, 1));
3467                                 conoutft(CON_EVENT, "%s completed in \fs\fg%s\fS (best: \fs\fy%s\fS, laps: \fs\fc%d\fS)", game::colourname(t), timestr(t->cplast, 1), best, t->points);
3468                             }
3469                         }
3470                     }
3471                     else
3472                     {
3473                         t->checkpoint = -1;
3474                         t->cpmillis = ent == -2 ? lastmillis : 0;
3475                     }
3476                 }
3477 
3478                 case N_SCORE:
3479                 {
3480                     int team = getint(p), total = getint(p);
3481                     if(m_team(game::gamemode, game::mutators))
3482                     {
3483                         score &ts = hud::teamscore(team);
3484                         ts.total = total;
3485                     }
3486                     break;
3487                 }
3488 
3489                 case N_DUELEND:
3490                 {
3491                     int winner = getint(p); // Round winner (-1 if everyone died, team in team modes, cn otherwise).
3492                     if(winner == -1)
3493                     { // If nobody won, just announce draw.
3494                         game::announcef(S_V_DRAW, CON_EVENT, NULL, false, "\fyEveryone died, \fzoyEPIC FAIL!");
3495                         break;
3496                     }
3497                     int playing = getint(p); // Were we playing?
3498                     if(playing >= 2)
3499                     {
3500                         defformatstring(msg, "\fyTeam %s are the winners", game::colourteam(winner));
3501                         // If we were playing, announce with appropriate sound for if we won or lost.
3502                         if(playing == 3) game::announcef((winner == game::player1->team) ? S_V_YOUWIN : S_V_YOULOSE, CON_EVENT, NULL, false, "%s", msg);
3503                         else game::announcef(S_V_BOMBSCORE, CON_EVENT, NULL, false, "%s", msg);
3504                     }
3505                     else
3506                     {
3507                         int wins = getint(p); // Winner's win streak.
3508                         gameent *t = game::getclient(winner);
3509                         if(!t) break;
3510                         string msg, hp = "";
3511                         if(!m_insta(game::gamemode, game::mutators))
3512                         { // If applicable, create remaining health or flawless message.
3513                             if(t->health >= t->gethealth(game::gamemode, game::mutators)) formatstring(hp, " with a \fs\fcflawless victory\fS");
3514                             else
3515                             {
3516                                 if(game::damageinteger) formatstring(hp, " with \fs\fc%d\fS health left", int(ceilf(t->health/game::damagedivisor)));
3517                                 else formatstring(hp, " with \fs\fc%.1f\fS health left", t->health/game::damagedivisor);
3518                             }
3519                         }
3520                         // Format win message with streak if it has more than one win.
3521                         if(wins == 1) formatstring(msg, "\fy%s was the winner%s", game::colourname(t), hp);
3522                         else formatstring(msg, "\fy%s was the winner%s (\fs\fc%d\fS in a row)", game::colourname(t), hp, wins);
3523                         // If we were playing, announce with appropriate sound for if we won or lost.
3524                         if(playing) game::announcef((winner == game::player1->clientnum) ? S_V_YOUWIN : S_V_YOULOSE, CON_EVENT, NULL, false, "%s", msg);
3525                         else game::announcef(S_V_BOMBSCORE, CON_EVENT, NULL, false, "%s", msg);
3526                     }
3527                     break;
3528                 }
3529 
3530                 case N_INITAFFIN:
3531                 {
3532                     if(m_capture(game::gamemode)) capture::parseaffinity(p);
3533                     else if(m_bomber(game::gamemode)) bomber::parseaffinity(p);
3534                     break;
3535                 }
3536 
3537                 case N_DROPAFFIN:
3538                 {
3539                     int ocn = getint(p), otc = getint(p), flag = getint(p);
3540                     vec droploc, inertia;
3541                     loopk(3) droploc[k] = getint(p)/DMF;
3542                     loopk(3) inertia[k] = getint(p)/DMF;
3543                     gameent *o = game::newclient(ocn);
3544                     if(o)
3545                     {
3546                         if(m_capture(game::gamemode)) capture::dropaffinity(o, flag, droploc, inertia, otc);
3547                         else if(m_bomber(game::gamemode)) bomber::dropaffinity(o, flag, droploc, inertia, otc);
3548                     }
3549                     break;
3550                 }
3551 
3552                 case N_SCOREAFFIN:
3553                 {
3554                     int ocn = getint(p), relayflag = getint(p), goalflag = getint(p), score = getint(p);
3555                     gameent *o = game::newclient(ocn);
3556                     if(o)
3557                     {
3558                         if(m_capture(game::gamemode)) capture::scoreaffinity(o, relayflag, goalflag, score);
3559                         else if(m_bomber(game::gamemode)) bomber::scoreaffinity(o, relayflag, goalflag, score);
3560                     }
3561                     break;
3562                 }
3563 
3564                 case N_RETURNAFFIN:
3565                 {
3566                     int ocn = getint(p), flag = getint(p);
3567                     gameent *o = game::newclient(ocn);
3568                     if(o && m_capture(game::gamemode)) capture::returnaffinity(o, flag);
3569                     break;
3570                 }
3571 
3572                 case N_TAKEAFFIN:
3573                 {
3574                     int ocn = getint(p), flag = getint(p);
3575                     gameent *o = game::newclient(ocn);
3576                     if(o)
3577                     {
3578                         o->lastaffinity = lastmillis;
3579                         if(m_capture(game::gamemode)) capture::takeaffinity(o, flag);
3580                         else if(m_bomber(game::gamemode)) bomber::takeaffinity(o, flag);
3581                     }
3582                     break;
3583                 }
3584 
3585                 case N_RESETAFFIN:
3586                 {
3587                     int flag = getint(p), value = getint(p);
3588                     vec resetpos(-1, -1, -1);
3589                     if(value == 2) loopk(3) resetpos[k] = getint(p)/DMF;
3590                     if(m_capture(game::gamemode)) capture::resetaffinity(flag, value, resetpos);
3591                     else if(m_bomber(game::gamemode)) bomber::resetaffinity(flag, value, resetpos);
3592                     break;
3593                 }
3594 
3595                 case N_GETMAP:
3596                 {
3597                     conoutf("\fyServer has requested we send the map..");
3598                     if(!needsmap && !gettingmap) sendmap();
3599                     else addmsg(N_GETMAP, "r");
3600                     break;
3601                 }
3602 
3603                 case N_SENDMAP:
3604                 {
3605                     conoutf("\fyMap data has been uploaded to the server");
3606                     break;
3607                 }
3608 
3609                 case N_FAILMAP:
3610                 {
3611                     conoutf("\fyFailed to get a valid map");
3612                     needsmap = gettingmap = 0;
3613                     break;
3614                 }
3615 
3616                 case N_NEWMAP:
3617                 {
3618                     int size = getint(p);
3619                     getstring(text, p);
3620                     if(size >= 0) emptymap(size, true, text);
3621                     else enlargemap(size == -2, true);
3622                     mapvotes.shrink(0);
3623                     needsmap = 0;
3624                     if(d)
3625                     {
3626                         int newsize = 0;
3627                         while(1<<newsize < worldsize) newsize++;
3628                         conoutf(size >= 0 ? "\fy%s started new map \fs\fc%s\fS of size \fs\fc%d\fS" : "\fy%s enlarged the map \fs\fc%s\fS to size \fs\fc%d\fS", game::colourname(d), mapname, newsize);
3629                     }
3630                     break;
3631                 }
3632 
3633                 case N_INITAI:
3634                 {
3635                     int bn = getint(p), on = getint(p), at = getint(p), et = getint(p), sk = clamp(getint(p), 1, 101);
3636                     getstring(text, p);
3637                     int tm = getint(p), cl = getint(p), md = getint(p), pt = getint(p);
3638                     string vanity;
3639                     getstring(vanity, p);
3640                     int lw = getint(p);
3641                     vector<int> lweaps;
3642                     loopk(lw) lweaps.add(getint(p));
3643                     gameent *b = game::newclient(bn);
3644                     if(!b) break;
3645                     ai::init(b, at, et, on, sk, bn, text, tm, cl, md, pt, vanity, lweaps);
3646                     break;
3647                 }
3648 
3649                 case N_AUTHCHAL:
3650                 {
3651                     uint id = (uint)getint(p);
3652                     getstring(text, p);
3653                     if(accountname[0] && accountpass[0])
3654                     {
3655                         conoutf("Identifying as: \fs\fc%s\fS (\fs\fy%u\fS)", accountname, id);
3656                         vector<char> buf;
3657                         answerchallenge(accountpass, text, buf);
3658                         addmsg(N_AUTHANS, "ris", id, buf.getbuf());
3659                     }
3660                     break;
3661                 }
3662 
3663                 case N_STEAMCHAL:
3664                 {
3665                     char token[1024];
3666                     uint tokenlen;
3667                     if(cdpi::steam::clientauthticket(token, &tokenlen))
3668                     {
3669                         packetbuf sc(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
3670                         putint(sc, N_STEAMANS);
3671                         sendstring(cdpi::steam::steamuserid, sc);
3672                         putuint(sc, tokenlen);
3673                         if(tokenlen > 0) sc.put((uchar *)token, tokenlen);
3674                         sendclientpacket(sc.finalize(), 1);
3675                         conoutft(CON_EVENT, "\fyPerforming Steam ID challenge...");
3676                     }
3677                     else
3678                     {
3679                         conoutft(CON_EVENT, "\frFailed to perform Steam ID challenge");
3680                         addmsg(N_STEAMFAIL, "r");
3681                     }
3682                     break;
3683                 }
3684 
3685                 case N_QUEUEPOS:
3686                 {
3687                     int qcn = getint(p), pos = getint(p);
3688                     gameent *o = game::newclient(qcn);
3689                     bool changed = o->queuepos != pos;
3690                     o->queuepos = pos;
3691                     if(o == game::focus && changed && o->state != CS_ALIVE)
3692                     {
3693                         if(o->queuepos < 0) conoutft(CON_EVENT, "\fyYou are now \fs\fzgcqueued\fS for the \fs\fcnext match\fS");
3694                         else if(o->queuepos) conoutft(CON_EVENT, "\fyYou are \fs\fzgc#%d\fS in the \fs\fcqueue\fS", o->queuepos+1);
3695                         else conoutft(CON_EVENT, "\fyYou are \fs\fzgcNEXT\fS in the \fs\fcqueue\fS");
3696                     }
3697                     break;
3698                 }
3699 
3700                 default:
3701                 {
3702                     defformatstring(err, "type (got: %d, prev: %d, sender: %s)", type, prevtype, d ? game::colourname(d) : "<none>");
3703                     neterr(err);
3704                     return;
3705                 }
3706             }
3707         }
3708     }
3709 
serverstat(serverinfo * a)3710     int serverstat(serverinfo *a)
3711     {
3712         if(a->attr.length() > 4 && a->numplayers >= a->attr[4])
3713         {
3714             return SSTAT_FULL;
3715         }
3716         else if(a->attr.length() > 5) switch(a->attr[5])
3717         {
3718             case MM_LOCKED:
3719             {
3720                 return SSTAT_LOCKED;
3721             }
3722             case MM_PRIVATE:
3723             case MM_PASSWORD:
3724             {
3725                 return SSTAT_PRIVATE;
3726             }
3727             default:
3728             {
3729                 return SSTAT_OPEN;
3730             }
3731         }
3732         return SSTAT_UNKNOWN;
3733     }
3734 
servercompare(serverinfo * a,serverinfo * b)3735     int servercompare(serverinfo *a, serverinfo *b)
3736     {
3737         int ac = 0, bc = 0;
3738         if(a->address.host == ENET_HOST_ANY || a->ping >= serverinfo::WAITING || a->attr.empty()) ac = -1;
3739         else ac = a->attr[0] == VERSION_GAME ? 0x7FFF : clamp(a->attr[0], 0, 0x7FFF-1);
3740         if(b->address.host == ENET_HOST_ANY || b->ping >= serverinfo::WAITING || b->attr.empty()) bc = -1;
3741         else bc = b->attr[0] == VERSION_GAME ? 0x7FFF : clamp(b->attr[0], 0, 0x7FFF-1);
3742         if(ac > bc) return -1;
3743         if(ac < bc) return 1;
3744 
3745         #define retcp(c) { int cv = (c); if(cv) { return reverse ? -cv : cv; } }
3746         #define retsw(c,d,e) { \
3747             int cv = (c), dv = (d); \
3748             if(cv != dv) \
3749             { \
3750                 if((e) != reverse ? cv < dv : cv > dv) return -1; \
3751                 if((e) != reverse ? cv > dv : cv < dv) return 1; \
3752             } \
3753         }
3754 
3755         if(serversortstyles.empty()) updateserversort();
3756         loopv(serversortstyles)
3757         {
3758             int style = serversortstyles[i];
3759             serverinfo *aa = a, *ab = b;
3760             bool reverse = false;
3761             if(style < 0)
3762             {
3763                 style = 0-style;
3764                 reverse = true;
3765             }
3766 
3767             switch(style)
3768             {
3769                 case SINFO_STATUS:
3770                 {
3771                     retsw(serverstat(aa), serverstat(ab), true);
3772                     break;
3773                 }
3774                 case SINFO_DESC:
3775                 {
3776                     retcp(strcmp(aa->sdesc, ab->sdesc));
3777                     break;
3778                 }
3779                 case SINFO_MODE:
3780                 {
3781                     if(aa->attr.length() > 1) ac = aa->attr[1];
3782                     else ac = 0;
3783 
3784                     if(ab->attr.length() > 1) bc = ab->attr[1];
3785                     else bc = 0;
3786 
3787                     retsw(ac, bc, true);
3788                 }
3789                 case SINFO_MUTS:
3790                 {
3791                     if(aa->attr.length() > 2) ac = aa->attr[2];
3792                     else ac = 0;
3793 
3794                     if(ab->attr.length() > 2) bc = ab->attr[2];
3795                     else bc = 0;
3796 
3797                     retsw(ac, bc, true);
3798                     break;
3799                 }
3800                 case SINFO_MAP:
3801                 {
3802                     retcp(strcmp(aa->map, ab->map));
3803                     break;
3804                 }
3805                 case SINFO_TIME:
3806                 {
3807                     if(aa->attr.length() > 3) ac = aa->attr[3];
3808                     else ac = 0;
3809 
3810                     if(ab->attr.length() > 3) bc = ab->attr[3];
3811                     else bc = 0;
3812 
3813                     retsw(ac, bc, false);
3814                     break;
3815                 }
3816                 case SINFO_NUMPLRS:
3817                 {
3818                     retsw(aa->numplayers, ab->numplayers, false);
3819                     break;
3820                 }
3821                 case SINFO_MAXPLRS:
3822                 {
3823                     if(aa->attr.length() > 4) ac = aa->attr[4];
3824                     else ac = 0;
3825 
3826                     if(ab->attr.length() > 4) bc = ab->attr[4];
3827                     else bc = 0;
3828 
3829                     retsw(ac, bc, false);
3830                     break;
3831                 }
3832                 case SINFO_PING:
3833                 {
3834                     retsw(aa->ping, ab->ping, true);
3835                     break;
3836                 }
3837                 case SINFO_PRIO:
3838                 {
3839                     retsw(aa->priority, ab->priority, false);
3840                     break;
3841                 }
3842                 default: break;
3843             }
3844         }
3845         return strcmp(a->name, b->name);
3846     }
3847 
parsepacketclient(int chan,packetbuf & p)3848     void parsepacketclient(int chan, packetbuf &p)  // processes any updates from the server
3849     {
3850         if(p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED) return;
3851         switch(chan)
3852         {
3853             case 0:
3854                 parsepositions(p);
3855                 break;
3856 
3857             case 1:
3858                 parsemessages(-1, NULL, p);
3859                 break;
3860 
3861             case 2:
3862                 receivefile(p.buf, p.maxlen);
3863                 break;
3864         }
3865     }
3866 
getservers(int server,int prop,int idx)3867     void getservers(int server, int prop, int idx)
3868     {
3869         if(server < 0) intret(servers.length());
3870         else if(servers.inrange(server))
3871         {
3872             serverinfo *si = servers[server];
3873             if(prop < 0) intret(4);
3874             else switch(prop)
3875             {
3876                 case 0:
3877                     if(idx < 0) intret(11);
3878                     else switch(idx)
3879                     {
3880                         case 0: intret(serverstat(si)); break;
3881                         case 1: result(si->name); break;
3882                         case 2: intret(si->port); break;
3883                         case 3: result(si->sdesc[0] ? si->sdesc : si->name); break;
3884                         case 4: result(si->map); break;
3885                         case 5: intret(si->numplayers); break;
3886                         case 6: intret(si->ping); break;
3887                         case 7: intret(si->lastinfo); break;
3888                         case 8: result(si->authhandle); break;
3889                         case 9: result(si->flags); break;
3890                         case 10: result(si->branch); break;
3891                         case 11: intret(si->priority); break;
3892                     }
3893                     break;
3894                 case 1:
3895                     if(idx < 0) intret(si->attr.length());
3896                     else if(si->attr.inrange(idx)) intret(si->attr[idx]);
3897                     break;
3898                 case 2:
3899                     if(idx < 0) intret(si->players.length());
3900                     else if(si->players.inrange(idx)) result(si->players[idx]);
3901                     break;
3902                 case 3:
3903                     if(idx < 0) intret(si->handles.length());
3904                     else if(si->handles.inrange(idx)) result(si->handles[idx]);
3905                     break;
3906             }
3907         }
3908     }
3909     ICOMMAND(0, getserver, "bbb", (int *server, int *prop, int *idx), getservers(*server, *prop, *idx));
3910 
3911     #define GETSERVER(name, test, incr) \
3912         ICOMMAND(0, getserver##name, "", (), \
3913         { \
3914             int n = 0; \
3915             loopv(servers) if(test) n += incr; \
3916             intret(n); \
3917         }); \
3918         ICOMMAND(0, getserver##name##if, "re", (ident *id, uint *cond), \
3919         { \
3920             int n = 0; \
3921             loopstart(id, stack); \
3922             loopv(servers) \
3923             { \
3924                 loopiter(id, stack, i); \
3925                 if(test && executebool(cond)) n += incr; \
3926             } \
3927             loopend(id, stack); \
3928             intret(n); \
3929         });
3930 
3931     GETSERVER(count, true, 1);
3932     GETSERVER(active, servers[i]->numplayers, 1);
3933     GETSERVER(players, servers[i]->numplayers, servers[i]->numplayers);
3934 
loopserver(ident * id,uint * cond,uint * body,uint * none)3935     void loopserver(ident *id, uint *cond, uint *body, uint *none)
3936     {
3937         loopstart(id, stack);
3938         if(servers.empty())
3939         {
3940             loopiter(id, stack, -1);
3941             execute(none);
3942         }
3943         else loopv(servers)
3944         {
3945             loopiter(id, stack, i);
3946             if(cond && !executebool(cond)) continue;
3947             execute(body);
3948         }
3949         loopend(id, stack);
3950     }
3951 
3952     ICOMMAND(0, loopservers, "ree", (ident *id, uint *body, uint *none), loopserver(id, NULL, body, none));
3953     ICOMMAND(0, loopserversif, "reee", (ident *id, uint *cond, uint *body, uint *none), loopserver(id, cond, body, none));
3954 
completeplayers(const char ** nextcomplete,const char * start,int commandsize,const char * lastcomplete,bool reverse)3955     void completeplayers(const char **nextcomplete, const char *start, int commandsize, const char *lastcomplete, bool reverse)
3956     {
3957         static vector<char *> names;
3958         if(!names.empty()) names.deletearrays();
3959         gameent *d = NULL;
3960         int size = completesize-commandsize;
3961         const char *name = game::colourname(game::player1, NULL, false, true, 0);
3962         if(!size || !strncmp(name, &start[commandsize], size))
3963             names.add(newstring(name));
3964         loopv(game::players) if((d = game::players[i]) != NULL)
3965         {
3966             name = game::colourname(d, NULL, false, true, 0);
3967             if(!size || !strncmp(name, &start[commandsize], size))
3968                 names.add(newstring(name));
3969         }
3970         loopv(names)
3971         {
3972             if(strcmp(names[i], lastcomplete)*(reverse ? -1 : 1) > 0 && (!*nextcomplete || strcmp(names[i], *nextcomplete)*(reverse ? -1 : 1) < 0))
3973                 *nextcomplete = names[i];
3974         }
3975     }
3976 }
3977