User ID support (compile with -DUID)
[divverent/nexuiz.git] / data / qcsrc / server / clientcommands.qc
1 entity nagger;
2 float readycount;
3 float Nagger_SendEntity(entity to, float sendflags)
4 {
5         float nags, i, f, b;
6         entity e;
7         WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
8
9         nags = 0;
10         if(readycount)
11         {
12                 nags |= 1;
13                 if(to.ready == 0)
14                         nags |= 2;
15         }
16         if(votecalled)
17         {
18                 nags |= 4;
19                 if(to.vote_vote == 0)
20                         nags |= 8;
21         }
22         if(inWarmupStage)
23                 nags |= 16;
24
25         if(sendflags & 128)
26                 nags |= 128;
27
28         WriteByte(MSG_ENTITY, nags);
29
30         if(nags & 128)
31         {
32                 if(votecalled)
33                         WriteString(MSG_ENTITY, votecalledvote_display);
34                 else
35                         WriteString(MSG_ENTITY, "");
36         }
37
38         if(nags & 1)
39         {
40                 for(i = 1; i <= maxclients; i += 8)
41                 {
42                         for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
43                                 if(clienttype(e) != CLIENTTYPE_REAL || e.ready)
44                                         f |= b;
45                         WriteByte(MSG_ENTITY, f);
46                 }
47         }
48
49         return TRUE;
50 }
51 void Nagger_Init()
52 {
53         Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
54 }
55 void Nagger_VoteChanged()
56 {
57         if(nagger)
58                 nagger.SendFlags |= 128;
59 }
60 void Nagger_VoteCountChanged()
61 {
62         if(nagger)
63                 nagger.SendFlags |= 1;
64 }
65 void Nagger_ReadyCounted()
66 {
67         if(nagger)
68                 nagger.SendFlags |= 1;
69 }
70
71 void ReadyCount();
72 string MapVote_Suggest(string m);
73
74 entity GetPlayer(string name)
75 {
76         float num;
77         entity e;
78         string ns;
79
80         if(substring(name, 0, 1) == "#") {
81                 num = stof(substring(name, 1, 999));
82                 if(num >= 1 && num <= maxclients) {
83                         for((e = world); num > 0; --num, (e = nextent(e)))
84                                 ;
85                         //if(clienttype(e) == CLIENTTYPE_REAL)
86                         if(e.classname == "player")
87                                 return e;
88                 }
89         } else {
90                 ns = strdecolorize(name);
91                 FOR_EACH_REALPLAYER(e) {
92                         if(!strcasecmp(strdecolorize(e.netname), ns)) {
93                                 return e;
94                         }
95                 }
96         }
97         return world;
98 }
99
100 float drag_lastcnt;
101 void DragBox_Think()
102 {
103         if(self.aiment && self.enemy)
104         {
105                 self.origin_x = (self.aiment.origin_x + self.enemy.origin_x) * 0.5;
106                 self.origin_y = (self.aiment.origin_y + self.enemy.origin_y) * 0.5;
107                 self.origin_z = (self.aiment.origin_z + self.enemy.origin_z) * 0.5;
108                 self.maxs_x = fabs(self.aiment.origin_x - self.enemy.origin_x) * 0.5;
109                 self.maxs_y = fabs(self.aiment.origin_y - self.enemy.origin_y) * 0.5;
110                 self.maxs_z = fabs(self.aiment.origin_z - self.enemy.origin_z) * 0.5;
111                 self.mins = -1 * self.maxs;
112                 setorigin(self, self.origin); setsize(self, self.mins, self.maxs); // link edict
113         }
114
115         if(self.cnt == -1) // actually race_place -1
116         {
117                 // show "10 10" for qualifying spawns
118                 setmodel(self.killindicator, "models/sprites/10.spr32");
119                 setmodel(self.killindicator.killindicator, "models/sprites/10.spr32");
120         }
121         else if(self.cnt == -2) // actually race_place 0
122         {
123                 // show "10 0" for loser spawns
124                 setmodel(self.killindicator, "models/sprites/10.spr32");
125                 setmodel(self.killindicator.killindicator, "models/sprites/0.spr32");
126         }
127         else
128         {
129                 setmodel(self.killindicator, strcat("models/sprites/", ftos(mod(self.cnt, 10)), ".spr32"));
130                 setmodel(self.killindicator.killindicator, strcat("models/sprites/", ftos(floor(self.cnt / 10)), ".spr32"));
131         }
132
133         self.nextthink = time;
134 }
135
136 //float ctf_clientcommand();
137 float readyrestart_happened;
138 .float lms_spectate_warning;
139 void spawnfunc_func_breakable();
140
141 .float cmd_floodtime;
142 .float cmd_floodcount;
143 float cmd_floodcheck()
144 {
145         if (timeoutStatus != 2)
146         {
147                 if(time == self.cmd_floodtime)
148                 {
149                         self.cmd_floodcount += 1;
150                         if(self.cmd_floodcount > 8)
151                                 return TRUE;
152                 }
153                 else
154                 {
155                         self.cmd_floodtime = time;
156                         self.cmd_floodcount = 1;
157                 }
158         }
159         return FALSE;
160 }
161
162 void SV_ParseClientCommand(string s) {
163         string cmd;
164         float tokens, f, effectnum;
165         vector start, end;
166         entity e, oldself;
167
168         tokens = tokenize_console(s);
169
170         cmd = argv(0);
171         if(cmd != "reportcvar")
172         if(cmd != "sentcvar")
173         if(cmd != "pause")
174         if(cmd != "prespawn")
175         if(cmd != "spawn")
176         if(cmd != "begin")
177         {
178                 if(cmd_floodcheck())
179                         return;
180         }
181
182         if(GameCommand_Vote(s, self)) {
183                 return;
184         } else if(GameCommand_MapVote(argv(0))) {
185                 return;
186         } else if(cmd == "autoswitch") {
187                 // be backwards compatible with older clients (enabled)
188                 self.autoswitch = ("0" != argv(1));
189                 local string autoswitchmsg;
190                 if (self.autoswitch) {
191                         autoswitchmsg = "on";
192                 } else {
193                         autoswitchmsg = "off";
194                 }
195                 sprint(self, strcat("^1autoswitch turned ", autoswitchmsg, "\n"));
196         } else if(cmd == "clientversion") {
197                 if not(self.flags & FL_CLIENT)
198                         return;
199                 if (argv(1) == "$gameversion") {
200                         //versionmsg = "^1client is too old to get versioninfo.\nUPDATE!!! (http://www.nexuiz.com)^8";
201                         // either that or someone wants to be funny
202                         self.version = 1;
203                 } else {
204                         self.version = stof(argv(1));
205                 }
206                 if(self.version != cvar("gameversion"))
207                 {
208                         self.classname = "observer";
209                         self.version_mismatch = 1;
210                         PutClientInServer();
211                 } else if(cvar("g_campaign") || cvar("g_balance_teams") || cvar("g_balance_teams_force")) {
212                         //JoinBestTeam(self, FALSE, TRUE);
213                 } else if(teams_matter && !cvar("sv_spectate")) {
214                         self.classname = "observer";
215                         stuffcmd(self,"menu_showteamselect\n");
216                 }
217         } else if(cmd == "reportcvar") { // old system
218                 if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
219                 {
220                         s = strcat(substring(s, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
221                         tokens = tokenize_console(s);
222                 }
223                 GetCvars(1);
224 #ifdef UID
225         } else if(cmd == "uid") {
226                 if not(self.uid)
227                 {
228                         self.uid = strzone(argv(1));
229                         self.uid_kicktime = 0;
230                         print("Client ", etos(self), " has UID ", self.uid, "\n");
231                         Ban_MaybeEnforceBan(self);
232                 }
233 #endif
234         } else if(cmd == "sentcvar") { // new system
235                 if(tokens == 2) // undefined cvar: use the default value on the server then
236                 {
237                         s = strcat(substring(s, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
238                         tokens = tokenize_console(s);
239                 }
240                 GetCvars(1);
241         } else if(cmd == "spectate") {
242                 if(cmd_floodcheck())
243                         return;
244                 if not(self.flags & FL_CLIENT)
245                         return;
246                 if(g_arena)
247                         return;
248                 if(g_lms)
249                 {
250                         if(self.lms_spectate_warning)
251                         {
252                                 // mark player as spectator
253                                 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
254                         }
255                         else
256                         {
257                                 self.lms_spectate_warning = 1;
258                                 sprint(self, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
259                                 return;
260                         }
261                 }
262                 if(self.classname == "player" && cvar("sv_spectate") == 1) {
263                         if(self.flagcarried)
264                                 DropFlag(self.flagcarried, world, world);
265                         if(self.ballcarried)
266                                 DropBall(self.ballcarried, self.origin, self.velocity);
267                         kh_Key_DropAll(self, TRUE);
268                         WaypointSprite_PlayerDead();
269                         self.classname = "observer";
270                         if(g_ca)
271                                 self.caplayer = 0;
272                         if(blockSpectators)
273                                 sprint(self, strcat("^7You have to become a player within the next ", ftos(cvar("g_maxplayers_spectator_blocktime")), " seconds, otherwise you will be kicked, because spectators aren't allowed at this time!\n"));
274                         PutClientInServer();
275                 }
276         } else if(cmd == "join") {
277                 if not(self.flags & FL_CLIENT)
278                         return;
279                 if(!g_arena)
280                 if (self.classname != "player" && !lockteams)
281                 {
282                         if(isJoinAllowed()) {
283                                 self.classname = "player";
284                                 if(g_ca)
285                                         self.caplayer = 1;
286                                 PlayerScore_Clear(self);
287                                 bprint ("^4", self.netname, "^4 is playing now\n");
288                                 self.stat_count = WEP_LAST;
289                                 PutClientInServer();
290                                 if(cvar("g_campaign"))
291                                         campaign_bots_may_start = 1;
292                         }
293                         else {
294                                 //player may not join because of g_maxplayers is set
295                                 centerprint_atprio(self, CENTERPRIO_MAPVOTE, PREVENT_JOIN_TEXT);
296                         }
297                 }
298         } else if( cmd == "selectteam" ) {
299                 if not(self.flags & FL_CLIENT)
300                         return;
301                 if( !teams_matter ) {
302                         sprint( self, "selecteam can only be used in teamgames\n");
303                 } else if(cvar("g_campaign")) {
304                         //JoinBestTeam(self, 0);
305                 } else if(lockteams) {
306                         sprint( self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
307                 } else if( argv(1) == "red" ) {
308                         DoTeamChange(COLOR_TEAM1);
309                 } else if( argv(1) == "blue" ) {
310                         DoTeamChange(COLOR_TEAM2);
311                 } else if( argv(1) == "yellow" ) {
312                         DoTeamChange(COLOR_TEAM3);
313                 } else if( argv(1) == "pink" ) {
314                         DoTeamChange(COLOR_TEAM4);
315                 } else if( argv(1) == "auto" ) {
316                         DoTeamChange(-1);
317                 } else {
318                         sprint( self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
319                 }
320         } else if(cmd == "ready") {
321                 if not(self.flags & FL_CLIENT)
322                         return;
323
324                 if((inWarmupStage && 0 >= g_warmup_limit) // with unlimited warmup players have to be able to restart
325                    || cvar("sv_ready_restart") || g_race_qualifying == 2)
326                 {
327                         if(!readyrestart_happened || cvar("sv_ready_restart_repeatable"))
328                         {
329                                 if (self.ready) // toggle
330                                 {
331                                         self.ready = FALSE;
332                                         bprint(self.netname, "^2 is ^1NOT^2 ready\n");
333                                 }
334                                 else
335                                 {
336                                         self.ready = TRUE;
337                                         bprint(self.netname, "^2 is ready\n");
338                                 }
339
340                                 // cannot reset the game while a timeout is active!
341                                 if(!timeoutStatus)
342                                         ReadyCount();
343                         } else {
344                                 sprint(self, "^1Game has already been restarted\n");
345                         }
346                 }
347         } else if(cmd == "maplist") {
348                 sprint(self, maplist_reply);
349         } else if(cmd == "lsmaps") {
350                 sprint(self, lsmaps_reply);
351         } else if(cmd == "records") {
352                 sprint(self, records_reply);
353         } else if(cmd == "rankings") {
354                 sprint(self, rankings_reply);
355         } else if(cmd == "voice") {
356                 if(tokens >= 3)
357                         VoiceMessage(argv(1), substring(s, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
358                 else
359                         VoiceMessage(argv(1), "");
360         } else if(cmd == "say") {
361                 if(tokens >= 2)
362                         Say(self, FALSE, world, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
363                 //clientcommand(self, formatmessage(s));
364         } else if(cmd == "say_team") {
365                 if(tokens >= 2)
366                         Say(self, TRUE, world, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
367                 //clientcommand(self, formatmessage(s));
368         } else if(cmd == "tell") {
369                 e = GetCommandPlayerSlotTargetFromTokenizedCommand(tokens, 1);
370                 if(e && tokens > ParseCommandPlayerSlotTarget_firsttoken)
371                 {
372                         Say(self, FALSE, e, substring(s, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)), TRUE);
373                 }
374                 else
375                 {
376                         if(tokens > ParseCommandPlayerSlotTarget_firsttoken)
377                                 trigger_magicear_processmessage_forallears(self, -1, world, substring(s, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)));
378                         sprint(self, "ERROR: usage: tell # playerid text...\n");
379                 }
380                 //clientcommand(self, formatmessage(s));
381         } else if(cmd == "info") {
382                 cmd = cvar_string_builtin(strcat("sv_info_", argv(1))); // This needed fixed for the cvar check
383                 if(cmd == "")
384                         sprint(self, "ERROR: unsupported info command\n");
385                 else
386                         wordwrap_sprint(cmd, 1111);
387         } else if(cmd == "suggestmap") {
388                 sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
389         } else if(cmd == "timeout") {
390                 if not(self.flags & FL_CLIENT)
391                         return;
392                 if(cvar("sv_timeout")) {
393                         if(self.classname == "player") {
394                                 if(votecalled)
395                                         sprint(self, "^7Error: you can not call a timeout while a vote is active!\n");
396                                 else
397                                         evaluateTimeout();
398                         }
399                         else
400                                 sprint(self, "^7Error: only players can call a timeout!\n");
401                 }
402         } else if(cmd == "timein") {
403                 if not(self.flags & FL_CLIENT)
404                         return;
405                 if(cvar("sv_timeout")) {
406                         evaluateTimein();
407                 }
408         } else if(cmd == "teamstatus") {
409                 Score_NicePrint(self);
410         } else if(cmd == "cvar_changes") {
411                 sprint(self, cvar_changes);
412         } else if(cmd == "pointparticles") {
413                 if((sv_cheats || self.maycheat) && tokens == 5)
414                 {
415                         // arguments:
416                         //   effectname
417                         //   origin (0..1, on crosshair line)
418                         //   velocity
419                         //   howmany
420                         effectnum = particleeffectnum(argv(1));
421                         f = stof(argv(2));
422                         start = (1-f) * self.origin + f * self.cursor_trace_endpos;
423                         end = stov(argv(3));
424                         f = stof(argv(4));
425                         pointparticles(effectnum, start, end, f);
426                 }
427                 else
428                         sprint(self, "Usage: sv_cheats 1; restart; cmd pointparticles effectname position(0..1) velocityvector multiplier\n");
429         } else if(cmd == "trailparticles") {
430                 if((sv_cheats || self.maycheat) && tokens == 2)
431                 {
432                         // arguments:
433                         //   effectname
434                         effectnum = particleeffectnum(argv(1));
435                         W_SetupShot(self, FALSE, FALSE, "",0);
436                         traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, self);
437                         trailparticles(self, effectnum, w_shotorg, trace_endpos);
438                 }
439                 else
440                         sprint(self, "Usage: sv_cheats 1; restart; cmd trailparticles effectname\n");
441         } else if(cmd == "make") {
442                 if((sv_cheats || self.maycheat) && tokens == 3)
443                 {
444                         // arguments:
445                         //   modelname mode
446                         f = stof(argv(2));
447                         W_SetupShot(self, FALSE, FALSE, "", 0);
448                         traceline(w_shotorg, w_shotorg + w_shotdir * 2048, MOVE_NORMAL, self);
449                         if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) || trace_fraction == 1)
450                         {
451                                 sprint(self, "cannot make stuff there (bad surface)\n");
452                         }
453                         else
454                         {
455                                 oldself = self;
456                                 self = spawn();
457                                 self.model = strzone(argv(1));
458                                 self.mdl = "rocket_explode";
459                                 self.health = 1000;
460                                 setorigin(self, trace_endpos);
461                                 self.effects = EF_NOMODELFLAGS;
462                                 if(f == 1)
463                                 {
464                                         self.angles = fixedvectoangles2(trace_plane_normal, v_forward);
465                                         self.angles = AnglesTransform_Multiply(self.angles, '-90 0 0'); // so unrotated models work
466                                 }
467                                 spawnfunc_func_breakable();
468                                 // now, is it valid?
469                                 if(f == 0)
470                                 {
471                                         tracebox(self.origin, self.mins, self.maxs, self.origin, MOVE_NORMAL, self);
472                                         if(trace_startsolid)
473                                         {
474                                                 sprint(oldself, "cannot make stuff there (no space)\n");
475                                                 remove(self);
476                                         }
477                                 }
478                                 self = oldself;
479                         }
480                 }
481                 else
482                         sprint(self, "Usage: sv_cheats 1; restart; cmd make models/... 0/1/2\n");
483         } else if(cmd == "penalty") {
484                 if((sv_cheats || self.maycheat) && tokens == 3)
485                         race_ImposePenaltyTime(self, stof(argv(1)), argv(2));
486                 else
487                         sprint(self, "Usage: sv_cheats 1; restart; cmd penalty 5.0 AHAHAHAHAHAHAH))\n");
488         } else if(cmd == "dragbox_spawn") {
489                 if(sv_cheats || self.maycheat)
490                 {
491                         e = spawn();
492                         e.classname = "dragbox_box";
493                         e.think = DragBox_Think;
494                         e.nextthink = time;
495                         e.solid = -1; // black
496                         setmodel(e, "null"); // network it
497                         if(tokens == 4)
498                                 e.cnt = stof(argv(1));
499                         else
500                                 e.cnt = max(0, drag_lastcnt);
501
502                         e.aiment = spawn();
503                         e.aiment.classname = "dragbox_corner_1";
504                         e.aiment.owner = e;
505                         setmodel(e.aiment, "models/marker.md3");
506                         e.aiment.skin = 0;
507                         setsize(e.aiment, '0 0 0', '0 0 0');
508                         if(tokens == 4)
509                                 setorigin(e.aiment, stov(argv(2)));
510                         else
511                                 setorigin(e.aiment, self.cursor_trace_endpos);
512
513                         e.enemy = spawn();
514                         e.enemy.classname = "dragbox_corner_2";
515                         e.enemy.owner = e;
516                         setmodel(e.enemy, "models/marker.md3");
517                         e.enemy.skin = 1;
518                         setsize(e.enemy, '0 0 0', '0 0 0');
519                         end = normalize(self.cursor_trace_start - e.aiment.origin);
520                         end_x = (end_x > 0) * 2 - 1;
521                         end_y = (end_y > 0) * 2 - 1;
522                         end_z = (end_z > 0) * 2 - 1;
523                         if(tokens == 4)
524                                 setorigin(e.enemy, stov(argv(3)));
525                         else
526                                 setorigin(e.enemy, e.aiment.origin + 32 * end);
527
528                         e.killindicator = spawn();
529                         e.killindicator.classname = "drag_digit";
530                         e.killindicator.owner = e;
531                         setattachment(e.killindicator, e, "");
532                         setorigin(e.killindicator, '0 0 -8');
533                         e.killindicator.killindicator = spawn();
534                         e.killindicator.killindicator.classname = "drag_digit";
535                         e.killindicator.killindicator.owner = e;
536                         setattachment(e.killindicator.killindicator, e, "");
537                         setorigin(e.killindicator.killindicator, '0 0 8');
538                 }
539                 else
540                         sprint(self, "Usage: sv_cheats 1; r_showbboxes 1.5; restart; cmd dragbox_spawn\n");
541         } else if(cmd == "dragpoint_spawn") {
542                 if(sv_cheats || self.maycheat)
543                 {
544                         e = spawn();
545                         e.classname = "dragpoint";
546                         e.think = DragBox_Think;
547                         e.nextthink = time;
548                         e.solid = 0; // nothing special
549                         setmodel(e, "models/marker.md3");
550                         setsize(e, PL_MIN, PL_MAX);
551                         e.skin = 2;
552                         if(tokens == 3)
553                                 e.cnt = stof(argv(1));
554                         else
555                                 e.cnt = drag_lastcnt;
556                         if(tokens == 3)
557                                 setorigin(e, stov(argv(2)));
558                         else
559                         {
560                                 setorigin(e, self.cursor_trace_endpos + normalize(self.cursor_trace_start - self.cursor_trace_endpos));
561                                 move_out_of_solid(e);
562                         }
563
564                         e.killindicator = spawn();
565                         e.killindicator.classname = "drag_digit";
566                         e.killindicator.owner = e;
567                         setattachment(e.killindicator, e, "");
568                         setorigin(e.killindicator, '0 0 40');
569                         e.killindicator.killindicator = spawn();
570                         e.killindicator.killindicator.classname = "drag_digit";
571                         e.killindicator.killindicator.owner = e;
572                         setattachment(e.killindicator.killindicator, e, "");
573                         setorigin(e.killindicator.killindicator, '0 0 56');
574                 }
575                 else
576                         sprint(self, "Usage: sv_cheats 1; r_showbboxes 1.5; restart; cmd dragbox_spawn\n");
577         } else if(cmd == "drag_remove") {
578                 if(sv_cheats || self.maycheat)
579                 {
580                         RandomSelection_Init();
581                         for(e = world; (e = find(e, classname, "dragbox_box")); )
582                                 RandomSelection_Add(e, 0, string_null, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - self.cursor_trace_endpos));
583                         for(e = world; (e = find(e, classname, "dragpoint")); )
584                                 RandomSelection_Add(e, 0, string_null, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - self.cursor_trace_endpos));
585                         if(RandomSelection_chosen_ent)
586                         {
587                                 remove(RandomSelection_chosen_ent.killindicator.killindicator);
588                                 remove(RandomSelection_chosen_ent.killindicator);
589                                 if(RandomSelection_chosen_ent.aiment)
590                                         remove(RandomSelection_chosen_ent.aiment);
591                                 if(RandomSelection_chosen_ent.enemy)
592                                         remove(RandomSelection_chosen_ent.enemy);
593                                 remove(RandomSelection_chosen_ent);
594                         }
595                 }
596                 else
597                         sprint(self, "Usage: sv_cheats 1; restart; cmd dragbox_remove\n");
598         } else if(cmd == "drag_setcnt") {
599                 if((sv_cheats || self.maycheat) && tokens >= 2)
600                 {
601                         RandomSelection_Init();
602                         for(e = world; (e = find(e, classname, "dragbox_box")); )
603                                 RandomSelection_Add(e, 0, string_null, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - self.cursor_trace_endpos));
604                         for(e = world; (e = find(e, classname, "dragpoint")); )
605                                 RandomSelection_Add(e, 0, string_null, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - self.cursor_trace_endpos));
606                         if(RandomSelection_chosen_ent)
607                         {
608                                 if(substring(argv(1), 0, 1) == "*")
609                                         RandomSelection_chosen_ent.cnt = drag_lastcnt = RandomSelection_chosen_ent.cnt + stof(substring(argv(1), 1, -1));
610                                 else
611                                         RandomSelection_chosen_ent.cnt = drag_lastcnt = stof(argv(1));
612                         }
613                 }
614                 else
615                         sprint(self, "Usage: sv_cheats 1; restart; cmd dragbox_setcnt cnt\n");
616         } else if(cmd == "drag_save") {
617                 if((sv_cheats || self.maycheat) && tokens >= 2)
618                 {
619                         f = fopen(argv(1), FILE_WRITE);
620                         fputs(f, "cmd drag_clear\n");
621                         for(e = world; (e = find(e, classname, "dragbox_box")); )
622                         {
623                                 fputs(f, strcat("cmd dragbox_spawn ", ftos(e.cnt), " \"", vtos(e.aiment.origin), "\" \"", vtos(e.enemy.origin), "\"\n"));
624                         }
625                         for(e = world; (e = find(e, classname, "dragpoint")); )
626                         {
627                                 fputs(f, strcat("cmd dragpoint_spawn ", ftos(e.cnt), " \"", vtos(e.origin), "\"\n"));
628                         }
629                         fclose(f);
630                 }
631                 else
632                         sprint(self, "Usage: sv_cheats 1; restart; cmd dragbox_save filename\n");
633         } else if(cmd == "drag_saveraceent") {
634                 if((sv_cheats || self.maycheat) && tokens >= 2)
635                 {
636                         f = fopen(argv(1), FILE_WRITE);
637                         for(e = world; (e = find(e, classname, "dragbox_box")); )
638                         {
639                                 fputs(f, "{\n");
640                                 fputs(f, "\"classname\" \"trigger_race_checkpoint\"\n");
641                                 fputs(f, strcat("\"origin\" \"", ftos(e.absmin_x), " ", ftos(e.absmin_y), " ", ftos(e.absmin_z), "\"\n"));
642                                 fputs(f, strcat("\"maxs\" \"", ftos(e.absmax_x - e.absmin_x), " ", ftos(e.absmax_y - e.absmin_y), " ", ftos(e.absmax_z - e.absmin_z), "\"\n"));
643                                 fputs(f, strcat("\"cnt\" \"", ftos(e.cnt), "\"\n"));
644                                 fputs(f, strcat("\"targetname\" \"checkpoint", ftos(e.cnt), "\"\n"));
645                                 fputs(f, "}\n");
646                         }
647                         for(e = world; (e = find(e, classname, "dragpoint")); )
648                         {
649                                 start = '0 0 0';
650                                 effectnum = 0;
651                                 for(oldself = world; (oldself = find(oldself, classname, "dragbox_box")); )
652                                 {
653                                         if(e.cnt <= 0 && oldself.cnt == 0 || e.cnt == oldself.cnt)
654                                         {
655                                                 start = start + oldself.origin;
656                                                 ++effectnum;
657                                         }
658                                 }
659                                 start *= 1 / effectnum;
660                                 fputs(f, "{\n");
661                                 fputs(f, "\"classname\" \"info_player_race\"\n");
662                                 fputs(f, strcat("\"angle\" \"", ftos(vectoyaw(start - e.origin)), "\"\n"));
663                                 fputs(f, strcat("\"origin\" \"", ftos(e.origin_x), " ", ftos(e.origin_y), " ", ftos(e.origin_z), "\"\n"));
664                                 if(e.cnt == -2)
665                                 {
666                                         fputs(f, "\"target\" \"checkpoint0\"\n");
667                                         fputs(f, "\"race_place\" \"0\"\n");
668                                 }
669                                 else if(e.cnt == -1)
670                                 {
671                                         fputs(f, "\"target\" \"checkpoint0\"\n");
672                                         fputs(f, "\"race_place\" \"-1\"\n");
673                                 }
674                                 else
675                                 {
676                                         fputs(f, strcat("\"target\" \"checkpoint", ftos(e.cnt), "\"\n"));
677                                         if(e.cnt == 0)
678                                         {
679                                                 // these need race_place
680                                                 // counting...
681                                                 effectnum = 1;
682                                                 for(oldself = world; (oldself = find(oldself, classname, "dragpoint")); )
683                                                 if(oldself.cnt == 0)
684                                                 {
685                                                         if(vlen(oldself.origin - start) < vlen(e.origin - start))
686                                                                 ++effectnum;
687                                                         else if(vlen(oldself.origin - start) == vlen(e.origin - start) && num_for_edict(oldself) < num_for_edict(e))
688                                                                 ++effectnum;
689                                                 }
690                                                 fputs(f, strcat("\"race_place\" \"", ftos(effectnum), "\"\n"));
691                                         }
692                                 }
693                                 fputs(f, "}\n");
694                         }
695                         fclose(f);
696                 }
697                 else
698                         sprint(self, "Usage: sv_cheats 1; restart; cmd dragbox_save filename\n");
699         } else if(cmd == "drag_clear") {
700                 if(sv_cheats || self.maycheat)
701                 {
702                         for(e = world; (e = find(e, classname, "dragbox_box")); )
703                                 remove(e);
704                         for(e = world; (e = find(e, classname, "dragbox_corner_1")); )
705                                 remove(e);
706                         for(e = world; (e = find(e, classname, "dragbox_corner_2")); )
707                                 remove(e);
708                         for(e = world; (e = find(e, classname, "dragpoint")); )
709                                 remove(e);
710                         for(e = world; (e = find(e, classname, "drag_digit")); )
711                                 remove(e);
712                 }
713                 else
714                         sprint(self, "Usage: sv_cheats 1; restart; cmd dragbox_clear\n");
715         } else {
716                 //if(ctf_clientcommand())
717                 //      return;
718                 // grep for Cmd_AddCommand_WithClientCommand to find them all
719                 if(cmd != "status")
720                 if(cmd != "max")
721                 if(cmd != "monster")
722                 if(cmd != "scrag")
723                 if(cmd != "wraith")
724                 if(cmd != "gimme")
725                 if(cmd != "god")
726                 if(cmd != "notarget")
727                 if(cmd != "fly")
728                 if(cmd != "noclip")
729                 if(cmd != "give")
730                 //if(cmd != "say") // handled above
731                 //if(cmd != "say_team") // handled above
732                 if(cmd != "kill")
733                 if(cmd != "pause")
734                 if(cmd != "ping")
735                 if(cmd != "name")
736                 if(cmd != "color")
737                 if(cmd != "rate")
738                 if(cmd != "pmodel")
739                 if(cmd != "playermodel")
740                 if(cmd != "playerskin")
741                 if(cmd != "prespawn")
742                 if(cmd != "spawn")
743                 if(cmd != "begin")
744                 if(cmd != "pings")
745                 if(cmd != "sv_startdownload")
746                 if(cmd != "download")
747                 {
748                         print("WARNING: Invalid clientcommand by ", self.netname, ": ", s, "\n");
749                         return;
750                 }
751
752                 if(self.jointime > 0 && time > self.jointime + 10 && time > self.nickspamtime) // allow any changes in the first 10 seconds since joining
753                 if(cmd == "name" || cmd == "playermodel") // TODO also playerskin and color?
754                 {
755                         if(self.nickspamtime == 0 || time > self.nickspamtime + cvar("g_nick_flood_timeout"))
756                                 // good, no serious flood
757                                 self.nickspamcount = 1;
758                         else
759                                 self.nickspamcount += 1;
760                         self.nickspamtime = time + cvar("g_nick_flood_penalty");
761
762                         if (timeoutStatus == 2) //when game is paused, no flood protection
763                                 self.nickspamcount = self.nickspamtime = 0;
764                 }
765
766                 clientcommand(self,s);
767         }
768 }
769
770 void ReadyRestartForce()
771 {
772         local entity e;
773
774         bprint("^1Server is restarting...\n");
775
776         VoteReset();
777
778         // clear overtime
779         if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) {
780                 //we have to decrease timelimit to its original value again!!
781                 float newTL;
782                 newTL = cvar("timelimit");
783                 newTL -= checkrules_overtimesadded * cvar("timelimit_overtime");
784                 cvar_set("timelimit", ftos(newTL));
785         }
786
787         checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
788
789
790         readyrestart_happened = 1;
791         game_starttime = time;
792         if(!g_ca)
793                 game_starttime += RESTART_COUNTDOWN;
794         restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
795
796         inWarmupStage = 0; //once the game is restarted the game is in match stage
797
798         //reset the .ready status of all players (also spectators)
799         FOR_EACH_CLIENTSLOT(e)
800                 e.ready = 0;
801         readycount = 0;
802         Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
803
804         if(cvar("teamplay_lockonrestart") && teams_matter) {
805                 lockteams = 1;
806                 bprint("^1The teams are now locked.\n");
807         }
808
809         //initiate the restart-countdown-announcer entity
810         if(cvar("sv_ready_restart_after_countdown"))
811         {
812                 restartTimer = spawn();
813                 restartTimer.think = restartTimer_Think;
814                 restartTimer.nextthink = game_starttime;
815         }
816
817         //after a restart every players number of allowed timeouts gets reset, too
818         if(cvar("sv_timeout"))
819         {
820                 FOR_EACH_REALPLAYER(e)
821                         e.allowedTimeouts = cvar("sv_timeout_number");
822         }
823
824         //reset map immediately if this cvar is not set
825         if (!cvar("sv_ready_restart_after_countdown"))
826                 reset_map(TRUE);
827
828         if(cvar("sv_eventlog"))
829                 GameLogEcho(":restart");
830 }
831
832 void ReadyRestart()
833 {
834         // no arena, assault support yet...
835         if(g_arena | g_assault | gameover | intermission_running | race_completing)
836                 localcmd("restart\n");
837         else
838                 localcmd("\nsv_hook_gamerestart;");
839
840         ReadyRestartForce();
841
842         // reset ALL scores, but only do that at the beginning
843         //of the countdown if sv_ready_restart_after_countdown is off!
844         //Otherwise scores could be manipulated during the countdown!
845         if (!cvar("sv_ready_restart_after_countdown"))
846                 Score_ClearAll();
847 }
848
849 /**
850  * Counts how many players are ready. If not enough players are ready, the function
851  * does nothing. If all players are ready, the timelimit will be extended and the
852  * restart_countdown variable is set to allow other functions like PlayerPostThink
853  * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
854  * is not set the map will be resetted.
855  *
856  * Function is called after the server receives a 'ready' sign from a player.
857  */
858 void ReadyCount()
859 {
860         local entity e;
861         local float r, p;
862
863         r = p = 0;
864
865         FOR_EACH_REALPLAYER(e)
866         {
867                 p += 1;
868                 if(e.ready)
869                         r += 1;
870         }
871
872         readycount = r;
873
874         Nagger_ReadyCounted();
875
876         if(r) // at least one is ready
877         if(r == p) // and, everyone is ready
878                 ReadyRestart();
879 }
880
881 /**
882  * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
883  * is set)
884  */
885 void restartTimer_Think() {
886         restart_mapalreadyrestarted = 1;
887         reset_map(TRUE);
888         Score_ClearAll();
889         remove(self);
890         return;
891 }
892
893 /**
894  * Checks whether the player who calls the timeout is allowed to do so.
895  * If so, it initializes the timeout countdown. It also checks whether another
896  * timeout was already running at this time and reacts correspondingly.
897  *
898  * affected globals/fields: .allowedTimeouts, remainingTimeoutTime, remainingLeadTime,
899  *                          timeoutInitiator, timeoutStatus, timeoutHandler
900  *
901  * This function is called when a player issues the calltimeout command.
902  */
903 void evaluateTimeout() {
904         if (inWarmupStage && !g_warmup_allow_timeout)
905                 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
906         if (time < game_starttime )
907                 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
908         if (timeoutStatus != 2) {
909                 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
910                 if (cvar("timelimit")) {
911                         //a timelimit was used
912                         local float myTl;
913                         myTl = cvar("timelimit");
914
915                         local float lastPossibleTimeout;
916                         lastPossibleTimeout = (myTl*60) - cvar("sv_timeout_leadtime") - 1;
917
918                         if (lastPossibleTimeout < time - game_starttime)
919                                 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
920                 }
921         }
922         //player may not call a timeout if he has no calls left
923         if (self.allowedTimeouts < 1)
924                 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
925         //now all required checks are passed
926         self.allowedTimeouts -= 1;
927         bprint(self.netname, " ^7called a timeout (", ftos(self.allowedTimeouts), " timeouts left)!\n"); //write a bprint who started the timeout (and how many he has left)
928         remainingTimeoutTime = cvar("sv_timeout_length");
929         remainingLeadTime = cvar("sv_timeout_leadtime");
930         timeoutInitiator = self;
931         if (timeoutStatus == 0) { //if another timeout was already active, don't change its status (which was 1 or 2) to 1, only change it to 1 if no timeout was active yet
932                 timeoutStatus = 1;
933                 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
934                 timeoutHandler = spawn();
935                 timeoutHandler.think = timeoutHandler_Think;
936         }
937         timeoutHandler.nextthink = time; //always let the entity think asap
938
939         //inform all connected clients about the timeout call
940         play2all("announcer/robotic/timeoutcalled.wav");
941 }
942
943 /**
944  * Checks whether a player is allowed to resume the game. If he is allowed to do it,
945  * and the lead time for the timeout is still active, this countdown just will be aborted (the
946  * game will never be paused). Otherwise the remainingTimeoutTime will be set to the corresponding
947  * value of the cvar sv_timeout_resumetime.
948  *
949  * This function is called when a player issues the resumegame command.
950  */
951 void evaluateTimein() {
952         if (!timeoutStatus)
953                 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
954         if (self != timeoutInitiator)
955                 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
956         if (timeoutStatus == 1) {
957                 remainingTimeoutTime = timeoutStatus = 0;
958                 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
959                 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
960         }
961         else if (timeoutStatus == 2) {
962                 //only shorten the remainingTimeoutTime if it makes sense
963                 if( remainingTimeoutTime > (cvar("sv_timeout_resumetime") + 1) ) {
964                         bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
965                         remainingTimeoutTime = cvar("sv_timeout_resumetime");
966                         timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
967                 }
968                 else
969                         sprint(self, "^7Error: Your resumegame call was discarded!\n");
970
971         }
972 }