1 // =======================================================
2 // Server side client commands code, reworked by Samual
3 // Last updated: July 24th, 2011
4 // =======================================================
6 #define CC_REQUEST_HELP 1
7 #define CC_REQUEST_COMMAND 2
8 #define CC_REQUEST_USAGE 3
13 .float cmd_floodcount;
16 float readyrestart_happened;
19 string MapVote_Suggest(string m);
24 // ============================
25 // Misc. Supporting Functions
26 // ============================
28 float SV_ParseClientCommand_floodcheck()
30 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 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 self.cmd_floodcount += 1;
35 if(self.cmd_floodcount > 8) // todo: replace constant 8 with a cvar for the server to control
36 return FALSE; // too much spam, halt
40 self.cmd_floodtime = time;
41 self.cmd_floodcount = 1;
44 return TRUE; // continue, as we're not flooding yet
47 float Nagger_SendEntity(entity to, float sendflags)
51 WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
72 if(!(nags & 4)) // no vote called? send no string
75 WriteByte(MSG_ENTITY, nags);
79 WriteString(MSG_ENTITY, votecalledvote_display);
84 for(i = 1; i <= maxclients; i += 8)
86 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
87 if(clienttype(e) != CLIENTTYPE_REAL || e.ready)
89 WriteByte(MSG_ENTITY, f);
97 Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
99 void Nagger_VoteChanged()
102 nagger.SendFlags |= 128;
104 void Nagger_VoteCountChanged()
107 nagger.SendFlags |= 1;
109 void Nagger_ReadyCounted()
112 nagger.SendFlags |= 1;
115 void ReadyRestartForce()
119 bprint("^1Server is restarting...\n");
124 if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) {
125 //we have to decrease timelimit to its original value again!!
127 newTL = autocvar_timelimit;
128 newTL -= checkrules_overtimesadded * autocvar_timelimit_overtime;
129 cvar_set("timelimit", ftos(newTL));
132 checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
135 readyrestart_happened = 1;
136 game_starttime = time;
137 if(!g_ca && !g_arena)
138 game_starttime += RESTART_COUNTDOWN;
139 restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
141 inWarmupStage = 0; //once the game is restarted the game is in match stage
143 //reset the .ready status of all players (also spectators)
144 FOR_EACH_CLIENTSLOT(e)
147 Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
149 if(autocvar_teamplay_lockonrestart && teamplay) {
151 bprint("^1The teams are now locked.\n");
154 //initiate the restart-countdown-announcer entity
155 if(autocvar_sv_ready_restart_after_countdown && !g_ca && !g_arena)
157 restartTimer = spawn();
158 restartTimer.think = restartTimer_Think;
159 restartTimer.nextthink = game_starttime;
162 //after a restart every players number of allowed timeouts gets reset, too
163 if(autocvar_sv_timeout)
165 FOR_EACH_REALPLAYER(e)
166 e.allowedTimeouts = autocvar_sv_timeout_number;
169 //reset map immediately if this cvar is not set
170 if (!autocvar_sv_ready_restart_after_countdown)
173 if(autocvar_sv_eventlog)
174 GameLogEcho(":restart");
179 // no arena, assault support yet...
180 if(g_arena | g_assault | gameover | intermission_running | race_completing)
181 localcmd("restart\n");
183 localcmd("\nsv_hook_gamerestart\n");
187 // reset ALL scores, but only do that at the beginning
188 //of the countdown if sv_ready_restart_after_countdown is off!
189 //Otherwise scores could be manipulated during the countdown!
190 if (!autocvar_sv_ready_restart_after_countdown)
195 * Counts how many players are ready. If not enough players are ready, the function
196 * does nothing. If all players are ready, the timelimit will be extended and the
197 * restart_countdown variable is set to allow other functions like PlayerPostThink
198 * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
199 * is not set the map will be resetted.
201 * Function is called after the server receives a 'ready' sign from a player.
210 FOR_EACH_REALPLAYER(e)
219 Nagger_ReadyCounted();
221 if(r) // at least one is ready
222 if(r == p) // and, everyone is ready
227 * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
230 void restartTimer_Think() {
231 restart_mapalreadyrestarted = 1;
239 * Checks whether the player who calls the timeout is allowed to do so.
240 * If so, it initializes the timeout countdown. It also checks whether another
241 * timeout was already running at this time and reacts correspondingly.
243 * affected globals/fields: .allowedTimeouts, remainingTimeoutTime, remainingLeadTime,
244 * timeoutInitiator, timeoutStatus, timeoutHandler
246 * This function is called when a player issues the calltimeout command.
248 void evaluateTimeout() {
249 if (inWarmupStage && !g_warmup_allow_timeout)
250 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
251 if (time < game_starttime )
252 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
253 if (timeoutStatus != 2) {
254 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
255 if (autocvar_timelimit) {
256 //a timelimit was used
258 myTl = autocvar_timelimit;
260 local float lastPossibleTimeout;
261 lastPossibleTimeout = (myTl*60) - autocvar_sv_timeout_leadtime - 1;
263 if (lastPossibleTimeout < time - game_starttime)
264 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
267 //player may not call a timeout if he has no calls left
268 if (self.allowedTimeouts < 1)
269 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
270 //now all required checks are passed
271 self.allowedTimeouts -= 1;
272 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)
273 remainingTimeoutTime = autocvar_sv_timeout_length;
274 remainingLeadTime = autocvar_sv_timeout_leadtime;
275 timeoutInitiator = self;
276 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
278 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
279 timeoutHandler = spawn();
280 timeoutHandler.think = timeoutHandler_Think;
282 timeoutHandler.nextthink = time; //always let the entity think asap
284 //inform all connected clients about the timeout call
285 Announce("timeoutcalled");
289 * Checks whether a player is allowed to resume the game. If he is allowed to do it,
290 * and the lead time for the timeout is still active, this countdown just will be aborted (the
291 * game will never be paused). Otherwise the remainingTimeoutTime will be set to the corresponding
292 * value of the cvar sv_timeout_resumetime.
294 * This function is called when a player issues the resumegame command.
296 void evaluateTimein() {
298 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
299 if (self != timeoutInitiator)
300 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
301 if (timeoutStatus == 1) {
302 remainingTimeoutTime = timeoutStatus = 0;
303 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
304 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
306 else if (timeoutStatus == 2) {
307 //only shorten the remainingTimeoutTime if it makes sense
308 if( remainingTimeoutTime > (autocvar_sv_timeout_resumetime + 1) ) {
309 bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
310 remainingTimeoutTime = autocvar_sv_timeout_resumetime;
311 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
314 sprint(self, "^7Error: Your resumegame call was discarded!\n");
320 // =======================
321 // Command Sub-Functions
322 // =======================
324 void ClientCommand_autoswitch(float request, entity client, float argc)
328 case CC_REQUEST_HELP:
329 sprint(client, " ^2autoswitch^7: Whether or not to switch automatically when getting a better weapon\n");
332 case CC_REQUEST_COMMAND:
333 client.autoswitch = ("0" != argv(1));
334 sprint(client, strcat("^1autoswitch is currently turned ", (client.autoswitch ? "on" : "off"), ".\n"));
335 return; // never fall through to usage
338 case CC_REQUEST_USAGE:
339 sprint(client, "\nUsage:^3 cmd autoswitch selection\n");
340 sprint(client, " Where 'selection' is 1 or 0 for on or off.\n");
345 void ClientCommand_checkfail(float request, entity client, string command) // used only by client side code
349 case CC_REQUEST_HELP:
350 sprint(client, " ^2checkfail^7: Report if a client-side check failed\n");
353 case CC_REQUEST_COMMAND:
354 print(sprintf("CHECKFAIL: %s (%s) epically failed check %s\n", client.netname, client.netaddress, substring(command, argv_start_index(1), argv_end_index(-1) - argv_start_index(1))));
355 client.checkfail = 1;
356 return; // never fall through to usage
359 case CC_REQUEST_USAGE:
360 sprint(client, "\nUsage:^3 cmd checkfail message\n");
361 sprint(client, " Where 'message' is the message reported by client about the fail.\n");
366 void ClientCommand_clientversion(float request, entity client, float argc) // used only by client side code
370 case CC_REQUEST_HELP:
371 sprint(client, " ^2clientversion^7: Release version of the game\n");
374 case CC_REQUEST_COMMAND:
375 if(client.flags & FL_CLIENT)
377 client.version = ((argv(1) == "$gameversion") ? 1 : stof(argv(1)));
379 if(client.version < autocvar_gameversion_min || client.version > autocvar_gameversion_max)
381 client.version_mismatch = 1;
382 ClientKill_TeamChange(-2); // observe
384 else if(autocvar_g_campaign || autocvar_g_balance_teams || autocvar_g_balance_teams_force)
386 //JoinBestTeam(self, FALSE, TRUE);
388 else if(teamplay && !autocvar_sv_spectate && !(client.team_forced > 0))
390 client.classname = "observer"; // really?
391 stuffcmd(client, "menu_showteamselect\n");
394 return; // never fall through to usage
397 case CC_REQUEST_USAGE:
398 sprint(client, "\nUsage:^3 cmd clientversion version\n");
399 sprint(client, " Where 'version' is the game version reported by client.\n");
404 void ClientCommand_cvar_changes(float request, entity client)
408 case CC_REQUEST_HELP:
409 sprint(client, " ^2cvar_changes^7: Prints a list of all changed server cvars\n");
412 case CC_REQUEST_COMMAND:
413 sprint(client, cvar_changes);
414 return; // never fall through to usage
417 case CC_REQUEST_USAGE:
418 sprint(client, "\nUsage:^3 sv_cmd cvar_changes\n");
419 sprint(client, " No arguments required.\n");
420 //sprint(client, "See also: ^2cvar_purechanges^7\n");
425 void ClientCommand_cvar_purechanges(float request, entity client)
429 case CC_REQUEST_HELP:
430 sprint(client, " ^2cvar_purechanges^7: Prints a list of all changed gameplay cvars\n");
433 case CC_REQUEST_COMMAND:
434 sprint(client, cvar_purechanges);
435 return; // never fall through to usage
438 case CC_REQUEST_USAGE:
439 sprint(client, "\nUsage:^3 sv_cmd cvar_purechanges\n");
440 sprint(client, " No arguments required.\n");
441 //sprint(client, "See also: ^2cvar_changes^7\n");
446 void ClientCommand_info(float request, entity client, float argc)
452 case CC_REQUEST_HELP:
453 sprint(client, " ^2info^7: Request for unique server information set up by admin\n");
456 case CC_REQUEST_COMMAND:
457 command = cvar_string_builtin(strcat("sv_info_", argv(1)));
459 wordwrap_sprint(command, 1111); // why 1111?
461 sprint(client, "ERROR: unsupported info command\n");
462 return; // never fall through to usage
465 case CC_REQUEST_USAGE:
466 sprint(client, "\nUsage:^3 cmd info request\n");
467 sprint(client, " Where 'request' is the suffixed string appended onto the request for cvar.\n");
472 void ClientCommand_join(float request, entity client)
478 case CC_REQUEST_HELP:
479 sprint(client, " ^2join^7: Become a player in the game\n");
482 case CC_REQUEST_COMMAND:
483 if(client.flags & FL_CLIENT)
485 if(client.classname != "player" && !lockteams && !g_arena)
491 if(g_ca) { client.caplayer = 1; }
492 if(autocvar_g_campaign) { campaign_bots_may_start = 1; }
494 client.classname = "player";
495 PlayerScore_Clear(client);
496 bprint ("^4", client.netname, "^4 is playing now\n");
502 //player may not join because of g_maxplayers is set
503 centerprint_atprio(client, CENTERPRIO_MAPVOTE, PREVENT_JOIN_TEXT);
507 return; // never fall through to usage
510 case CC_REQUEST_USAGE:
511 sprint(client, "\nUsage:^3 cmd join\n");
512 sprint(client, " No arguments required.\n");
517 void ClientCommand_ladder(float request, entity client)
521 case CC_REQUEST_HELP:
522 sprint(client, " ^2ladder^7: Get information about top players if supported\n");
525 case CC_REQUEST_COMMAND:
526 sprint(client, ladder_reply);
527 return; // never fall through to usage
530 case CC_REQUEST_USAGE:
531 sprint(client, "\nUsage:^3 cmd ladder\n");
532 sprint(client, " No arguments required.\n");
537 void ClientCommand_lsmaps(float request, entity client)
541 case CC_REQUEST_HELP:
542 sprint(client, " ^2lsmaps^7: List maps which can be used with the current game mode\n");
545 case CC_REQUEST_COMMAND:
546 sprint(client, lsmaps_reply);
547 return; // never fall through to usage
550 case CC_REQUEST_USAGE:
551 sprint(client, "\nUsage:^3 cmd lsmaps\n");
552 sprint(client, " No arguments required.\n");
557 void ClientCommand_lsnewmaps(float request, entity client)
561 case CC_REQUEST_HELP:
562 sprint(client, " ^2lsnewmaps^7: List maps which TODO\n");
565 case CC_REQUEST_COMMAND:
566 sprint(client, lsnewmaps_reply);
567 return; // never fall through to usage
570 case CC_REQUEST_USAGE:
571 sprint(client, "\nUsage:^3 cmd lsnewmaps\n");
572 sprint(client, " No arguments required.\n");
577 void ClientCommand_maplist(float request, entity client)
581 case CC_REQUEST_HELP:
582 sprint(client, " ^2maplist^7: Full server maplist reply\n");
585 case CC_REQUEST_COMMAND:
586 sprint(client, maplist_reply);
587 return; // never fall through to usage
590 case CC_REQUEST_USAGE:
591 sprint(client, "\nUsage:^3 cmd maplist\n");
592 sprint(client, " No arguments required.\n");
597 void ClientCommand_rankings(float request, entity client)
601 case CC_REQUEST_HELP:
602 sprint(client, " ^2rankings^7: Print information about rankings\n");
605 case CC_REQUEST_COMMAND:
606 sprint(client, rankings_reply);
607 return; // never fall through to usage
610 case CC_REQUEST_USAGE:
611 sprint(client, "\nUsage:^3 cmd rankings\n");
612 sprint(client, " No arguments required.\n");
617 void ClientCommand_ready(float request, entity client) // TODO: reimplement how this works
621 case CC_REQUEST_HELP:
622 sprint(client, " ^2ready^7: Qualify as ready to end warmup stage (or restart server if allowed)\n");
625 case CC_REQUEST_COMMAND:
626 if(client.flags & FL_CLIENT)
628 if(inWarmupStage || autocvar_sv_ready_restart || g_race_qualifying == 2)
630 if(!readyrestart_happened || autocvar_sv_ready_restart_repeatable)
632 if (client.ready) // toggle
634 client.ready = FALSE;
635 bprint(client.netname, "^2 is ^1NOT^2 ready\n");
640 bprint(client.netname, "^2 is ready\n");
643 // cannot reset the game while a timeout is active!
647 sprint(client, "^1Game has already been restarted\n");
651 return; // never fall through to usage
654 case CC_REQUEST_USAGE:
655 sprint(client, "\nUsage:^3 cmd ready\n");
656 sprint(client, " No arguments required.\n");
661 void ClientCommand_records(float request, entity client)
667 case CC_REQUEST_HELP:
668 sprint(client, " ^2records^7: List top 10 records for the current map\n");
671 case CC_REQUEST_COMMAND:
672 for(i = 0; i < 10; ++i)
673 sprint(client, records_reply[i]);
674 return; // never fall through to usage
677 case CC_REQUEST_USAGE:
678 sprint(client, "\nUsage:^3 cmd records\n");
679 sprint(client, " No arguments required.\n");
684 void ClientCommand_reportcvar(float request, entity client, string command)
691 case CC_REQUEST_HELP:
692 sprint(client, " ^2reportcvar^7: Old system for sending a client cvar to the server\n");
695 case CC_REQUEST_COMMAND:
696 if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
698 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
699 tokens = tokenize_console(s);
705 case CC_REQUEST_USAGE:
706 sprint(client, "\nUsage:^3 cmd reportcvar\n");
707 sprint(client, " No arguments required.\n");
712 void ClientCommand_(float request, entity client)
716 case CC_REQUEST_HELP:
717 sprint(client, " ^2blah^7: foobar\n");
720 case CC_REQUEST_COMMAND:
725 case CC_REQUEST_USAGE:
726 sprint(client, "\nUsage:^3 cmd \n");
727 sprint(client, " No arguments required.\n");
732 void ClientCommand_(float request, entity client)
736 case CC_REQUEST_HELP:
737 sprint(client, " ^2blah^7: foobar\n");
740 case CC_REQUEST_COMMAND:
745 case CC_REQUEST_USAGE:
746 sprint(client, "\nUsage:^3 cmd \n");
747 sprint(client, " No arguments required.\n");
754 // ======================================
755 // Main Function Called By Engine (cmd)
756 // ======================================
757 // If this function exists, server game code parses clientcommand before the engine code gets it.
759 void SV_ParseClientCommand(string command)
761 float search_request_type;
762 float argc = tokenize_console(command);
765 switch(strtolower(argv(0)))
767 // exempt commands which are not subject to floodcheck
768 case "begin": break; // handled by engine in host_cmd.c
769 case "pause": break; // handled by engine in host_cmd.c
770 case "prespawn": break; // handled by engine in host_cmd.c
771 case "reportcvar": break; // handled by server in this file
772 case "sentcvar": break; // handled by server in this file
773 case "spawn": break; // handled by engine in host_cmd.c
776 if(SV_ParseClientCommand_floodcheck())
777 break; // "TRUE": continue, as we're not flooding yet
779 return; // "FALSE": not allowed to continue, halt
782 /* NOTE: totally disabled for now, however the functionality and descriptions are there if we ever want it.
783 if(argv(0) == "help")
787 sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are:\n");
788 ClientCommand_autoswitch(CC_REQUEST_HELP, self, 0);
789 ClientCommand_checkfail(CC_REQUEST_HELP, self, "");
790 ClientCommand_clientversion(CC_REQUEST_HELP, self, 0);
791 ClientCommand_cvar_changes(CC_REQUEST_HELP, self);
792 ClientCommand_cvar_purechanges(CC_REQUEST_HELP, self);
793 ClientCommand_info(CC_REQUEST_HELP, self, 0);
794 ClientCommand_join(CC_REQUEST_HELP, self);
795 ClientCommand_ladder(CC_REQUEST_HELP, self);
796 ClientCommand_lsmaps(CC_REQUEST_HELP, self);
797 ClientCommand_lsnewmaps(CC_REQUEST_HELP, self);
798 ClientCommand_maplist(CC_REQUEST_HELP, self);
799 ClientCommand_rankings(CC_REQUEST_HELP, self);
800 ClientCommand_ready(CC_REQUEST_HELP, self);
801 ClientCommand_records(CC_REQUEST_HELP, self);
802 sprint(self, "For help about specific commands, type cmd help COMMAND\n");
806 search_request_type = CC_REQUEST_USAGE; // Instead of trying to call a command, we're going to see detailed information about it
808 else*/ if(GameCommand_Vote(command, self))
810 return; // handled by server/vote.qc
812 else if(GameCommand_MapVote(argv(0)))
814 return; // handled by server/g_world.qc
816 else if(CheatCommand(argc))
818 return; // handled by server/cheats.qc
821 search_request_type = CC_REQUEST_COMMAND; // continue as usual and scan for normal commands
823 switch(strtolower((search_request_type == CC_REQUEST_USAGE) ? argv(1) : argv(0)))
825 // Do not hard code aliases for these, instead create them in defaultXonotic.cfg
826 // also: keep in alphabetical order, please ;)
828 case "autoswitch": ClientCommand_autoswitch(search_request_type, self, argc); break;
829 case "checkfail": ClientCommand_checkfail(search_request_type, self, command); break;
830 case "clientversion": ClientCommand_clientversion(search_request_type, self, argc); break;
831 case "cvar_changes": ClientCommand_cvar_changes(search_request_type, self); break;
832 case "cvar_purechanges": ClientCommand_cvar_purechanges(search_request_type, self); break;
833 case "info": ClientCommand_info(search_request_type, self, argc); break;
834 case "join": ClientCommand_join(search_request_type, self); break;
835 case "ladder": ClientCommand_ladder(search_request_type, self); break;
836 case "lsmaps": ClientCommand_lsmaps(search_request_type, self); break;
837 case "lsnewmaps": ClientCommand_lsnewmaps(search_request_type, self); break;
838 case "maplist": ClientCommand_maplist(search_request_type, self); break;
839 case "rankings": ClientCommand_rankings(search_request_type, self); break;
840 case "ready": ClientCommand_ready(search_request_type, self); break;
841 case "records": ClientCommand_records(search_request_type, self); break;
844 clientcommand(self, command); //print("Invalid command. For a list of supported commands, try cmd help.\n");