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