]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/clientcommands.qc
Merge remote branch 'origin/master' into samual/updatecommands
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / clientcommands.qc
1 // =========================================================
2 //  Server side networked commands code, reworked by Samual
3 //  Last updated: November 26th, 2011
4 // =========================================================
5
6 #define CC_REQUEST_COMMAND 1
7 #define CC_REQUEST_USAGE 2
8
9 .float cmd_floodtime;
10 .float cmd_floodcount;
11 .float checkfail;
12 .float lms_spectate_warning;
13
14 string MapVote_Suggest(string m);
15
16
17 // ============================
18 //  Misc. Supporting Functions
19 // ============================
20
21 float SV_ParseClientCommand_floodcheck()
22 {
23         if (timeoutStatus != 2) // if the game is not paused... but wait, doesn't that mean it could be dos'd by pausing it? eh? (old code)
24         {
25                 if(time == self.cmd_floodtime) // todo: add buffer time as well, ONLY one second is a short amount of time for someone to be spamming. 
26                 {
27                         self.cmd_floodcount += 1;
28                         if(self.cmd_floodcount > 8) // todo: replace constant 8 with a cvar for the server to control
29                                 return FALSE; // too much spam, halt
30                 }
31                 else
32                 {
33                         self.cmd_floodtime = time;
34                         self.cmd_floodcount = 1;
35                 }
36         }
37         return TRUE; // continue, as we're not flooding yet
38 }
39
40
41 // =======================
42 //  Command Sub-Functions
43 // =======================
44
45 void ClientCommand_autoswitch(float request, float argc)
46 {
47         switch(request)
48         {
49                 case CC_REQUEST_COMMAND:
50                 {
51                         self.autoswitch = ("0" != argv(1));
52                         sprint(self, strcat("^1autoswitch is currently turned ", (self.autoswitch ? "on" : "off"), ".\n"));
53                         return; // never fall through to usage
54                 }
55                         
56                 default:
57                 case CC_REQUEST_USAGE:
58                 {
59                         sprint(self, "\nUsage:^3 cmd autoswitch selection\n");
60                         sprint(self, "  Where 'selection' is 1 or 0 for on or off.\n"); 
61                         return;
62                 }
63         }
64 }
65
66 void ClientCommand_checkfail(float request, string command) // used only by client side code
67 {
68         switch(request)
69         {
70                 case CC_REQUEST_COMMAND:
71                 {
72                         print(sprintf("CHECKFAIL: %s (%s) epically failed check %s\n", self.netname, self.netaddress, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1))));
73                         self.checkfail = 1;
74                         return; // never fall through to usage
75                 }
76                         
77                 default:
78                 case CC_REQUEST_USAGE:
79                 {
80                         sprint(self, "\nUsage:^3 cmd checkfail message\n");
81                         sprint(self, "  Where 'message' is the message reported by client about the fail.\n");
82                         return;
83                 }
84         }
85 }
86
87 void ClientCommand_clientversion(float request, float argc) // used only by client side code
88 {
89         switch(request)
90         {
91                 case CC_REQUEST_COMMAND:
92                 {
93                         if(self.flags & FL_CLIENT)
94                         {
95                                 self.version = ((argv(1) == "$gameversion") ? 1 : stof(argv(1)));
96                                 
97                                 if(self.version < autocvar_gameversion_min || self.version > autocvar_gameversion_max)
98                                 {
99                                         self.version_mismatch = 1;
100                                         ClientKill_TeamChange(-2); // observe
101                                 } 
102                                 else if(autocvar_g_campaign || autocvar_g_balance_teams || autocvar_g_balance_teams_force) 
103                                 {
104                                         //JoinBestTeam(self, FALSE, TRUE);
105                                 } 
106                                 else if(teamplay && !autocvar_sv_spectate && !(self.team_forced > 0)) 
107                                 {
108                                         self.classname = "observer"; // really?
109                                         stuffcmd(self, "menu_showteamselect\n");
110                                 }
111                         }
112                         return; // never fall through to usage
113                 }
114                         
115                 default:
116                 case CC_REQUEST_USAGE:
117                 {
118                         sprint(self, "\nUsage:^3 cmd clientversion version\n");
119                         sprint(self, "  Where 'version' is the game version reported by self.\n");
120                         return;
121                 }
122         }
123 }
124
125 void ClientCommand_cvar_changes(float request)
126 {
127         switch(request)
128         {
129                 case CC_REQUEST_COMMAND:
130                 {
131                         sprint(self, cvar_changes);
132                         return; // never fall through to usage
133                 }
134                         
135                 default:
136                 case CC_REQUEST_USAGE:
137                 {
138                         sprint(self, "\nUsage:^3 sv_cmd cvar_changes\n");
139                         sprint(self, "  No arguments required.\n");
140                         //sprint(self, "See also: ^2cvar_purechanges^7\n");
141                         return;
142                 }
143         }
144 }
145
146 void ClientCommand_cvar_purechanges(float request)
147 {
148         switch(request)
149         {
150                 case CC_REQUEST_COMMAND:
151                 {
152                         sprint(self, cvar_purechanges);
153                         return; // never fall through to usage
154                 }
155                         
156                 default:
157                 case CC_REQUEST_USAGE:
158                 {
159                         sprint(self, "\nUsage:^3 sv_cmd cvar_purechanges\n");
160                         sprint(self, "  No arguments required.\n");
161                         //sprint(self, "See also: ^2cvar_changes^7\n");
162                         return;
163                 }
164         }
165 }
166
167 void ClientCommand_info(float request, float argc)
168 {       
169         switch(request)
170         {
171                 case CC_REQUEST_COMMAND:
172                 {
173                         string command;
174                         
175                         command = builtin_cvar_string(strcat("sv_info_", argv(1))); 
176                         if(command)
177                                 wordwrap_sprint(command, 1111); // why 1111?
178                         else
179                                 sprint(self, "ERROR: unsupported info command\n");
180                                 
181                         return; // never fall through to usage
182                 }
183                         
184                 default:
185                 case CC_REQUEST_USAGE:
186                 {
187                         sprint(self, "\nUsage:^3 cmd info request\n");
188                         sprint(self, "  Where 'request' is the suffixed string appended onto the request for cvar.\n");
189                         return;
190                 }
191         }
192 }
193
194 void ClientCommand_join(float request)
195 {
196         switch(request)
197         {
198                 case CC_REQUEST_COMMAND:
199                 {
200                         if(self.flags & FL_CLIENT)
201                         {
202                                 if(self.classname != "player" && !lockteams && !g_arena)
203                                 {
204                                         if(nJoinAllowed(1)) 
205                                         {
206                                                 if(g_ca) { self.caplayer = 1; }
207                                                 if(autocvar_g_campaign) { campaign_bots_may_start = 1; }
208                                                 
209                                                 self.classname = "player";
210                                                 PlayerScore_Clear(self);
211                                                 bprint ("^4", self.netname, "^4 is playing now\n");
212                                                 PutClientInServer();
213                                         }
214                                         else 
215                                         {
216                                                 //player may not join because of g_maxplayers is set
217                                                 centerprint(self, PREVENT_JOIN_TEXT);
218                                         }
219                                 }
220                         }
221                         return; // never fall through to usage
222                 }
223                         
224                 default:
225                 case CC_REQUEST_USAGE:
226                 {
227                         sprint(self, "\nUsage:^3 cmd join\n");
228                         sprint(self, "  No arguments required.\n");
229                         return;
230                 }
231         }
232 }
233
234 void ClientCommand_ladder(float request)
235 {
236         switch(request)
237         {
238                 case CC_REQUEST_COMMAND:
239                 {
240                         sprint(self, ladder_reply);
241                         return; // never fall through to usage
242                 }
243                         
244                 default:
245                 case CC_REQUEST_USAGE:
246                 {
247                         sprint(self, "\nUsage:^3 cmd ladder\n");
248                         sprint(self, "  No arguments required.\n");
249                         return;
250                 }
251         }
252 }
253
254 void ClientCommand_lsmaps(float request)
255 {
256         switch(request)
257         {
258                 case CC_REQUEST_COMMAND:
259                 {
260                         sprint(self, lsmaps_reply);
261                         return; // never fall through to usage
262                 }
263                         
264                 default:
265                 case CC_REQUEST_USAGE:
266                 {
267                         sprint(self, "\nUsage:^3 cmd lsmaps\n");
268                         sprint(self, "  No arguments required.\n");
269                         return;
270                 }
271         }
272 }
273
274 void ClientCommand_lsnewmaps(float request)
275 {
276         switch(request)
277         {
278                 case CC_REQUEST_COMMAND:
279                 {
280                         sprint(self, lsnewmaps_reply);
281                         return; // never fall through to usage
282                 }
283                         
284                 default:
285                 case CC_REQUEST_USAGE:
286                 {
287                         sprint(self, "\nUsage:^3 cmd lsnewmaps\n");
288                         sprint(self, "  No arguments required.\n");
289                         return;
290                 }
291         }
292 }
293
294 void ClientCommand_maplist(float request)
295 {
296         switch(request)
297         {
298                 case CC_REQUEST_COMMAND:
299                 {
300                         sprint(self, maplist_reply);
301                         return; // never fall through to usage
302                 }
303                         
304                 default:
305                 case CC_REQUEST_USAGE:
306                 {
307                         sprint(self, "\nUsage:^3 cmd maplist\n");
308                         sprint(self, "  No arguments required.\n");
309                         return;
310                 }
311         }
312 }
313
314 void ClientCommand_rankings(float request)
315 {
316         switch(request)
317         {
318                 case CC_REQUEST_COMMAND:
319                 {
320                         sprint(self, rankings_reply);
321                         return; // never fall through to usage
322                 }
323                         
324                 default:
325                 case CC_REQUEST_USAGE:
326                 {
327                         sprint(self, "\nUsage:^3 cmd rankings\n");
328                         sprint(self, "  No arguments required.\n");
329                         return;
330                 }
331         }
332 }
333
334 void ClientCommand_ready(float request) 
335 {
336         switch(request)
337         {
338                 case CC_REQUEST_COMMAND:
339                 {
340                         if(self.flags & FL_CLIENT)
341                         {
342                                 if(inWarmupStage || autocvar_sv_ready_restart || g_race_qualifying == 2)
343                                 {
344                                         if(!readyrestart_happened || autocvar_sv_ready_restart_repeatable)
345                                         {
346                                                 if (self.ready) // toggle
347                                                 {
348                                                         self.ready = FALSE;
349                                                         bprint(self.netname, "^2 is ^1NOT^2 ready\n");
350                                                 }
351                                                 else
352                                                 {
353                                                         self.ready = TRUE;
354                                                         bprint(self.netname, "^2 is ready\n");
355                                                 }
356
357                                                 // cannot reset the game while a timeout is active!
358                                                 if(!timeoutStatus)
359                                                         ReadyCount();
360                                         } else {
361                                                 sprint(self, "^1Game has already been restarted\n");
362                                         }
363                                 }
364                         }
365                         return; // never fall through to usage
366                 }
367                         
368                 default:
369                 case CC_REQUEST_USAGE:
370                 {
371                         sprint(self, "\nUsage:^3 cmd ready\n");
372                         sprint(self, "  No arguments required.\n");
373                         return;
374                 }
375         }
376 }
377
378 void ClientCommand_records(float request) // TODO: Isn't this flooding with the sprint messages? Old code, but perhaps bad?
379 {       
380         switch(request)
381         {
382                 case CC_REQUEST_COMMAND:
383                 {
384                         float i;
385                         
386                         for(i = 0; i < 10; ++i)
387                                 sprint(self, records_reply[i]);
388                                 
389                         return; // never fall through to usage
390                 }
391                         
392                 default:
393                 case CC_REQUEST_USAGE:
394                 {
395                         sprint(self, "\nUsage:^3 cmd records\n");
396                         sprint(self, "  No arguments required.\n");
397                         return;
398                 }
399         }
400 }
401
402 void ClientCommand_reportcvar(float request, float argc, string command) // TODO: confirm this works
403 {       
404         switch(request)
405         {
406                 case CC_REQUEST_COMMAND:
407                 {
408                         float tokens;
409                         string s;
410                         
411                         if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
412                         {
413                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
414                                 tokens = tokenize_console(s);
415                         }
416                         GetCvars(1);
417                         return; // never fall through to usage
418                 }
419                         
420                 default:
421                 case CC_REQUEST_USAGE:
422                 {
423                         sprint(self, "\nUsage:^3 cmd reportcvar <cvar>\n");
424                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
425                         return;
426                 }
427         }
428 }
429
430 void ClientCommand_say(float request, float argc, string command)
431 {
432         switch(request)
433         {
434                 case CC_REQUEST_COMMAND:
435                 {
436                         if(argc >= 2) { Say(self, FALSE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
437                         return; // never fall through to usage
438                 }
439                         
440                 default:
441                 case CC_REQUEST_USAGE:
442                 {
443                         sprint(self, "\nUsage:^3 cmd say <message>\n");
444                         sprint(self, "  Where 'message' is the string of text to say.\n");
445                         return;
446                 }
447         }
448 }
449
450 void ClientCommand_say_team(float request, float argc, string command)
451 {
452         switch(request)
453         {
454                 case CC_REQUEST_COMMAND:
455                 {
456                         if(argc >= 2) { Say(self, TRUE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
457                         return; // never fall through to usage
458                 }
459                         
460                 default:
461                 case CC_REQUEST_USAGE:
462                 {
463                         sprint(self, "\nUsage:^3 cmd say_team <message>\n");
464                         sprint(self, "  Where 'message' is the string of text to say.\n");
465                         return;
466                 }
467         }
468 }
469
470 void ClientCommand_selectteam(float request, float argc) // TODO: Update the messages for this command
471 {
472         switch(request)
473         {
474                 case CC_REQUEST_COMMAND:
475                 {
476                         float selection;
477                         
478                         if (self.flags & FL_CLIENT)
479                         {
480                                 if(teamplay)
481                                         if not(self.team_forced > 0) 
482                                                 if not(lockteams) 
483                                                 {
484                                                         switch(argv(1))
485                                                         {
486                                                                 case "red": selection = COLOR_TEAM1; break;
487                                                                 case "blue": selection = COLOR_TEAM2; break;
488                                                                 case "yellow": selection = COLOR_TEAM3; break;
489                                                                 case "pink": selection = COLOR_TEAM4; break;
490                                                                 case "auto": selection = (-1); break;
491                                                                 
492                                                                 default: break;
493                                                         }
494                                                         
495                                                         if(selection)
496                                                         {
497                                                                 if(self.team != selection || self.deadflag != DEAD_NO)
498                                                                         ClientKill_TeamChange(selection);
499                                                                 else
500                                                                         sprint(self, "^7You already are on that team.\n");
501                                                         }
502                                                 }
503                                                 else
504                                                         sprint(self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
505                                         else
506                                                 sprint(self, "^7selectteam can not be used as your team is forced\n");
507                                 else
508                                         sprint(self, "^7selectteam can only be used in teamgames\n");
509                         }
510                         return; // never fall through to usage
511                 }
512
513                 default:
514                 case CC_REQUEST_USAGE:
515                 {
516                         //sprint(self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
517                         sprint(self, "\nUsage:^3 cmd selectteam team\n");
518                         sprint(self, "  Where 'team' is the prefered team to try and join.\n");
519                         sprint(self, "  Full list of options here: \"red, blue, yellow, pink, auto\"\n");
520                         return;
521                 }
522         }
523 }
524
525 void ClientCommand_sentcvar(float request, float argc, string command)
526 {
527         switch(request)
528         {
529                 case CC_REQUEST_COMMAND:
530                 {
531                         float tokens;
532                         string s;
533                         
534                         if(argc == 2) // undefined cvar: use the default value on the server then
535                         {
536                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
537                                 tokens = tokenize_console(s);
538                         }
539                         GetCvars(1);
540                         return; // never fall through to usage
541                 }
542                         
543                 default:
544                 case CC_REQUEST_USAGE:
545                 {
546                         sprint(self, "\nUsage:^3 cmd sentcvar <cvar>\n");
547                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
548                         return;
549                 }
550         }
551 }
552
553 void ClientCommand_spectate(float request)
554 {
555         switch(request)
556         {
557                 case CC_REQUEST_COMMAND:
558                 {
559                         if(self.flags & FL_CLIENT)
560                         {
561                                 if(g_arena) { return; } 
562                                 if(g_lms)
563                                 {
564                                         if(self.lms_spectate_warning)
565                                         {
566                                                 // mark player as spectator
567                                                 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
568                                         }
569                                         else
570                                         {
571                                                 self.lms_spectate_warning = 1;
572                                                 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");
573                                                 return;
574                                         }
575                                 }
576                                 
577                                 if(self.classname == "player" && autocvar_sv_spectate == 1) 
578                                         ClientKill_TeamChange(-2); // observe
579                                 
580                                 // in CA, allow a dead player to move to spectatators (without that, caplayer!=0 will be moved back to the player list)
581                                 // note: if arena game mode is ever done properly, this needs to be removed.
582                                 if(g_ca && self.caplayer && (self.classname == "spectator" || self.classname == "observer"))
583                                 {
584                                         sprint(self, "WARNING: you will spectate in the next round.\n");
585                                         self.caplayer = 0;
586                                 }
587                         }
588                         return; // never fall through to usage
589                 }
590                         
591                 default:
592                 case CC_REQUEST_USAGE:
593                 {
594                         sprint(self, "\nUsage:^3 cmd spectate\n");
595                         sprint(self, "  No arguments required.\n");
596                         return;
597                 }
598         }
599 }
600
601 void ClientCommand_suggestmap(float request, float argc)
602 {
603         switch(request)
604         {
605                 case CC_REQUEST_COMMAND:
606                 {
607                         sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
608                         return; // never fall through to usage
609                 }
610                         
611                 default:
612                 case CC_REQUEST_USAGE:
613                 {
614                         sprint(self, "\nUsage:^3 cmd suggestmap map\n");
615                         sprint(self, "  Where 'map' is the name of the map to suggest.\n");
616                         return;
617                 }
618         }
619 }
620
621 void ClientCommand_teamstatus(float request)
622 {
623         switch(request)
624         {
625                 case CC_REQUEST_COMMAND:
626                 {
627                         Score_NicePrint(self);
628                         return; // never fall through to usage
629                 }
630                         
631                 default:
632                 case CC_REQUEST_USAGE:
633                 {
634                         sprint(self, "\nUsage:^3 cmd teamstatus\n");
635                         sprint(self, "  No arguments required.\n");
636                         return;
637                 }
638         }
639 }
640
641 void ClientCommand_tell(float request, float argc, string command)
642 {
643         switch(request)
644         {
645                 case CC_REQUEST_COMMAND:
646                 {
647                         entity e = GetCommandPlayerSlotTargetFromTokenizedCommand(argc, 1);
648                         
649                         if(e && argc > ParseCommandPlayerSlotTarget_firsttoken)
650                         {
651                                 Say(self, FALSE, e, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)), TRUE);
652                         }
653                         else
654                         {
655                                 if(argc > ParseCommandPlayerSlotTarget_firsttoken)
656                                         trigger_magicear_processmessage_forallears(self, -1, world, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)));
657                         }
658                         return; // never fall through to usage
659                 }
660                         
661                 default:
662                 case CC_REQUEST_USAGE:
663                 {
664                         sprint(self, "\nUsage:^3 cmd tell playerid <message>\n");
665                         sprint(self, "  Where 'playerid' is the entity number of the player to send the 'message' to.\n");
666                         return;
667                 }
668         }
669 }
670
671 void ClientCommand_timein(float request)
672 {
673         switch(request)
674         {
675                 case CC_REQUEST_COMMAND:
676                 {
677                         if(self.flags & FL_CLIENT)
678                         {
679                                 if(autocvar_sv_timeout)
680                                 {
681                                         if (!timeoutStatus)
682                                                 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
683                                         if (self != timeoutInitiator)
684                                                 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
685                                                 
686                                         if (timeoutStatus == 1) 
687                                         {
688                                                 remainingTimeoutTime = timeoutStatus = 0;
689                                                 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
690                                                 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
691                                         }
692                                         else if (timeoutStatus == 2) 
693                                         {
694                                                 //only shorten the remainingTimeoutTime if it makes sense
695                                                 if( remainingTimeoutTime > (autocvar_sv_timeout_resumetime + 1) ) 
696                                                 {
697                                                         bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
698                                                         remainingTimeoutTime = autocvar_sv_timeout_resumetime;
699                                                         timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
700                                                 }
701                                                 else
702                                                         sprint(self, "^7Error: Your resumegame call was discarded!\n");
703                                         }
704                                 }
705                         }
706                         return; // never fall through to usage
707                 }
708                         
709                 default:
710                 case CC_REQUEST_USAGE:
711                 {
712                         sprint(self, "\nUsage:^3 cmd timein\n");
713                         sprint(self, "  No arguments required.\n");
714                         return;
715                 }
716         }
717 }
718
719 void ClientCommand_timeout(float request) // DEAR GOD THIS COMMAND IS TERRIBLE.
720 {
721         switch(request)
722         {
723                 case CC_REQUEST_COMMAND:
724                 {
725                         if(self.flags & FL_CLIENT)
726                         {
727                                 if(autocvar_sv_timeout) 
728                                 {
729                                         if(self.classname == "player") 
730                                         {
731                                                 if(votecalled)
732                                                         sprint(self, "^7Error: you can not call a timeout while a vote is active!\n");
733                                                 else
734                                                 {
735                                                         if (inWarmupStage && !g_warmup_allow_timeout)
736                                                                 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
737                                                         if (time < game_starttime )
738                                                                 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
739                                                                 
740                                                         if (timeoutStatus != 2) {
741                                                                 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
742                                                                 if (autocvar_timelimit) {
743                                                                         //a timelimit was used
744                                                                         float myTl;
745                                                                         myTl = autocvar_timelimit;
746
747                                                                         float lastPossibleTimeout;
748                                                                         lastPossibleTimeout = (myTl*60) - autocvar_sv_timeout_leadtime - 1;
749
750                                                                         if (lastPossibleTimeout < time - game_starttime)
751                                                                                 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
752                                                                 }
753                                                         }
754                                                         
755                                                         //player may not call a timeout if he has no calls left
756                                                         if (self.allowedTimeouts < 1)
757                                                                 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
758                                                                 
759                                                                 
760                                                         //now all required checks are passed
761                                                         self.allowedTimeouts -= 1;
762                                                         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)
763                                                         remainingTimeoutTime = autocvar_sv_timeout_length;
764                                                         remainingLeadTime = autocvar_sv_timeout_leadtime;
765                                                         timeoutInitiator = self;
766                                                         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
767                                                                 timeoutStatus = 1;
768                                                                 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
769                                                                 timeoutHandler = spawn();
770                                                                 timeoutHandler.think = timeoutHandler_Think;
771                                                         }
772                                                         timeoutHandler.nextthink = time; //always let the entity think asap
773
774                                                         //inform all connected clients about the timeout call
775                                                         Announce("timeoutcalled");
776                                                 }
777                                         }
778                                         else
779                                                 sprint(self, "^7Error: only players can call a timeout!\n");
780                                 }
781                         }
782                         return; // never fall through to usage
783                 }
784                         
785                 default:
786                 case CC_REQUEST_USAGE:
787                 {
788                         sprint(self, "\nUsage:^3 cmd timeout\n");
789                         sprint(self, "  No arguments required.\n");
790                         return;
791                 }
792         }
793 }
794
795 void ClientCommand_voice(float request, float argc, string command)
796 {
797         switch(request)
798         {
799                 case CC_REQUEST_COMMAND:
800                 {
801                         if(argc >= 3)
802                                 VoiceMessage(argv(1), substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
803                         else
804                                 VoiceMessage(argv(1), "");
805                         return; // never fall through to usage
806                 }
807                         
808                 default:
809                 case CC_REQUEST_USAGE:
810                 {
811                         sprint(self, "\nUsage:^3 cmd voice\n");
812                         sprint(self, "  FIXME ARGUMENTS UNKNOWN.\n");
813                         return;
814                 }
815         }
816 }
817
818 /* use this when creating a new command, making sure to place it in alphabetical order.
819 void ClientCommand_(float request)
820 {
821         switch(request)
822         {
823                 case CC_REQUEST_COMMAND:
824                 {
825                         
826                         return; // never fall through to usage
827                 }
828                         
829                 default:
830                 case CC_REQUEST_USAGE:
831                 {
832                         sprint(self, "\nUsage:^3 cmd \n");
833                         sprint(self, "  No arguments required.\n");
834                         return;
835                 }
836         }
837 }
838 */
839
840
841 // =====================================
842 //  Macro system for networked commands
843 // =====================================
844
845 // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
846 #define CLIENT_COMMANDS(request,arguments,command) \
847         CLIENT_COMMAND("autoswitch", ClientCommand_autoswitch(request, arguments), "Whether or not to switch automatically when getting a better weapon") \
848         CLIENT_COMMAND("checkfail", ClientCommand_checkfail(request, command), "Report if a client-side check failed") \
849         CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \
850         CLIENT_COMMAND("cvar_changes", ClientCommand_cvar_changes(request), "Prints a list of all changed server cvars") \
851         CLIENT_COMMAND("cvar_purechanges", ClientCommand_cvar_purechanges(request), "Prints a list of all changed gameplay cvars") \
852         CLIENT_COMMAND("info", ClientCommand_info(request, arguments), "Request for unique server information set up by admin") \
853         CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \
854         CLIENT_COMMAND("ladder", ClientCommand_ladder(request), "Get information about top players if supported") \
855         CLIENT_COMMAND("lsmaps", ClientCommand_lsmaps(request), "List maps which can be used with the current game mode") \
856         CLIENT_COMMAND("lsnewmaps", ClientCommand_lsnewmaps(request), "List maps which TODO") \
857         CLIENT_COMMAND("maplist", ClientCommand_maplist(request), "Full server maplist reply") \
858         CLIENT_COMMAND("rankings", ClientCommand_rankings(request), "Print information about rankings") \
859         CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
860         CLIENT_COMMAND("records", ClientCommand_records(request), "List top 10 records for the current map") \
861         CLIENT_COMMAND("reportcvar", ClientCommand_reportcvar(request, arguments, command), "Old system for sending a client cvar to the server") \
862         CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \
863         CLIENT_COMMAND("say_team", ClientCommand_say_team(request, arguments, command), "Print a message to chat to all team mates") \
864         CLIENT_COMMAND("selectteam", ClientCommand_selectteam(request, arguments), "Attempt to choose a team to join into") \
865         CLIENT_COMMAND("sentcvar", ClientCommand_sentcvar(request, arguments, command), "New system for sending a client cvar to the server") \
866         CLIENT_COMMAND("spectate", ClientCommand_spectate(request), "Become an observer") \
867         CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(request, arguments), "Suggest a map to the mapvote at match end") \
868         CLIENT_COMMAND("teamstatus", ClientCommand_teamstatus(request), "Print detailed score information for all players") \
869         CLIENT_COMMAND("tell", ClientCommand_tell(request, arguments, command), "Send a message directly to a player") \
870         CLIENT_COMMAND("timein", ClientCommand_timein(request), "Resume the game from being paused with a timeout") \
871         CLIENT_COMMAND("timeout", ClientCommand_timeout(request), "Call a timeout which pauses the game for certain amount of time unless unpaused") \
872         CLIENT_COMMAND("voice", ClientCommand_voice(request, arguments, command), "Send voice message via sound") \
873         /* nothing */
874         
875 void ClientCommand_macro_help()
876 {
877         #define CLIENT_COMMAND(name,function,description) \
878                 { print("  ^2", name, "^7: ", description, "\n"); }
879                 
880         CLIENT_COMMANDS(0, 0, "")
881         #undef CLIENT_COMMAND
882         
883         return;
884 }
885
886 float ClientCommand_macro_command(float argc, string command)
887 {
888         #define CLIENT_COMMAND(name,function,description) \
889                 { if(name == strtolower(argv(0))) { function; return TRUE; } }
890                 
891         CLIENT_COMMANDS(CC_REQUEST_COMMAND, argc, command)
892         #undef CLIENT_COMMAND
893         
894         return FALSE;
895 }
896
897 float ClientCommand_macro_usage(float argc, string command)
898 {
899         #define CLIENT_COMMAND(name,function,description) \
900                 { if(name == strtolower(argv(1))) { function; return TRUE; } }
901                 
902         CLIENT_COMMANDS(CC_REQUEST_USAGE, argc, command)
903         #undef CLIENT_COMMAND
904         
905         return FALSE;
906 }
907
908
909 // ======================================
910 //  Main Function Called By Engine (cmd)
911 // ======================================
912 // If this function exists, server game code parses clientcommand before the engine code gets it.
913
914 void SV_ParseClientCommand(string command)
915 {
916         float argc = tokenize_console(command);
917         
918         // for floodcheck
919         switch(strtolower(argv(0)))
920         {
921                 // exempt commands which are not subject to floodcheck
922                 case "begin": break; // handled by engine in host_cmd.c
923                 case "pause": break; // handled by engine in host_cmd.c
924                 case "prespawn": break; // handled by engine in host_cmd.c
925                 case "reportcvar": break; // handled by server in this file
926                 case "sentcvar": break; // handled by server in this file
927                 case "spawn": break; // handled by engine in host_cmd.c
928                 
929                 default: 
930                         if(SV_ParseClientCommand_floodcheck())
931                                 break; // "TRUE": continue, as we're not flooding yet
932                         else
933                                 return; // "FALSE": not allowed to continue, halt
934         }
935         
936         /* NOTE: totally disabled for now, however the functionality and descriptions are there if we ever want it.
937         if(argv(0) == "help") 
938         {
939                 if(argc == 1) 
940                 {
941                         sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are:\n");
942                         ClientCommand_macro_help;
943                         sprint(self, "For help about specific commands, type cmd help COMMAND\n");
944                         return;
945                 } 
946                 else if(ClientCommand_macro_usage(argc, command)) // Instead of trying to call a command, we're going to see detailed information about it
947                 {
948                         return;
949                 }
950         } 
951         else*/ if(MUTATOR_CALLHOOK(SV_ParseClientCommand))
952         {
953                 return; // handled by a mutator
954         }
955         else if(GameCommand_Vote(command, self)) 
956         {
957                 return; // handled by server/vote.qc 
958         }
959         else if(GameCommand_MapVote(argv(0))) 
960         {
961                 return; // handled by server/g_world.qc
962         }
963         else if(CheatCommand(argc)) 
964         {
965                 return; // handled by server/cheats.qc
966         }
967         else if(ClientCommand_macro_command(argc, command)) // continue as usual and scan for normal commands
968         {
969                 return; // handled by one of the above GameCommand_* functions
970         }
971         else
972                 clientcommand(self, command);
973 }