1 // clientgame.cpp: core game related stuff
2 
3 #include "cube.h"
4 #include "bot/bot.h"
5 
6 int nextmode = 0;   // nextmode becomes gamemode after next map load
7 VAR(gamemode, 1, 0, 0);
8 VAR(nextGameMode, 1, 0, 0);
9 VARP(modeacronyms, 0, 1, 1);
10 
11 flaginfo flaginfos[2];
12 
mode(int * n)13 void mode(int *n)
14 {
15     nextmode = *n;
16     nextGameMode = nextmode;
17     if(m_mp(*n) || !multiplayer()) addmsg(SV_GAMEMODE, "ri", *n);
18 }
19 COMMAND(mode, "i");
20 
21 bool intermission = false;
22 int arenaintermission = 0;
23 struct serverstate servstate = { 0 };
24 
25 playerent *player1 = newplayerent();          // our client
26 vector<playerent *> players;                  // other clients
27 
28 int lastmillis = 0, totalmillis = 0, nextmillis = 0;
29 int lasthit = 0;
30 int curtime = 0;
31 string clientmap = "";
32 int spawnpermission = SP_WRONGMAP;
33 
getclientmap()34 char *getclientmap() { return clientmap; }
35 
getclientmode()36 int getclientmode() { return gamemode; }
37 
38 extern bool sendmapidenttoserver;
39 
setskin(playerent * pl,int skin,int team)40 void setskin(playerent *pl, int skin, int team)
41 {
42     if(!pl) return;
43     pl->setskin(team, skin);
44 }
45 
46 extern char *global_name;
47 
duplicatename(playerent * d,char * name=NULL)48 bool duplicatename(playerent *d, char *name = NULL)
49 {
50     if(!name) name = d->name;
51     if(d!=player1 && !strcmp(name, player1->name)) return true;
52     if(!strcmp(name, "you")) return true;
53     loopv(players) if(players[i] && d!=players[i] && !strcmp(name, players[i]->name)) return true;
54     global_name = player1->name; // this certainly is not the best place to put this
55     return false;
56 }
57 
colorname(playerent * d,char * name,const char * prefix)58 char *colorname(playerent *d, char *name, const char *prefix)
59 {
60     if(!name) name = d->name;
61     if(name[0] && !duplicatename(d, name)) return name;
62     static string cname[4];
63     static int num = 0;
64     num = (num + 1) % 4;
65     formatstring(cname[num])("%s%s \fs\f6(%d)\fr", prefix, name, d->clientnum);
66     return cname[num];
67 }
68 
colorping(int ping)69 char *colorping(int ping)
70 {
71     static string cping;
72     formatstring(cping)("\fs\f%d%d\fr", ping <= 500 ? 0 : ping <= 1000 ? 2 : 3, ping);
73     return cping;
74 }
75 
colorpj(int pj)76 char *colorpj(int pj)
77 {
78     static string cpj;
79     formatstring(cpj)("\fs\f%d%d\fr", pj <= 90 ? 0 : pj <= 170 ? 2 : 3, pj);
80     return cpj;
81 }
82 
highlight(const char * text)83 const char *highlight(const char *text)
84 {
85     static char result[MAXTRANS + 10];
86     const char *marker = getalias("HIGHLIGHT"), *sep = " ,;:!\"'";
87     if(!marker || !strstr(text, player1->name)) return text;
88     filterrichtext(result, marker);
89     defformatstring(subst)("\fs%s%s\fr", result, player1->name);
90     char *temp = newstring(text);
91     char *s = strtok(temp, sep), *l = temp, *c, *r = result;
92     result[0] = '\0';
93     while(s)
94     {
95         if(!strcmp(s, player1->name))
96         {
97             if(MAXTRANS - strlen(result) > strlen(subst) + (s - l))
98             {
99                 for(c = l; c < s; c++) *r++ = text[c - temp];
100                 *r = '\0';
101                 strcat(r, subst);
102             }
103             l = s + strlen(s);
104         }
105         s = strtok(NULL, sep);
106     }
107     if(MAXTRANS - strlen(result) > strlen(text) - (l - temp)) strcat(result, text + (l - temp));
108     delete[] temp;
109     return *result ? result : text;
110 }
111 
ignore(int * cn)112 void ignore(int *cn)
113 {
114     playerent *d = getclient(*cn);
115     if(d && d != player1) d->ignored = true;
116 }
117 
listignored()118 void listignored()
119 {
120     string pl;
121     pl[0] = '\0';
122     loopv(players) if(players[i] && players[i]->ignored) concatformatstring(pl, ", %s", colorname(players[i]));
123     if(*pl) conoutf(_("ignored players: %s"), pl + 2);
124     else conoutf(_("no players were ignored."));
125 }
126 
clearignored(int * cn)127 void clearignored(int *cn)
128 {
129     loopv(players) if(players[i] && (*cn < 0 || *cn == i)) players[i]->ignored = false;
130 }
131 
muteplayer(int * cn)132 void muteplayer(int *cn)
133 {
134     playerent *d = getclient(*cn);
135     if(d && d != player1) d->muted = true;
136 }
137 
listmuted()138 void listmuted()
139 {
140     string pl;
141     pl[0] = '\0';
142     loopv(players) if(players[i] && players[i]->muted) concatformatstring(pl, ", %s", colorname(players[i]));
143     if(*pl) conoutf(_("muted players: %s"), pl + 2);
144     else conoutf(_("no players were muted."));
145 }
146 
clearmuted(char * cn)147 void clearmuted(char *cn)
148 {
149     loopv(players) if(players[i] && (*cn < 0 || *cn == i)) players[i]->muted = false;
150 }
151 
152 COMMAND(ignore, "i");
153 COMMAND(listignored, "");
154 COMMAND(clearignored, "i");
155 COMMAND(muteplayer, "i");
156 COMMAND(listmuted, "");
157 COMMAND(clearmuted, "i");
158 
newname(const char * name)159 void newname(const char *name)
160 {
161     if(name[0])
162     {
163         string tmpname;
164         filtertext(tmpname, name, 0, MAXNAMELEN);
165         if(identexists("onNameChange"))
166         {
167             defformatstring(onnamechange)("onNameChange %d \"%s\"", player1->clientnum, tmpname);
168             execute(onnamechange);
169         }
170         copystring(player1->name, tmpname);//12345678901234//
171         if(!player1->name[0]) copystring(player1->name, "unarmed");
172         updateclientname(player1);
173         addmsg(SV_SWITCHNAME, "rs", player1->name);
174     }
175     else conoutf(_("your name is: %s"), player1->name);
176     //alias(_("curname"), player1->name); // WTF? stef went crazy - this isn't something to translate either.
177     alias("curname", player1->name);
178 }
179 
teamatoi(const char * name)180 int teamatoi(const char *name)
181 {
182     string uc;
183     strtoupper(uc, name);
184     loopi(TEAM_NUM) if(!strcmp(teamnames[i], uc)) return i;
185     return -1;
186 }
187 
newteam(char * name)188 void newteam(char *name)
189 {
190     if(*name)
191     {
192         int nt = teamatoi(name);
193         if(nt == player1->team) return; // same team
194         if(!team_isvalid(nt)) { conoutf(_("%c3\"%s\" is not a valid team name (try CLA, RVSF or SPECTATOR)"), CC, name); return; }
195         if(team_isspect(nt))
196         {
197             if(player1->state != CS_DEAD) { conoutf(_("you'll need to be in a \"dead\" state to become a spectator")); return; }
198             if(!multiplayer()) { conoutf(_("you cannot spectate in singleplayer")); return; }
199         }
200         if(player1->state == CS_EDITING) conoutf(_("you can't change team while editing"));
201         else addmsg(SV_SWITCHTEAM, "ri", nt);
202     }
203     else conoutf(_("your team is: %s"), team_string(player1->team));
204 }
205 
benchme()206 void benchme()
207 {
208     if(team_isactive(player1->team) && servstate.mastermode == MM_MATCH)
209         addmsg(SV_SWITCHTEAM, "ri", team_tospec(player1->team));
210 }
211 
_setskin(int s,int t)212 int _setskin(int s, int t)
213 {
214     setskin(player1, s, t);
215     addmsg(SV_SWITCHSKIN, "rii", player1->skin(0), player1->skin(1));
216     return player1->skin(t);
217 }
218 
219 COMMANDF(skin_cla, "i", (int *s) { intret(_setskin(*s, TEAM_CLA)); });
220 COMMANDF(skin_rvsf, "i", (int *s) { intret(_setskin(*s, TEAM_RVSF)); });
221 COMMANDF(skin, "i", (int *s) { intret(_setskin(*s, player1->team)); });
222 
curmodeattr(char * attr)223 void curmodeattr(char *attr)
224 {
225     if(!strcmp(attr, "team")) { intret(m_teammode); return; }
226     else if(!strcmp(attr, "arena")) { intret(m_arena); return; }
227     else if(!strcmp(attr, "flag")) { intret(m_flags); return; }
228     else if(!strcmp(attr, "bot")) { intret(m_botmode); return; }
229     intret(0);
230 }
231 
232 COMMANDN(team, newteam, "s");
233 COMMANDN(name, newname, "s");
234 COMMAND(benchme, "");
235 COMMANDF(isclient, "i", (int *cn) { intret(getclient(*cn) != NULL ? 1 : 0); } );
236 COMMANDF(curmastermode, "", (void) { intret(servstate.mastermode); });
237 COMMANDF(curautoteam, "", (void) { intret(servstate.autoteam); });
238 COMMAND(curmodeattr, "s");
239 COMMANDF(curmap, "i", (int *cleaned) { result(*cleaned ? behindpath(getclientmap()) : getclientmap()); });
240 COMMANDF(curplayers, "", (void) { intret(players.length() + 1); });
241 VARP(showscoresondeath, 0, 1, 1);
242 VARP(autoscreenshot, 0, 0, 1);
243 
stopdemo()244 void stopdemo()
245 {
246     if(watchingdemo) enddemoplayback();
247     else conoutf(_("not playing a demo"));
248 }
249 COMMAND(stopdemo, "");
250 
251 // macros for playerinfo() & teaminfo(). Use this to replace pstats_xxx ?
252 #define ATTR_INT(name, attribute)    if(!strcmp(attr, #name)) { intret(attribute); return; }
253 #define ATTR_FLOAT(name, attribute)  if(!strcmp(attr, #name)) { floatret(attribute); return; }
254 #define ATTR_STR(name, attribute)    if(!strcmp(attr, #name)) { result(attribute); return; }
255 
playerinfo(int * cn,const char * attr)256 void playerinfo(int *cn, const char *attr)
257 {
258     if(!*attr || !attr) return;
259 
260     int clientnum = *cn; // get player clientnum
261     playerent *p = clientnum < 0 ? player1 : getclient(clientnum);
262     if(!p)
263     {
264         if(!m_botmode && multiplayer(false)) // bot clientnums are still glitchy, causing this message to sometimes appear in offline/singleplayer when it shouldn't??? -Bukz 2012may
265             conoutf("invalid clientnum cn: %s attr: %s", cn, attr);
266         return;
267     }
268 
269     if(p == player1)
270     {
271         ATTR_INT(magcontent, p->weaponsel->mag);
272         ATTR_INT(ammo, p->weaponsel->ammo);
273         ATTR_INT(primary, p->primary);
274         ATTR_INT(curweapon, p->weaponsel->type);
275         ATTR_INT(nextprimary, p->nextprimary);
276     }
277 
278     if(p == player1
279         || (team_base(p->team) == team_base(player1->team) && m_teammode)
280         || player1->team == TEAM_SPECT
281         || m_coop)
282     {
283         ATTR_INT(health, p->health);
284         ATTR_INT(armour, p->armour);
285         ATTR_INT(attacking, p->attacking);
286         ATTR_INT(scoping, p->scoping);
287         ATTR_FLOAT(x, p->o.x);
288         ATTR_FLOAT(y, p->o.y);
289         ATTR_FLOAT(z, p->o.z);
290     }
291     ATTR_STR(name, p->name);
292     ATTR_INT(team, p->team);
293     ATTR_INT(ping, p->ping);
294     ATTR_INT(pj, p->plag);
295     ATTR_INT(state, p->state);
296     ATTR_INT(role, p->clientrole);
297     ATTR_INT(frags, p->frags);
298     ATTR_INT(flags, p->flagscore);
299     ATTR_INT(points, p->points);
300     ATTR_INT(deaths, p->deaths);
301     ATTR_INT(tks, p->tks);
302     ATTR_INT(alive, p->state == CS_ALIVE ? 1 : 0);
303     ATTR_INT(spect, p->team == TEAM_SPECT || p->spectatemode == SM_FLY ? 1 : 0);
304     ATTR_INT(cn, p->clientnum); // only useful to get player1's client number.
305     ATTR_INT(skin_cla, p->skin(TEAM_CLA));
306     ATTR_INT(skin_rvsf, p->skin(TEAM_RVSF));
307     ATTR_INT(skin, p->skin(player1->team));
308 
309     string addrstr = "";
310     uint2ip(p->address, addr);
311     if(addr[3] != 0 || player1->clientrole==CR_ADMIN)
312         formatstring(addrstr)("%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); // full IP
313     else formatstring(addrstr)("%d.%d.%d.x", addr[0], addr[1], addr[2]); // censored IP
314     ATTR_STR(ip, addrstr);
315 
316     conoutf("invalid attribute: %s", attr);
317 }
318 
playerinfolocal(const char * attr)319 void playerinfolocal(const char *attr)
320 {
321     int cn = -1;
322     playerinfo(&cn, attr);
323 }
324 
325 COMMANDN(player, playerinfo, "is");
326 COMMANDN(player1, playerinfolocal, "s");
327 
teaminfo(const char * team,const char * attr)328 void teaminfo(const char *team, const char *attr)
329 {
330     if(!team || !attr || !m_teammode) return;
331     int t = teamatoi(team); // get player clientnum
332     if(!team_isactive(t))
333     {
334         conoutf("invalid team: %s", team);
335         return;
336     }
337     int t_flags = 0;
338     int t_frags = 0;
339     int t_deaths = 0;
340     int t_points = 0;
341 
342     string teammembers = "", tmp;
343 
344     loopv(players) if(players[i] && players[i]->team == t)
345     {
346         t_frags += players[i]->frags;
347         t_deaths += players[i]->deaths;
348         t_points += players[i]->points;
349         t_flags += players[i]->flagscore;
350         sprintf(tmp, "%s%d ", teammembers, players[i]->clientnum);
351         concatstring(teammembers, tmp);
352     }
353 
354     loopv(discscores) if(discscores[i].team == t)
355     {
356         t_frags += discscores[i].frags;
357         t_deaths += discscores[i].deaths;
358         t_points += discscores[i].points;
359         t_flags += discscores[i].flags;
360     }
361 
362     if(player1->team == t)
363     {
364         t_frags += player1->frags;
365         t_deaths += player1->deaths;
366         t_points += player1->points;
367         t_flags += player1->flagscore;
368         sprintf(tmp, "%s%d ", teammembers, player1->clientnum);
369         concatstring(teammembers, tmp);
370     }
371 
372     ATTR_INT(flags, t_flags);
373     ATTR_INT(frags, t_frags);
374     ATTR_INT(deaths, t_deaths);
375     ATTR_INT(points, t_points);
376     ATTR_STR(name, team_string(t));
377     ATTR_STR(players, teammembers);
378     conoutf("invalid attribute: %s", attr);
379 }
380 
381 COMMAND(teaminfo, "ss");
382 
deathstate(playerent * pl)383 void deathstate(playerent *pl)
384 {
385     pl->state = CS_DEAD;
386     pl->spectatemode = SM_DEATHCAM;
387     pl->respawnoffset = pl->lastpain = lastmillis;
388     pl->move = pl->strafe = 0;
389     pl->pitch = pl->roll = 0;
390     pl->attacking = false;
391     pl->weaponsel->onownerdies();
392 
393     if(pl==player1)
394     {
395         if(showscoresondeath) showscores(true);
396         setscope(false);
397         setburst(false);
398         if(editmode) toggleedit(true);
399         damageblend(-1);
400         if(pl->team == TEAM_SPECT) spectatemode(SM_FLY);
401         else if(team_isspect(pl->team)) spectatemode(SM_FOLLOW1ST);
402         if(pl->spectatemode == SM_DEATHCAM) player1->followplayercn = FPCN_DEATHCAM;
403     }
404     else pl->resetinterp();
405 }
406 
spawnstate(playerent * d)407 void spawnstate(playerent *d)              // reset player state not persistent accross spawns
408 {
409     d->respawn();
410     d->spawnstate(gamemode);
411     if(d==player1)
412     {
413         setscope(false);
414         setburst(false);
415     }
416     if(d->deaths==0) d->resetstats();
417 }
418 
newplayerent()419 playerent *newplayerent()                 // create a new blank player
420 {
421     playerent *d = new playerent;
422     d->lastupdate = totalmillis;
423     setskin(d, rnd(6));
424     weapon::equipplayer(d); // flowtron : avoid overwriting d->spawnstate(gamemode) stuff from the following line (this used to be called afterwards)
425     spawnstate(d);
426     return d;
427 }
428 
newbotent()429 botent *newbotent()                 // create a new blank player
430 {
431     botent *d = new botent;
432     d->lastupdate = totalmillis;
433     setskin(d, rnd(6));
434     weapon::equipplayer(d);
435     spawnstate(d); // move like above
436     int nextcn = 0;
437     bool lukin = true;
438     while(lukin)
439     {
440         bool used = nextcn==getclientnum();
441         loopv(players) if(!used && players[i]) if(players[i]->clientnum==nextcn) used = true;
442         if(!used) lukin = false; else nextcn++;
443     }
444     loopv(players) if(i!=getclientnum() && !players[i])
445     {
446         players[i] = d;
447         d->clientnum = nextcn;
448         return d;
449     }
450     if(players.length()==getclientnum()) players.add(NULL);
451     d->clientnum = nextcn;
452     players.add(d);
453     return d;
454 }
455 
freebotent(botent * d)456 void freebotent(botent *d)
457 {
458     loopv(players) if(players[i]==d)
459     {
460         DELETEP(players[i]);
461         players.remove(i);
462     }
463 }
464 
465 VAR(lastpm, 1, -1, 0);
zapplayer(playerent * & d)466 void zapplayer(playerent *&d)
467 {
468     if(d && d->clientnum == lastpm) lastpm = -1;
469     DELETEP(d);
470 }
471 
movelocalplayer()472 void movelocalplayer()
473 {
474     if(player1->state==CS_DEAD && !player1->allowmove())
475     {
476         if(lastmillis-player1->lastpain<2000)
477         {
478             player1->move = player1->strafe = 0;
479             moveplayer(player1, 10, false);
480         }
481     }
482     else if(!intermission)
483     {
484         moveplayer(player1, 10, true);
485         checkitems(player1);
486     }
487 }
488 
489 // use physics to extrapolate player position
490 VARP(smoothmove, 0, 75, 100);
491 VARP(smoothdist, 0, 8, 16);
492 
predictplayer(playerent * d,bool move)493 void predictplayer(playerent *d, bool move)
494 {
495     d->o = d->newpos;
496     d->o.z += d->eyeheight;
497     d->yaw = d->newyaw;
498     d->pitch = d->newpitch;
499     if(move)
500     {
501         moveplayer(d, 1, false);
502         d->newpos = d->o;
503         d->newpos.z -= d->eyeheight;
504     }
505     float k = 1.0f - float(lastmillis - d->smoothmillis)/smoothmove;
506     if(k>0)
507     {
508         d->o.add(vec(d->deltapos).mul(k));
509         d->yaw += d->deltayaw*k;
510         if(d->yaw<0) d->yaw += 360;
511         else if(d->yaw>=360) d->yaw -= 360;
512         d->pitch += d->deltapitch*k;
513     }
514 }
515 
moveotherplayers()516 void moveotherplayers()
517 {
518     loopv(players) if(players[i] && players[i]->type==ENT_PLAYER)
519     {
520         playerent *d = players[i];
521         const int lagtime = totalmillis-d->lastupdate;
522         if(!lagtime || intermission) continue;
523         else if(lagtime>1000 && d->state==CS_ALIVE)
524         {
525             d->state = CS_LAGGED;
526             continue;
527         }
528         if(d->state==CS_ALIVE || d->state==CS_EDITING)
529         {
530             if(smoothmove && d->smoothmillis>0) predictplayer(d, true);
531             else moveplayer(d, 1, false);
532         }
533         else if(d->state==CS_DEAD && lastmillis-d->lastpain<2000) moveplayer(d, 1, true);
534     }
535 }
536 
537 
showhudtimer(int maxsecs,int startmillis,const char * msg,bool flash)538 bool showhudtimer(int maxsecs, int startmillis, const char *msg, bool flash)
539 {
540     static string str = "";
541     static int tickstart = 0, curticks = -1, maxticks = -1;
542     int nextticks = (lastmillis - startmillis) / 200;
543     if(tickstart!=startmillis || maxticks != 5*maxsecs)
544     {
545         tickstart = startmillis;
546         maxticks = 5*maxsecs;
547         curticks = -1;
548         copystring(str, "\f3");
549     }
550     if(curticks >= maxticks) return false;
551     nextticks = min(nextticks, maxticks);
552     while(curticks < nextticks)
553     {
554         if(++curticks%5) concatstring(str, ".");
555         else
556         {
557             defformatstring(sec)("%d", maxsecs - (curticks/5));
558             concatstring(str, sec);
559         }
560     }
561     if(nextticks < maxticks) hudeditf(HUDMSG_TIMER|HUDMSG_OVERWRITE, flash ? str : str+2);
562     else hudeditf(HUDMSG_TIMER, msg);
563     return true;
564 }
565 
566 int lastspawnattempt = 0;
567 
showrespawntimer()568 void showrespawntimer()
569 {
570     if(intermission || spawnpermission > SP_OK_NUM) return;
571     if(m_arena)
572     {
573         if(!arenaintermission) return;
574         showhudtimer(5, arenaintermission, "FIGHT!", lastspawnattempt >= arenaintermission && lastmillis < lastspawnattempt+100);
575     }
576     else if(player1->state==CS_DEAD && m_flags && (!player1->isspectating() || player1->spectatemode==SM_DEATHCAM))
577     {
578         int secs = 5;
579         showhudtimer(secs, player1->respawnoffset, "READY!", lastspawnattempt >= arenaintermission && lastmillis < lastspawnattempt+100);
580     }
581 }
582 
583 struct scriptsleep { int wait, millis; char *cmd; bool persist; };
584 vector<scriptsleep> sleeps;
585 
addsleep(int msec,const char * cmd,bool persist)586 void addsleep(int msec, const char *cmd, bool persist)
587 {
588     scriptsleep &s = sleeps.add();
589     s.wait = max(msec, 1);
590     s.millis = lastmillis;
591     s.cmd = newstring(cmd);
592     s.persist = persist;
593 }
594 
addsleep_(int * msec,char * cmd,int * persist)595 void addsleep_(int *msec, char *cmd, int *persist)
596 {
597     addsleep(*msec, cmd, *persist != 0);
598 }
599 
resetsleep(bool force)600 void resetsleep(bool force)
601 {
602     loopv(sleeps) if(!sleeps[i].persist || force)
603     {
604         DELETEA(sleeps[i].cmd);
605         sleeps.remove(i);
606     }
607 }
608 
609 COMMANDN(sleep, addsleep_, "isi");
610 COMMANDF(resetsleeps, "", (void) { resetsleep(true); });
611 
updateworld(int curtime,int lastmillis)612 void updateworld(int curtime, int lastmillis)        // main game update loop
613 {
614     // process command sleeps
615     loopv(sleeps)
616     {
617         if(lastmillis - sleeps[i].millis >= sleeps[i].wait)
618         {
619             char *cmd = sleeps[i].cmd;
620             sleeps[i].cmd = NULL;
621             execute(cmd);
622             delete[] cmd;
623             if(sleeps[i].cmd || !sleeps.inrange(i)) break;
624             sleeps.remove(i--);
625         }
626     }
627 
628     syncentchanges();
629     physicsframe();
630     checkweaponstate();
631     if(getclientnum()>=0) shoot(player1, worldpos);     // only shoot when connected to server
632     movebounceents();
633     moveotherplayers();
634     gets2c();
635     showrespawntimer();
636 
637     // Added by Rick: let bots think
638     if(m_botmode) BotManager.Think();
639 
640     movelocalplayer();
641     c2sinfo(player1);   // do this last, to reduce the effective frame lag
642 }
643 
644 #define SECURESPAWNDIST 15
645 int spawncycle = -1;
646 int fixspawn = 2;
647 
648 // returns -1 for a free place, else dist to the nearest enemy
nearestenemy(vec place,int team)649 float nearestenemy(vec place, int team)
650 {
651     float nearestenemydist = -1;
652     loopv(players)
653     {
654         playerent *other = players[i];
655         if(!other || isteam(team, other->team)) continue;
656         float dist = place.dist(other->o);
657         if(dist < nearestenemydist || nearestenemydist == -1) nearestenemydist = dist;
658     }
659     if(nearestenemydist >= SECURESPAWNDIST || nearestenemydist < 0) return -1;
660     else return nearestenemydist;
661 }
662 
findplayerstart(playerent * d,bool mapcenter,int arenaspawn)663 void findplayerstart(playerent *d, bool mapcenter, int arenaspawn)
664 {
665     int r = fixspawn-->0 ? 4 : rnd(10)+1;
666     entity *e = NULL;
667     if(!mapcenter)
668     {
669         int type = m_teammode ? team_base(d->team) : 100;
670         if(m_arena && arenaspawn >= 0)
671         {
672             int x = -1;
673             loopi(arenaspawn + 1) x = findentity(PLAYERSTART, x+1, type);
674             if(x >= 0) e = &ents[x];
675         }
676         else if((m_teammode || m_arena) && !m_ktf) // ktf uses ffa spawns
677         {
678             loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle+1, type);
679             if(spawncycle >= 0) e = &ents[spawncycle];
680         }
681         else
682         {
683             float bestdist = -1;
684 
685             loopi(r)
686             {
687                 // 2013jun28:lucas: SKB suggested to use FFA spawns only in FFA modes, which seems reasonable.
688                 spawncycle = /*m_ktf && */numspawn[2] > 4 ? findentity(PLAYERSTART, spawncycle+1, 100) : findentity(PLAYERSTART, spawncycle+1);
689                 if(spawncycle < 0) continue;
690                 float dist = nearestenemy(vec(ents[spawncycle].x, ents[spawncycle].y, ents[spawncycle].z), d->team);
691                 if(!e || dist < 0 || (bestdist >= 0 && dist > bestdist)) { e = &ents[spawncycle]; bestdist = dist; }
692             }
693         }
694     }
695 
696     if(e)
697     {
698         d->o.x = e->x;
699         d->o.y = e->y;
700         d->o.z = e->z;
701         d->yaw = e->attr1;
702         d->pitch = 0;
703         d->roll = 0;
704     }
705     else
706     {
707         d->o.x = d->o.y = (float)ssize/2;
708         d->o.z = 4;
709     }
710     entinmap(d);
711     if(identexists("onSpawn")/* && (m_teammode && d->team == player1->team)*/)
712     {
713         defformatstring(onspawn)("onSpawn %d", d->clientnum);
714         execute(onspawn);
715     }
716 }
717 
spawnplayer(playerent * d)718 void spawnplayer(playerent *d)
719 {
720     d->respawn();
721     d->spawnstate(gamemode);
722     d->state = (d==player1 && editmode) ? CS_EDITING : CS_ALIVE;
723     findplayerstart(d);
724 }
725 
respawnself()726 void respawnself()
727 {
728     if( m_mp(gamemode) ) addmsg(SV_TRYSPAWN, "r");
729     else
730     {
731         showscores(false);
732         setscope(false);
733         setburst(false);
734         lasthit = 0;
735         spawnplayer(player1);
736         player1->lifesequence++;
737         player1->weaponswitch(player1->primweap);
738         player1->weaponchanging -= player1->weapons[player1->gunselect]->weaponchangetime/2; // 2011jan16:ft: for a little no-shoot after spawn
739     }
740 }
741 
742 extern int checkarea(int maplayout_factor, char *maplayout);
743 extern int MA;
744 extern float Mh;
745 
bad_map()746 bool bad_map() // this function makes a pair with good_map from clients2c
747 {
748     return (gamemode != GMODE_COOPEDIT && ( Mh >= MAXMHEIGHT || MA >= MAXMAREA ));
749 }
750 
spawn_message()751 inline const char * spawn_message()
752 {
753     if (spawnpermission == SP_WRONGMAP)
754     {
755         // Don't use "/n" within these messages. If you do, the words won't align to the middle of the screen.
756         if (securemapcheck(getclientmap()))
757         return "3The server will NOT allow spawning or getmap!";   // Also see client.cpp which has a conoutf message
758         else return "3You must be on the correct map to spawn. Type /getmap to download it.";
759     }
760     else if (m_coop) return "3Type /getmap or send a map and vote for it to start co-op edit.";
761     else if (multiplayer(false)) return "4Awaiting permission to spawn. \f2DON'T PANIC!";
762     else return ""; // theres no waiting for permission in sp
763     // Despite its many glaring (and occasionally fatal) inaccuracies, AssaultCube itself has outsold the
764     // Encyclopedia Galactica because it is slightly cheaper, and because it has the words "Don't Panic"
765     // in large, friendly letters.
766 }
767 
768 int waiting_permission = 0;
769 
tryrespawn()770 bool tryrespawn()
771 {
772     if ( m_mp(gamemode) && multiplayer(false) && bad_map() )
773     {
774         hudoutf("This map is not supported in multiplayer. Read the docs about map quality/dimensions.");
775     }
776     else if(spawnpermission > SP_OK_NUM)
777     {
778         hudeditf(HUDMSG_TIMER, "\f%s", spawn_message());
779     }
780     else if(player1->state==CS_DEAD || player1->state==CS_SPECTATE)
781     {
782         if(team_isspect(player1->team))
783         {
784             respawnself();
785             return true;
786         }
787         else
788         {
789             int respawnmillis = player1->respawnoffset+(m_arena ? 0 : (m_flags ? 5000 : 2000));
790             if(lastmillis>respawnmillis)
791             {
792                 player1->attacking = false;
793                 if(m_arena)
794                 {
795                     if(!arenaintermission) hudeditf(HUDMSG_TIMER, "waiting for new round to start...");
796                     else lastspawnattempt = lastmillis;
797                     return false;
798                 }
799                 if (lastmillis > waiting_permission)
800                 {
801                     waiting_permission = lastmillis + 1000;
802                     respawnself();
803                 }
804                 else hudeditf(HUDMSG_TIMER, "\f%s", spawn_message());
805                 return true;
806             }
807             else lastspawnattempt = lastmillis;
808         }
809     }
810     return false;
811 }
812 
813 VARP(hitsound, 0, 0, 2);
814 
815 // client kill messages
setkillmessage(int gun,bool gib,const char * message)816 void setkillmessage(int gun, bool gib, const char *message)
817 {
818     if(!message || !*message)
819     {
820         result(killmessage(gun, gib));
821         return;
822     }
823     if(gun < 0 || gun >= NUMGUNS)
824     {
825         conoutf("invalid gun specified");
826         return;
827     }
828     copystring(killmessages[gib?1:0][gun], message, sizeof(killmessages[gib?1:0][gun]));
829 }
830 
831 COMMANDF(fragmessage, "is", (int *gun, const char *message) { setkillmessage(*gun, false, message); });
832 COMMANDF(gibmessage, "is", (int *gun, const char *message) { setkillmessage(*gun, true, message); });
833 
burstshots(int gun,int shots)834 void burstshots(int gun, int shots)
835 {
836     // args are passed as strings to differentiate 2 cases : shots_str == "0" or shots_str is empty (not specified from cubescript).
837     if(gun >= 0 && gun < NUMGUNS && guns[gun].isauto)
838     {
839         if(shots >= 0) burstshotssettings[gun] = min(shots, (guns[gun].magsize-1));
840         else intret(burstshotssettings[gun]);
841     }
842     else conoutf(_("invalid gun specified"));
843 }
844 
845 COMMANDF(burstshots, "ii", (int *g, int *s) { burstshots(*g, *s); });
846 
847 // damage arriving from the network, monsters, yourself, all ends up here.
848 
dodamage(int damage,playerent * pl,playerent * actor,int gun,bool gib,bool local)849 void dodamage(int damage, playerent *pl, playerent *actor, int gun, bool gib, bool local)
850 {
851     if(pl->state != CS_ALIVE || intermission) return;
852     pl->respawnoffset = pl->lastpain = lastmillis;
853     // could the author of the FIXME below please elaborate what's to fix?! (ft:2011mar28)
854     // I suppose someone wanted to play the hitsound for player1 or spectated player (lucas:2011may22)
855     playerent *h = player1->isspectating() && player1->followplayercn >= 0 && (player1->spectatemode == SM_FOLLOW1ST || player1->spectatemode == SM_FOLLOW3RD || player1->spectatemode == SM_FOLLOW3RD_TRANSPARENT) ? getclient(player1->followplayercn) : NULL;
856     if(!h) h = player1;
857     if(identexists("onHit"))
858     {
859         defformatstring(o)("onHit %d %d %d %d %d", actor->clientnum, pl->clientnum, damage, gun, gib ? 1 : 0);
860         execute(o);
861     }
862     if(actor==h && pl!=actor)
863     {
864         if( (hitsound == 1 || (hitsound && h != player1) ) && lasthit != lastmillis) audiomgr.playsound(S_HITSOUND, SP_HIGH);
865         lasthit = lastmillis;
866     }
867 
868     if (pl != player1)
869     {
870         damageeffect(damage, pl);
871         audiomgr.playsound(S_PAIN1+rnd(5), pl);
872     }
873 
874     if(local) damage = pl->dodamage(damage, gun);
875     else if(actor==player1) return;
876 
877     if(pl==player1)
878     {
879         updatedmgindicator(actor->o);
880         damageblend(damage);
881         pl->damageroll(damage);
882     }
883 
884     if(pl->health<=0) { if(local) dokill(pl, actor, gib, gun >= 0 ? gun : actor->weaponsel->type); }
885     else if(pl==player1) audiomgr.playsound(S_PAIN6, SP_HIGH);
886     else audiomgr.playsound(S_PAIN1+rnd(5), pl);
887 }
888 
dokill(playerent * pl,playerent * act,bool gib,int gun)889 void dokill(playerent *pl, playerent *act, bool gib, int gun)
890 {
891     if(pl->state!=CS_ALIVE || intermission) return;
892 
893     if(identexists("onKill"))
894     {
895         defformatstring(killevent)("onKill %d %d %d %d", act->clientnum, pl->clientnum, gun, gib ? 1 : 0);
896         execute(killevent);
897     }
898 
899     string pname, aname, death;
900     copystring(pname, pl==player1 ? "you" : colorname(pl));
901     copystring(aname, act==player1 ? "you" : colorname(act));
902     copystring(death, killmessage(gun, gib));
903     void (*outf)(const char *s, ...) = (pl == player1 || act == player1) ? hudoutf : conoutf;
904 
905     if(pl==act)
906     {
907         outf("\f2%s suicided%s", pname, pl==player1 ? "!" : "");
908     }
909     else if(isteam(pl->team, act->team))
910     {
911         if(pl==player1) outf("\f2you were %s by teammate %s", death, aname);
912         else outf("%s%s %s teammate %s", act==player1 ? "\f3" : "\f2", aname, death, pname);
913     }
914     else
915     {
916         if(pl==player1) outf("\f2you were %s by %s", death, aname);
917         else outf("\f2%s %s %s", aname, death, pname);
918     }
919 
920     if(pl == act || isteam(pl->team, act->team))
921     {
922         if(pl != act) act->tks++;
923         if(!m_mp(gamemode)) act->frags--;
924     }
925     else if(!m_mp(gamemode)) act->frags += ( gib && gun != GUN_GRENADE && gun != GUN_SHOTGUN) ? 2 : 1;
926 
927     if(gib)
928     {
929         if(pl!=act && gun == GUN_SNIPER) audiomgr.playsound(S_HEADSHOT, SP_LOW);
930         addgib(pl);
931     }
932 
933     deathstate(pl);
934     pl->deaths++;
935     audiomgr.playsound(S_DIE1+rnd(2), pl);
936 }
937 
pstat_weap(int * cn)938 void pstat_weap(int *cn)
939 {
940     string weapstring = "";
941     playerent *pl = getclient(*cn);
942     if(pl) loopi(NUMGUNS) concatformatstring(weapstring, "%s%d %d", strlen(weapstring) ? " " : "", pl->pstatshots[i], pl->pstatdamage[i]);
943     result(weapstring);
944 }
945 
946 COMMAND(pstat_weap, "i");
947 
948 VAR(minutesremaining, 1, 0, 0);
949 VAR(gametimecurrent, 1, 0, 0);
950 VAR(gametimemaximum, 1, 0, 0);
951 VAR(lastgametimeupdate, 1, 0, 0);
952 
silenttimeupdate(int milliscur,int millismax)953 void silenttimeupdate(int milliscur, int millismax)
954 {
955     lastgametimeupdate = lastmillis;
956     gametimecurrent = milliscur;
957     gametimemaximum = millismax;
958     minutesremaining = (gametimemaximum - gametimecurrent + 60000 - 1) / 60000;
959 }
960 
timeupdate(int milliscur,int millismax)961 void timeupdate(int milliscur, int millismax)
962 {
963     bool display = lastmillis - lastgametimeupdate > 1000; // avoid double-output
964 
965     silenttimeupdate(milliscur, millismax);
966 
967     if(!display) return;
968     if(!minutesremaining)
969     {
970         intermission = true;
971         extern bool needsautoscreenshot;
972         if(autoscreenshot) needsautoscreenshot = true;
973         player1->attacking = false;
974         conoutf(_("intermission:"));
975         conoutf(_("game has ended!"));
976         consolescores();
977         showscores(true);
978         if(identexists("start_intermission")) execute("start_intermission");
979     }
980     else
981     {
982         extern int clockdisplay; // only output to console if no hud-clock is being shown
983         if(minutesremaining==1)
984         {
985             audiomgr.musicsuggest(M_LASTMINUTE1 + rnd(2), 70*1000, true);
986             hudoutf("1 minute left!");
987             if(identexists("onLastMin")) execute("onLastMin");
988         }
989         else if(clockdisplay==0) conoutf(_("time remaining: %d minutes"), minutesremaining);
990     }
991 }
992 
newclient(int cn)993 playerent *newclient(int cn)   // ensure valid entity
994 {
995     if(cn<0 || cn>=MAXCLIENTS)
996     {
997         neterr("clientnum");
998         return NULL;
999     }
1000     while(cn>=players.length()) players.add(NULL);
1001     playerent *d = players[cn];
1002     if(d) return d;
1003     d = newplayerent();
1004     players[cn] = d;
1005     d->clientnum = cn;
1006     return d;
1007 }
1008 
getclient(int cn)1009 playerent *getclient(int cn)   // ensure valid entity
1010 {
1011     if(cn == player1->clientnum) return player1;
1012     return players.inrange(cn) ? players[cn] : NULL;
1013 }
1014 
initclient()1015 void initclient()
1016 {
1017     newname("unarmed");
1018     player1->team = TEAM_SPECT;
1019 }
1020 
1021 entity flagdummies[2] = // in case the map does not provide flags
1022 {
1023     entity(-1, -1, -1, CTF_FLAG, 0, 0, 0, 0),
1024     entity(-1, -1, -1, CTF_FLAG, 0, 1, 0, 0)
1025 };
1026 
initflag(int i)1027 void initflag(int i)
1028 {
1029     flaginfo &f = flaginfos[i];
1030     f.flagent = &flagdummies[i];
1031     f.pos = vec(f.flagent->x, f.flagent->y, f.flagent->z);
1032     f.ack = true;
1033     f.actor = NULL;
1034     f.actor_cn = -1;
1035     f.team = i;
1036     f.state = m_ktf ? CTFF_IDLE : CTFF_INBASE;
1037 }
1038 
zapplayerflags(playerent * p)1039 void zapplayerflags(playerent *p)
1040 {
1041     loopi(2) if(flaginfos[i].state==CTFF_STOLEN && flaginfos[i].actor==p) initflag(i);
1042 }
1043 
preparectf(bool cleanonly=false)1044 void preparectf(bool cleanonly=false)
1045 {
1046     loopi(2) initflag(i);
1047     if(!cleanonly)
1048     {
1049         loopv(ents)
1050         {
1051             entity &e = ents[i];
1052             if(e.type==CTF_FLAG)
1053             {
1054                 e.spawned = true;
1055                 if(e.attr2>=2) { conoutf(_("%c3invalid ctf-flag entity (%i)"), CC, i); e.attr2 = 0; }
1056                 flaginfo &f = flaginfos[e.attr2];
1057                 f.flagent = &e;
1058                 f.pos.x = (float) e.x;
1059                 f.pos.y = (float) e.y;
1060                 f.pos.z = (float) e.z;
1061             }
1062         }
1063     }
1064 }
1065 
1066 struct gmdesc { int mode; char *desc; };
1067 vector<gmdesc> gmdescs;
1068 
gamemodedesc(int * modenr,char * desc)1069 void gamemodedesc(int *modenr, char *desc)
1070 {
1071     if(!desc) return;
1072     struct gmdesc &gd = gmdescs.add();
1073     gd.mode = *modenr;
1074     gd.desc = newstring(desc);
1075 }
1076 
1077 COMMAND(gamemodedesc, "is");
1078 
resetmap(bool mrproper)1079 void resetmap(bool mrproper)
1080 {
1081     resetsleep();
1082     resetzones();
1083     clearminimap();
1084     cleardynlights();
1085     pruneundos();
1086     changedents.setsize(0);
1087     particlereset();
1088     if(mrproper)
1089     {
1090         audiomgr.clearworldsounds();
1091         setvar("gamespeed", 100);
1092         setvar("paused", 0);
1093         setvar("fog", 180);
1094         setvar("fogcolour", 0x8099B3);
1095         setvar("shadowyaw", 45);
1096     }
1097 }
1098 
1099 int suicided = -1;
1100 extern bool good_map();
1101 extern bool item_fail;
1102 extern int MA, F2F, Ma, Hhits;
1103 extern float Mh;
1104 
1105 VARP(mapstats_hud, 0, 0, 1);
1106 
showmapstats()1107 void showmapstats()
1108 {
1109     conoutf("\f2Map Quality Stats");
1110     conoutf("  The mean height is: %.2f", Mh);
1111     if (Hhits) conoutf("  Height check is: %d", Hhits);
1112     if (MA) conoutf("  The max area is: %d (of %d)", MA, Ma);
1113     if (m_flags && F2F < 1000) conoutf("  Flag-to-flag distance is: %d", (int)fSqrt(F2F));
1114     if (item_fail) conoutf("  There are one or more items too close to each other in this map");
1115 }
1116 COMMAND(showmapstats, "");
1117 
1118 VARP(showmodedescriptions, 0, 1, 1);
1119 extern bool canceldownloads;
1120 
startmap(const char * name,bool reset)1121 void startmap(const char *name, bool reset)   // called just after a map load
1122 {
1123     canceldownloads = false;
1124     copystring(clientmap, name);
1125     sendmapidenttoserver = true;
1126     // Added by Rick
1127     if(m_botmode) BotManager.BeginMap(name);
1128     else kickallbots();
1129     // End add by Rick
1130     clearbounceents();
1131     preparectf(!m_flags);
1132     suicided = -1;
1133     spawncycle = -1;
1134     lasthit = 0;
1135     if(m_valid(gamemode) && !m_mp(gamemode)) respawnself();
1136     else findplayerstart(player1);
1137     if(good_map()==MAP_IS_BAD) conoutf(_("You cannot play in this map due to quality requisites. Please, report this incident."));
1138     if (mapstats_hud) showmapstats();
1139 
1140     if(!reset) return;
1141 
1142     player1->frags = player1->flagscore = player1->deaths = player1->lifesequence = player1->points = player1->tks = 0;
1143     loopv(players) if(players[i]) players[i]->frags = players[i]->flagscore = players[i]->deaths = players[i]->lifesequence = players[i]->points = players[i]->tks = 0;
1144     if(editmode) toggleedit(true);
1145     intermission = false;
1146     showscores(false);
1147     needscoresreorder = true;
1148     minutesremaining = -1;
1149     lastgametimeupdate = 0;
1150     arenaintermission = 0;
1151     bool noflags = (m_ctf || m_ktf) && (!numflagspawn[0] || !numflagspawn[1]);
1152     if(*clientmap) conoutf(_("game mode is \"%s\"%s"), modestr(gamemode, modeacronyms > 0), noflags ? " - \f2but there are no flag bases on this map" : "");
1153 
1154     if(showmodedescriptions && (multiplayer(false) || m_botmode))
1155     {
1156         loopv(gmdescs) if(gmdescs[i].mode == gamemode)
1157         {
1158             //conoutf(_("%c1%s"), CC, gmdescs[i].desc); // 3rd useless call to translation - these should be translated inside the cube-script-definition
1159             conoutf("\f1%s", gmdescs[i].desc);
1160         }
1161     }
1162 
1163     // run once
1164     if(firstrun)
1165     {
1166         per_idents = false;
1167         execfile("config/firstrun.cfg");
1168         per_idents = true;
1169         firstrun = false;
1170     }
1171     // execute mapstart event once
1172     const char *mapstartonce = getalias("mapstartonce");
1173     if(mapstartonce && mapstartonce[0])
1174     {
1175         addsleep(0, mapstartonce); // do this as a sleep to make sure map changes don't recurse inside a welcome packet
1176         // BTW: in v1.0.4 sleep 1 was required to make it work on initial mapload [flowtron:2010jun25]
1177         alias("mapstartonce", "");
1178     }
1179     // execute mapstart event
1180     const char *mapstartalways = getalias("mapstartalways");
1181     if(mapstartalways && mapstartalways[0])
1182     {
1183         addsleep(0, mapstartalways);
1184     }
1185 }
1186 
suicide()1187 void suicide()
1188 {
1189     if(player1->state == CS_ALIVE && suicided!=player1->lifesequence)
1190     {
1191         addmsg(SV_SUICIDE, "r");
1192         suicided = player1->lifesequence;
1193     }
1194 }
1195 
1196 COMMAND(suicide, "");
1197 
1198 // console and audio feedback
1199 
flagmsg(int flag,int message,int actor,int flagtime)1200 void flagmsg(int flag, int message, int actor, int flagtime)
1201 {
1202     static int musicplaying = -1;
1203     playerent *act = getclient(actor);
1204     if(actor != getclientnum() && !act && message != FM_RESET) return;
1205     bool own = flag == team_base(player1->team);
1206     bool neutral = team_isspect(player1->team);
1207     bool firstperson = actor == getclientnum();
1208     bool teammate = !act ? true : isteam(player1->team, act->team);
1209     bool firstpersondrop = false;
1210     defformatstring(ownerstr)("the %s", teamnames[flag]);
1211     const char *teamstr = m_ktf ? "the" : neutral ? ownerstr : own ? "your" : "the enemy";
1212     const char *flagteam = (m_ktf && !neutral) ? (teammate ? "your teammate " : "your enemy ") : "";
1213 
1214     if(identexists("onFlag"))
1215     {
1216         defformatstring(onflagevent)("onFlag %d %d %d", message, actor, flag);
1217         execute(onflagevent);
1218     }
1219 
1220     switch(message)
1221     {
1222         case FM_PICKUP:
1223             audiomgr.playsound(S_FLAGPICKUP, SP_HIGHEST);
1224             if(firstperson)
1225             {
1226                 hudoutf("\f2you have the %sflag", m_ctf ? "enemy " : "");
1227                 audiomgr.musicsuggest(M_FLAGGRAB, m_ctf ? 90*1000 : 900*1000, true);
1228                 musicplaying = flag;
1229             }
1230             else hudoutf("\f2%s%s has %s flag", flagteam, colorname(act), teamstr);
1231             break;
1232         case FM_LOST:
1233         case FM_DROP:
1234         {
1235             const char *droplost = message == FM_LOST ? "lost" : "dropped";
1236             audiomgr.playsound(S_FLAGDROP, SP_HIGHEST);
1237             if(firstperson)
1238             {
1239                 hudoutf("\f2you %s the flag", droplost);
1240                 firstpersondrop = true;
1241             }
1242             else hudoutf("\f2%s %s %s flag", colorname(act), droplost, teamstr);
1243             break;
1244         }
1245         case FM_RETURN:
1246             audiomgr.playsound(S_FLAGRETURN, SP_HIGHEST);
1247             if(firstperson) hudoutf("\f2you returned your flag");
1248             else hudoutf("\f2%s returned %s flag", colorname(act), teamstr);
1249             break;
1250         case FM_SCORE:
1251             audiomgr.playsound(S_FLAGSCORE, SP_HIGHEST);
1252             if(firstperson)
1253             {
1254                 hudoutf("\f2you scored");
1255                 if(m_ctf) firstpersondrop = true;
1256             }
1257             else hudoutf("\f2%s scored for %s", colorname(act), neutral ? teamnames[act->team] : teammate ? "your team" : "the enemy team");
1258             break;
1259         case FM_KTFSCORE:
1260         {
1261             audiomgr.playsound(S_KTFSCORE, SP_HIGHEST);
1262             const char *ta = firstperson ? "you have" : colorname(act);
1263             const char *tb = firstperson ? "" : " has";
1264             const char *tc = firstperson ? "" : flagteam;
1265             int m = flagtime / 60;
1266             if(m)
1267                 hudoutf("\f2%s%s%s kept the flag for %d minute%s %d seconds now", tc, ta, tb, m, m == 1 ? "" : "s", flagtime % 60);
1268             else
1269                 hudoutf("\f2%s%s%s kept the flag for %d seconds now", tc, ta, tb, flagtime);
1270             break;
1271         }
1272         case FM_SCOREFAIL: // sound?
1273             hudoutf("\f2%s failed to score (own team flag not taken)", firstperson ? "you" : colorname(act));
1274             break;
1275         case FM_RESET:
1276             audiomgr.playsound(S_FLAGRETURN, SP_HIGHEST);
1277             hudoutf("the server reset the flag");
1278             firstpersondrop = true;
1279             break;
1280     }
1281     if(firstpersondrop && flag == musicplaying)
1282     {
1283         audiomgr.musicfadeout(M_FLAGGRAB);
1284         musicplaying = -1;
1285     }
1286 }
1287 
dropflag()1288 void dropflag() { tryflagdrop(true); }
1289 COMMAND(dropflag, "");
1290 
votestring(int type,const char * arg1,const char * arg2,const char * arg3)1291 char *votestring(int type, const char *arg1, const char *arg2, const char *arg3)
1292 {
1293     const char *msgs[] = { "kick player %s, reason: %s", "ban player %s, reason: %s", "remove all bans", "set mastermode to %s", "%s autoteam", "force player %s to team %s", "give admin to player %s", "load map %s in mode %s%s%s", "%s demo recording for the next match", "stop demo recording", "clear all demos", "set server description to '%s'", "shuffle teams"};
1294     const char *msg = msgs[type];
1295     char *out = newstring(MAXSTRLEN);
1296     out[MAXSTRLEN] = '\0';
1297     switch(type)
1298     {
1299         case SA_KICK:
1300         case SA_BAN:
1301         case SA_FORCETEAM:
1302         case SA_GIVEADMIN:
1303         {
1304             int cn = atoi(arg1);
1305             playerent *p = getclient(cn);
1306             if(!p) break;
1307             if (type == SA_KICK || type == SA_BAN)
1308             {
1309                 string reason = "";
1310                 if(m_teammode) formatstring(reason)("%s (%d tks, ping %d)", arg2, p->tks, p->ping);
1311                 else formatstring(reason)("%s (ping %d)", arg2, p->ping);
1312                 formatstring(out)(msg, colorname(p), reason);
1313             }
1314             else if(type == SA_FORCETEAM)
1315             {
1316                 int team = atoi(arg2);
1317                 formatstring(out)(msg, colorname(p), team_isvalid(team) ? teamnames[team] : "");
1318             }
1319             else formatstring(out)(msg, colorname(p));
1320             break;
1321         }
1322         case SA_MASTERMODE:
1323             formatstring(out)(msg, mmfullname(atoi(arg1)));
1324             break;
1325         case SA_AUTOTEAM:
1326         case SA_RECORDDEMO:
1327             formatstring(out)(msg, atoi(arg1) == 0 ? "disable" : "enable");
1328             break;
1329         case SA_MAP:
1330         {
1331             int n = atoi(arg2);
1332             string timestr = "";
1333             if(arg3 && arg3[0])
1334             {
1335                 int time = atoi(arg3);
1336                 if(time > 0 && time != defaultgamelimit(n)) formatstring(timestr)(" for %d minutes", time);
1337             }
1338 
1339             if ( n >= GMODE_NUM )
1340             {
1341                 formatstring(out)(msg, arg1, modestr(n-GMODE_NUM, modeacronyms > 0)," (in the next game)", timestr);
1342             }
1343             else
1344             {
1345                 formatstring(out)(msg, arg1, modestr(n, modeacronyms > 0), "", timestr);
1346             }
1347             break;
1348         }
1349         case SA_SERVERDESC:
1350             formatstring(out)(msg, arg1);
1351             break;
1352         default:
1353             formatstring(out)(msg, arg1, arg2);
1354             break;
1355     }
1356     return out;
1357 }
1358 
newvotedisplayinfo(playerent * owner,int type,const char * arg1,const char * arg2,const char * arg3)1359 votedisplayinfo *newvotedisplayinfo(playerent *owner, int type, const char *arg1, const char *arg2, const char *arg3)
1360 {
1361     if(type < 0 || type >= SA_NUM) return NULL;
1362     votedisplayinfo *v = new votedisplayinfo();
1363     v->owner = owner;
1364     v->type = type;
1365     v->millis = totalmillis + (30+10)*1000;
1366     char *votedesc = votestring(type, arg1, arg2, arg3);
1367     copystring(v->desc, votedesc);
1368     DELETEA(votedesc);
1369     return v;
1370 }
1371 
1372 votedisplayinfo *curvote = NULL, *calledvote = NULL;
1373 
callvote(int type,const char * arg1,const char * arg2,const char * arg3)1374 void callvote(int type, const char *arg1, const char *arg2, const char *arg3)
1375 {
1376     if(calledvote) return;
1377     votedisplayinfo *v = newvotedisplayinfo(player1, type, arg1, arg2, arg3);
1378     if(v)
1379     {
1380         calledvote = v;
1381         packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1382         putint(p, SV_CALLVOTE);
1383         putint(p, v->type);
1384         switch(v->type)
1385         {
1386             case SA_KICK:
1387             case SA_BAN:
1388                 putint(p, atoi(arg1));
1389                 sendstring(arg2, p);
1390                 break;
1391             case SA_MAP:
1392                 sendstring(arg1, p);
1393                 putint(p, atoi(arg2));
1394                 putint(p, atoi(arg3));
1395                 break;
1396             case SA_SERVERDESC:
1397                 sendstring(arg1, p);
1398                 break;
1399             case SA_STOPDEMO:
1400                 // compatibility
1401                 break;
1402             case SA_REMBANS:
1403             case SA_SHUFFLETEAMS:
1404                 break;
1405             case SA_FORCETEAM:
1406                 putint(p, atoi(arg1));
1407                 putint(p, atoi(arg2));
1408                 break;
1409             default:
1410 
1411 
1412 
1413 
1414 				putint(p, atoi(arg1));
1415                 break;
1416         }
1417         sendpackettoserv(1, p.finalize());
1418         if(identexists("onCallVote"))
1419         {
1420             defformatstring(runas)("%s %d %d [%s] [%s]", "onCallVote", type, player1->clientnum, arg1, arg2);
1421             execute(runas);
1422         }
1423     }
1424     else conoutf(_("%c3invalid vote"), CC);
1425 }
1426 
scallvote(int * type,const char * arg1,const char * arg2)1427 void scallvote(int *type, const char *arg1, const char *arg2)
1428 {
1429     if(type && inmainloop)
1430     {
1431         int t = *type;
1432         switch (t)
1433         {
1434             case SA_MAP:
1435             {
1436                 //FIXME: this stupid conversion of ints to strings and back should
1437                 //  really be replaced with a saner method
1438                 char m[4];
1439                 sprintf(&m[0], "%d", nextmode);
1440                 callvote(t, arg1, &m[0], arg2);
1441                 break;
1442             }
1443             case SA_KICK:
1444             case SA_BAN:
1445             {
1446                 if (!arg1 || !isdigit(arg1[0]) || !arg2 || strlen(arg2) <= 3 || !multiplayer(false))
1447                 {
1448                     if(!multiplayer(false))
1449                         conoutf(_("%c3%s is not available in singleplayer."), CC, t == SA_BAN ? "Ban" : "Kick");
1450                     else if(arg1 && !isdigit(arg1[0])) conoutf(_("%c3invalid vote"), CC);
1451                     else conoutf(_("%c3invalid reason"), CC);
1452                     break;
1453                 }
1454             }
1455             case SA_FORCETEAM:
1456             {
1457                 int team = atoi(arg2);
1458                 if(team < 0) arg2 = (team == 0) ? "RVSF" : "CLA";
1459                 // fall through
1460             }
1461             default:
1462                 callvote(t, arg1, arg2);
1463         }
1464     }
1465 }
1466 
vote(int v)1467 int vote(int v)
1468 {
1469     if(!curvote || v < 0 || v >= VOTE_NUM) return 0;
1470     if(curvote->localplayervoted) { conoutf(_("%c3you voted already"), CC); return 0; }
1471     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1472     putint(p, SV_VOTE);
1473     putint(p, v);
1474     sendpackettoserv(1, p.finalize());
1475     if(!curvote) return 0;
1476     curvote->stats[v]++;
1477     curvote->localplayervoted = true;
1478     return 1;
1479 }
1480 
1481 VAR(votepending, 1, 0, 0);
1482 
displayvote(votedisplayinfo * v)1483 void displayvote(votedisplayinfo *v)
1484 {
1485     if(!v) return;
1486     DELETEP(curvote);
1487     curvote = v;
1488     conoutf(_("%s called a vote: %s"), v->owner ? colorname(v->owner) : "", curvote->desc);
1489     audiomgr.playsound(S_CALLVOTE, SP_HIGHEST);
1490     curvote->localplayervoted = false;
1491     votepending = 1;
1492 }
1493 
callvotesuc()1494 void callvotesuc()
1495 {
1496     if(!calledvote) return;
1497     displayvote(calledvote);
1498     calledvote = NULL;
1499     vote(VOTE_YES); // not automatically done by callvote to keep a clear sequence
1500 }
1501 
callvoteerr(int e)1502 void callvoteerr(int e)
1503 {
1504     if(e < 0 || e >= VOTEE_NUM) return;
1505     conoutf(_("%c3could not vote: %s"), CC, voteerrorstr(e));
1506     DELETEP(calledvote);
1507 }
1508 
votecount(int v)1509 void votecount(int v) { if(curvote && v >= 0 && v < VOTE_NUM) curvote->stats[v]++; }
voteresult(int v)1510 void voteresult(int v)
1511 {
1512     if(curvote && v >= 0 && v < VOTE_NUM)
1513     {
1514         curvote->result = v;
1515         curvote->millis = totalmillis + 5000;
1516         conoutf(_("vote %s"), v == VOTE_YES ? _("passed") : _("failed"));
1517         if(multiplayer(false)) audiomgr.playsound(v == VOTE_YES ? S_VOTEPASS : S_VOTEFAIL, SP_HIGH);
1518         if(identexists("onVoteEnd")) execute("onVoteEnd");
1519         votepending = 0;
1520     }
1521 }
1522 
clearvote()1523 void clearvote() { DELETEP(curvote); DELETEP(calledvote); }
1524 
1525 const char *modestrings[] =
1526 {
1527     "tdm", "coop", "dm", "lms", "ts", "ctf", "pf", "btdm", "bdm", "lss",
1528     "osok", "tosok", "bosok", "htf", "tktf", "ktf", "tpf", "tlss", "bpf", "blss", "btsurv", "btosok"
1529 };
1530 
setnext(char * mode,char * map)1531 void setnext(char *mode, char *map)
1532 {
1533     if(!multiplayer(false)) { //RR 10/12/12 - Is this the action we want?
1534         conoutf("You cannot use setnext in singleplayer.");
1535         return;
1536     }
1537     if(!mode || !map) return;
1538     loopi(GMODE_NUM)
1539     {
1540         switch(i)
1541         {
1542             case GMODE_COOPEDIT:
1543             case GMODE_BOTTEAMDEATHMATCH:
1544             case GMODE_BOTDEATHMATCH:
1545             case GMODE_BOTONESHOTONEKILL:
1546             case GMODE_BOTLSS:
1547 			case GMODE_BOTPISTOLFRENZY:
1548 			case GMODE_BOTTEAMONESHOTONKILL:
1549                 continue;
1550         }
1551         if(!strcmp(mode, modestrings[i]))
1552         {
1553             nextmode=i+GMODE_NUM;
1554 	        string nm = ""; itoa(nm, nextmode);
1555             callvote(SA_MAP, map, nm, "0");
1556             break;
1557         }
1558     }
1559 }
1560 COMMAND(setnext, "ss");
1561 
gonext(int * arg1)1562 void gonext(int *arg1)
1563 {
1564     if(calledvote || !multiplayer(false)) return;
1565     packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
1566     putint(p, SV_CALLVOTE);
1567     putint(p, SA_MAP);
1568     sendstring("+1", p);
1569     putint(p, *arg1);
1570     putint(p, -1);
1571     sendpackettoserv(1, p.finalize());
1572 }
1573 COMMAND(gonext, "i");
1574 
1575 COMMANDN(callvote, scallvote, "iss"); //fixme,ah
1576 COMMANDF(vote, "i", (int *v) { vote(*v); });
1577 
cleanplayervotes(playerent * p)1578 void cleanplayervotes(playerent *p)
1579 {
1580     if(calledvote && calledvote->owner==p) calledvote->owner = NULL;
1581     if(curvote && curvote->owner==p) curvote->owner = NULL;
1582 }
1583 
whois(int * cn)1584 void whois(int *cn)
1585 {
1586     loopv(players) if(players[i] && players[i]->type == ENT_PLAYER && (*cn == -1 || players[i]->clientnum == *cn))
1587     {
1588         playerent *p = players[i];
1589         uint2ip(p->address, ip);
1590         if(m_teammode) conoutf(_("%c0INFO: %c5%s has %d teamkills."), CC, CC, p->name, p->tks);
1591         if(ip[3] != 0 || player1->clientrole==CR_ADMIN)
1592             conoutf("WHOIS client %d:\n\f5name\t%s\n\f5IP\t%d.%d.%d.%d", *cn, colorname(p), ip[0], ip[1], ip[2], ip[3]); // full IP
1593         else conoutf("WHOIS client %d:\n\f5name\t%s\n\f5IP\t%d.%d.%d.x", *cn, colorname(p), ip[0], ip[1], ip[2]); // censored IP
1594     }
1595 }
1596 COMMAND(whois, "i");
1597 
findcn(char * name)1598 void findcn(char *name)
1599 {
1600     loopv(players) if(players[i] && !strcmp(name, players[i]->name))
1601     {
1602         intret(players[i]->clientnum);
1603         return;
1604     }
1605     if(!strcmp(name, player1->name)) { intret(player1->clientnum); return; }
1606     intret(-1);
1607 }
1608 COMMAND(findcn, "s");
1609 
1610 int sessionid = 0;
1611 
setadmin(int * claim,char * password)1612 void setadmin(int *claim, char *password)
1613 {
1614     if(!*claim && (player1->clientrole))
1615     {
1616         conoutf(_("you released admin status"));
1617         addmsg(SV_SETADMIN, "ri", 0);
1618     }
1619     else if(*claim != 0 && password)
1620         addmsg(SV_SETADMIN, "ris", *claim, genpwdhash(player1->name, password, sessionid));
1621 }
1622 
1623 COMMAND(setadmin, "is");
1624 
1625 struct mline { string name, cmd; };
1626 static vector<mline> mlines;
1627 
1628 void *kickmenu = NULL, *banmenu = NULL, *forceteammenu = NULL, *giveadminmenu = NULL;
1629 
refreshsopmenu(void * menu,bool init)1630 void refreshsopmenu(void *menu, bool init)
1631 {
1632     menureset(menu);
1633     mlines.shrink(0);
1634     mlines.reserve(players.length());
1635     loopv(players) if(players[i])
1636     {
1637         mline &m = mlines.add();
1638         copystring(m.name, colorname(players[i]));
1639         string kbr;
1640         if(getalias("_kickbanreason")!=NULL) formatstring(kbr)(" [ %s ]", getalias("_kickbanreason")); // leading space!
1641         formatstring(m.cmd)("%s %d%s", menu==kickmenu ? "kick" : (menu==banmenu ? "ban" : (menu==forceteammenu ? "forceteam" : "giveadmin")), i, (menu==kickmenu||menu==banmenu)?(strlen(kbr)>8?kbr:" NONE"):""); // 8==3 + "format-extra-chars"
1642         menumanual(menu, m.name, m.cmd);
1643     }
1644 }
1645 
1646 extern bool watchingdemo;
1647 
1648 // rotate through all spec-able players
updatefollowplayer(int shiftdirection)1649 playerent *updatefollowplayer(int shiftdirection)
1650 {
1651     if(!shiftdirection)
1652     {
1653         playerent *f = players.inrange(player1->followplayercn) ? players[player1->followplayercn] : NULL;
1654         if(f && (watchingdemo || !f->isspectating())) return f;
1655     }
1656 
1657     // collect spec-able players
1658     vector<playerent *> available;
1659     loopv(players) if(players[i])
1660     {
1661         if(player1->team != TEAM_SPECT && !watchingdemo && m_teammode && team_base(players[i]->team) != team_base(player1->team)) continue;
1662         if(players[i]->state==CS_DEAD || players[i]->isspectating()) continue;
1663         available.add(players[i]);
1664     }
1665     if(!available.length()) return NULL;
1666 
1667     // rotate
1668     int oldidx = -1;
1669     if(players.inrange(player1->followplayercn)) oldidx = available.find(players[player1->followplayercn]);
1670     if(oldidx<0) oldidx = 0;
1671     int idx = (oldidx+shiftdirection) % available.length();
1672     if(idx<0) idx += available.length();
1673 
1674     player1->followplayercn = available[idx]->clientnum;
1675     return players[player1->followplayercn];
1676 }
1677 
spectate()1678 void spectate()
1679 {
1680     if(m_demo) return;
1681     if(!team_isspect(player1->team)) addmsg(SV_SWITCHTEAM, "ri", TEAM_SPECT);
1682     else tryrespawn();
1683 }
1684 
setfollowplayer(int cn)1685 void setfollowplayer(int cn)
1686 {
1687     // silently ignores invalid player-cn value passed
1688     if(players.inrange(cn) && players[cn])
1689     {
1690         if(!(m_teammode && !watchingdemo && team_base(players[cn]->team) != team_base(player1->team)))
1691         {
1692             player1->followplayercn = cn;
1693             if(player1->spectatemode == SM_FLY) player1->spectatemode = SM_FOLLOW1ST;
1694         }
1695     }
1696 }
1697 
1698 // set new spect mode
spectatemode(int mode)1699 void spectatemode(int mode)
1700 {
1701     if((player1->state != CS_DEAD && player1->state != CS_SPECTATE && !team_isspect(player1->team)) || (!m_teammode && !team_isspect(player1->team) && servstate.mastermode == MM_MATCH)) return;  // during ffa matches only SPECTATORS can spectate
1702     if(mode == player1->spectatemode) return;
1703     showscores(false);
1704     switch(mode)
1705     {
1706         case SM_FOLLOW1ST:
1707         case SM_FOLLOW3RD:
1708         case SM_FOLLOW3RD_TRANSPARENT:
1709         {
1710             if(players.length() && updatefollowplayer()) break;
1711             else mode = SM_FLY;
1712         }
1713         case SM_FLY:
1714         {
1715             if(player1->spectatemode != SM_FLY)
1716             {
1717                 playerent *f = getclient(player1->followplayercn);
1718                 if(f)
1719                 {
1720                     player1->o = f->o;
1721                     player1->yaw = f->yaw;
1722                     player1->pitch = 0.0f;
1723                     player1->resetinterp();
1724                 }
1725                 else entinmap(player1); // or drop 'em at a random place
1726                 player1->followplayercn = FPCN_FLY;
1727             }
1728             break;
1729         }
1730         case SM_OVERVIEW:
1731             player1->followplayercn = FPCN_OVERVIEW;
1732         break;
1733         default: break;
1734     }
1735     player1->spectatemode = mode;
1736 }
1737 
togglespect()1738 void togglespect() // cycle through all spectating modes
1739 {
1740     if(m_botmode)
1741         spectatemode(SM_FLY);
1742     else
1743     {
1744         int mode;
1745         if(player1->spectatemode==SM_NONE) mode = SM_FOLLOW1ST; // start with 1st person spect
1746         else mode = SM_FOLLOW1ST + ((player1->spectatemode - SM_FOLLOW1ST + 1) % (SM_OVERVIEW-SM_FOLLOW1ST)); // replace SM_OVERVIEW by SM_NUM to enable overview mode
1747         spectatemode(mode);
1748     }
1749 }
1750 
changefollowplayer(int shift)1751 void changefollowplayer(int shift)
1752 {
1753     updatefollowplayer(shift);
1754 }
1755 
1756 COMMAND(spectate, "");
1757 COMMANDF(spectatemode, "i", (int *mode) { spectatemode(*mode); });
1758 COMMAND(togglespect, "");
1759 COMMANDF(changefollowplayer, "i", (int *dir) { changefollowplayer(*dir); });
1760 COMMANDF(setfollowplayer, "i", (int *cn) { setfollowplayer(*cn); });
1761 
serverextension(char * ext,char * args)1762 void serverextension(char *ext, char *args)
1763 {
1764     if(!ext || !ext[0]) return;
1765     size_t n = args ? strlen(args)+1 : 0;
1766     if(n>0) addmsg(SV_EXTENSION, "rsis", ext, n, args);
1767     else addmsg(SV_EXTENSION, "rsi", ext, n);
1768 }
1769 
1770 COMMAND(serverextension, "ss");
1771