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