]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/command/cmd.qc
Merge branch 'master' into Mario/physics
[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_physics(float request, float argc)
374 {
375         switch(request)
376         {
377                 case CMD_REQUEST_COMMAND:
378                 {
379                         string command = strtolower(argv(1));
380                         
381                         if(!autocvar_g_physics_clientselect)
382                         {
383                                 sprint(self, "Client physics selection is currently disabled.\n");
384                                 return;
385                         }
386
387                         if(command == "list" || command == "help")
388                         {
389                                 sprint(self, strcat("Available physics sets: \n\n", autocvar_g_physics_clientselect_options, " default\n"));
390                                 return;
391                         }
392                         
393                         if(Physics_Valid(command) || command == "default")
394                         {
395                                 stuffcmd(self, strcat("\nseta cl_physics ", command, "\nsendcvar cl_physics\n"));
396                                 sprint(self, strcat("^2Physics set successfully changed to ^3", command, "\n"));
397                                 return;
398                         }
399                 }
400         
401                 default:
402                         sprint(self, strcat("Current physics set: ^3", self.cvar_cl_physics, "\n"));
403                 case CMD_REQUEST_USAGE:
404                 {
405                         sprint(self, "\nUsage:^3 cmd physics <physics>\n");
406                         sprint(self, "  See 'cmd physics list' for available physics sets.\n");
407                         sprint(self, "  Argument 'default' resets to standard physics.\n");
408                         return;
409                 }
410         }
411 }
412
413 void ClientCommand_ready(float request) // todo: anti-spam for toggling readyness
414 {
415         switch(request)
416         {
417                 case CMD_REQUEST_COMMAND:
418                 {
419                         if(IS_CLIENT(self))
420                         {
421                                 if(warmup_stage || autocvar_sv_ready_restart || g_race_qualifying == 2)
422                                 {
423                                         if(!readyrestart_happened || autocvar_sv_ready_restart_repeatable)
424                                         {
425                                                 if(time < game_starttime) // game is already restarting
426                                                         return;
427                                                 if (self.ready) // toggle
428                                                 {
429                                                         self.ready = false;
430                                                         bprint(self.netname, "^2 is ^1NOT^2 ready\n");
431                                                 }
432                                                 else
433                                                 {
434                                                         self.ready = true;
435                                                         bprint(self.netname, "^2 is ready\n");
436                                                 }
437
438                                                 // cannot reset the game while a timeout is active!
439                                                 if (!timeout_status)
440                                                         ReadyCount();
441                                         } else {
442                                                 sprint(self, "^1Game has already been restarted\n");
443                                         }
444                                 }
445                         }
446                         return; // never fall through to usage
447                 }
448
449                 default:
450                 case CMD_REQUEST_USAGE:
451                 {
452                         sprint(self, "\nUsage:^3 cmd ready\n");
453                         sprint(self, "  No arguments required.\n");
454                         return;
455                 }
456         }
457 }
458
459 void ClientCommand_say(float request, float argc, string command)
460 {
461         switch(request)
462         {
463                 case CMD_REQUEST_COMMAND:
464                 {
465                         if(argc >= 2) { Say(self, false, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
466                         return; // never fall through to usage
467                 }
468
469                 default:
470                 case CMD_REQUEST_USAGE:
471                 {
472                         sprint(self, "\nUsage:^3 cmd say <message>\n");
473                         sprint(self, "  Where 'message' is the string of text to say.\n");
474                         return;
475                 }
476         }
477 }
478
479 void ClientCommand_say_team(float request, float argc, string command)
480 {
481         switch(request)
482         {
483                 case CMD_REQUEST_COMMAND:
484                 {
485                         if(argc >= 2) { Say(self, true, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1); }
486                         return; // never fall through to usage
487                 }
488
489                 default:
490                 case CMD_REQUEST_USAGE:
491                 {
492                         sprint(self, "\nUsage:^3 cmd say_team <message>\n");
493                         sprint(self, "  Where 'message' is the string of text to say.\n");
494                         return;
495                 }
496         }
497 }
498
499 void ClientCommand_selectteam(float request, float argc)
500 {
501         switch(request)
502         {
503                 case CMD_REQUEST_COMMAND:
504                 {
505                         if(argv(1) != "")
506                         {
507                                 if(IS_CLIENT(self))
508                                 {
509                                         if(teamplay)
510                                                 if(self.team_forced <= 0)
511                                                         if (!lockteams)
512                                                         {
513                                                                 float selection;
514
515                                                                 switch(argv(1))
516                                                                 {
517                                                                         case "red": selection = NUM_TEAM_1; break;
518                                                                         case "blue": selection = NUM_TEAM_2; break;
519                                                                         case "yellow": selection = NUM_TEAM_3; break;
520                                                                         case "pink": selection = NUM_TEAM_4; break;
521                                                                         case "auto": selection = (-1); break;
522
523                                                                         default: selection = 0; break;
524                                                                 }
525
526                                                                 if(selection)
527                                                                 {
528                                                                         if(self.team == selection && self.deadflag == DEAD_NO)
529                                                                                 sprint(self, "^7You already are on that team.\n");
530                                                                         else if(self.wasplayer && autocvar_g_changeteam_banned)
531                                                                                 sprint(self, "^1You cannot change team, forbidden by the server.\n");
532                                                                         else
533                                                                         {
534                                                                                 if(autocvar_g_balance_teams && autocvar_g_balance_teams_prevent_imbalance)
535                                                                                 {
536                                                                                         CheckAllowedTeams(self);
537                                                                                         GetTeamCounts(self);
538                                                                                         if(!TeamSmallerEqThanTeam(Team_TeamToNumber(selection), Team_TeamToNumber(self.team), self))
539                                                                                         {
540                                                                                                 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_TEAMCHANGE_LARGERTEAM);
541                                                                                                 return;
542                                                                                         }
543                                                                                 }
544                                                                                 ClientKill_TeamChange(selection);
545                                                                         }
546                                                                 }
547                                                         }
548                                                         else
549                                                                 sprint(self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
550                                                 else
551                                                         sprint(self, "^7selectteam can not be used as your team is forced\n");
552                                         else
553                                                 sprint(self, "^7selectteam can only be used in teamgames\n");
554                                 }
555                                 return;
556                         }
557                 }
558
559                 default:
560                         sprint(self, "Incorrect parameters for ^2selectteam^7\n");
561                 case CMD_REQUEST_USAGE:
562                 {
563                         sprint(self, "\nUsage:^3 cmd selectteam team\n");
564                         sprint(self, "  Where 'team' is the prefered team to try and join.\n");
565                         sprint(self, "  Full list of options here: \"red, blue, yellow, pink, auto\"\n");
566                         return;
567                 }
568         }
569 }
570
571 void ClientCommand_selfstuff(float request, string command)
572 {
573         switch(request)
574         {
575                 case CMD_REQUEST_COMMAND:
576                 {
577                         if(argv(1) != "")
578                         {
579                                 stuffcmd(self, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)));
580                                 return;
581                         }
582                 }
583
584                 default:
585                         sprint(self, "Incorrect parameters for ^2selectteam^7\n");
586                 case CMD_REQUEST_USAGE:
587                 {
588                         sprint(self, "\nUsage:^3 cmd selfstuff <command>\n");
589                         sprint(self, "  Where 'command' is the string to be stuffed to your client.\n");
590                         return;
591                 }
592         }
593 }
594
595 void ClientCommand_sentcvar(float request, float argc, string command)
596 {
597         switch(request)
598         {
599                 case CMD_REQUEST_COMMAND:
600                 {
601                         if(argv(1) != "")
602                         {
603                                 //float tokens;
604                                 string s;
605
606                                 if(argc == 2) // undefined cvar: use the default value on the server then
607                                 {
608                                         s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
609                                         tokenize_console(s);
610                                 }
611
612                                 GetCvars(1);
613
614                                 return;
615                         }
616                 }
617
618                 default:
619                         sprint(self, "Incorrect parameters for ^2sentcvar^7\n");
620                 case CMD_REQUEST_USAGE:
621                 {
622                         sprint(self, "\nUsage:^3 cmd sentcvar <cvar>\n");
623                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
624                         return;
625                 }
626         }
627 }
628
629 void ClientCommand_spectate(float request)
630 {
631         switch(request)
632         {
633                 case CMD_REQUEST_COMMAND:
634                 {
635                         if(IS_CLIENT(self))
636                         {
637                                 if(g_lms)
638                                 {
639                                         if(self.lms_spectate_warning)
640                                         {
641                                                 // for the forfeit message...
642                                                 self.lms_spectate_warning = 2;
643                                                 // mark player as spectator
644                                                 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
645                                         }
646                                         else
647                                         {
648                                                 self.lms_spectate_warning = 1;
649                                                 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");
650                                                 return;
651                                         }
652                                 }
653
654                                 if((IS_PLAYER(self) || self.caplayer) && autocvar_sv_spectate == 1)
655                                 {
656                                         if(self.caplayer && (IS_SPEC(self) || IS_OBSERVER(self)))
657                                                 Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_LEAVE);
658                                         ClientKill_TeamChange(-2); // observe
659                                 }
660                         }
661                         return; // never fall through to usage
662                 }
663
664                 default:
665                 case CMD_REQUEST_USAGE:
666                 {
667                         sprint(self, "\nUsage:^3 cmd spectate\n");
668                         sprint(self, "  No arguments required.\n");
669                         return;
670                 }
671         }
672 }
673
674 void ClientCommand_suggestmap(float request, float argc)
675 {
676         switch(request)
677         {
678                 case CMD_REQUEST_COMMAND:
679                 {
680                         if(argv(1) != "")
681                         {
682                                 sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
683                                 return;
684                         }
685                 }
686
687                 default:
688                         sprint(self, "Incorrect parameters for ^2suggestmap^7\n");
689                 case CMD_REQUEST_USAGE:
690                 {
691                         sprint(self, "\nUsage:^3 cmd suggestmap map\n");
692                         sprint(self, "  Where 'map' is the name of the map to suggest.\n");
693                         return;
694                 }
695         }
696 }
697
698 void ClientCommand_tell(float request, float argc, string command)
699 {
700         switch(request)
701         {
702                 case CMD_REQUEST_COMMAND:
703                 {
704                         if(argc >= 3)
705                         {
706                                 entity tell_to = GetIndexedEntity(argc, 1);
707                                 float tell_accepted = VerifyClientEntity(tell_to, true, false);
708
709                                 if(tell_accepted > 0) // the target is a real client
710                                 {
711                                         if(tell_to != self) // and we're allowed to send to them :D
712                                         {
713                                                 Say(self, false, tell_to, substring(command, argv_start_index(next_token), argv_end_index(-1) - argv_start_index(next_token)), true);
714                                                 return;
715                                         }
716                                         else { print_to(self, "You can't ^2tell^7 a message to yourself."); return; }
717                                 }
718                                 else if(argv(1) == "#0")
719                                 {
720                                         trigger_magicear_processmessage_forallears(self, -1, world, substring(command, argv_start_index(next_token), argv_end_index(-1) - argv_start_index(next_token)));
721                                         return;
722                                 }
723                                 else { print_to(self, strcat("tell: ", GetClientErrorString(tell_accepted, argv(1)), ".")); return; }
724                         }
725                 }
726
727                 default:
728                         sprint(self, "Incorrect parameters for ^2tell^7\n");
729                 case CMD_REQUEST_USAGE:
730                 {
731                         sprint(self, "\nUsage:^3 cmd tell client <message>\n");
732                         sprint(self, "  Where 'client' is the entity number or name of the player to send 'message' to.\n");
733                         return;
734                 }
735         }
736 }
737
738 void ClientCommand_voice(float request, float argc, string command)
739 {
740         switch(request)
741         {
742                 case CMD_REQUEST_COMMAND:
743                 {
744                         if(argv(1) != "")
745                         {
746                                 if(argc >= 3)
747                                         VoiceMessage(argv(1), substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
748                                 else
749                                         VoiceMessage(argv(1), "");
750
751                                 return;
752                         }
753                 }
754
755                 default:
756                         sprint(self, "Incorrect parameters for ^2voice^7\n");
757                 case CMD_REQUEST_USAGE:
758                 {
759                         sprint(self, "\nUsage:^3 cmd voice messagetype <soundname>\n");
760                         sprint(self, "  'messagetype' is the type of broadcast to do, like team only or such,\n");
761                         sprint(self, "  and 'soundname' is the string/filename of the sound/voice message to play.\n");
762                         return;
763                 }
764         }
765 }
766
767 /* use this when creating a new command, making sure to place it in alphabetical order... also,
768 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
769 void ClientCommand_(float request)
770 {
771         switch(request)
772         {
773                 case CMD_REQUEST_COMMAND:
774                 {
775
776                         return; // never fall through to usage
777                 }
778
779                 default:
780                 case CMD_REQUEST_USAGE:
781                 {
782                         sprint(self, "\nUsage:^3 cmd \n");
783                         sprint(self, "  No arguments required.\n");
784                         return;
785                 }
786         }
787 }
788 */
789
790
791 // =====================================
792 //  Macro system for networked commands
793 // =====================================
794
795 // Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
796 #define CLIENT_COMMANDS(request,arguments,command) \
797         CLIENT_COMMAND("autoswitch", ClientCommand_autoswitch(request, arguments), "Whether or not to switch automatically when getting a better weapon") \
798         CLIENT_COMMAND("checkfail", ClientCommand_checkfail(request, command), "Report if a client-side check failed") \
799         CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \
800         CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(request, arguments), "Retrieve mapshot picture from the server") \
801         CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \
802         CLIENT_COMMAND("mobedit", ClientCommand_mobedit(request, arguments), "Edit your monster's properties") \
803         CLIENT_COMMAND("mobkill", ClientCommand_mobkill(request), "Kills your monster") \
804         CLIENT_COMMAND("mobspawn", ClientCommand_mobspawn(request, arguments), "Spawn monsters infront of yourself") \
805         CLIENT_COMMAND("physics", ClientCommand_physics(request, arguments), "Change physics set") \
806         CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
807         CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \
808         CLIENT_COMMAND("say_team", ClientCommand_say_team(request, arguments, command), "Print a message to chat to all team mates") \
809         CLIENT_COMMAND("selectteam", ClientCommand_selectteam(request, arguments), "Attempt to choose a team to join into") \
810         CLIENT_COMMAND("selfstuff", ClientCommand_selfstuff(request, command), "Stuffcmd a command to your own client") \
811         CLIENT_COMMAND("sentcvar", ClientCommand_sentcvar(request, arguments, command), "New system for sending a client cvar to the server") \
812         CLIENT_COMMAND("spectate", ClientCommand_spectate(request), "Become an observer") \
813         CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(request, arguments), "Suggest a map to the mapvote at match end") \
814         CLIENT_COMMAND("tell", ClientCommand_tell(request, arguments, command), "Send a message directly to a player") \
815         CLIENT_COMMAND("voice", ClientCommand_voice(request, arguments, command), "Send voice message via sound") \
816         /* nothing */
817
818 void ClientCommand_macro_help()
819 {
820         #define CLIENT_COMMAND(name,function,description) \
821                 { sprint(self, "  ^2", name, "^7: ", description, "\n"); }
822
823         CLIENT_COMMANDS(0, 0, "");
824         #undef CLIENT_COMMAND
825
826         return;
827 }
828
829 float ClientCommand_macro_command(float argc, string command)
830 {
831         #define CLIENT_COMMAND(name,function,description) \
832                 { if(name == strtolower(argv(0))) { function; return true; } }
833
834         CLIENT_COMMANDS(CMD_REQUEST_COMMAND, argc, command);
835         #undef CLIENT_COMMAND
836
837         return false;
838 }
839
840 float ClientCommand_macro_usage(float argc)
841 {
842         #define CLIENT_COMMAND(name,function,description) \
843                 { if(name == strtolower(argv(1))) { function; return true; } }
844
845         CLIENT_COMMANDS(CMD_REQUEST_USAGE, argc, "");
846         #undef CLIENT_COMMAND
847
848         return false;
849 }
850
851 void ClientCommand_macro_write_aliases(float fh)
852 {
853         #define CLIENT_COMMAND(name,function,description) \
854                 { CMD_Write_Alias("qc_cmd_cmd", name, description); }
855
856         CLIENT_COMMANDS(0, 0, "");
857         #undef CLIENT_COMMAND
858
859         return;
860 }
861
862 // ======================================
863 //  Main Function Called By Engine (cmd)
864 // ======================================
865 // If this function exists, server game code parses clientcommand before the engine code gets it.
866
867 void SV_ParseClientCommand(string command)
868 {
869         // If invalid UTF-8, don't even parse it
870         string command2 = "";
871         float len = strlen(command);
872         float i;
873         for (i = 0; i < len; ++i)
874                 command2 = strcat(command2, chr2str(str2chr(command, i)));
875         if (command != command2)
876                 return;
877
878         // if we're banned, don't even parse the command
879         if(Ban_MaybeEnforceBanOnce(self))
880                 return;
881
882         float argc = tokenize_console(command);
883
884         // for the mutator hook system
885         cmd_name = strtolower(argv(0));
886         cmd_argc = argc;
887         cmd_string = command;
888
889         // Guide for working with argc arguments by example:
890         // argc:   1    - 2      - 3     - 4
891         // argv:   0    - 1      - 2     - 3
892         // cmd     vote - master - login - password
893
894         // for floodcheck
895         switch(strtolower(argv(0)))
896         {
897                 // exempt commands which are not subject to floodcheck
898                 case "begin": break; // handled by engine in host_cmd.c
899                 case "download": break; // handled by engine in cl_parse.c
900                 case "mv_getpicture": break; // handled by server in this file
901                 case "pause": break; // handled by engine in host_cmd.c
902                 case "prespawn": break; // handled by engine in host_cmd.c
903                 case "sentcvar": break; // handled by server in this file
904                 case "spawn": break; // handled by engine in host_cmd.c
905
906                 default:
907                         if(SV_ParseClientCommand_floodcheck())
908                                 break; // "true": continue, as we're not flooding yet
909                         else
910                                 return; // "false": not allowed to continue, halt // print("^1ERROR: ^7ANTISPAM CAUGHT: ", command, ".\n");
911         }
912
913         /* NOTE: should this be disabled? It can be spammy perhaps, but hopefully it's okay for now */
914         if(argv(0) == "help")
915         {
916                 if(argc == 1)
917                 {
918                         sprint(self, "\nClient networked commands:\n");
919                         ClientCommand_macro_help();
920
921                         sprint(self, "\nCommon networked commands:\n");
922                         CommonCommand_macro_help(self);
923
924                         sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are listed above.\n");
925                         sprint(self, "For help about a specific command, type cmd help COMMAND\n");
926                         return;
927                 }
928                 else if(CommonCommand_macro_usage(argc, self)) // Instead of trying to call a command, we're going to see detailed information about it
929                 {
930                         return;
931                 }
932                 else if(ClientCommand_macro_usage(argc)) // same, but for normal commands now
933                 {
934                         return;
935                 }
936         }
937         else if(MUTATOR_CALLHOOK(SV_ParseClientCommand))
938         {
939                 return; // handled by a mutator
940         }
941         else if(CheatCommand(argc))
942         {
943                 return; // handled by server/cheats.qc
944         }
945         else if(CommonCommand_macro_command(argc, self, command))
946         {
947                 return; // handled by server/command/common.qc
948         }
949         else if(ClientCommand_macro_command(argc, command)) // continue as usual and scan for normal commands
950         {
951                 return; // handled by one of the above ClientCommand_* functions
952         }
953         else
954                 clientcommand(self, command);
955 }