]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/clientcommands.qc
Merge remote 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_atprio(self, CENTERPRIO_MAPVOTE, 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         switch(request)
691         {
692                 case CC_REQUEST_HELP:
693                         sprint(self, "  ^2selectteam^7: Attempt to choose a team to join into\n");
694                         return;
695                         
696                 case CC_REQUEST_COMMAND:
697                         if (self.flags & FL_CLIENT)
698                         {
699                                 if(teamplay)
700                                         if not(self.team_forced > 0) 
701                                                 if not(lockteams) 
702                                                         switch(argv(1))
703                                                         {
704                                                                 case "red": ClientKill_TeamChange(COLOR_TEAM1); break;
705                                                                 case "blue": ClientKill_TeamChange(COLOR_TEAM2); break;
706                                                                 case "yellow": ClientKill_TeamChange(COLOR_TEAM3); break;
707                                                                 case "pink": ClientKill_TeamChange(COLOR_TEAM4); break;
708                                                                 case "auto": ClientKill_TeamChange(-1); break;
709                                                                 
710                                                                 default: break;
711                                                         }
712                                                 else
713                                                         sprint(self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
714                                         else
715                                                 sprint(self, "selectteam can not be used as your team is forced\n");
716                                 else
717                                         sprint(self, "selectteam can only be used in teamgames\n");
718                         }
719                         return; // never fall through to usage
720
721                 default:
722                 case CC_REQUEST_USAGE:
723                         //sprint(self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
724                         sprint(self, "\nUsage:^3 cmd selectteam team\n");
725                         sprint(self, "  Where 'team' is the prefered team to try and join.\n");
726                         sprint(self, "  Full list of options here: \"red, blue, yellow, pink, auto\"\n");
727                         return;
728         }
729 }
730
731 void ClientCommand_sentcvar(float request, float argc, string command)
732 {
733         float tokens;
734         string s;
735         
736         switch(request)
737         {
738                 case CC_REQUEST_HELP:
739                         sprint(self, "  ^2sentcvar^7: New system for sending a client cvar to the server\n");
740                         return;
741                         
742                 case CC_REQUEST_COMMAND:
743                         if(argc == 2) // undefined cvar: use the default value on the server then
744                         {
745                                 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
746                                 tokens = tokenize_console(s);
747                         }
748                         GetCvars(1);
749                         return; // never fall through to usage
750                         
751                 default:
752                 case CC_REQUEST_USAGE:
753                         sprint(self, "\nUsage:^3 cmd sentcvar <cvar>\n");
754                         sprint(self, "  Where 'cvar' is the cvar plus arguments to send to the server.\n");
755                         return;
756         }
757 }
758
759 void ClientCommand_spectate(float request)
760 {
761         switch(request)
762         {
763                 case CC_REQUEST_HELP:
764                         sprint(self, "  ^2spectate^7: Become an observer\n");
765                         return;
766                         
767                 case CC_REQUEST_COMMAND:
768                         if(self.flags & FL_CLIENT)
769                         {
770                                 if(g_arena) { return; } 
771                                 if(g_lms)
772                                 {
773                                         if(self.lms_spectate_warning)
774                                         {
775                                                 // mark player as spectator
776                                                 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
777                                         }
778                                         else
779                                         {
780                                                 self.lms_spectate_warning = 1;
781                                                 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");
782                                                 return;
783                                         }
784                                 }
785                                 
786                                 if(self.classname == "player" && autocvar_sv_spectate == 1) 
787                                         ClientKill_TeamChange(-2); // observe
788                                 
789                                 // in CA, allow a dead player to move to spectatators (without that, caplayer!=0 will be moved back to the player list)
790                                 // note: if arena game mode is ever done properly, this needs to be removed.
791                                 if(g_ca && self.caplayer && (self.classname == "spectator" || self.classname == "observer"))
792                                 {
793                                         sprint(self, "WARNING: you will spectate in the next round.\n");
794                                         self.caplayer = 0;
795                                 }
796                         }
797                         return; // never fall through to usage
798                         
799                 default:
800                 case CC_REQUEST_USAGE:
801                         sprint(self, "\nUsage:^3 cmd spectate\n");
802                         sprint(self, "  No arguments required.\n");
803                         return;
804         }
805 }
806
807 void ClientCommand_suggestmap(float request, float argc)
808 {
809         switch(request)
810         {
811                 case CC_REQUEST_HELP:
812                         sprint(self, "  ^2suggestmap^7: Suggest a map to the mapvote at match end\n");
813                         return;
814                         
815                 case CC_REQUEST_COMMAND:
816                         sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
817                         return; // never fall through to usage
818                         
819                 default:
820                 case CC_REQUEST_USAGE:
821                         sprint(self, "\nUsage:^3 cmd suggestmap map\n");
822                         sprint(self, "  Where 'map' is the name of the map to suggest.\n");
823                         return;
824         }
825 }
826
827 void ClientCommand_teamstatus(float request)
828 {
829         switch(request)
830         {
831                 case CC_REQUEST_HELP:
832                         sprint(self, "  ^2teamstatus^7: Print detailed score information for all players\n");
833                         return;
834                         
835                 case CC_REQUEST_COMMAND:
836                         Score_NicePrint(self);
837                         return; // never fall through to usage
838                         
839                 default:
840                 case CC_REQUEST_USAGE:
841                         sprint(self, "\nUsage:^3 cmd teamstatus\n");
842                         sprint(self, "  No arguments required.\n");
843                         return;
844         }
845 }
846
847 void ClientCommand_tell(float request, float argc, string command)
848 {
849         entity e;
850         
851         switch(request)
852         {
853                 case CC_REQUEST_HELP:
854                         sprint(self, "  ^2tell^7: Send a message directly to a player\n");
855                         return;
856                         
857                 case CC_REQUEST_COMMAND:
858                         e = GetCommandPlayerSlotTargetFromTokenizedCommand(argc, 1);
859                         if(e && argc > ParseCommandPlayerSlotTarget_firsttoken)
860                         {
861                                 Say(self, FALSE, e, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)), TRUE);
862                         }
863                         else
864                         {
865                                 if(argc > ParseCommandPlayerSlotTarget_firsttoken)
866                                         trigger_magicear_processmessage_forallears(self, -1, world, substring(command, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)));
867                         }
868                         return; // never fall through to usage
869                         
870                 default:
871                 case CC_REQUEST_USAGE:
872                         sprint(self, "\nUsage:^3 cmd tell playerid <message>\n");
873                         sprint(self, "  Where 'playerid' is the entity number of the player to send the 'message' to.\n");
874                         return;
875         }
876 }
877
878 void ClientCommand_timein(float request)
879 {
880         switch(request)
881         {
882                 case CC_REQUEST_HELP:
883                         sprint(self, "  ^2timein^7: Resume the game from being paused with a timeout\n");
884                         return;
885                         
886                 case CC_REQUEST_COMMAND:
887                         if(self.flags & FL_CLIENT)
888                         {
889                                 if(autocvar_sv_timeout)
890                                 {
891                                         if (!timeoutStatus)
892                                                 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
893                                         if (self != timeoutInitiator)
894                                                 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
895                                                 
896                                         if (timeoutStatus == 1) 
897                                         {
898                                                 remainingTimeoutTime = timeoutStatus = 0;
899                                                 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
900                                                 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
901                                         }
902                                         else if (timeoutStatus == 2) 
903                                         {
904                                                 //only shorten the remainingTimeoutTime if it makes sense
905                                                 if( remainingTimeoutTime > (autocvar_sv_timeout_resumetime + 1) ) 
906                                                 {
907                                                         bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
908                                                         remainingTimeoutTime = autocvar_sv_timeout_resumetime;
909                                                         timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
910                                                 }
911                                                 else
912                                                         sprint(self, "^7Error: Your resumegame call was discarded!\n");
913                                         }
914                                 }
915                         }
916                         return; // never fall through to usage
917                         
918                 default:
919                 case CC_REQUEST_USAGE:
920                         sprint(self, "\nUsage:^3 cmd timein\n");
921                         sprint(self, "  No arguments required.\n");
922                         return;
923         }
924 }
925
926 void ClientCommand_timeout(float request) // DEAR GOD THIS COMMAND IS TERRIBLE.
927 {
928         switch(request)
929         {
930                 case CC_REQUEST_HELP:
931                         sprint(self, "  ^2timeout^7: Call a timeout which pauses the game for certain amount of time unless unpaused\n");
932                         return;
933                         
934                 case CC_REQUEST_COMMAND:
935                         if(self.flags & FL_CLIENT)
936                         {
937                                 if(autocvar_sv_timeout) 
938                                 {
939                                         if(self.classname == "player") 
940                                         {
941                                                 if(votecalled)
942                                                         sprint(self, "^7Error: you can not call a timeout while a vote is active!\n");
943                                                 else
944                                                 {
945                                                         if (inWarmupStage && !g_warmup_allow_timeout)
946                                                                 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
947                                                         if (time < game_starttime )
948                                                                 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
949                                                                 
950                                                         if (timeoutStatus != 2) {
951                                                                 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
952                                                                 if (autocvar_timelimit) {
953                                                                         //a timelimit was used
954                                                                         float myTl;
955                                                                         myTl = autocvar_timelimit;
956
957                                                                         float lastPossibleTimeout;
958                                                                         lastPossibleTimeout = (myTl*60) - autocvar_sv_timeout_leadtime - 1;
959
960                                                                         if (lastPossibleTimeout < time - game_starttime)
961                                                                                 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
962                                                                 }
963                                                         }
964                                                         
965                                                         //player may not call a timeout if he has no calls left
966                                                         if (self.allowedTimeouts < 1)
967                                                                 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
968                                                                 
969                                                                 
970                                                         //now all required checks are passed
971                                                         self.allowedTimeouts -= 1;
972                                                         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)
973                                                         remainingTimeoutTime = autocvar_sv_timeout_length;
974                                                         remainingLeadTime = autocvar_sv_timeout_leadtime;
975                                                         timeoutInitiator = self;
976                                                         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
977                                                                 timeoutStatus = 1;
978                                                                 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
979                                                                 timeoutHandler = spawn();
980                                                                 timeoutHandler.think = timeoutHandler_Think;
981                                                         }
982                                                         timeoutHandler.nextthink = time; //always let the entity think asap
983
984                                                         //inform all connected clients about the timeout call
985                                                         Announce("timeoutcalled");
986                                                 }
987                                         }
988                                         else
989                                                 sprint(self, "^7Error: only players can call a timeout!\n");
990                                 }
991                         }
992                         return; // never fall through to usage
993                         
994                 default:
995                 case CC_REQUEST_USAGE:
996                         sprint(self, "\nUsage:^3 cmd timeout\n");
997                         sprint(self, "  No arguments required.\n");
998                         return;
999         }
1000 }
1001
1002 void ClientCommand_voice(float request, float argc, string command)
1003 {
1004         switch(request)
1005         {
1006                 case CC_REQUEST_HELP:
1007                         sprint(self, "  ^2voice^7: Send voice message via sound\n");
1008                         return;
1009                         
1010                 case CC_REQUEST_COMMAND:
1011                         if(argc >= 3)
1012                                 VoiceMessage(argv(1), substring(command, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
1013                         else
1014                                 VoiceMessage(argv(1), "");
1015                         return; // never fall through to usage
1016                         
1017                 default:
1018                 case CC_REQUEST_USAGE:
1019                         sprint(self, "\nUsage:^3 cmd voice\n");
1020                         sprint(self, "  FIXME ARGUMENTS UNKNOWN.\n");
1021                         return;
1022         }
1023 }
1024
1025 /*
1026 void ClientCommand_(float request)
1027 {
1028         switch(request)
1029         {
1030                 case CC_REQUEST_HELP:
1031                         sprint(self, "  ^2blah^7: foobar\n");
1032                         return;
1033                         
1034                 case CC_REQUEST_COMMAND:
1035                         
1036                         return; // never fall through to usage
1037                         
1038                 default:
1039                 case CC_REQUEST_USAGE:
1040                         sprint(self, "\nUsage:^3 cmd \n");
1041                         sprint(self, "  No arguments required.\n");
1042                         return;
1043         }
1044 }
1045 */
1046
1047
1048 // ======================================
1049 //  Main Function Called By Engine (cmd)
1050 // ======================================
1051 // If this function exists, server game code parses clientcommand before the engine code gets it.
1052
1053 void SV_ParseClientCommand(string command)
1054 {
1055         float search_request_type;
1056         float argc = tokenize_console(command);
1057         
1058         // for floodcheck
1059         switch(strtolower(argv(0)))
1060         {
1061                 // exempt commands which are not subject to floodcheck
1062                 case "begin": break; // handled by engine in host_cmd.c
1063                 case "pause": break; // handled by engine in host_cmd.c
1064                 case "prespawn": break; // handled by engine in host_cmd.c
1065                 case "reportcvar": break; // handled by server in this file
1066                 case "sentcvar": break; // handled by server in this file
1067                 case "spawn": break; // handled by engine in host_cmd.c
1068                 
1069                 default: 
1070                         if(SV_ParseClientCommand_floodcheck())
1071                                 break; // "TRUE": continue, as we're not flooding yet
1072                         else
1073                                 return; // "FALSE": not allowed to continue, halt
1074         }
1075         
1076         /* NOTE: totally disabled for now, however the functionality and descriptions are there if we ever want it.
1077         if(argv(0) == "help") 
1078         {
1079                 if(argc == 1) 
1080                 {
1081                         sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are:\n");
1082                         ClientCommand_autoswitch(CC_REQUEST_HELP, 0);
1083                         ClientCommand_checkfail(CC_REQUEST_HELP, "");
1084                         ClientCommand_clientversion(CC_REQUEST_HELP, 0);
1085                         ClientCommand_cvar_changes(CC_REQUEST_HELP);
1086                         ClientCommand_cvar_purechanges(CC_REQUEST_HELP);
1087                         ClientCommand_info(CC_REQUEST_HELP, 0);
1088                         ClientCommand_join(CC_REQUEST_HELP); 
1089                         ClientCommand_ladder(CC_REQUEST_HELP);
1090                         ClientCommand_lsmaps(CC_REQUEST_HELP);
1091                         ClientCommand_lsnewmaps(CC_REQUEST_HELP);
1092                         ClientCommand_maplist(CC_REQUEST_HELP);
1093                         ClientCommand_rankings(CC_REQUEST_HELP);
1094                         ClientCommand_ready(CC_REQUEST_HELP);
1095                         ClientCommand_records(CC_REQUEST_HELP);
1096                         ClientCommand_reportcvar(CC_REQUEST_HELP, 0, "");
1097                         ClientCommand_say(CC_REQUEST_HELP, 0, "");
1098                         ClientCommand_say_team(CC_REQUEST_HELP, 0, "");
1099                         ClientCommand_selectteam(CC_REQUEST_HELP, 0);
1100                         ClientCommand_sentcvar(CC_REQUEST_HELP, 0, "");
1101                         ClientCommand_spectate(CC_REQUEST_HELP);
1102                         ClientCommand_suggestmap(CC_REQUEST_HELP, 0);
1103                         ClientCommand_teamstatus(CC_REQUEST_HELP);
1104                         ClientCommand_tell(CC_REQUEST_HELP, 0, "");
1105                         ClientCommand_timein(CC_REQUEST_HELP);
1106                         ClientCommand_timeout(CC_REQUEST_HELP);
1107                         ClientCommand_voice(CC_REQUEST_HELP, 0, "");
1108                         sprint(self, "For help about specific commands, type cmd help COMMAND\n");
1109                         return;
1110                 } 
1111                 else
1112                         search_request_type = CC_REQUEST_USAGE; // Instead of trying to call a command, we're going to see detailed information about it
1113         } 
1114         else*/ if(GameCommand_Vote(command, self)) 
1115         {
1116                 return; // handled by server/vote.qc 
1117         }
1118         else if(GameCommand_MapVote(argv(0))) 
1119         {
1120                 return; // handled by server/g_world.qc
1121         }
1122         else if(CheatCommand(argc)) 
1123         {
1124                 return; // handled by server/cheats.qc
1125         }
1126         else
1127                 search_request_type = CC_REQUEST_COMMAND; // continue as usual and scan for normal commands
1128         
1129         switch(strtolower((search_request_type == CC_REQUEST_USAGE) ? argv(1) : argv(0)))
1130         {
1131                 // Do not hard code aliases for these, instead create them in defaultXonotic.cfg
1132                 // also: keep in alphabetical order, please ;)
1133                 
1134                 case "autoswitch": ClientCommand_autoswitch(search_request_type, argc); break;
1135                 case "checkfail": ClientCommand_checkfail(search_request_type, command); break;
1136                 case "clientversion": ClientCommand_clientversion(search_request_type, argc); break;
1137                 case "cvar_changes": ClientCommand_cvar_changes(search_request_type); break;
1138                 case "cvar_purechanges": ClientCommand_cvar_purechanges(search_request_type); break;
1139                 case "info": ClientCommand_info(search_request_type, argc); break;
1140                 case "join": ClientCommand_join(search_request_type); break;
1141                 case "ladder": ClientCommand_ladder(search_request_type); break;
1142                 case "lsmaps": ClientCommand_lsmaps(search_request_type); break;
1143                 case "lsnewmaps": ClientCommand_lsnewmaps(search_request_type); break;
1144                 case "maplist": ClientCommand_maplist(search_request_type); break;
1145                 case "rankings": ClientCommand_rankings(search_request_type); break;
1146                 case "ready": ClientCommand_ready(search_request_type); break;
1147                 case "records": ClientCommand_records(search_request_type); break;
1148                 case "reportcvar": ClientCommand_reportcvar(search_request_type, argc, command); break;
1149                 case "say": ClientCommand_say(search_request_type, argc, command); break;
1150                 case "say_team": ClientCommand_say_team(search_request_type, argc, command); break;
1151                 case "selectteam": ClientCommand_selectteam(search_request_type, argc); break;
1152                 case "sentcvar": ClientCommand_sentcvar(search_request_type, argc, command); break;
1153                 case "spectate": ClientCommand_spectate(search_request_type); break;
1154                 case "suggestmap": ClientCommand_suggestmap(search_request_type, argc); break;
1155                 case "teamstatus": ClientCommand_teamstatus(search_request_type); break;
1156                 case "tell": ClientCommand_tell(search_request_type, argc, command); break;
1157                 case "timein": ClientCommand_timein(search_request_type); break;
1158                 case "timeout": ClientCommand_timeout(search_request_type); break;
1159                 case "voice": ClientCommand_voice(search_request_type, argc, command); break;
1160                 
1161                 default:
1162                         clientcommand(self, command); //print("Invalid command. For a list of supported commands, try cmd help.\n");
1163         }
1164 }