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