]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/clientcommands.qc
Merge remote-tracking branch 'origin/master' into samual/updatecommands
[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 // =======================
260 //  Command Sub-Functions
261 // =======================
262
263 void ClientCommand_autoswitch(float request, float argc)
264 {
265         switch(request)
266         {
267                 case CC_REQUEST_HELP:
268                         sprint(self, "  ^2autoswitch^7: Whether or not to switch automatically when getting a better weapon\n");
269                         return;
270                         
271                 case CC_REQUEST_COMMAND:
272                         self.autoswitch = ("0" != argv(1));
273                         sprint(self, strcat("^1autoswitch is currently turned ", (self.autoswitch ? "on" : "off"), ".\n"));
274                         return; // never fall through to usage
275                         
276                 default:
277                 case CC_REQUEST_USAGE:
278                         sprint(self, "\nUsage:^3 cmd autoswitch selection\n");
279                         sprint(self, "  Where 'selection' is 1 or 0 for on or off.\n"); 
280                         return;
281         }
282 }
283
284 void ClientCommand_checkfail(float request, string command) // used only by client side code
285 {
286         switch(request)
287         {
288                 case CC_REQUEST_HELP:
289                         sprint(self, "  ^2checkfail^7: Report if a client-side check failed\n");
290                         return;
291                         
292                 case CC_REQUEST_COMMAND:
293                         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))));
294                         self.checkfail = 1;
295                         return; // never fall through to usage
296                         
297                 default:
298                 case CC_REQUEST_USAGE:
299                         sprint(self, "\nUsage:^3 cmd checkfail message\n");
300                         sprint(self, "  Where 'message' is the message reported by client about the fail.\n");
301                         return;
302         }
303 }
304
305 void ClientCommand_clientversion(float request, float argc) // used only by client side code
306 {
307         switch(request)
308         {
309                 case CC_REQUEST_HELP:
310                         sprint(self, "  ^2clientversion^7: Release version of the game\n");
311                         return;
312                         
313                 case CC_REQUEST_COMMAND:
314                         if(self.flags & FL_CLIENT)
315                         {
316                                 self.version = ((argv(1) == "$gameversion") ? 1 : stof(argv(1)));
317                                 
318                                 if(self.version < autocvar_gameversion_min || self.version > autocvar_gameversion_max)
319                                 {
320                                         self.version_mismatch = 1;
321                                         ClientKill_TeamChange(-2); // observe
322                                 } 
323                                 else if(autocvar_g_campaign || autocvar_g_balance_teams || autocvar_g_balance_teams_force) 
324                                 {
325                                         //JoinBestTeam(self, FALSE, TRUE);
326                                 } 
327                                 else if(teamplay && !autocvar_sv_spectate && !(self.team_forced > 0)) 
328                                 {
329                                         self.classname = "observer"; // really?
330                                         stuffcmd(self, "menu_showteamselect\n");
331                                 }
332                         }
333                         return; // never fall through to usage
334                         
335                 default:
336                 case CC_REQUEST_USAGE:
337                         sprint(self, "\nUsage:^3 cmd clientversion version\n");
338                         sprint(self, "  Where 'version' is the game version reported by self.\n");
339                         return;
340         }
341 }
342
343 void ClientCommand_cvar_changes(float request)
344 {
345         switch(request)
346         {
347                 case CC_REQUEST_HELP:
348                         sprint(self, "  ^2cvar_changes^7: Prints a list of all changed server cvars\n");
349                         return;
350                         
351                 case CC_REQUEST_COMMAND:
352                         sprint(self, cvar_changes);
353                         return; // never fall through to usage
354                         
355                 default:
356                 case CC_REQUEST_USAGE:
357                         sprint(self, "\nUsage:^3 sv_cmd cvar_changes\n");
358                         sprint(self, "  No arguments required.\n");
359                         //sprint(self, "See also: ^2cvar_purechanges^7\n");
360                         return;
361         }
362 }
363
364 void ClientCommand_cvar_purechanges(float request)
365 {
366         switch(request)
367         {
368                 case CC_REQUEST_HELP:
369                         sprint(self, "  ^2cvar_purechanges^7: Prints a list of all changed gameplay cvars\n");
370                         return;
371                         
372                 case CC_REQUEST_COMMAND:
373                         sprint(self, cvar_purechanges);
374                         return; // never fall through to usage
375                         
376                 default:
377                 case CC_REQUEST_USAGE:
378                         sprint(self, "\nUsage:^3 sv_cmd cvar_purechanges\n");
379                         sprint(self, "  No arguments required.\n");
380                         //sprint(self, "See also: ^2cvar_changes^7\n");
381                         return;
382         }
383 }
384
385 void ClientCommand_info(float request, float argc)
386 {
387         string command;
388         
389         switch(request)
390         {
391                 case CC_REQUEST_HELP:
392                         sprint(self, "  ^2info^7: Request for unique server information set up by admin\n");
393                         return;
394                         
395                 case CC_REQUEST_COMMAND:
396                         command = cvar_string_builtin(strcat("sv_info_", argv(1))); 
397                         if(command)
398                                 wordwrap_sprint(command, 1111); // why 1111?
399                         else
400                                 sprint(self, "ERROR: unsupported info command\n");
401                         return; // never fall through to usage
402                         
403                 default:
404                 case CC_REQUEST_USAGE:
405                         sprint(self, "\nUsage:^3 cmd info request\n");
406                         sprint(self, "  Where 'request' is the suffixed string appended onto the request for cvar.\n");
407                         return;
408         }
409 }
410
411 void ClientCommand_join(float request)
412 {
413         switch(request)
414         {
415                 case CC_REQUEST_HELP:
416                         sprint(self, "  ^2join^7: Become a player in the game\n");
417                         return;
418                         
419                 case CC_REQUEST_COMMAND:
420                         if(self.flags & FL_CLIENT)
421                         {
422                                 if(self.classname != "player" && !lockteams && !g_arena)
423                                 {
424                                         if(nJoinAllowed(1)) 
425                                         {
426                                                 if(g_ca) { self.caplayer = 1; }
427                                                 if(autocvar_g_campaign) { campaign_bots_may_start = 1; }
428                                                 
429                                                 self.classname = "player";
430                                                 PlayerScore_Clear(self);
431                                                 bprint ("^4", self.netname, "^4 is playing now\n");
432                                                 PutClientInServer();
433                                         }
434                                         else 
435                                         {
436                                                 //player may not join because of g_maxplayers is set
437                                                 centerprint(self, PREVENT_JOIN_TEXT);
438                                         }
439                                 }
440                         }
441                         return; // never fall through to usage
442                         
443                 default:
444                 case CC_REQUEST_USAGE:
445                         sprint(self, "\nUsage:^3 cmd join\n");
446                         sprint(self, "  No arguments required.\n");
447                         return;
448         }
449 }
450
451 void ClientCommand_ladder(float request)
452 {
453         switch(request)
454         {
455                 case CC_REQUEST_HELP:
456                         sprint(self, "  ^2ladder^7: Get information about top players if supported\n");
457                         return;
458                         
459                 case CC_REQUEST_COMMAND:
460                         sprint(self, ladder_reply);
461                         return; // never fall through to usage
462                         
463                 default:
464                 case CC_REQUEST_USAGE:
465                         sprint(self, "\nUsage:^3 cmd ladder\n");
466                         sprint(self, "  No arguments required.\n");
467                         return;
468         }
469 }
470
471 void ClientCommand_lsmaps(float request)
472 {
473         switch(request)
474         {
475                 case CC_REQUEST_HELP:
476                         sprint(self, "  ^2lsmaps^7: List maps which can be used with the current game mode\n");
477                         return;
478                         
479                 case CC_REQUEST_COMMAND:
480                         sprint(self, lsmaps_reply);
481                         return; // never fall through to usage
482                         
483                 default:
484                 case CC_REQUEST_USAGE:
485                         sprint(self, "\nUsage:^3 cmd lsmaps\n");
486                         sprint(self, "  No arguments required.\n");
487                         return;
488         }
489 }
490
491 void ClientCommand_lsnewmaps(float request)
492 {
493         switch(request)
494         {
495                 case CC_REQUEST_HELP:
496                         sprint(self, "  ^2lsnewmaps^7: List maps which TODO\n");
497                         return;
498                         
499                 case CC_REQUEST_COMMAND:
500                         sprint(self, lsnewmaps_reply);
501                         return; // never fall through to usage
502                         
503                 default:
504                 case CC_REQUEST_USAGE:
505                         sprint(self, "\nUsage:^3 cmd lsnewmaps\n");
506                         sprint(self, "  No arguments required.\n");
507                         return;
508         }
509 }
510
511 void ClientCommand_maplist(float request)
512 {
513         switch(request)
514         {
515                 case CC_REQUEST_HELP:
516                         sprint(self, "  ^2maplist^7: Full server maplist reply\n");
517                         return;
518                         
519                 case CC_REQUEST_COMMAND:
520                         sprint(self, maplist_reply);
521                         return; // never fall through to usage
522                         
523                 default:
524                 case CC_REQUEST_USAGE:
525                         sprint(self, "\nUsage:^3 cmd maplist\n");
526                         sprint(self, "  No arguments required.\n");
527                         return;
528         }
529 }
530
531 void ClientCommand_rankings(float request)
532 {
533         switch(request)
534         {
535                 case CC_REQUEST_HELP:
536                         sprint(self, "  ^2rankings^7: Print information about rankings\n");
537                         return;
538                         
539                 case CC_REQUEST_COMMAND:
540                         sprint(self, rankings_reply);
541                         return; // never fall through to usage
542                         
543                 default:
544                 case CC_REQUEST_USAGE:
545                         sprint(self, "\nUsage:^3 cmd rankings\n");
546                         sprint(self, "  No arguments required.\n");
547                         return;
548         }
549 }
550
551 void ClientCommand_ready(float request) 
552 {
553         switch(request)
554         {
555                 case CC_REQUEST_HELP:
556                         sprint(self, "  ^2ready^7: Qualify as ready to end warmup stage (or restart server if allowed)\n");
557                         return;
558                         
559                 case CC_REQUEST_COMMAND:
560                         if(self.flags & FL_CLIENT)
561                         {
562                                 if(inWarmupStage || autocvar_sv_ready_restart || g_race_qualifying == 2)
563                                 {
564                                         if(!readyrestart_happened || autocvar_sv_ready_restart_repeatable)
565                                         {
566                                                 if (self.ready) // toggle
567                                                 {
568                                                         self.ready = FALSE;
569                                                         bprint(self.netname, "^2 is ^1NOT^2 ready\n");
570                                                 }
571                                                 else
572                                                 {
573                                                         self.ready = TRUE;
574                                                         bprint(self.netname, "^2 is ready\n");
575                                                 }
576
577                                                 // cannot reset the game while a timeout is active!
578                                                 if(!timeoutStatus)
579                                                         ReadyCount();
580                                         } else {
581                                                 sprint(self, "^1Game has already been restarted\n");
582                                         }
583                                 }
584                         }
585                         return; // never fall through to usage
586                         
587                 default:
588                 case CC_REQUEST_USAGE:
589                         sprint(self, "\nUsage:^3 cmd ready\n");
590                         sprint(self, "  No arguments required.\n");
591                         return;
592         }
593 }
594
595 void ClientCommand_records(float request) // TODO: Isn't this flooding with the sprint messages? Old code, but perhaps bad?
596 {
597         float i;
598         
599         switch(request)
600         {
601                 case CC_REQUEST_HELP:
602                         sprint(self, "  ^2records^7: List top 10 records for the current map\n");
603                         return;
604                         
605                 case CC_REQUEST_COMMAND:
606                         for(i = 0; i < 10; ++i)
607                                 sprint(self, records_reply[i]);
608                         return; // never fall through to usage
609                         
610                 default:
611                 case CC_REQUEST_USAGE:
612                         sprint(self, "\nUsage:^3 cmd records\n");
613                         sprint(self, "  No arguments required.\n");
614                         return;
615         }
616 }
617
618 void ClientCommand_reportcvar(float request, float argc, string command) // TODO: confirm this works
619 {
620         float tokens;
621         string s;
622         
623         switch(request)
624         {
625                 case CC_REQUEST_HELP:
626                         sprint(self, "  ^2reportcvar^7: Old system for sending a client cvar to the server\n");
627                         return;
628                         
629                 case CC_REQUEST_COMMAND:
630                         if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
631                         {
632                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
633                                 tokens = tokenize_console(s);
634                         }
635                         GetCvars(1);
636                         return; // never fall through to usage
637                         
638                 default:
639                 case CC_REQUEST_USAGE:
640                         sprint(self, "\nUsage:^3 cmd reportcvar <cvar>\n");
641                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
642                         return;
643         }
644 }
645
646 void ClientCommand_say(float request, float argc, string command)
647 {
648         switch(request)
649         {
650                 case CC_REQUEST_HELP:
651                         sprint(self, "  ^2say^7: Print a message to chat to all players\n");
652                         return;
653                         
654                 case CC_REQUEST_COMMAND:
655                         if(argc >= 2)
656                                 Say(self, FALSE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
657                         return; // never fall through to usage
658                         
659                 default:
660                 case CC_REQUEST_USAGE:
661                         sprint(self, "\nUsage:^3 cmd say <message>\n");
662                         sprint(self, "  Where 'message' is the string of text to say.\n");
663                         return;
664         }
665 }
666
667 void ClientCommand_say_team(float request, float argc, string command)
668 {
669         switch(request)
670         {
671                 case CC_REQUEST_HELP:
672                         sprint(self, "  ^2say_team^7: Print a message to chat to all team mates\n");
673                         return;
674                         
675                 case CC_REQUEST_COMMAND:
676                         if(argc >= 2)
677                                 Say(self, TRUE, world, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
678                         return; // never fall through to usage
679                         
680                 default:
681                 case CC_REQUEST_USAGE:
682                         sprint(self, "\nUsage:^3 cmd say_team <message>\n");
683                         sprint(self, "  Where 'message' is the string of text to say.\n");
684                         return;
685         }
686 }
687
688 void ClientCommand_selectteam(float request, float argc) // TODO: Update the messages for this command
689 {
690         float selection;
691         
692         switch(request)
693         {
694                 case CC_REQUEST_HELP:
695                         sprint(self, "  ^2selectteam^7: Attempt to choose a team to join into\n");
696                         return;
697                         
698                 case CC_REQUEST_COMMAND:
699                         if (self.flags & FL_CLIENT)
700                         {
701                                 if(teamplay)
702                                         if not(self.team_forced > 0) 
703                                                 if not(lockteams) 
704                                                 {
705                                                         switch(argv(1))
706                                                         {
707                                                                 case "red": selection = COLOR_TEAM1; break;
708                                                                 case "blue": selection = COLOR_TEAM2; break;
709                                                                 case "yellow": selection = COLOR_TEAM3; break;
710                                                                 case "pink": selection = COLOR_TEAM4; break;
711                                                                 case "auto": selection = (-1); break;
712                                                                 
713                                                                 default: break;
714                                                         }
715                                                         
716                                                         if(selection)
717                                                         {
718                                                                 if(self.team != selection || self.deadflag != DEAD_NO)
719                                                                         ClientKill_TeamChange(selection);
720                                                                 else
721                                                                         sprint(self, "^7You already are on that team.\n");
722                                                         }
723                                                 }
724                                                 else
725                                                         sprint(self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
726                                         else
727                                                 sprint(self, "^7selectteam can not be used as your team is forced\n");
728                                 else
729                                         sprint(self, "^7selectteam can only be used in teamgames\n");
730                         }
731                         return; // never fall through to usage
732
733                 default:
734                 case CC_REQUEST_USAGE:
735                         //sprint(self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
736                         sprint(self, "\nUsage:^3 cmd selectteam team\n");
737                         sprint(self, "  Where 'team' is the prefered team to try and join.\n");
738                         sprint(self, "  Full list of options here: \"red, blue, yellow, pink, auto\"\n");
739                         return;
740         }
741 }
742
743 void ClientCommand_sentcvar(float request, float argc, string command)
744 {
745         float tokens;
746         string s;
747         
748         switch(request)
749         {
750                 case CC_REQUEST_HELP:
751                         sprint(self, "  ^2sentcvar^7: New system for sending a client cvar to the server\n");
752                         return;
753                         
754                 case CC_REQUEST_COMMAND:
755                         if(argc == 2) // undefined cvar: use the default value on the server then
756                         {
757                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
758                                 tokens = tokenize_console(s);
759                         }
760                         GetCvars(1);
761                         return; // never fall through to usage
762                         
763                 default:
764                 case CC_REQUEST_USAGE:
765                         sprint(self, "\nUsage:^3 cmd sentcvar <cvar>\n");
766                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
767                         return;
768         }
769 }
770
771 void ClientCommand_spectate(float request)
772 {
773         switch(request)
774         {
775                 case CC_REQUEST_HELP:
776                         sprint(self, "  ^2spectate^7: Become an observer\n");
777                         return;
778                         
779                 case CC_REQUEST_COMMAND:
780                         if(self.flags & FL_CLIENT)
781                         {
782                                 if(g_arena) { return; } 
783                                 if(g_lms)
784                                 {
785                                         if(self.lms_spectate_warning)
786                                         {
787                                                 // mark player as spectator
788                                                 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
789                                         }
790                                         else
791                                         {
792                                                 self.lms_spectate_warning = 1;
793                                                 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");
794                                                 return;
795                                         }
796                                 }
797                                 
798                                 if(self.classname == "player" && autocvar_sv_spectate == 1) 
799                                         ClientKill_TeamChange(-2); // observe
800                                 
801                                 // in CA, allow a dead player to move to spectatators (without that, caplayer!=0 will be moved back to the player list)
802                                 // note: if arena game mode is ever done properly, this needs to be removed.
803                                 if(g_ca && self.caplayer && (self.classname == "spectator" || self.classname == "observer"))
804                                 {
805                                         sprint(self, "WARNING: you will spectate in the next round.\n");
806                                         self.caplayer = 0;
807                                 }
808                         }
809                         return; // never fall through to usage
810                         
811                 default:
812                 case CC_REQUEST_USAGE:
813                         sprint(self, "\nUsage:^3 cmd spectate\n");
814                         sprint(self, "  No arguments required.\n");
815                         return;
816         }
817 }
818
819 void ClientCommand_suggestmap(float request, float argc)
820 {
821         switch(request)
822         {
823                 case CC_REQUEST_HELP:
824                         sprint(self, "  ^2suggestmap^7: Suggest a map to the mapvote at match end\n");
825                         return;
826                         
827                 case CC_REQUEST_COMMAND:
828                         sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
829                         return; // never fall through to usage
830                         
831                 default:
832                 case CC_REQUEST_USAGE:
833                         sprint(self, "\nUsage:^3 cmd suggestmap map\n");
834                         sprint(self, "  Where 'map' is the name of the map to suggest.\n");
835                         return;
836         }
837 }
838
839 void ClientCommand_teamstatus(float request)
840 {
841         switch(request)
842         {
843                 case CC_REQUEST_HELP:
844                         sprint(self, "  ^2teamstatus^7: Print detailed score information for all players\n");
845                         return;
846                         
847                 case CC_REQUEST_COMMAND:
848                         Score_NicePrint(self);
849                         return; // never fall through to usage
850                         
851                 default:
852                 case CC_REQUEST_USAGE:
853                         sprint(self, "\nUsage:^3 cmd teamstatus\n");
854                         sprint(self, "  No arguments required.\n");
855                         return;
856         }
857 }
858
859 void ClientCommand_tell(float request, float argc, string command)
860 {
861         entity e;
862         
863         switch(request)
864         {
865                 case CC_REQUEST_HELP:
866                         sprint(self, "  ^2tell^7: Send a message directly to a player\n");
867                         return;
868                         
869                 case CC_REQUEST_COMMAND:
870                         e = GetCommandPlayerSlotTargetFromTokenizedCommand(argc, 1);
871                         if(e && argc > ParseCommandPlayerSlotTarget_firsttoken)
872                         {
873                                 Say(self, FALSE, e, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)), TRUE);
874                         }
875                         else
876                         {
877                                 if(argc > ParseCommandPlayerSlotTarget_firsttoken)
878                                         trigger_magicear_processmessage_forallears(self, -1, world, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)));
879                         }
880                         return; // never fall through to usage
881                         
882                 default:
883                 case CC_REQUEST_USAGE:
884                         sprint(self, "\nUsage:^3 cmd tell playerid <message>\n");
885                         sprint(self, "  Where 'playerid' is the entity number of the player to send the 'message' to.\n");
886                         return;
887         }
888 }
889
890 void ClientCommand_timein(float request)
891 {
892         switch(request)
893         {
894                 case CC_REQUEST_HELP:
895                         sprint(self, "  ^2timein^7: Resume the game from being paused with a timeout\n");
896                         return;
897                         
898                 case CC_REQUEST_COMMAND:
899                         if(self.flags & FL_CLIENT)
900                         {
901                                 if(autocvar_sv_timeout)
902                                 {
903                                         if (!timeoutStatus)
904                                                 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
905                                         if (self != timeoutInitiator)
906                                                 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
907                                                 
908                                         if (timeoutStatus == 1) 
909                                         {
910                                                 remainingTimeoutTime = timeoutStatus = 0;
911                                                 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
912                                                 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
913                                         }
914                                         else if (timeoutStatus == 2) 
915                                         {
916                                                 //only shorten the remainingTimeoutTime if it makes sense
917                                                 if( remainingTimeoutTime > (autocvar_sv_timeout_resumetime + 1) ) 
918                                                 {
919                                                         bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
920                                                         remainingTimeoutTime = autocvar_sv_timeout_resumetime;
921                                                         timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
922                                                 }
923                                                 else
924                                                         sprint(self, "^7Error: Your resumegame call was discarded!\n");
925                                         }
926                                 }
927                         }
928                         return; // never fall through to usage
929                         
930                 default:
931                 case CC_REQUEST_USAGE:
932                         sprint(self, "\nUsage:^3 cmd timein\n");
933                         sprint(self, "  No arguments required.\n");
934                         return;
935         }
936 }
937
938 void ClientCommand_timeout(float request) // DEAR GOD THIS COMMAND IS TERRIBLE.
939 {
940         switch(request)
941         {
942                 case CC_REQUEST_HELP:
943                         sprint(self, "  ^2timeout^7: Call a timeout which pauses the game for certain amount of time unless unpaused\n");
944                         return;
945                         
946                 case CC_REQUEST_COMMAND:
947                         if(self.flags & FL_CLIENT)
948                         {
949                                 if(autocvar_sv_timeout) 
950                                 {
951                                         if(self.classname == "player") 
952                                         {
953                                                 if(votecalled)
954                                                         sprint(self, "^7Error: you can not call a timeout while a vote is active!\n");
955                                                 else
956                                                 {
957                                                         if (inWarmupStage && !g_warmup_allow_timeout)
958                                                                 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
959                                                         if (time < game_starttime )
960                                                                 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
961                                                                 
962                                                         if (timeoutStatus != 2) {
963                                                                 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
964                                                                 if (autocvar_timelimit) {
965                                                                         //a timelimit was used
966                                                                         float myTl;
967                                                                         myTl = autocvar_timelimit;
968
969                                                                         float lastPossibleTimeout;
970                                                                         lastPossibleTimeout = (myTl*60) - autocvar_sv_timeout_leadtime - 1;
971
972                                                                         if (lastPossibleTimeout < time - game_starttime)
973                                                                                 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
974                                                                 }
975                                                         }
976                                                         
977                                                         //player may not call a timeout if he has no calls left
978                                                         if (self.allowedTimeouts < 1)
979                                                                 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
980                                                                 
981                                                                 
982                                                         //now all required checks are passed
983                                                         self.allowedTimeouts -= 1;
984                                                         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)
985                                                         remainingTimeoutTime = autocvar_sv_timeout_length;
986                                                         remainingLeadTime = autocvar_sv_timeout_leadtime;
987                                                         timeoutInitiator = self;
988                                                         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
989                                                                 timeoutStatus = 1;
990                                                                 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
991                                                                 timeoutHandler = spawn();
992                                                                 timeoutHandler.think = timeoutHandler_Think;
993                                                         }
994                                                         timeoutHandler.nextthink = time; //always let the entity think asap
995
996                                                         //inform all connected clients about the timeout call
997                                                         Announce("timeoutcalled");
998                                                 }
999                                         }
1000                                         else
1001                                                 sprint(self, "^7Error: only players can call a timeout!\n");
1002                                 }
1003                         }
1004                         return; // never fall through to usage
1005                         
1006                 default:
1007                 case CC_REQUEST_USAGE:
1008                         sprint(self, "\nUsage:^3 cmd timeout\n");
1009                         sprint(self, "  No arguments required.\n");
1010                         return;
1011         }
1012 }
1013
1014 void ClientCommand_voice(float request, float argc, string command)
1015 {
1016         switch(request)
1017         {
1018                 case CC_REQUEST_HELP:
1019                         sprint(self, "  ^2voice^7: Send voice message via sound\n");
1020                         return;
1021                         
1022                 case CC_REQUEST_COMMAND:
1023                         if(argc >= 3)
1024                                 VoiceMessage(argv(1), substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
1025                         else
1026                                 VoiceMessage(argv(1), "");
1027                         return; // never fall through to usage
1028                         
1029                 default:
1030                 case CC_REQUEST_USAGE:
1031                         sprint(self, "\nUsage:^3 cmd voice\n");
1032                         sprint(self, "  FIXME ARGUMENTS UNKNOWN.\n");
1033                         return;
1034         }
1035 }
1036
1037 /*
1038 void ClientCommand_(float request)
1039 {
1040         switch(request)
1041         {
1042                 case CC_REQUEST_HELP:
1043                         sprint(self, "  ^2blah^7: foobar\n");
1044                         return;
1045                         
1046                 case CC_REQUEST_COMMAND:
1047                         
1048                         return; // never fall through to usage
1049                         
1050                 default:
1051                 case CC_REQUEST_USAGE:
1052                         sprint(self, "\nUsage:^3 cmd \n");
1053                         sprint(self, "  No arguments required.\n");
1054                         return;
1055         }
1056 }
1057 */
1058
1059
1060 // ======================================
1061 //  Main Function Called By Engine (cmd)
1062 // ======================================
1063 // If this function exists, server game code parses clientcommand before the engine code gets it.
1064
1065 void SV_ParseClientCommand(string command)
1066 {
1067         float search_request_type;
1068         float argc = tokenize_console(command);
1069         
1070         // for floodcheck
1071         switch(strtolower(argv(0)))
1072         {
1073                 // exempt commands which are not subject to floodcheck
1074                 case "begin": break; // handled by engine in host_cmd.c
1075                 case "pause": break; // handled by engine in host_cmd.c
1076                 case "prespawn": break; // handled by engine in host_cmd.c
1077                 case "reportcvar": break; // handled by server in this file
1078                 case "sentcvar": break; // handled by server in this file
1079                 case "spawn": break; // handled by engine in host_cmd.c
1080                 
1081                 default: 
1082                         if(SV_ParseClientCommand_floodcheck())
1083                                 break; // "TRUE": continue, as we're not flooding yet
1084                         else
1085                                 return; // "FALSE": not allowed to continue, halt
1086         }
1087         
1088         /* NOTE: totally disabled for now, however the functionality and descriptions are there if we ever want it.
1089         if(argv(0) == "help") 
1090         {
1091                 if(argc == 1) 
1092                 {
1093                         sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are:\n");
1094                         ClientCommand_autoswitch(CC_REQUEST_HELP, 0);
1095                         ClientCommand_checkfail(CC_REQUEST_HELP, "");
1096                         ClientCommand_clientversion(CC_REQUEST_HELP, 0);
1097                         ClientCommand_cvar_changes(CC_REQUEST_HELP);
1098                         ClientCommand_cvar_purechanges(CC_REQUEST_HELP);
1099                         ClientCommand_info(CC_REQUEST_HELP, 0);
1100                         ClientCommand_join(CC_REQUEST_HELP); 
1101                         ClientCommand_ladder(CC_REQUEST_HELP);
1102                         ClientCommand_lsmaps(CC_REQUEST_HELP);
1103                         ClientCommand_lsnewmaps(CC_REQUEST_HELP);
1104                         ClientCommand_maplist(CC_REQUEST_HELP);
1105                         ClientCommand_rankings(CC_REQUEST_HELP);
1106                         ClientCommand_ready(CC_REQUEST_HELP);
1107                         ClientCommand_records(CC_REQUEST_HELP);
1108                         ClientCommand_reportcvar(CC_REQUEST_HELP, 0, "");
1109                         ClientCommand_say(CC_REQUEST_HELP, 0, "");
1110                         ClientCommand_say_team(CC_REQUEST_HELP, 0, "");
1111                         ClientCommand_selectteam(CC_REQUEST_HELP, 0);
1112                         ClientCommand_sentcvar(CC_REQUEST_HELP, 0, "");
1113                         ClientCommand_spectate(CC_REQUEST_HELP);
1114                         ClientCommand_suggestmap(CC_REQUEST_HELP, 0);
1115                         ClientCommand_teamstatus(CC_REQUEST_HELP);
1116                         ClientCommand_tell(CC_REQUEST_HELP, 0, "");
1117                         ClientCommand_timein(CC_REQUEST_HELP);
1118                         ClientCommand_timeout(CC_REQUEST_HELP);
1119                         ClientCommand_voice(CC_REQUEST_HELP, 0, "");
1120                         sprint(self, "For help about specific commands, type cmd help COMMAND\n");
1121                         return;
1122                 } 
1123                 else
1124                         search_request_type = CC_REQUEST_USAGE; // Instead of trying to call a command, we're going to see detailed information about it
1125         } 
1126         else*/ if(GameCommand_Vote(command, self)) 
1127         {
1128                 return; // handled by server/vote.qc 
1129         }
1130         else if(GameCommand_MapVote(argv(0))) 
1131         {
1132                 return; // handled by server/g_world.qc
1133         }
1134         else if(CheatCommand(argc)) 
1135         {
1136                 return; // handled by server/cheats.qc
1137         }
1138         else
1139                 search_request_type = CC_REQUEST_COMMAND; // continue as usual and scan for normal commands
1140         
1141         switch(strtolower((search_request_type == CC_REQUEST_USAGE) ? argv(1) : argv(0)))
1142         {
1143                 // Do not hard code aliases for these, instead create them in defaultXonotic.cfg
1144                 // also: keep in alphabetical order, please ;)
1145                 
1146                 case "autoswitch": ClientCommand_autoswitch(search_request_type, argc); break;
1147                 case "checkfail": ClientCommand_checkfail(search_request_type, command); break;
1148                 case "clientversion": ClientCommand_clientversion(search_request_type, argc); break;
1149                 case "cvar_changes": ClientCommand_cvar_changes(search_request_type); break;
1150                 case "cvar_purechanges": ClientCommand_cvar_purechanges(search_request_type); break;
1151                 case "info": ClientCommand_info(search_request_type, argc); break;
1152                 case "join": ClientCommand_join(search_request_type); break;
1153                 case "ladder": ClientCommand_ladder(search_request_type); break;
1154                 case "lsmaps": ClientCommand_lsmaps(search_request_type); break;
1155                 case "lsnewmaps": ClientCommand_lsnewmaps(search_request_type); break;
1156                 case "maplist": ClientCommand_maplist(search_request_type); break;
1157                 case "rankings": ClientCommand_rankings(search_request_type); break;
1158                 case "ready": ClientCommand_ready(search_request_type); break;
1159                 case "records": ClientCommand_records(search_request_type); break;
1160                 case "reportcvar": ClientCommand_reportcvar(search_request_type, argc, command); break;
1161                 case "say": ClientCommand_say(search_request_type, argc, command); break;
1162                 case "say_team": ClientCommand_say_team(search_request_type, argc, command); break;
1163                 case "selectteam": ClientCommand_selectteam(search_request_type, argc); break;
1164                 case "sentcvar": ClientCommand_sentcvar(search_request_type, argc, command); break;
1165                 case "spectate": ClientCommand_spectate(search_request_type); break;
1166                 case "suggestmap": ClientCommand_suggestmap(search_request_type, argc); break;
1167                 case "teamstatus": ClientCommand_teamstatus(search_request_type); break;
1168                 case "tell": ClientCommand_tell(search_request_type, argc, command); break;
1169                 case "timein": ClientCommand_timein(search_request_type); break;
1170                 case "timeout": ClientCommand_timeout(search_request_type); break;
1171                 case "voice": ClientCommand_voice(search_request_type, argc, command); break;
1172                 
1173                 default:
1174                         clientcommand(self, command); //print("Invalid command. For a list of supported commands, try cmd help.\n");
1175         }
1176 }