]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/command/cmd.qc
Merge branch 'master' into terencehill/menu_hudskin_selector
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / command / cmd.qc
1 #include "../../common/command/command.qh"
2 #include "cmd.qh"
3
4 #include "common.qh"
5 #include "vote.qh"
6
7 #include "../campaign.qh"
8 #include "../cheats.qh"
9 #include "../cl_player.qh"
10 #include "../ipban.qh"
11 #include "../mapvoting.qh"
12 #include "../scores.qh"
13 #include "../teamplay.qh"
14
15 #include "../mutators/all.qh"
16
17 #ifdef SVQC
18         #include "../../common/vehicles/all.qh"
19 #endif
20
21 #include "../../common/constants.qh"
22 #include "../../common/deathtypes/all.qh"
23 #include "../../common/mapinfo.qh"
24 #include "../../common/notifications.qh"
25 #include "../../common/physics.qh"
26 #include "../../common/teams.qh"
27 #include "../../common/util.qh"
28 #include "../../common/triggers/triggers.qh"
29
30 #include "../../common/minigames/sv_minigames.qh"
31
32 #include "../../common/monsters/all.qc"
33 #include "../../common/monsters/spawn.qh"
34 #include "../../common/monsters/sv_monsters.qh"
35
36 #include "../../lib/warpzone/common.qh"
37
38 void ClientKill_TeamChange(float targetteam);  // 0 = don't change, -1 = auto, -2 = spec
39
40 // =========================================================
41 //  Server side networked commands code, reworked by Samual
42 //  Last updated: December 28th, 2011
43 // =========================================================
44
45 float SV_ParseClientCommand_floodcheck()
46 {
47         SELFPARAM();
48         if (!timeout_status)  // not while paused
49         {
50                 if (time <= (self.cmd_floodtime + autocvar_sv_clientcommand_antispam_time))
51                 {
52                         self.cmd_floodcount += 1;
53                         if (self.cmd_floodcount > autocvar_sv_clientcommand_antispam_count)   return false;  // too much spam, halt
54                 }
55                 else
56                 {
57                         self.cmd_floodtime = time;
58                         self.cmd_floodcount = 1;
59                 }
60         }
61         return true;  // continue, as we're not flooding yet
62 }
63
64
65 // =======================
66 //  Command Sub-Functions
67 // =======================
68
69 void ClientCommand_autoswitch(float request, float argc)
70 {
71         SELFPARAM();
72         switch (request)
73         {
74                 case CMD_REQUEST_COMMAND:
75                 {
76                         if (argv(1) != "")
77                         {
78                                 self.autoswitch = InterpretBoolean(argv(1));
79                                 sprint(self, strcat("^1autoswitch is currently turned ", (self.autoswitch ? "on" : "off"), ".\n"));
80                                 return;
81                         }
82                 }
83
84                 default:
85                         sprint(self, "Incorrect parameters for ^2autoswitch^7\n");
86                 case CMD_REQUEST_USAGE:
87                 {
88                         sprint(self, "\nUsage:^3 cmd autoswitch selection\n");
89                         sprint(self, "  Where 'selection' controls if autoswitch is on or off.\n");
90                         return;
91                 }
92         }
93 }
94
95 void ClientCommand_clientversion(float request, float argc)  // internal command, used only by code
96 {
97         SELFPARAM();
98         switch (request)
99         {
100                 case CMD_REQUEST_COMMAND:
101                 {
102                         if (argv(1) != "")
103                         {
104                                 if (IS_CLIENT(self))
105                                 {
106                                         self.version = ((argv(1) == "$gameversion") ? 1 : stof(argv(1)));
107
108                                         if (self.version < autocvar_gameversion_min || self.version > autocvar_gameversion_max)
109                                         {
110                                                 self.version_mismatch = 1;
111                                                 ClientKill_TeamChange(-2);  // observe
112                                         }
113                                         else if (autocvar_g_campaign || autocvar_g_balance_teams)
114                                         {
115                                                 // JoinBestTeam(self, false, true);
116                                         }
117                                         else if (teamplay && !autocvar_sv_spectate && !(self.team_forced > 0))
118                                         {
119                                                 self.classname = STR_OBSERVER;  // really?
120                                                 stuffcmd(self, "menu_showteamselect\n");
121                                         }
122                                 }
123
124                                 return;
125                         }
126                 }
127
128                 default:
129                         sprint(self, "Incorrect parameters for ^2clientversion^7\n");
130                 case CMD_REQUEST_USAGE:
131                 {
132                         sprint(self, "\nUsage:^3 cmd clientversion version\n");
133                         sprint(self, "  Where 'version' is the game version reported by self.\n");
134                         return;
135                 }
136         }
137 }
138
139 void ClientCommand_mv_getpicture(float request, float argc)  // internal command, used only by code
140 {
141         SELFPARAM();
142         switch (request)
143         {
144                 case CMD_REQUEST_COMMAND:
145                 {
146                         if (argv(1) != "")
147                         {
148                                 if (intermission_running) MapVote_SendPicture(stof(argv(1)));
149
150                                 return;
151                         }
152                 }
153
154                 default:
155                         sprint(self, "Incorrect parameters for ^2mv_getpicture^7\n");
156                 case CMD_REQUEST_USAGE:
157                 {
158                         sprint(self, "\nUsage:^3 cmd mv_getpicture mapid\n");
159                         sprint(self, "  Where 'mapid' is the id number of the map to request an image of on the map vote selection menu.\n");
160                         return;
161                 }
162         }
163 }
164
165 void ClientCommand_join(float request)
166 {
167         SELFPARAM();
168         switch (request)
169         {
170                 case CMD_REQUEST_COMMAND:
171                 {
172                         if (IS_CLIENT(self))
173                         {
174                                 if (!IS_PLAYER(self) && !lockteams && !gameover)
175                                 {
176                                         if (self.caplayer) return;
177                                         if (nJoinAllowed(self))
178                                         {
179                                                 if (autocvar_g_campaign)   campaign_bots_may_start = 1;
180                                                 self.classname = STR_PLAYER;
181                                                 PlayerScore_Clear(self);
182                                                 Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER_CPID, CPID_PREVENT_JOIN);
183                                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JOIN_PLAY, self.netname);
184                                                 PutClientInServer();
185                                         }
186                                         else
187                                         {
188                                                 // player may not join because of g_maxplayers is set
189                                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_JOIN_PREVENT);
190                                         }
191                                 }
192                         }
193                         return;  // never fall through to usage
194                 }
195
196                 default:
197                 case CMD_REQUEST_USAGE:
198                 {
199                         sprint(self, "\nUsage:^3 cmd join\n");
200                         sprint(self, "  No arguments required.\n");
201                         return;
202                 }
203         }
204 }
205
206 void ClientCommand_physics(float request, float argc)
207 {
208         SELFPARAM();
209         switch (request)
210         {
211                 case CMD_REQUEST_COMMAND:
212                 {
213                         string command = strtolower(argv(1));
214
215                         if (!autocvar_g_physics_clientselect)
216                         {
217                                 sprint(self, "Client physics selection is currently disabled.\n");
218                                 return;
219                         }
220
221                         if (command == "list" || command == "help")
222                         {
223                                 sprint(self, strcat("Available physics sets: \n\n", autocvar_g_physics_clientselect_options, " default\n"));
224                                 return;
225                         }
226
227                         if (Physics_Valid(command) || command == "default")
228                         {
229                                 stuffcmd(self, strcat("\nseta cl_physics ", command, "\nsendcvar cl_physics\n"));
230                                 sprint(self, strcat("^2Physics set successfully changed to ^3", command, "\n"));
231                                 return;
232                         }
233                 }
234
235                 default:
236                         sprint(self, strcat("Current physics set: ^3", self.cvar_cl_physics, "\n"));
237                 case CMD_REQUEST_USAGE:
238                 {
239                         sprint(self, "\nUsage:^3 cmd physics <physics>\n");
240                         sprint(self, "  See 'cmd physics list' for available physics sets.\n");
241                         sprint(self, "  Argument 'default' resets to standard physics.\n");
242                         return;
243                 }
244         }
245 }
246
247 void ClientCommand_ready(float request)  // todo: anti-spam for toggling readyness
248 {
249         SELFPARAM();
250         switch (request)
251         {
252                 case CMD_REQUEST_COMMAND:
253                 {
254                         if (IS_CLIENT(self))
255                         {
256                                 if (warmup_stage || autocvar_sv_ready_restart || g_race_qualifying == 2)
257                                 {
258                                         if (!readyrestart_happened || autocvar_sv_ready_restart_repeatable)
259                                         {
260                                                 if (time < game_starttime) // game is already restarting
261                                                         return;
262                                                 if (self.ready)            // toggle
263                                                 {
264                                                         self.ready = false;
265                                                         bprint(self.netname, "^2 is ^1NOT^2 ready\n");
266                                                 }
267                                                 else
268                                                 {
269                                                         self.ready = true;
270                                                         bprint(self.netname, "^2 is ready\n");
271                                                 }
272
273                                                 // cannot reset the game while a timeout is active!
274                                                 if (!timeout_status) ReadyCount();
275                                         }
276                                         else
277                                         {
278                                                 sprint(self, "^1Game has already been restarted\n");
279                                         }
280                                 }
281                         }
282                         return;  // never fall through to usage
283                 }
284
285                 default:
286                 case CMD_REQUEST_USAGE:
287                 {
288                         sprint(self, "\nUsage:^3 cmd ready\n");
289                         sprint(self, "  No arguments required.\n");
290                         return;
291                 }
292         }
293 }
294
295 void ClientCommand_say(float request, float argc, string command)
296 {
297         SELFPARAM();
298         switch (request)
299         {
300                 case CMD_REQUEST_COMMAND:
301                 {
302                         if (argc >= 2)   Say(self, false, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
303                         return;  // never fall through to usage
304                 }
305
306                 default:
307                 case CMD_REQUEST_USAGE:
308                 {
309                         sprint(self, "\nUsage:^3 cmd say <message>\n");
310                         sprint(self, "  Where 'message' is the string of text to say.\n");
311                         return;
312                 }
313         }
314 }
315
316 void ClientCommand_say_team(float request, float argc, string command)
317 {
318         SELFPARAM();
319         switch (request)
320         {
321                 case CMD_REQUEST_COMMAND:
322                 {
323                         if (argc >= 2)   Say(self, true, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
324                         return;  // never fall through to usage
325                 }
326
327                 default:
328                 case CMD_REQUEST_USAGE:
329                 {
330                         sprint(self, "\nUsage:^3 cmd say_team <message>\n");
331                         sprint(self, "  Where 'message' is the string of text to say.\n");
332                         return;
333                 }
334         }
335 }
336
337 void ClientCommand_selectteam(float request, float argc)
338 {
339         SELFPARAM();
340         switch (request)
341         {
342                 case CMD_REQUEST_COMMAND:
343                 {
344                         if (argv(1) != "")
345                         {
346                                 if (IS_CLIENT(self))
347                                 {
348                                         if (teamplay)
349                                         {
350                                                 if (self.team_forced <= 0)
351                                                 {
352                                                         if (!lockteams)
353                                                         {
354                                                                 float selection;
355
356                                                                 switch (argv(1))
357                                                                 {
358                                                                         case "red": selection = NUM_TEAM_1;
359                                                                                 break;
360                                                                         case "blue": selection = NUM_TEAM_2;
361                                                                                 break;
362                                                                         case "yellow": selection = NUM_TEAM_3;
363                                                                                 break;
364                                                                         case "pink": selection = NUM_TEAM_4;
365                                                                                 break;
366                                                                         case "auto": selection = (-1);
367                                                                                 break;
368
369                                                                         default: selection = 0;
370                                                                                 break;
371                                                                 }
372
373                                                                 if (selection)
374                                                                 {
375                                                                         if (self.team == selection && self.deadflag == DEAD_NO)
376                                                                         {
377                                                                                 sprint(self, "^7You already are on that team.\n");
378                                                                         }
379                                                                         else if (self.wasplayer && autocvar_g_changeteam_banned)
380                                                                         {
381                                                                                 sprint(self, "^1You cannot change team, forbidden by the server.\n");
382                                                                         }
383                                                                         else
384                                                                         {
385                                                                                 if (autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
386                                                                                 {
387                                                                                         CheckAllowedTeams(self);
388                                                                                         GetTeamCounts(self);
389                                                                                         if (!TeamSmallerEqThanTeam(Team_TeamToNumber(selection), Team_TeamToNumber(self.team), self))
390                                                                                         {
391                                                                                                 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
392                                                                                                 return;
393                                                                                         }
394                                                                                 }
395                                                                                 ClientKill_TeamChange(selection);
396                                                                         }
397                                                                 }
398                                                         }
399                                                         else
400                                                         {
401                                                                 sprint(self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
402                                                         }
403                                                 }
404                                                 else
405                                                 {
406                                                         sprint(self, "^7selectteam can not be used as your team is forced\n");
407                                                 }
408                                         }
409                                         else
410                                         {
411                                                 sprint(self, "^7selectteam can only be used in teamgames\n");
412                                         }
413                                 }
414                                 return;
415                         }
416                 }
417
418                 default:
419                         sprint(self, "Incorrect parameters for ^2selectteam^7\n");
420                 case CMD_REQUEST_USAGE:
421                 {
422                         sprint(self, "\nUsage:^3 cmd selectteam team\n");
423                         sprint(self, "  Where 'team' is the prefered team to try and join.\n");
424                         sprint(self, "  Full list of options here: \"red, blue, yellow, pink, auto\"\n");
425                         return;
426                 }
427         }
428 }
429
430 void ClientCommand_selfstuff(float request, string command)
431 {
432         SELFPARAM();
433         switch (request)
434         {
435                 case CMD_REQUEST_COMMAND:
436                 {
437                         if (argv(1) != "")
438                         {
439                                 stuffcmd(self, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)));
440                                 return;
441                         }
442                 }
443
444                 default:
445                         sprint(self, "Incorrect parameters for ^2selectteam^7\n");
446                 case CMD_REQUEST_USAGE:
447                 {
448                         sprint(self, "\nUsage:^3 cmd selfstuff <command>\n");
449                         sprint(self, "  Where 'command' is the string to be stuffed to your client.\n");
450                         return;
451                 }
452         }
453 }
454
455 void ClientCommand_sentcvar(float request, float argc, string command)
456 {
457         SELFPARAM();
458         switch (request)
459         {
460                 case CMD_REQUEST_COMMAND:
461                 {
462                         if (argv(1) != "")
463                         {
464                                 // float tokens;
465                                 string s;
466
467                                 if (argc == 2)  // undefined cvar: use the default value on the server then
468                                 {
469                                         s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
470                                         tokenize_console(s);
471                                 }
472
473                                 GetCvars(1);
474
475                                 return;
476                         }
477                 }
478
479                 default:
480                         sprint(self, "Incorrect parameters for ^2sentcvar^7\n");
481                 case CMD_REQUEST_USAGE:
482                 {
483                         sprint(self, "\nUsage:^3 cmd sentcvar <cvar>\n");
484                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
485                         return;
486                 }
487         }
488 }
489
490 void ClientCommand_spectate(float request)
491 {
492         SELFPARAM();
493         switch (request)
494         {
495                 case CMD_REQUEST_COMMAND:
496                 {
497                         if (IS_CLIENT(self))
498                         {
499                                 int mutator_returnvalue = MUTATOR_CALLHOOK(ClientCommand_Spectate, self);
500
501                                 if (mutator_returnvalue == MUT_SPECCMD_RETURN) return;
502
503                                 if ((IS_PLAYER(self) || mutator_returnvalue == MUT_SPECCMD_FORCE) && autocvar_sv_spectate == 1) ClientKill_TeamChange(-2); // observe
504                         }
505                         return;                                                                                                                        // never fall through to usage
506                 }
507
508                 default:
509                 case CMD_REQUEST_USAGE:
510                 {
511                         sprint(self, "\nUsage:^3 cmd spectate\n");
512                         sprint(self, "  No arguments required.\n");
513                         return;
514                 }
515         }
516 }
517
518 void ClientCommand_suggestmap(float request, float argc)
519 {
520         SELFPARAM();
521         switch (request)
522         {
523                 case CMD_REQUEST_COMMAND:
524                 {
525                         if (argv(1) != "")
526                         {
527                                 sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
528                                 return;
529                         }
530                 }
531
532                 default:
533                         sprint(self, "Incorrect parameters for ^2suggestmap^7\n");
534                 case CMD_REQUEST_USAGE:
535                 {
536                         sprint(self, "\nUsage:^3 cmd suggestmap map\n");
537                         sprint(self, "  Where 'map' is the name of the map to suggest.\n");
538                         return;
539                 }
540         }
541 }
542
543 void ClientCommand_tell(float request, float argc, string command)
544 {
545         SELFPARAM();
546         switch (request)
547         {
548                 case CMD_REQUEST_COMMAND:
549                 {
550                         if (argc >= 3)
551                         {
552                                 entity tell_to = GetIndexedEntity(argc, 1);
553                                 float tell_accepted = VerifyClientEntity(tell_to, true, false);
554
555                                 if (tell_accepted > 0)   // the target is a real client
556                                 {
557                                         if (tell_to != self) // and we're allowed to send to them :D
558                                         {
559                                                 // workaround for argv indexes indexing ascii chars instead of utf8 chars
560                                                 // In this case when the player name contains utf8 chars
561                                                 // the message gets partially trimmed in the beginning.
562                                                 // Potentially this bug affects any substring call that uses
563                                                 // argv_start_index and argv_end_index.
564
565                                                 string utf8_enable_save = cvar_string("utf8_enable");
566                                                 cvar_set("utf8_enable", "0");
567                                                 string msg = substring(command, argv_start_index(next_token), argv_end_index(-1) - argv_start_index(next_token));
568                                                 cvar_set("utf8_enable", utf8_enable_save);
569
570                                                 Say(self, false, tell_to, msg, true);
571                                                 return;
572                                         }
573                                         else { print_to(self, "You can't ^2tell^7 a message to yourself."); return; }
574                                 }
575                                 else if (argv(1) == "#0")
576                                 {
577                                         trigger_magicear_processmessage_forallears(self, -1, world, substring(command, argv_start_index(next_token), argv_end_index(-1) - argv_start_index(next_token)));
578                                         return;
579                                 }
580                                 else { print_to(self, strcat("tell: ", GetClientErrorString(tell_accepted, argv(1)), ".")); return; }
581                         }
582                 }
583
584                 default:
585                         sprint(self, "Incorrect parameters for ^2tell^7\n");
586                 case CMD_REQUEST_USAGE:
587                 {
588                         sprint(self, "\nUsage:^3 cmd tell client <message>\n");
589                         sprint(self, "  Where 'client' is the entity number or name of the player to send 'message' to.\n");
590                         return;
591                 }
592         }
593 }
594
595 void ClientCommand_voice(float request, float argc, string command)
596 {
597         SELFPARAM();
598         switch (request)
599         {
600                 case CMD_REQUEST_COMMAND:
601                 {
602                         if (argv(1) != "")
603                         {
604                                 entity e = GetVoiceMessage(argv(1));
605                                 if (!e)
606                                 {
607                                         sprint(this, sprintf("Invalid voice. Use one of: %s\n", allvoicesamples));
608                                         return;
609                                 }
610                                 if (argc >= 3) VoiceMessage(this, e, substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
611                                 else VoiceMessage(this, e, "");
612
613                                 return;
614                         }
615                 }
616
617                 default:
618                         sprint(self, "Incorrect parameters for ^2voice^7\n");
619                 case CMD_REQUEST_USAGE:
620                 {
621                         sprint(self, "\nUsage:^3 cmd voice messagetype <soundname>\n");
622                         sprint(self, "  'messagetype' is the type of broadcast to do, like team only or such,\n");
623                         sprint(self, "  and 'soundname' is the string/filename of the sound/voice message to play.\n");
624                         return;
625                 }
626         }
627 }
628
629 /* use this when creating a new command, making sure to place it in alphabetical order... also,
630 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
631 void ClientCommand_(float request)
632 {
633     switch(request)
634     {
635         case CMD_REQUEST_COMMAND:
636         {
637
638             return; // never fall through to usage
639         }
640
641         default:
642         case CMD_REQUEST_USAGE:
643         {
644             sprint(self, "\nUsage:^3 cmd \n");
645             sprint(self, "  No arguments required.\n");
646             return;
647         }
648     }
649 }
650 */
651
652
653 // =====================================
654 //  Macro system for networked commands
655 // =====================================
656
657 // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
658 #define CLIENT_COMMANDS(request, arguments, command) \
659         CLIENT_COMMAND("autoswitch", ClientCommand_autoswitch(request, arguments), "Whether or not to switch automatically when getting a better weapon") \
660         CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \
661         CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(request, arguments), "Retrieve mapshot picture from the server") \
662         CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \
663         CLIENT_COMMAND("physics", ClientCommand_physics(request, arguments), "Change physics set") \
664         CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
665         CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \
666         CLIENT_COMMAND("say_team", ClientCommand_say_team(request, arguments, command), "Print a message to chat to all team mates") \
667         CLIENT_COMMAND("selectteam", ClientCommand_selectteam(request, arguments), "Attempt to choose a team to join into") \
668         CLIENT_COMMAND("selfstuff", ClientCommand_selfstuff(request, command), "Stuffcmd a command to your own client") \
669         CLIENT_COMMAND("sentcvar", ClientCommand_sentcvar(request, arguments, command), "New system for sending a client cvar to the server") \
670         CLIENT_COMMAND("spectate", ClientCommand_spectate(request), "Become an observer") \
671         CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(request, arguments), "Suggest a map to the mapvote at match end") \
672         CLIENT_COMMAND("tell", ClientCommand_tell(request, arguments, command), "Send a message directly to a player") \
673         CLIENT_COMMAND("voice", ClientCommand_voice(request, arguments, command), "Send voice message via sound") \
674         CLIENT_COMMAND("minigame", ClientCommand_minigame(request, arguments, command), "Start a minigame") \
675         /* nothing */
676
677 void ClientCommand_macro_help()
678 {
679         SELFPARAM();
680         #define CLIENT_COMMAND(name, function, description) \
681                 { sprint(self, "  ^2", name, "^7: ", description, "\n"); }
682
683         CLIENT_COMMANDS(0, 0, "");
684 #undef CLIENT_COMMAND
685 }
686
687 float ClientCommand_macro_command(float argc, string command)
688 {
689         #define CLIENT_COMMAND(name, function, description) \
690                 { if (name == strtolower(argv(0))) { function; return true; } }
691
692         CLIENT_COMMANDS(CMD_REQUEST_COMMAND, argc, command);
693 #undef CLIENT_COMMAND
694
695         return false;
696 }
697
698 float ClientCommand_macro_usage(float argc)
699 {
700         #define CLIENT_COMMAND(name, function, description) \
701                 { if (name == strtolower(argv(1))) { function; return true; } }
702
703         CLIENT_COMMANDS(CMD_REQUEST_USAGE, argc, "");
704 #undef CLIENT_COMMAND
705
706         return false;
707 }
708
709 void ClientCommand_macro_write_aliases(float fh)
710 {
711         #define CLIENT_COMMAND(name, function, description) \
712                 { CMD_Write_Alias("qc_cmd_cmd", name, description); }
713
714         CLIENT_COMMANDS(0, 0, "");
715 #undef CLIENT_COMMAND
716 }
717
718 // ======================================
719 //  Main Function Called By Engine (cmd)
720 // ======================================
721 // If this function exists, server game code parses clientcommand before the engine code gets it.
722
723 void SV_ParseClientCommand(string command)
724 {
725         SELFPARAM();
726         // If invalid UTF-8, don't even parse it
727         string command2 = "";
728         float len = strlen(command);
729         float i;
730         for (i = 0; i < len; ++i)
731                 command2 = strcat(command2, chr2str(str2chr(command, i)));
732         if (command != command2) return;
733
734         // if we're banned, don't even parse the command
735         if (Ban_MaybeEnforceBanOnce(self)) return;
736
737         float argc = tokenize_console(command);
738
739         // Guide for working with argc arguments by example:
740         // argc:   1    - 2      - 3     - 4
741         // argv:   0    - 1      - 2     - 3
742         // cmd     vote - master - login - password
743
744         // for floodcheck
745         switch (strtolower(argv(0)))
746         {
747                 // exempt commands which are not subject to floodcheck
748                 case "begin": break;                               // handled by engine in host_cmd.c
749                 case "download": break;                            // handled by engine in cl_parse.c
750                 case "mv_getpicture": break;                       // handled by server in this file
751                 case "pause": break;                               // handled by engine in host_cmd.c
752                 case "prespawn": break;                            // handled by engine in host_cmd.c
753                 case "sentcvar": break;                            // handled by server in this file
754                 case "spawn": break;                               // handled by engine in host_cmd.c
755
756                 default:
757                         if (SV_ParseClientCommand_floodcheck()) break; // "true": continue, as we're not flooding yet
758                         else return;                                   // "false": not allowed to continue, halt // print("^1ERROR: ^7ANTISPAM CAUGHT: ", command, ".\n");
759         }
760
761         /* NOTE: should this be disabled? It can be spammy perhaps, but hopefully it's okay for now */
762         if (argv(0) == "help")
763         {
764                 if (argc == 1)
765                 {
766                         sprint(self, "\nClient networked commands:\n");
767                         ClientCommand_macro_help();
768
769                         sprint(self, "\nCommon networked commands:\n");
770                         CommonCommand_macro_help(self);
771
772                         sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are listed above.\n");
773                         sprint(self, "For help about a specific command, type cmd help COMMAND\n");
774                         return;
775                 }
776                 else if (CommonCommand_macro_usage(argc, self))  // Instead of trying to call a command, we're going to see detailed information about it
777                 {
778                         return;
779                 }
780                 else if (ClientCommand_macro_usage(argc))  // same, but for normal commands now
781                 {
782                         return;
783                 }
784         }
785         else if (MUTATOR_CALLHOOK(SV_ParseClientCommand, strtolower(argv(0)), argc, command))
786         {
787                 return;  // handled by a mutator
788         }
789         else if (CheatCommand(argc))
790         {
791                 return;  // handled by server/cheats.qc
792         }
793         else if (CommonCommand_macro_command(argc, self, command))
794         {
795                 return;                                          // handled by server/command/common.qc
796         }
797         else if (ClientCommand_macro_command(argc, command)) // continue as usual and scan for normal commands
798         {
799                 return;                                          // handled by one of the above ClientCommand_* functions
800         }
801         else
802         {
803                 clientcommand(self, command);
804         }
805 }