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