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