]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/command/cmd.qc
Now sv_cmd.qc uses common commands too -- plus some extra fixes.
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / command / cmd.qc
1 // =========================================================
2 //  Server side networked commands code, reworked by Samual
3 //  Last updated: December 13th, 2011
4 // =========================================================
5
6 // declarations in cmd.qh
7
8 // move any necessary sprint statements to "print_to"
9
10 float SV_ParseClientCommand_floodcheck()
11 {
12         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)
13         {
14                 if(time <= (self.cmd_floodtime + autocvar_sv_clientcommand_antispam_time))
15                 {
16                         self.cmd_floodcount += 1;
17                         if(self.cmd_floodcount > autocvar_sv_clientcommand_antispam_count) { return FALSE; } // too much spam, halt
18                 }
19                 else
20                 {
21                         self.cmd_floodtime = time;
22                         self.cmd_floodcount = 1;
23                 }
24         }
25         return TRUE; // continue, as we're not flooding yet
26 }
27
28
29 // =======================
30 //  Command Sub-Functions
31 // =======================
32
33 void ClientCommand_autoswitch(float request, float argc)
34 {
35         switch(request)
36         {
37                 case CMD_REQUEST_COMMAND:
38                 {
39                         self.autoswitch = ("0" != argv(1));
40                         sprint(self, strcat("^1autoswitch is currently turned ", (self.autoswitch ? "on" : "off"), ".\n"));
41                         return; // never fall through to usage
42                 }
43                         
44                 default:
45                 case CMD_REQUEST_USAGE:
46                 {
47                         sprint(self, "\nUsage:^3 cmd autoswitch selection\n");
48                         sprint(self, "  Where 'selection' is 1 or 0 for on or off.\n"); 
49                         return;
50                 }
51         }
52 }
53
54 void ClientCommand_checkfail(float request, string command) // used only by client side code
55 {
56         switch(request)
57         {
58                 case CMD_REQUEST_COMMAND:
59                 {
60                         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))));
61                         self.checkfail = 1;
62                         return; // never fall through to usage
63                 }
64                         
65                 default:
66                 case CMD_REQUEST_USAGE:
67                 {
68                         sprint(self, "\nUsage:^3 cmd checkfail message\n");
69                         sprint(self, "  Where 'message' is the message reported by client about the fail.\n");
70                         return;
71                 }
72         }
73 }
74
75 void ClientCommand_clientversion(float request, float argc) // used only by client side code
76 {
77         switch(request)
78         {
79                 case CMD_REQUEST_COMMAND:
80                 {
81                         if(self.flags & FL_CLIENT)
82                         {
83                                 self.version = ((argv(1) == "$gameversion") ? 1 : stof(argv(1)));
84                                 
85                                 if(self.version < autocvar_gameversion_min || self.version > autocvar_gameversion_max)
86                                 {
87                                         self.version_mismatch = 1;
88                                         ClientKill_TeamChange(-2); // observe
89                                 } 
90                                 else if(autocvar_g_campaign || autocvar_g_balance_teams || autocvar_g_balance_teams_force) 
91                                 {
92                                         //JoinBestTeam(self, FALSE, TRUE);
93                                 } 
94                                 else if(teamplay && !autocvar_sv_spectate && !(self.team_forced > 0)) 
95                                 {
96                                         self.classname = "observer"; // really?
97                                         stuffcmd(self, "menu_showteamselect\n");
98                                 }
99                         }
100                         return; // never fall through to usage
101                 }
102                         
103                 default:
104                 case CMD_REQUEST_USAGE:
105                 {
106                         sprint(self, "\nUsage:^3 cmd clientversion version\n");
107                         sprint(self, "  Where 'version' is the game version reported by self.\n");
108                         return;
109                 }
110         }
111 }
112
113 void ClientCommand_getmapvotepic(float request, float argc)
114 {
115         switch(request)
116         {
117                 case CMD_REQUEST_COMMAND:
118                 {
119                         if(intermission_running)                                
120                                 MapVote_SendPicture(stof(argv(1)));
121
122                         return; // never fall through to usage
123                 }
124                         
125                 default:
126                 case CMD_REQUEST_USAGE:
127                 {
128                         sprint(self, "\nUsage:^3 cmd getmapvotepic mapid\n");
129                         sprint(self, "  Where 'mapid' is the id number of the map to request an image of on the map vote selection menu.\n");
130                         return;
131                 }
132         }
133 }
134
135 void ClientCommand_join(float request)
136 {
137         switch(request)
138         {
139                 case CMD_REQUEST_COMMAND:
140                 {
141                         if(self.flags & FL_CLIENT)
142                         {
143                                 if(self.classname != "player" && !lockteams && !g_arena)
144                                 {
145                                         if(nJoinAllowed(1)) 
146                                         {
147                                                 if(g_ca) { self.caplayer = 1; }
148                                                 if(autocvar_g_campaign) { campaign_bots_may_start = 1; }
149                                                 
150                                                 self.classname = "player";
151                                                 PlayerScore_Clear(self);
152                                                 bprint ("^4", self.netname, "^4 is playing now\n");
153                                                 PutClientInServer();
154                                         }
155                                         else 
156                                         {
157                                                 //player may not join because of g_maxplayers is set
158                                                 centerprint(self, PREVENT_JOIN_TEXT);
159                                         }
160                                 }
161                         }
162                         return; // never fall through to usage
163                 }
164                         
165                 default:
166                 case CMD_REQUEST_USAGE:
167                 {
168                         sprint(self, "\nUsage:^3 cmd join\n");
169                         sprint(self, "  No arguments required.\n");
170                         return;
171                 }
172         }
173 }
174
175 void ClientCommand_ready(float request) 
176 {
177         switch(request)
178         {
179                 case CMD_REQUEST_COMMAND:
180                 {
181                         if(self.flags & FL_CLIENT)
182                         {
183                                 if(inWarmupStage || autocvar_sv_ready_restart || g_race_qualifying == 2)
184                                 {
185                                         if(!readyrestart_happened || autocvar_sv_ready_restart_repeatable)
186                                         {
187                                                 if (self.ready) // toggle
188                                                 {
189                                                         self.ready = FALSE;
190                                                         bprint(self.netname, "^2 is ^1NOT^2 ready\n");
191                                                 }
192                                                 else
193                                                 {
194                                                         self.ready = TRUE;
195                                                         bprint(self.netname, "^2 is ready\n");
196                                                 }
197
198                                                 // cannot reset the game while a timeout is active!
199                                                 if(!timeoutStatus)
200                                                         ReadyCount();
201                                         } else {
202                                                 sprint(self, "^1Game has already been restarted\n");
203                                         }
204                                 }
205                         }
206                         return; // never fall through to usage
207                 }
208                         
209                 default:
210                 case CMD_REQUEST_USAGE:
211                 {
212                         sprint(self, "\nUsage:^3 cmd ready\n");
213                         sprint(self, "  No arguments required.\n");
214                         return;
215                 }
216         }
217 }
218
219 void ClientCommand_reportcvar(float request, float argc, string command) // TODO: confirm this works
220 {       
221         switch(request)
222         {
223                 case CMD_REQUEST_COMMAND:
224                 {
225                         float tokens;
226                         string s;
227                         
228                         if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
229                         {
230                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
231                                 tokens = tokenize_console(s);
232                         }
233                         GetCvars(1);
234                         return; // never fall through to usage
235                 }
236                         
237                 default:
238                 case CMD_REQUEST_USAGE:
239                 {
240                         sprint(self, "\nUsage:^3 cmd reportcvar <cvar>\n");
241                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
242                         return;
243                 }
244         }
245 }
246
247 void ClientCommand_say(float request, float argc, string command)
248 {
249         switch(request)
250         {
251                 case CMD_REQUEST_COMMAND:
252                 {
253                         if(argc >= 2) { Say(self, FALSE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
254                         return; // never fall through to usage
255                 }
256                         
257                 default:
258                 case CMD_REQUEST_USAGE:
259                 {
260                         sprint(self, "\nUsage:^3 cmd say <message>\n");
261                         sprint(self, "  Where 'message' is the string of text to say.\n");
262                         return;
263                 }
264         }
265 }
266
267 void ClientCommand_say_team(float request, float argc, string command)
268 {
269         switch(request)
270         {
271                 case CMD_REQUEST_COMMAND:
272                 {
273                         if(argc >= 2) { Say(self, TRUE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
274                         return; // never fall through to usage
275                 }
276                         
277                 default:
278                 case CMD_REQUEST_USAGE:
279                 {
280                         sprint(self, "\nUsage:^3 cmd say_team <message>\n");
281                         sprint(self, "  Where 'message' is the string of text to say.\n");
282                         return;
283                 }
284         }
285 }
286
287 void ClientCommand_selectteam(float request, float argc) // TODO: Update the messages for this command
288 {
289         switch(request)
290         {
291                 case CMD_REQUEST_COMMAND:
292                 {
293                         float selection;
294                         
295                         if (self.flags & FL_CLIENT)
296                         {
297                                 if(teamplay)
298                                         if not(self.team_forced > 0) 
299                                                 if not(lockteams) 
300                                                 {
301                                                         switch(argv(1))
302                                                         {
303                                                                 case "red": selection = COLOR_TEAM1; break;
304                                                                 case "blue": selection = COLOR_TEAM2; break;
305                                                                 case "yellow": selection = COLOR_TEAM3; break;
306                                                                 case "pink": selection = COLOR_TEAM4; break;
307                                                                 case "auto": selection = (-1); break;
308                                                                 
309                                                                 default: break;
310                                                         }
311                                                         
312                                                         if(selection)
313                                                         {
314                                                                 if(self.team == selection && self.deadflag == DEAD_NO)
315                                                                         sprint(self, "^7You already are on that team.\n");
316                                                                 else if(self.wasplayer && autocvar_g_changeteam_banned)
317                                                                         sprint(self, "^1You cannot change team, forbidden by the server.\n");
318                                                                 else
319                                                                         ClientKill_TeamChange(selection);
320                                                         }
321                                                 }
322                                                 else
323                                                         sprint(self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
324                                         else
325                                                 sprint(self, "^7selectteam can not be used as your team is forced\n");
326                                 else
327                                         sprint(self, "^7selectteam can only be used in teamgames\n");
328                         }
329                         return; // never fall through to usage
330                 }
331
332                 default:
333                 case CMD_REQUEST_USAGE:
334                 {
335                         //sprint(self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
336                         sprint(self, "\nUsage:^3 cmd selectteam team\n");
337                         sprint(self, "  Where 'team' is the prefered team to try and join.\n");
338                         sprint(self, "  Full list of options here: \"red, blue, yellow, pink, auto\"\n");
339                         return;
340                 }
341         }
342 }
343
344 void ClientCommand_selfstuff(float request, string command)
345 {
346         switch(request)
347         {
348                 case CMD_REQUEST_COMMAND:
349                 {
350                         stuffcmd(self, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)));
351                         return; // never fall through to usage
352                 }
353                         
354                 default:
355                 case CMD_REQUEST_USAGE:
356                 {
357                         sprint(self, "\nUsage:^3 cmd selfstuff command\n");
358                         sprint(self, "  Where 'command' is the string to be stuffed to your client.\n");
359                         return;
360                 }
361         }
362 }
363
364 void ClientCommand_sentcvar(float request, float argc, string command)
365 {
366         switch(request)
367         {
368                 case CMD_REQUEST_COMMAND:
369                 {
370                         float tokens;
371                         string s;
372                         
373                         if(argc == 2) // undefined cvar: use the default value on the server then
374                         {
375                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
376                                 tokens = tokenize_console(s);
377                         }
378                         GetCvars(1);
379                         return; // never fall through to usage
380                 }
381                         
382                 default:
383                 case CMD_REQUEST_USAGE:
384                 {
385                         sprint(self, "\nUsage:^3 cmd sentcvar <cvar>\n");
386                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
387                         return;
388                 }
389         }
390 }
391
392 void ClientCommand_spectate(float request)
393 {
394         switch(request)
395         {
396                 case CMD_REQUEST_COMMAND:
397                 {
398                         if(self.flags & FL_CLIENT)
399                         {
400                                 if(g_arena) { return; } 
401                                 if(g_lms)
402                                 {
403                                         if(self.lms_spectate_warning)
404                                         {
405                                                 // mark player as spectator
406                                                 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
407                                         }
408                                         else
409                                         {
410                                                 self.lms_spectate_warning = 1;
411                                                 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");
412                                                 return;
413                                         }
414                                 }
415                                 
416                                 if(self.classname == "player" && autocvar_sv_spectate == 1) 
417                                         ClientKill_TeamChange(-2); // observe
418                                 
419                                 // in CA, allow a dead player to move to spectatators (without that, caplayer!=0 will be moved back to the player list)
420                                 // note: if arena game mode is ever done properly, this needs to be removed.
421                                 if(g_ca && self.caplayer && (self.classname == "spectator" || self.classname == "observer"))
422                                 {
423                                         sprint(self, "WARNING: you will spectate in the next round.\n");
424                                         self.caplayer = 0;
425                                 }
426                         }
427                         return; // never fall through to usage
428                 }
429                         
430                 default:
431                 case CMD_REQUEST_USAGE:
432                 {
433                         sprint(self, "\nUsage:^3 cmd spectate\n");
434                         sprint(self, "  No arguments required.\n");
435                         return;
436                 }
437         }
438 }
439
440 void ClientCommand_suggestmap(float request, float argc)
441 {
442         switch(request)
443         {
444                 case CMD_REQUEST_COMMAND:
445                 {
446                         sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
447                         return; // never fall through to usage
448                 }
449                         
450                 default:
451                 case CMD_REQUEST_USAGE:
452                 {
453                         sprint(self, "\nUsage:^3 cmd suggestmap map\n");
454                         sprint(self, "  Where 'map' is the name of the map to suggest.\n");
455                         return;
456                 }
457         }
458 }
459
460 void ClientCommand_tell(float request, float argc, string command)
461 {
462         switch(request)
463         {
464                 case CMD_REQUEST_COMMAND:
465                 {
466                         entity e = GetCommandPlayerSlotTargetFromTokenizedCommand(argc, 1);
467                         
468                         if(e && argc > ParseCommandPlayerSlotTarget_firsttoken)
469                         {
470                                 Say(self, FALSE, e, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)), TRUE);
471                         }
472                         else
473                         {
474                                 if(argc > ParseCommandPlayerSlotTarget_firsttoken)
475                                         trigger_magicear_processmessage_forallears(self, -1, world, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)));
476                         }
477                         return; // never fall through to usage
478                 }
479                         
480                 default:
481                 case CMD_REQUEST_USAGE:
482                 {
483                         sprint(self, "\nUsage:^3 cmd tell playerid <message>\n");
484                         sprint(self, "  Where 'playerid' is the entity number of the player to send the 'message' to.\n");
485                         return;
486                 }
487         }
488 }
489
490 void ClientCommand_voice(float request, float argc, string command)
491 {
492         switch(request)
493         {
494                 case CMD_REQUEST_COMMAND:
495                 {
496                         if(argc >= 3)
497                                 VoiceMessage(argv(1), substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
498                         else
499                                 VoiceMessage(argv(1), "");
500                         return; // never fall through to usage
501                 }
502                         
503                 default:
504                 case CMD_REQUEST_USAGE:
505                 {
506                         sprint(self, "\nUsage:^3 cmd voice\n");
507                         sprint(self, "  FIXME ARGUMENTS UNKNOWN.\n");
508                         return;
509                 }
510         }
511 }
512
513 /* use this when creating a new command, making sure to place it in alphabetical order.
514 void ClientCommand_(float request)
515 {
516         switch(request)
517         {
518                 case CMD_REQUEST_COMMAND:
519                 {
520                         
521                         return; // never fall through to usage
522                 }
523                         
524                 default:
525                 case CMD_REQUEST_USAGE:
526                 {
527                         sprint(self, "\nUsage:^3 cmd \n");
528                         sprint(self, "  No arguments required.\n");
529                         return;
530                 }
531         }
532 }
533 */
534
535
536 // =====================================
537 //  Macro system for networked commands
538 // =====================================
539
540 // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
541 #define CLIENT_COMMANDS(request,arguments,command) \
542         CLIENT_COMMAND("autoswitch", ClientCommand_autoswitch(request, arguments), "Whether or not to switch automatically when getting a better weapon") \
543         CLIENT_COMMAND("checkfail", ClientCommand_checkfail(request, command), "Report if a client-side check failed") \
544         CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \
545         CLIENT_COMMAND("cvar_changes", CommonCommand_cvar_changes(request), "Prints a list of all changed server cvars") \
546         CLIENT_COMMAND("cvar_purechanges", CommonCommand_cvar_purechanges(request), "Prints a list of all changed gameplay cvars") \
547         CLIENT_COMMAND("getmapvotepic", ClientCommand_getmapvotepic(request, arguments), "Retrieve mapshot picture from the server") \
548         CLIENT_COMMAND("info", CommonCommand_info(request, arguments), "Request for unique server information set up by admin") \
549         CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \
550         CLIENT_COMMAND("ladder", CommonCommand_ladder(request), "Get information about top players if supported") \
551         CLIENT_COMMAND("lsmaps", CommonCommand_lsmaps(request), "List maps which can be used with the current game mode") \
552         CLIENT_COMMAND("lsnewmaps", CommonCommand_lsnewmaps(request), "List maps which TODO") \
553         CLIENT_COMMAND("maplist", CommonCommand_maplist(request), "Display full server maplist reply") \
554         CLIENT_COMMAND("rankings", CommonCommand_rankings(request), "Print information about rankings") \
555         CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
556         CLIENT_COMMAND("records", CommonCommand_records(request), "List top 10 records for the current map") \
557         CLIENT_COMMAND("reportcvar", ClientCommand_reportcvar(request, arguments, command), "Old system for sending a client cvar to the server") \
558         CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \
559         CLIENT_COMMAND("say_team", ClientCommand_say_team(request, arguments, command), "Print a message to chat to all team mates") \
560         CLIENT_COMMAND("selectteam", ClientCommand_selectteam(request, arguments), "Attempt to choose a team to join into") \
561         CLIENT_COMMAND("selfstuff", ClientCommand_selfstuff(request, command), "Stuffcmd a command to your own client") \
562         CLIENT_COMMAND("sentcvar", ClientCommand_sentcvar(request, arguments, command), "New system for sending a client cvar to the server") \
563         CLIENT_COMMAND("spectate", ClientCommand_spectate(request), "Become an observer") \
564         CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(request, arguments), "Suggest a map to the mapvote at match end") \
565         CLIENT_COMMAND("teamstatus", CommonCommand_teamstatus(request), "Show information about player and team scores") \
566         CLIENT_COMMAND("tell", ClientCommand_tell(request, arguments, command), "Send a message directly to a player") \
567         CLIENT_COMMAND("timein", CommonCommand_timein(request), "Resume the game from being paused with a timeout") \
568         CLIENT_COMMAND("timeout", CommonCommand_timeout(request), "Call a timeout which pauses the game for certain amount of time unless unpaused") \
569         CLIENT_COMMAND("voice", ClientCommand_voice(request, arguments, command), "Send voice message via sound") \
570         CLIENT_COMMAND("vote", VoteCommand(request, self, arguments, command), "Request an action to be voted upon by players") \
571         CLIENT_COMMAND("who", CommonCommand_who(request), "Display detailed client information about all players") \
572         /* nothing */
573         
574 void ClientCommand_macro_help()
575 {
576         #define CLIENT_COMMAND(name,function,description) \
577                 { print("  ^2", name, "^7: ", description, "\n"); }
578                 
579         CLIENT_COMMANDS(0, 0, "")
580         #undef CLIENT_COMMAND
581         
582         return;
583 }
584
585 float ClientCommand_macro_command(float argc, string command)
586 {
587         #define CLIENT_COMMAND(name,function,description) \
588                 { if(name == strtolower(argv(0))) { function; return TRUE; } }
589                 
590         CLIENT_COMMANDS(CMD_REQUEST_COMMAND, argc, command)
591         #undef CLIENT_COMMAND
592         
593         return FALSE;
594 }
595
596 float ClientCommand_macro_usage(float argc, string command)
597 {
598         #define CLIENT_COMMAND(name,function,description) \
599                 { if(name == strtolower(argv(1))) { function; return TRUE; } }
600                 
601         CLIENT_COMMANDS(CMD_REQUEST_USAGE, argc, command)
602         #undef CLIENT_COMMAND
603         
604         return FALSE;
605 }
606
607
608 // ======================================
609 //  Main Function Called By Engine (cmd)
610 // ======================================
611 // If this function exists, server game code parses clientcommand before the engine code gets it.
612
613 void SV_ParseClientCommand(string command)
614 {
615         float argc = tokenize_console(command);
616         
617         // for floodcheck
618         switch(strtolower(argv(0)))
619         {
620                 // exempt commands which are not subject to floodcheck
621                 case "begin": break; // handled by engine in host_cmd.c
622                 case "getmapvotepic": break; // handled by server in this file
623                 case "pause": break; // handled by engine in host_cmd.c
624                 case "prespawn": break; // handled by engine in host_cmd.c
625                 case "reportcvar": break; // handled by server in this file
626                 case "sentcvar": break; // handled by server in this file
627                 case "spawn": break; // handled by engine in host_cmd.c
628                 
629                 default: 
630                         if(SV_ParseClientCommand_floodcheck())
631                                 break; // "TRUE": continue, as we're not flooding yet
632                         else
633                                 return; // "FALSE": not allowed to continue, halt
634         }
635         
636         /* NOTE: totally disabled for now, however the functionality and descriptions are there if we ever want it.
637         if(argv(0) == "help") 
638         {
639                 if(argc == 1) 
640                 {
641                         sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are:\n");
642                         ClientCommand_macro_help;
643                         sprint(self, "For help about specific commands, type cmd help COMMAND\n");
644                         return;
645                 } 
646                 else if(ClientCommand_macro_usage(argc, command)) // Instead of trying to call a command, we're going to see detailed information about it
647                 {
648                         return;
649                 }
650         } 
651         else*/ if(MUTATOR_CALLHOOK(SV_ParseClientCommand))
652         {
653                 return; // handled by a mutator
654         }
655         else if(CheatCommand(argc)) 
656         {
657                 return; // handled by server/cheats.qc
658         }
659         else if(ClientCommand_macro_command(argc, command)) // continue as usual and scan for normal commands
660         {
661                 return; // handled by one of the above GameCommand_* functions
662         }
663         else
664                 clientcommand(self, command);
665 }