]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/clientcommands.qc
b90151f04e6b4ee8c8b87e7bcec18f451d6b41c2
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / clientcommands.qc
1 // =======================================================
2 //  Server side client commands code, reworked by Samual
3 //  Last updated: August 4th, 2011
4 // =======================================================
5
6 #define CC_REQUEST_HELP 1
7 #define CC_REQUEST_COMMAND 2
8 #define CC_REQUEST_USAGE 3
9
10 entity nagger;
11
12 .float cmd_floodtime;
13 .float cmd_floodcount;
14 .float checkfail;
15 .float lms_spectate_warning;
16
17 float readyrestart_happened;
18 float readycount;
19
20 string MapVote_Suggest(string m);
21
22 void ReadyCount();
23
24
25 // ============================
26 //  Misc. Supporting Functions
27 // ============================
28
29 float SV_ParseClientCommand_floodcheck()
30 {
31         if (timeoutStatus != 2) // if the game is not paused... but wait, doesn't that mean it could be dos'd by pausing it? eh? (old code)
32         {
33                 if(time == self.cmd_floodtime) // todo: add buffer time as well, ONLY one second is a short amount of time for someone to be spamming. 
34                 {
35                         self.cmd_floodcount += 1;
36                         if(self.cmd_floodcount > 8) // todo: replace constant 8 with a cvar for the server to control
37                                 return FALSE; // too much spam, halt
38                 }
39                 else
40                 {
41                         self.cmd_floodtime = time;
42                         self.cmd_floodcount = 1;
43                 }
44         }
45         return TRUE; // continue, as we're not flooding yet
46 }
47
48 float Nagger_SendEntity(entity to, float sendflags)
49 {
50         float nags, i, f, b;
51         entity e;
52         WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
53
54         // bits:
55         //   1 = ready
56         //   2 = player needs to ready up
57         //   4 = vote
58         //   8 = player needs to vote
59         //  16 = warmup
60         // sendflags:
61         //  64 = vote counts
62         // 128 = vote string
63
64         nags = 0;
65         if(readycount)
66         {
67                 nags |= 1;
68                 if(to.ready == 0)
69                         nags |= 2;
70         }
71         if(votecalled)
72         {
73                 nags |= 4;
74                 if(to.vote_vote == 0)
75                         nags |= 8;
76         }
77         if(inWarmupStage)
78                 nags |= 16;
79
80         if(sendflags & 64)
81                 nags |= 64;
82
83         if(sendflags & 128)
84                 nags |= 128;
85
86         if(!(nags & 4)) // no vote called? send no string
87                 nags &~= (64 | 128);
88
89         WriteByte(MSG_ENTITY, nags);
90
91         if(nags & 64)
92         {
93                 WriteByte(MSG_ENTITY, vote_yescount);
94                 WriteByte(MSG_ENTITY, vote_nocount);
95                 WriteByte(MSG_ENTITY, vote_needed_absolute);
96                 WriteChar(MSG_ENTITY, to.vote_vote);
97         }
98
99         if(nags & 128)
100                 WriteString(MSG_ENTITY, votecalledvote_display);
101
102         if(nags & 1)
103         {
104                 for(i = 1; i <= maxclients; i += 8)
105                 {
106                         for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
107                                 if(clienttype(e) != CLIENTTYPE_REAL || e.ready)
108                                         f |= b;
109                         WriteByte(MSG_ENTITY, f);
110                 }
111         }
112
113         return TRUE;
114 }
115 void Nagger_Init()
116 {
117         Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
118 }
119 void Nagger_VoteChanged()
120 {
121         if(nagger)
122                 nagger.SendFlags |= 128;
123 }
124 void Nagger_VoteCountChanged()
125 {
126         if(nagger)
127                 nagger.SendFlags |= 64;
128 }
129 void Nagger_ReadyCounted()
130 {
131         if(nagger)
132                 nagger.SendFlags |= 1;
133 }
134
135 void ReadyRestartForce()
136 {
137         local entity e;
138
139         bprint("^1Server is restarting...\n");
140
141         VoteReset();
142
143         // clear overtime
144         if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) {
145                 //we have to decrease timelimit to its original value again!!
146                 float newTL;
147                 newTL = autocvar_timelimit;
148                 newTL -= checkrules_overtimesadded * autocvar_timelimit_overtime;
149                 cvar_set("timelimit", ftos(newTL));
150         }
151
152         checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
153
154
155         readyrestart_happened = 1;
156         game_starttime = time;
157         if(!g_ca && !g_arena)
158                 game_starttime += RESTART_COUNTDOWN;
159         restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
160
161         inWarmupStage = 0; //once the game is restarted the game is in match stage
162
163         //reset the .ready status of all players (also spectators)
164         FOR_EACH_CLIENTSLOT(e)
165                 e.ready = 0;
166         readycount = 0;
167         Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
168
169         if(autocvar_teamplay_lockonrestart && teamplay) {
170                 lockteams = 1;
171                 bprint("^1The teams are now locked.\n");
172         }
173
174         //initiate the restart-countdown-announcer entity
175         if(autocvar_sv_ready_restart_after_countdown && !g_ca && !g_arena)
176         {
177                 restartTimer = spawn();
178                 restartTimer.think = restartTimer_Think;
179                 restartTimer.nextthink = game_starttime;
180         }
181
182         //after a restart every players number of allowed timeouts gets reset, too
183         if(autocvar_sv_timeout)
184         {
185                 FOR_EACH_REALPLAYER(e)
186                         e.allowedTimeouts = autocvar_sv_timeout_number;
187         }
188
189         //reset map immediately if this cvar is not set
190         if (!autocvar_sv_ready_restart_after_countdown)
191                 reset_map(TRUE);
192
193         if(autocvar_sv_eventlog)
194                 GameLogEcho(":restart");
195 }
196
197 void ReadyRestart()
198 {
199         // no arena, assault support yet...
200         if(g_arena | g_assault | gameover | intermission_running | race_completing)
201                 localcmd("restart\n");
202         else
203                 localcmd("\nsv_hook_gamerestart\n");
204
205         ReadyRestartForce();
206
207         // reset ALL scores, but only do that at the beginning
208         //of the countdown if sv_ready_restart_after_countdown is off!
209         //Otherwise scores could be manipulated during the countdown!
210         if (!autocvar_sv_ready_restart_after_countdown)
211                 Score_ClearAll();
212 }
213
214 /**
215  * Counts how many players are ready. If not enough players are ready, the function
216  * does nothing. If all players are ready, the timelimit will be extended and the
217  * restart_countdown variable is set to allow other functions like PlayerPostThink
218  * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
219  * is not set the map will be resetted.
220  *
221  * Function is called after the server receives a 'ready' sign from a player.
222  */
223 void ReadyCount()
224 {
225         local entity e;
226         local float r, p;
227
228         r = p = 0;
229
230         FOR_EACH_REALPLAYER(e)
231         {
232                 p += 1;
233                 if(e.ready)
234                         r += 1;
235         }
236
237         readycount = r;
238
239         Nagger_ReadyCounted();
240
241         if(r) // at least one is ready
242         if(r == p) // and, everyone is ready
243                 ReadyRestart();
244 }
245
246 /**
247  * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
248  * is set)
249  */
250 void restartTimer_Think() {
251         restart_mapalreadyrestarted = 1;
252         reset_map(TRUE);
253         Score_ClearAll();
254         remove(self);
255         return;
256 }
257
258 /**
259  * Checks whether the player who calls the timeout is allowed to do so.
260  * If so, it initializes the timeout countdown. It also checks whether another
261  * timeout was already running at this time and reacts correspondingly.
262  *
263  * affected globals/fields: .allowedTimeouts, remainingTimeoutTime, remainingLeadTime,
264  *                          timeoutInitiator, timeoutStatus, timeoutHandler
265  *
266  * This function is called when a player issues the calltimeout command.
267  */
268 void evaluateTimeout() {
269         if (inWarmupStage && !g_warmup_allow_timeout)
270                 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
271         if (time < game_starttime )
272                 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
273         if (timeoutStatus != 2) {
274                 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
275                 if (autocvar_timelimit) {
276                         //a timelimit was used
277                         local float myTl;
278                         myTl = autocvar_timelimit;
279
280                         local float lastPossibleTimeout;
281                         lastPossibleTimeout = (myTl*60) - autocvar_sv_timeout_leadtime - 1;
282
283                         if (lastPossibleTimeout < time - game_starttime)
284                                 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
285                 }
286         }
287         //player may not call a timeout if he has no calls left
288         if (self.allowedTimeouts < 1)
289                 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
290         //now all required checks are passed
291         self.allowedTimeouts -= 1;
292         bprint(self.netname, " ^7called a timeout (", ftos(self.allowedTimeouts), " timeouts left)!\n"); //write a bprint who started the timeout (and how many he has left)
293         remainingTimeoutTime = autocvar_sv_timeout_length;
294         remainingLeadTime = autocvar_sv_timeout_leadtime;
295         timeoutInitiator = self;
296         if (timeoutStatus == 0) { //if another timeout was already active, don't change its status (which was 1 or 2) to 1, only change it to 1 if no timeout was active yet
297                 timeoutStatus = 1;
298                 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
299                 timeoutHandler = spawn();
300                 timeoutHandler.think = timeoutHandler_Think;
301         }
302         timeoutHandler.nextthink = time; //always let the entity think asap
303
304         //inform all connected clients about the timeout call
305         Announce("timeoutcalled");
306 }
307
308 /**
309  * Checks whether a player is allowed to resume the game. If he is allowed to do it,
310  * and the lead time for the timeout is still active, this countdown just will be aborted (the
311  * game will never be paused). Otherwise the remainingTimeoutTime will be set to the corresponding
312  * value of the cvar sv_timeout_resumetime.
313  *
314  * This function is called when a player issues the resumegame command.
315  */
316 void evaluateTimein() {
317         if (!timeoutStatus)
318                 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
319         if (self != timeoutInitiator)
320                 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
321         if (timeoutStatus == 1) {
322                 remainingTimeoutTime = timeoutStatus = 0;
323                 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
324                 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
325         }
326         else if (timeoutStatus == 2) {
327                 //only shorten the remainingTimeoutTime if it makes sense
328                 if( remainingTimeoutTime > (autocvar_sv_timeout_resumetime + 1) ) {
329                         bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
330                         remainingTimeoutTime = autocvar_sv_timeout_resumetime;
331                         timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
332                 }
333                 else
334                         sprint(self, "^7Error: Your resumegame call was discarded!\n");
335
336         }
337 }
338
339
340 // =======================
341 //  Command Sub-Functions
342 // =======================
343
344 void ClientCommand_autoswitch(float request, float argc)
345 {
346         switch(request)
347         {
348                 case CC_REQUEST_HELP:
349                         sprint(self, "  ^2autoswitch^7: Whether or not to switch automatically when getting a better weapon\n");
350                         return;
351                         
352                 case CC_REQUEST_COMMAND:
353                         self.autoswitch = ("0" != argv(1));
354                         sprint(self, strcat("^1autoswitch is currently turned ", (self.autoswitch ? "on" : "off"), ".\n"));
355                         return; // never fall through to usage
356                         
357                 default:
358                 case CC_REQUEST_USAGE:
359                         sprint(self, "\nUsage:^3 cmd autoswitch selection\n");
360                         sprint(self, "  Where 'selection' is 1 or 0 for on or off.\n"); 
361                         return;
362         }
363 }
364
365 void ClientCommand_checkfail(float request, string command) // used only by client side code
366 {
367         switch(request)
368         {
369                 case CC_REQUEST_HELP:
370                         sprint(self, "  ^2checkfail^7: Report if a client-side check failed\n");
371                         return;
372                         
373                 case CC_REQUEST_COMMAND:
374                         print(sprintf("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))));
375                         self.checkfail = 1;
376                         return; // never fall through to usage
377                         
378                 default:
379                 case CC_REQUEST_USAGE:
380                         sprint(self, "\nUsage:^3 cmd checkfail message\n");
381                         sprint(self, "  Where 'message' is the message reported by client about the fail.\n");
382                         return;
383         }
384 }
385
386 void ClientCommand_clientversion(float request, float argc) // used only by client side code
387 {
388         switch(request)
389         {
390                 case CC_REQUEST_HELP:
391                         sprint(self, "  ^2clientversion^7: Release version of the game\n");
392                         return;
393                         
394                 case CC_REQUEST_COMMAND:
395                         if(self.flags & FL_CLIENT)
396                         {
397                                 self.version = ((argv(1) == "$gameversion") ? 1 : stof(argv(1)));
398                                 
399                                 if(self.version < autocvar_gameversion_min || self.version > autocvar_gameversion_max)
400                                 {
401                                         self.version_mismatch = 1;
402                                         ClientKill_TeamChange(-2); // observe
403                                 } 
404                                 else if(autocvar_g_campaign || autocvar_g_balance_teams || autocvar_g_balance_teams_force) 
405                                 {
406                                         //JoinBestTeam(self, FALSE, TRUE);
407                                 } 
408                                 else if(teamplay && !autocvar_sv_spectate && !(self.team_forced > 0)) 
409                                 {
410                                         self.classname = "observer"; // really?
411                                         stuffcmd(self, "menu_showteamselect\n");
412                                 }
413                         }
414                         return; // never fall through to usage
415                         
416                 default:
417                 case CC_REQUEST_USAGE:
418                         sprint(self, "\nUsage:^3 cmd clientversion version\n");
419                         sprint(self, "  Where 'version' is the game version reported by self.\n");
420                         return;
421         }
422 }
423
424 void ClientCommand_cvar_changes(float request)
425 {
426         switch(request)
427         {
428                 case CC_REQUEST_HELP:
429                         sprint(self, "  ^2cvar_changes^7: Prints a list of all changed server cvars\n");
430                         return;
431                         
432                 case CC_REQUEST_COMMAND:
433                         sprint(self, cvar_changes);
434                         return; // never fall through to usage
435                         
436                 default:
437                 case CC_REQUEST_USAGE:
438                         sprint(self, "\nUsage:^3 sv_cmd cvar_changes\n");
439                         sprint(self, "  No arguments required.\n");
440                         //sprint(self, "See also: ^2cvar_purechanges^7\n");
441                         return;
442         }
443 }
444
445 void ClientCommand_cvar_purechanges(float request)
446 {
447         switch(request)
448         {
449                 case CC_REQUEST_HELP:
450                         sprint(self, "  ^2cvar_purechanges^7: Prints a list of all changed gameplay cvars\n");
451                         return;
452                         
453                 case CC_REQUEST_COMMAND:
454                         sprint(self, cvar_purechanges);
455                         return; // never fall through to usage
456                         
457                 default:
458                 case CC_REQUEST_USAGE:
459                         sprint(self, "\nUsage:^3 sv_cmd cvar_purechanges\n");
460                         sprint(self, "  No arguments required.\n");
461                         //sprint(self, "See also: ^2cvar_changes^7\n");
462                         return;
463         }
464 }
465
466 void ClientCommand_info(float request, float argc)
467 {
468         string command;
469         
470         switch(request)
471         {
472                 case CC_REQUEST_HELP:
473                         sprint(self, "  ^2info^7: Request for unique server information set up by admin\n");
474                         return;
475                         
476                 case CC_REQUEST_COMMAND:
477                         command = cvar_string_builtin(strcat("sv_info_", argv(1))); 
478                         if(command)
479                                 wordwrap_sprint(command, 1111); // why 1111?
480                         else
481                                 sprint(self, "ERROR: unsupported info command\n");
482                         return; // never fall through to usage
483                         
484                 default:
485                 case CC_REQUEST_USAGE:
486                         sprint(self, "\nUsage:^3 cmd info request\n");
487                         sprint(self, "  Where 'request' is the suffixed string appended onto the request for cvar.\n");
488                         return;
489         }
490 }
491
492 void ClientCommand_join(float request)
493 {
494         switch(request)
495         {
496                 case CC_REQUEST_HELP:
497                         sprint(self, "  ^2join^7: Become a player in the game\n");
498                         return;
499                         
500                 case CC_REQUEST_COMMAND:
501                         if(self.flags & FL_CLIENT)
502                         {
503                                 if(self.classname != "player" && !lockteams && !g_arena)
504                                 {
505                                         if(nJoinAllowed(1)) 
506                                         {
507                                                 if(g_ca) { self.caplayer = 1; }
508                                                 if(autocvar_g_campaign) { campaign_bots_may_start = 1; }
509                                                 
510                                                 self.classname = "player";
511                                                 PlayerScore_Clear(self);
512                                                 bprint ("^4", self.netname, "^4 is playing now\n");
513                                                 PutClientInServer();
514                                         }
515                                         else 
516                                         {
517                                                 //player may not join because of g_maxplayers is set
518                                                 centerprint_atprio(self, CENTERPRIO_MAPVOTE, PREVENT_JOIN_TEXT);
519                                         }
520                                 }
521                         }
522                         return; // never fall through to usage
523                         
524                 default:
525                 case CC_REQUEST_USAGE:
526                         sprint(self, "\nUsage:^3 cmd join\n");
527                         sprint(self, "  No arguments required.\n");
528                         return;
529         }
530 }
531
532 void ClientCommand_ladder(float request)
533 {
534         switch(request)
535         {
536                 case CC_REQUEST_HELP:
537                         sprint(self, "  ^2ladder^7: Get information about top players if supported\n");
538                         return;
539                         
540                 case CC_REQUEST_COMMAND:
541                         sprint(self, ladder_reply);
542                         return; // never fall through to usage
543                         
544                 default:
545                 case CC_REQUEST_USAGE:
546                         sprint(self, "\nUsage:^3 cmd ladder\n");
547                         sprint(self, "  No arguments required.\n");
548                         return;
549         }
550 }
551
552 void ClientCommand_lsmaps(float request)
553 {
554         switch(request)
555         {
556                 case CC_REQUEST_HELP:
557                         sprint(self, "  ^2lsmaps^7: List maps which can be used with the current game mode\n");
558                         return;
559                         
560                 case CC_REQUEST_COMMAND:
561                         sprint(self, lsmaps_reply);
562                         return; // never fall through to usage
563                         
564                 default:
565                 case CC_REQUEST_USAGE:
566                         sprint(self, "\nUsage:^3 cmd lsmaps\n");
567                         sprint(self, "  No arguments required.\n");
568                         return;
569         }
570 }
571
572 void ClientCommand_lsnewmaps(float request)
573 {
574         switch(request)
575         {
576                 case CC_REQUEST_HELP:
577                         sprint(self, "  ^2lsnewmaps^7: List maps which TODO\n");
578                         return;
579                         
580                 case CC_REQUEST_COMMAND:
581                         sprint(self, lsnewmaps_reply);
582                         return; // never fall through to usage
583                         
584                 default:
585                 case CC_REQUEST_USAGE:
586                         sprint(self, "\nUsage:^3 cmd lsnewmaps\n");
587                         sprint(self, "  No arguments required.\n");
588                         return;
589         }
590 }
591
592 void ClientCommand_maplist(float request)
593 {
594         switch(request)
595         {
596                 case CC_REQUEST_HELP:
597                         sprint(self, "  ^2maplist^7: Full server maplist reply\n");
598                         return;
599                         
600                 case CC_REQUEST_COMMAND:
601                         sprint(self, maplist_reply);
602                         return; // never fall through to usage
603                         
604                 default:
605                 case CC_REQUEST_USAGE:
606                         sprint(self, "\nUsage:^3 cmd maplist\n");
607                         sprint(self, "  No arguments required.\n");
608                         return;
609         }
610 }
611
612 void ClientCommand_rankings(float request)
613 {
614         switch(request)
615         {
616                 case CC_REQUEST_HELP:
617                         sprint(self, "  ^2rankings^7: Print information about rankings\n");
618                         return;
619                         
620                 case CC_REQUEST_COMMAND:
621                         sprint(self, rankings_reply);
622                         return; // never fall through to usage
623                         
624                 default:
625                 case CC_REQUEST_USAGE:
626                         sprint(self, "\nUsage:^3 cmd rankings\n");
627                         sprint(self, "  No arguments required.\n");
628                         return;
629         }
630 }
631
632 void ClientCommand_ready(float request) 
633 {
634         switch(request)
635         {
636                 case CC_REQUEST_HELP:
637                         sprint(self, "  ^2ready^7: Qualify as ready to end warmup stage (or restart server if allowed)\n");
638                         return;
639                         
640                 case CC_REQUEST_COMMAND:
641                         if(self.flags & FL_CLIENT)
642                         {
643                                 if(inWarmupStage || autocvar_sv_ready_restart || g_race_qualifying == 2)
644                                 {
645                                         if(!readyrestart_happened || autocvar_sv_ready_restart_repeatable)
646                                         {
647                                                 if (self.ready) // toggle
648                                                 {
649                                                         self.ready = FALSE;
650                                                         bprint(self.netname, "^2 is ^1NOT^2 ready\n");
651                                                 }
652                                                 else
653                                                 {
654                                                         self.ready = TRUE;
655                                                         bprint(self.netname, "^2 is ready\n");
656                                                 }
657
658                                                 // cannot reset the game while a timeout is active!
659                                                 if(!timeoutStatus)
660                                                         ReadyCount();
661                                         } else {
662                                                 sprint(self, "^1Game has already been restarted\n");
663                                         }
664                                 }
665                         }
666                         return; // never fall through to usage
667                         
668                 default:
669                 case CC_REQUEST_USAGE:
670                         sprint(self, "\nUsage:^3 cmd ready\n");
671                         sprint(self, "  No arguments required.\n");
672                         return;
673         }
674 }
675
676 void ClientCommand_records(float request) // TODO: Isn't this flooding with the sprint messages? Old code, but perhaps bad?
677 {
678         float i;
679         
680         switch(request)
681         {
682                 case CC_REQUEST_HELP:
683                         sprint(self, "  ^2records^7: List top 10 records for the current map\n");
684                         return;
685                         
686                 case CC_REQUEST_COMMAND:
687                         for(i = 0; i < 10; ++i)
688                                 sprint(self, records_reply[i]);
689                         return; // never fall through to usage
690                         
691                 default:
692                 case CC_REQUEST_USAGE:
693                         sprint(self, "\nUsage:^3 cmd records\n");
694                         sprint(self, "  No arguments required.\n");
695                         return;
696         }
697 }
698
699 void ClientCommand_reportcvar(float request, float argc, string command) // TODO: confirm this works
700 {
701         float tokens;
702         string s;
703         
704         switch(request)
705         {
706                 case CC_REQUEST_HELP:
707                         sprint(self, "  ^2reportcvar^7: Old system for sending a client cvar to the server\n");
708                         return;
709                         
710                 case CC_REQUEST_COMMAND:
711                         if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
712                         {
713                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
714                                 tokens = tokenize_console(s);
715                         }
716                         GetCvars(1);
717                         return; // never fall through to usage
718                         
719                 default:
720                 case CC_REQUEST_USAGE:
721                         sprint(self, "\nUsage:^3 cmd reportcvar <cvar>\n");
722                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
723                         return;
724         }
725 }
726
727 void ClientCommand_say(float request, float argc, string command)
728 {
729         switch(request)
730         {
731                 case CC_REQUEST_HELP:
732                         sprint(self, "  ^2say^7: Print a message to chat to all players\n");
733                         return;
734                         
735                 case CC_REQUEST_COMMAND:
736                         if(argc >= 2)
737                                 Say(self, FALSE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
738                         return; // never fall through to usage
739                         
740                 default:
741                 case CC_REQUEST_USAGE:
742                         sprint(self, "\nUsage:^3 cmd say <message>\n");
743                         sprint(self, "  Where 'message' is the string of text to say.\n");
744                         return;
745         }
746 }
747
748 void ClientCommand_say_team(float request, float argc, string command)
749 {
750         switch(request)
751         {
752                 case CC_REQUEST_HELP:
753                         sprint(self, "  ^2say_team^7: Print a message to chat to all team mates\n");
754                         return;
755                         
756                 case CC_REQUEST_COMMAND:
757                         if(argc >= 2)
758                                 Say(self, TRUE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
759                         return; // never fall through to usage
760                         
761                 default:
762                 case CC_REQUEST_USAGE:
763                         sprint(self, "\nUsage:^3 cmd say_team <message>\n");
764                         sprint(self, "  Where 'message' is the string of text to say.\n");
765                         return;
766         }
767 }
768
769 void ClientCommand_selectteam(float request, float argc) // TODO: Update the messages for this command
770 {
771         switch(request)
772         {
773                 case CC_REQUEST_HELP:
774                         sprint(self, "  ^2selectteam^7: Attempt to choose a team to join into\n");
775                         return;
776                         
777                 case CC_REQUEST_COMMAND:
778                         if (self.flags & FL_CLIENT)
779                         {
780                                 if(teamplay)
781                                         if not(self.team_forced > 0) 
782                                                 if not(lockteams) 
783                                                         switch(argv(1))
784                                                         {
785                                                                 case "red": ClientKill_TeamChange(COLOR_TEAM1); break;
786                                                                 case "blue": ClientKill_TeamChange(COLOR_TEAM2); break;
787                                                                 case "yellow": ClientKill_TeamChange(COLOR_TEAM3); break;
788                                                                 case "pink": ClientKill_TeamChange(COLOR_TEAM4); break;
789                                                                 case "auto": ClientKill_TeamChange(-1); break;
790                                                                 
791                                                                 default: break;
792                                                         }
793                                                 else
794                                                         sprint(self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
795                                         else
796                                                 sprint(self, "selectteam can not be used as your team is forced\n");
797                                 else
798                                         sprint(self, "selectteam can only be used in teamgames\n");
799                         }
800                         return; // never fall through to usage
801
802                 default:
803                 case CC_REQUEST_USAGE:
804                         //sprint(self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
805                         sprint(self, "\nUsage:^3 cmd selectteam team\n");
806                         sprint(self, "  Where 'team' is the prefered team to try and join.\n");
807                         sprint(self, "  Full list of options here: \"red, blue, yellow, pink, auto\"\n");
808                         return;
809         }
810 }
811
812 void ClientCommand_sentcvar(float request, float argc, string command)
813 {
814         float tokens;
815         string s;
816         
817         switch(request)
818         {
819                 case CC_REQUEST_HELP:
820                         sprint(self, "  ^2sentcvar^7: New system for sending a client cvar to the server\n");
821                         return;
822                         
823                 case CC_REQUEST_COMMAND:
824                         if(argc == 2) // undefined cvar: use the default value on the server then
825                         {
826                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
827                                 tokens = tokenize_console(s);
828                         }
829                         GetCvars(1);
830                         return; // never fall through to usage
831                         
832                 default:
833                 case CC_REQUEST_USAGE:
834                         sprint(self, "\nUsage:^3 cmd sentcvar <cvar>\n");
835                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
836                         return;
837         }
838 }
839
840 void ClientCommand_spectate(float request)
841 {
842         switch(request)
843         {
844                 case CC_REQUEST_HELP:
845                         sprint(self, "  ^2spectate^7: Become an observer\n");
846                         return;
847                         
848                 case CC_REQUEST_COMMAND:
849                         if(self.flags & FL_CLIENT)
850                         {
851                                 if(g_arena) { return; } 
852                                 if(g_lms)
853                                 {
854                                         if(self.lms_spectate_warning)
855                                         {
856                                                 // mark player as spectator
857                                                 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
858                                         }
859                                         else
860                                         {
861                                                 self.lms_spectate_warning = 1;
862                                                 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");
863                                                 return;
864                                         }
865                                 }
866                                 
867                                 if(self.classname == "player" && autocvar_sv_spectate == 1) 
868                                         ClientKill_TeamChange(-2); // observe
869                                 
870                                 // in CA, allow a dead player to move to spectatators (without that, caplayer!=0 will be moved back to the player list)
871                                 // note: if arena game mode is ever done properly, this needs to be removed.
872                                 if(g_ca && self.caplayer && (self.classname == "spectator" || self.classname == "observer"))
873                                 {
874                                         sprint(self, "WARNING: you will spectate in the next round.\n");
875                                         self.caplayer = 0;
876                                 }
877                         }
878                         return; // never fall through to usage
879                         
880                 default:
881                 case CC_REQUEST_USAGE:
882                         sprint(self, "\nUsage:^3 cmd spectate\n");
883                         sprint(self, "  No arguments required.\n");
884                         return;
885         }
886 }
887
888 void ClientCommand_suggestmap(float request, float argc)
889 {
890         switch(request)
891         {
892                 case CC_REQUEST_HELP:
893                         sprint(self, "  ^2suggestmap^7: Suggest a map to the mapvote at match end\n");
894                         return;
895                         
896                 case CC_REQUEST_COMMAND:
897                         sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
898                         return; // never fall through to usage
899                         
900                 default:
901                 case CC_REQUEST_USAGE:
902                         sprint(self, "\nUsage:^3 cmd suggestmap map\n");
903                         sprint(self, "  Where 'map' is the name of the map to suggest.\n");
904                         return;
905         }
906 }
907
908 /*
909 void ClientCommand_(float request)
910 {
911         switch(request)
912         {
913                 case CC_REQUEST_HELP:
914                         sprint(self, "  ^2blah^7: foobar\n");
915                         return;
916                         
917                 case CC_REQUEST_COMMAND:
918                         
919                         return; // never fall through to usage
920                         
921                 default:
922                 case CC_REQUEST_USAGE:
923                         sprint(self, "\nUsage:^3 cmd \n");
924                         sprint(self, "  No arguments required.\n");
925                         return;
926         }
927 }
928 */
929
930
931 // ======================================
932 //  Main Function Called By Engine (cmd)
933 // ======================================
934 // If this function exists, server game code parses clientcommand before the engine code gets it.
935
936 void SV_ParseClientCommand(string command)
937 {
938         float search_request_type;
939         float argc = tokenize_console(command);
940         
941         // for floodcheck
942         switch(strtolower(argv(0)))
943         {
944                 // exempt commands which are not subject to floodcheck
945                 case "begin": break; // handled by engine in host_cmd.c
946                 case "pause": break; // handled by engine in host_cmd.c
947                 case "prespawn": break; // handled by engine in host_cmd.c
948                 case "reportcvar": break; // handled by server in this file
949                 case "sentcvar": break; // handled by server in this file
950                 case "spawn": break; // handled by engine in host_cmd.c
951                 
952                 default: 
953                         if(SV_ParseClientCommand_floodcheck())
954                                 break; // "TRUE": continue, as we're not flooding yet
955                         else
956                                 return; // "FALSE": not allowed to continue, halt
957         }
958         
959         /* NOTE: totally disabled for now, however the functionality and descriptions are there if we ever want it.
960         if(argv(0) == "help") 
961         {
962                 if(argc == 1) 
963                 {
964                         sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are:\n");
965                         ClientCommand_autoswitch(CC_REQUEST_HELP, 0);
966                         ClientCommand_checkfail(CC_REQUEST_HELP, "");
967                         ClientCommand_clientversion(CC_REQUEST_HELP, 0);
968                         ClientCommand_cvar_changes(CC_REQUEST_HELP);
969                         ClientCommand_cvar_purechanges(CC_REQUEST_HELP);
970                         ClientCommand_info(CC_REQUEST_HELP, 0);
971                         ClientCommand_join(CC_REQUEST_HELP); 
972                         ClientCommand_ladder(CC_REQUEST_HELP);
973                         ClientCommand_lsmaps(CC_REQUEST_HELP);
974                         ClientCommand_lsnewmaps(CC_REQUEST_HELP);
975                         ClientCommand_maplist(CC_REQUEST_HELP);
976                         ClientCommand_rankings(CC_REQUEST_HELP);
977                         ClientCommand_ready(CC_REQUEST_HELP);
978                         ClientCommand_records(CC_REQUEST_HELP);
979                         ClientCommand_reportcvar(CC_REQUEST_HELP, 0, "");
980                         ClientCommand_say(CC_REQUEST_HELP, 0, "");
981                         ClientCommand_say_team(CC_REQUEST_HELP, 0, "");
982                         ClientCommand_selectteam(CC_REQUEST_HELP, 0);
983                         ClientCommand_sentcvar(CC_REQUEST_HELP, 0, "");
984                         ClientCommand_spectate(CC_REQUEST_HELP);
985                         ClientCommand_suggestmap(CC_REQUEST_HELP, 0);
986                         sprint(self, "For help about specific commands, type cmd help COMMAND\n");
987                         return;
988                 } 
989                 else
990                         search_request_type = CC_REQUEST_USAGE; // Instead of trying to call a command, we're going to see detailed information about it
991         } 
992         else*/ if(GameCommand_Vote(command, self)) 
993         {
994                 return; // handled by server/vote.qc 
995         }
996         else if(GameCommand_MapVote(argv(0))) 
997         {
998                 return; // handled by server/g_world.qc
999         }
1000         else if(CheatCommand(argc)) 
1001         {
1002                 return; // handled by server/cheats.qc
1003         }
1004         else
1005                 search_request_type = CC_REQUEST_COMMAND; // continue as usual and scan for normal commands
1006         
1007         switch(strtolower((search_request_type == CC_REQUEST_USAGE) ? argv(1) : argv(0)))
1008         {
1009                 // Do not hard code aliases for these, instead create them in defaultXonotic.cfg
1010                 // also: keep in alphabetical order, please ;)
1011                 
1012                 case "autoswitch": ClientCommand_autoswitch(search_request_type, argc); break;
1013                 case "checkfail": ClientCommand_checkfail(search_request_type, command); break;
1014                 case "clientversion": ClientCommand_clientversion(search_request_type, argc); break;
1015                 case "cvar_changes": ClientCommand_cvar_changes(search_request_type); break;
1016                 case "cvar_purechanges": ClientCommand_cvar_purechanges(search_request_type); break;
1017                 case "info": ClientCommand_info(search_request_type, argc); break;
1018                 case "join": ClientCommand_join(search_request_type); break;
1019                 case "ladder": ClientCommand_ladder(search_request_type); break;
1020                 case "lsmaps": ClientCommand_lsmaps(search_request_type); break;
1021                 case "lsnewmaps": ClientCommand_lsnewmaps(search_request_type); break;
1022                 case "maplist": ClientCommand_maplist(search_request_type); break;
1023                 case "rankings": ClientCommand_rankings(search_request_type); break;
1024                 case "ready": ClientCommand_ready(search_request_type); break;
1025                 case "records": ClientCommand_records(search_request_type); break;
1026                 case "reportcvar": ClientCommand_reportcvar(search_request_type, argc, command); break;
1027                 case "say": ClientCommand_say(search_request_type, argc, command); break;
1028                 case "say_team": ClientCommand_say_team(search_request_type, argc, command); break;
1029                 case "selectteam": ClientCommand_selectteam(search_request_type, argc); break;
1030                 case "sentcvar": ClientCommand_sentcvar(search_request_type, argc, command); break;
1031                 case "spectate": ClientCommand_spectate(search_request_type); break;
1032                 case "suggestmap": ClientCommand_suggestmap(search_request_type, argc); break;
1033                 
1034                 default:
1035                         clientcommand(self, command); //print("Invalid command. For a list of supported commands, try cmd help.\n");
1036         }
1037 }