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