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