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);
55 // 2 = player needs to ready up
57 // 8 = player needs to vote
85 if(!(nags & 4)) // no vote called? send no string
88 WriteByte(MSG_ENTITY, nags);
92 WriteByte(MSG_ENTITY, vote_yescount);
93 WriteByte(MSG_ENTITY, vote_nocount);
94 WriteByte(MSG_ENTITY, vote_needed_absolute);
95 WriteChar(MSG_ENTITY, to.vote_vote);
99 WriteString(MSG_ENTITY, votecalledvote_display);
103 for(i = 1; i <= maxclients; i += 8)
105 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
106 if(clienttype(e) != CLIENTTYPE_REAL || e.ready)
108 WriteByte(MSG_ENTITY, f);
116 Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
118 void Nagger_VoteChanged()
121 nagger.SendFlags |= 128;
123 void Nagger_VoteCountChanged()
126 nagger.SendFlags |= 64;
128 void Nagger_ReadyCounted()
131 nagger.SendFlags |= 1;
134 void ReadyRestartForce()
138 bprint("^1Server is restarting...\n");
143 if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) {
144 //we have to decrease timelimit to its original value again!!
146 newTL = autocvar_timelimit;
147 newTL -= checkrules_overtimesadded * autocvar_timelimit_overtime;
148 cvar_set("timelimit", ftos(newTL));
151 checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
154 readyrestart_happened = 1;
155 game_starttime = time;
156 if(!g_ca && !g_arena)
157 game_starttime += RESTART_COUNTDOWN;
158 restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
160 inWarmupStage = 0; //once the game is restarted the game is in match stage
162 //reset the .ready status of all players (also spectators)
163 FOR_EACH_CLIENTSLOT(e)
166 Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
168 if(autocvar_teamplay_lockonrestart && teamplay) {
170 bprint("^1The teams are now locked.\n");
173 //initiate the restart-countdown-announcer entity
174 if(autocvar_sv_ready_restart_after_countdown && !g_ca && !g_arena)
176 restartTimer = spawn();
177 restartTimer.think = restartTimer_Think;
178 restartTimer.nextthink = game_starttime;
181 //after a restart every players number of allowed timeouts gets reset, too
182 if(autocvar_sv_timeout)
184 FOR_EACH_REALPLAYER(e)
185 e.allowedTimeouts = autocvar_sv_timeout_number;
188 //reset map immediately if this cvar is not set
189 if (!autocvar_sv_ready_restart_after_countdown)
192 if(autocvar_sv_eventlog)
193 GameLogEcho(":restart");
198 // no arena, assault support yet...
199 if(g_arena | g_assault | gameover | intermission_running | race_completing)
200 localcmd("restart\n");
202 localcmd("\nsv_hook_gamerestart\n");
206 // reset ALL scores, but only do that at the beginning
207 //of the countdown if sv_ready_restart_after_countdown is off!
208 //Otherwise scores could be manipulated during the countdown!
209 if (!autocvar_sv_ready_restart_after_countdown)
214 * Counts how many players are ready. If not enough players are ready, the function
215 * does nothing. If all players are ready, the timelimit will be extended and the
216 * restart_countdown variable is set to allow other functions like PlayerPostThink
217 * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
218 * is not set the map will be resetted.
220 * Function is called after the server receives a 'ready' sign from a player.
229 FOR_EACH_REALPLAYER(e)
238 Nagger_ReadyCounted();
240 if(r) // at least one is ready
241 if(r == p) // and, everyone is ready
246 * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
249 void restartTimer_Think() {
250 restart_mapalreadyrestarted = 1;
258 * Checks whether the player who calls the timeout is allowed to do so.
259 * If so, it initializes the timeout countdown. It also checks whether another
260 * timeout was already running at this time and reacts correspondingly.
262 * affected globals/fields: .allowedTimeouts, remainingTimeoutTime, remainingLeadTime,
263 * timeoutInitiator, timeoutStatus, timeoutHandler
265 * This function is called when a player issues the calltimeout command.
267 void evaluateTimeout() {
268 if (inWarmupStage && !g_warmup_allow_timeout)
269 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
270 if (time < game_starttime )
271 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
272 if (timeoutStatus != 2) {
273 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
274 if (autocvar_timelimit) {
275 //a timelimit was used
277 myTl = autocvar_timelimit;
279 local float lastPossibleTimeout;
280 lastPossibleTimeout = (myTl*60) - autocvar_sv_timeout_leadtime - 1;
282 if (lastPossibleTimeout < time - game_starttime)
283 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
286 //player may not call a timeout if he has no calls left
287 if (self.allowedTimeouts < 1)
288 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
289 //now all required checks are passed
290 self.allowedTimeouts -= 1;
291 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)
292 remainingTimeoutTime = autocvar_sv_timeout_length;
293 remainingLeadTime = autocvar_sv_timeout_leadtime;
294 timeoutInitiator = self;
295 if (timeoutStatus == 0) { //if another timeout was already active, don't change its status (which was 1 or 2) to 1, only change it to 1 if no timeout was active yet
297 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
298 timeoutHandler = spawn();
299 timeoutHandler.think = timeoutHandler_Think;
301 timeoutHandler.nextthink = time; //always let the entity think asap
303 //inform all connected clients about the timeout call
304 Announce("timeoutcalled");
308 * Checks whether a player is allowed to resume the game. If he is allowed to do it,
309 * and the lead time for the timeout is still active, this countdown just will be aborted (the
310 * game will never be paused). Otherwise the remainingTimeoutTime will be set to the corresponding
311 * value of the cvar sv_timeout_resumetime.
313 * This function is called when a player issues the resumegame command.
315 void evaluateTimein() {
317 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
318 if (self != timeoutInitiator)
319 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
320 if (timeoutStatus == 1) {
321 remainingTimeoutTime = timeoutStatus = 0;
322 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
323 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
325 else if (timeoutStatus == 2) {
326 //only shorten the remainingTimeoutTime if it makes sense
327 if( remainingTimeoutTime > (autocvar_sv_timeout_resumetime + 1) ) {
328 bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
329 remainingTimeoutTime = autocvar_sv_timeout_resumetime;
330 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
333 sprint(self, "^7Error: Your resumegame call was discarded!\n");
339 // =======================
340 // Command Sub-Functions
341 // =======================
343 void ClientCommand_autoswitch(float request, entity client, float argc)
347 case CC_REQUEST_HELP:
348 sprint(client, " ^2autoswitch^7: Whether or not to switch automatically when getting a better weapon\n");
351 case CC_REQUEST_COMMAND:
352 client.autoswitch = ("0" != argv(1));
353 sprint(client, strcat("^1autoswitch is currently turned ", (client.autoswitch ? "on" : "off"), ".\n"));
354 return; // never fall through to usage
357 case CC_REQUEST_USAGE:
358 sprint(client, "\nUsage:^3 cmd autoswitch selection\n");
359 sprint(client, " Where 'selection' is 1 or 0 for on or off.\n");
364 void ClientCommand_checkfail(float request, entity client, string command) // used only by client side code
368 case CC_REQUEST_HELP:
369 sprint(client, " ^2checkfail^7: Report if a client-side check failed\n");
372 case CC_REQUEST_COMMAND:
373 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))));
374 client.checkfail = 1;
375 return; // never fall through to usage
378 case CC_REQUEST_USAGE:
379 sprint(client, "\nUsage:^3 cmd checkfail message\n");
380 sprint(client, " Where 'message' is the message reported by client about the fail.\n");
385 void ClientCommand_clientversion(float request, entity client, float argc) // used only by client side code
389 case CC_REQUEST_HELP:
390 sprint(client, " ^2clientversion^7: Release version of the game\n");
393 case CC_REQUEST_COMMAND:
394 if(client.flags & FL_CLIENT)
396 client.version = ((argv(1) == "$gameversion") ? 1 : stof(argv(1)));
398 if(client.version < autocvar_gameversion_min || client.version > autocvar_gameversion_max)
400 client.version_mismatch = 1;
401 ClientKill_TeamChange(-2); // observe
403 else if(autocvar_g_campaign || autocvar_g_balance_teams || autocvar_g_balance_teams_force)
405 //JoinBestTeam(self, FALSE, TRUE);
407 else if(teamplay && !autocvar_sv_spectate && !(client.team_forced > 0))
409 client.classname = "observer"; // really?
410 stuffcmd(client, "menu_showteamselect\n");
413 return; // never fall through to usage
416 case CC_REQUEST_USAGE:
417 sprint(client, "\nUsage:^3 cmd clientversion version\n");
418 sprint(client, " Where 'version' is the game version reported by client.\n");
423 void ClientCommand_cvar_changes(float request, entity client)
427 case CC_REQUEST_HELP:
428 sprint(client, " ^2cvar_changes^7: Prints a list of all changed server cvars\n");
431 case CC_REQUEST_COMMAND:
432 sprint(client, cvar_changes);
433 return; // never fall through to usage
436 case CC_REQUEST_USAGE:
437 sprint(client, "\nUsage:^3 sv_cmd cvar_changes\n");
438 sprint(client, " No arguments required.\n");
439 //sprint(client, "See also: ^2cvar_purechanges^7\n");
444 void ClientCommand_cvar_purechanges(float request, entity client)
448 case CC_REQUEST_HELP:
449 sprint(client, " ^2cvar_purechanges^7: Prints a list of all changed gameplay cvars\n");
452 case CC_REQUEST_COMMAND:
453 sprint(client, cvar_purechanges);
454 return; // never fall through to usage
457 case CC_REQUEST_USAGE:
458 sprint(client, "\nUsage:^3 sv_cmd cvar_purechanges\n");
459 sprint(client, " No arguments required.\n");
460 //sprint(client, "See also: ^2cvar_changes^7\n");
465 void ClientCommand_info(float request, entity client, float argc)
471 case CC_REQUEST_HELP:
472 sprint(client, " ^2info^7: Request for unique server information set up by admin\n");
475 case CC_REQUEST_COMMAND:
476 command = cvar_string_builtin(strcat("sv_info_", argv(1)));
478 wordwrap_sprint(command, 1111); // why 1111?
480 sprint(client, "ERROR: unsupported info command\n");
481 return; // never fall through to usage
484 case CC_REQUEST_USAGE:
485 sprint(client, "\nUsage:^3 cmd info request\n");
486 sprint(client, " Where 'request' is the suffixed string appended onto the request for cvar.\n");
491 void ClientCommand_join(float request, entity client)
497 case CC_REQUEST_HELP:
498 sprint(client, " ^2join^7: Become a player in the game\n");
501 case CC_REQUEST_COMMAND:
502 if(client.flags & FL_CLIENT)
504 if(client.classname != "player" && !lockteams && !g_arena)
510 if(g_ca) { client.caplayer = 1; }
511 if(autocvar_g_campaign) { campaign_bots_may_start = 1; }
513 client.classname = "player";
514 PlayerScore_Clear(client);
515 bprint ("^4", client.netname, "^4 is playing now\n");
521 //player may not join because of g_maxplayers is set
522 centerprint_atprio(client, CENTERPRIO_MAPVOTE, PREVENT_JOIN_TEXT);
526 return; // never fall through to usage
529 case CC_REQUEST_USAGE:
530 sprint(client, "\nUsage:^3 cmd join\n");
531 sprint(client, " No arguments required.\n");
536 void ClientCommand_ladder(float request, entity client)
540 case CC_REQUEST_HELP:
541 sprint(client, " ^2ladder^7: Get information about top players if supported\n");
544 case CC_REQUEST_COMMAND:
545 sprint(client, ladder_reply);
546 return; // never fall through to usage
549 case CC_REQUEST_USAGE:
550 sprint(client, "\nUsage:^3 cmd ladder\n");
551 sprint(client, " No arguments required.\n");
556 void ClientCommand_lsmaps(float request, entity client)
560 case CC_REQUEST_HELP:
561 sprint(client, " ^2lsmaps^7: List maps which can be used with the current game mode\n");
564 case CC_REQUEST_COMMAND:
565 sprint(client, lsmaps_reply);
566 return; // never fall through to usage
569 case CC_REQUEST_USAGE:
570 sprint(client, "\nUsage:^3 cmd lsmaps\n");
571 sprint(client, " No arguments required.\n");
576 void ClientCommand_lsnewmaps(float request, entity client)
580 case CC_REQUEST_HELP:
581 sprint(client, " ^2lsnewmaps^7: List maps which TODO\n");
584 case CC_REQUEST_COMMAND:
585 sprint(client, lsnewmaps_reply);
586 return; // never fall through to usage
589 case CC_REQUEST_USAGE:
590 sprint(client, "\nUsage:^3 cmd lsnewmaps\n");
591 sprint(client, " No arguments required.\n");
596 void ClientCommand_maplist(float request, entity client)
600 case CC_REQUEST_HELP:
601 sprint(client, " ^2maplist^7: Full server maplist reply\n");
604 case CC_REQUEST_COMMAND:
605 sprint(client, maplist_reply);
606 return; // never fall through to usage
609 case CC_REQUEST_USAGE:
610 sprint(client, "\nUsage:^3 cmd maplist\n");
611 sprint(client, " No arguments required.\n");
616 void ClientCommand_rankings(float request, entity client)
620 case CC_REQUEST_HELP:
621 sprint(client, " ^2rankings^7: Print information about rankings\n");
624 case CC_REQUEST_COMMAND:
625 sprint(client, rankings_reply);
626 return; // never fall through to usage
629 case CC_REQUEST_USAGE:
630 sprint(client, "\nUsage:^3 cmd rankings\n");
631 sprint(client, " No arguments required.\n");
636 void ClientCommand_ready(float request, entity client) // TODO: reimplement how this works
640 case CC_REQUEST_HELP:
641 sprint(client, " ^2ready^7: Qualify as ready to end warmup stage (or restart server if allowed)\n");
644 case CC_REQUEST_COMMAND:
645 if(client.flags & FL_CLIENT)
647 if(inWarmupStage || autocvar_sv_ready_restart || g_race_qualifying == 2)
649 if(!readyrestart_happened || autocvar_sv_ready_restart_repeatable)
651 if (client.ready) // toggle
653 client.ready = FALSE;
654 bprint(client.netname, "^2 is ^1NOT^2 ready\n");
659 bprint(client.netname, "^2 is ready\n");
662 // cannot reset the game while a timeout is active!
666 sprint(client, "^1Game has already been restarted\n");
670 return; // never fall through to usage
673 case CC_REQUEST_USAGE:
674 sprint(client, "\nUsage:^3 cmd ready\n");
675 sprint(client, " No arguments required.\n");
680 void ClientCommand_records(float request, entity client)
686 case CC_REQUEST_HELP:
687 sprint(client, " ^2records^7: List top 10 records for the current map\n");
690 case CC_REQUEST_COMMAND:
691 for(i = 0; i < 10; ++i)
692 sprint(client, records_reply[i]);
693 return; // never fall through to usage
696 case CC_REQUEST_USAGE:
697 sprint(client, "\nUsage:^3 cmd records\n");
698 sprint(client, " No arguments required.\n");
703 void ClientCommand_reportcvar(float request, entity client, string command)
710 case CC_REQUEST_HELP:
711 sprint(client, " ^2reportcvar^7: Old system for sending a client cvar to the server\n");
714 case CC_REQUEST_COMMAND:
715 if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
717 s = strcat(substring(command, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
718 tokens = tokenize_console(s);
724 case CC_REQUEST_USAGE:
725 sprint(client, "\nUsage:^3 cmd reportcvar\n");
726 sprint(client, " No arguments required.\n");
731 void ClientCommand_(float request, entity client)
735 case CC_REQUEST_HELP:
736 sprint(client, " ^2blah^7: foobar\n");
739 case CC_REQUEST_COMMAND:
744 case CC_REQUEST_USAGE:
745 sprint(client, "\nUsage:^3 cmd \n");
746 sprint(client, " No arguments required.\n");
751 void ClientCommand_(float request, entity client)
755 case CC_REQUEST_HELP:
756 sprint(client, " ^2blah^7: foobar\n");
759 case CC_REQUEST_COMMAND:
764 case CC_REQUEST_USAGE:
765 sprint(client, "\nUsage:^3 cmd \n");
766 sprint(client, " No arguments required.\n");
773 // ======================================
774 // Main Function Called By Engine (cmd)
775 // ======================================
776 // If this function exists, server game code parses clientcommand before the engine code gets it.
778 void SV_ParseClientCommand(string command)
780 float search_request_type;
781 float argc = tokenize_console(command);
784 switch(strtolower(argv(0)))
786 // exempt commands which are not subject to floodcheck
787 case "begin": break; // handled by engine in host_cmd.c
788 case "pause": break; // handled by engine in host_cmd.c
789 case "prespawn": break; // handled by engine in host_cmd.c
790 case "reportcvar": break; // handled by server in this file
791 case "sentcvar": break; // handled by server in this file
792 case "spawn": break; // handled by engine in host_cmd.c
795 if(SV_ParseClientCommand_floodcheck())
796 break; // "TRUE": continue, as we're not flooding yet
798 return; // "FALSE": not allowed to continue, halt
801 /* NOTE: totally disabled for now, however the functionality and descriptions are there if we ever want it.
802 if(argv(0) == "help")
806 sprint(self, "\nUsage:^3 cmd COMMAND...^7, where possible commands are:\n");
807 ClientCommand_autoswitch(CC_REQUEST_HELP, self, 0);
808 ClientCommand_checkfail(CC_REQUEST_HELP, self, "");
809 ClientCommand_clientversion(CC_REQUEST_HELP, self, 0);
810 ClientCommand_cvar_changes(CC_REQUEST_HELP, self);
811 ClientCommand_cvar_purechanges(CC_REQUEST_HELP, self);
812 ClientCommand_info(CC_REQUEST_HELP, self, 0);
813 ClientCommand_join(CC_REQUEST_HELP, self);
814 ClientCommand_ladder(CC_REQUEST_HELP, self);
815 ClientCommand_lsmaps(CC_REQUEST_HELP, self);
816 ClientCommand_lsnewmaps(CC_REQUEST_HELP, self);
817 ClientCommand_maplist(CC_REQUEST_HELP, self);
818 ClientCommand_rankings(CC_REQUEST_HELP, self);
819 ClientCommand_ready(CC_REQUEST_HELP, self);
820 ClientCommand_records(CC_REQUEST_HELP, self);
821 sprint(self, "For help about specific commands, type cmd help COMMAND\n");
825 search_request_type = CC_REQUEST_USAGE; // Instead of trying to call a command, we're going to see detailed information about it
827 else*/ if(GameCommand_Vote(command, self))
829 return; // handled by server/vote.qc
831 else if(GameCommand_MapVote(argv(0)))
833 return; // handled by server/g_world.qc
835 else if(CheatCommand(argc))
837 return; // handled by server/cheats.qc
840 search_request_type = CC_REQUEST_COMMAND; // continue as usual and scan for normal commands
842 switch(strtolower((search_request_type == CC_REQUEST_USAGE) ? argv(1) : argv(0)))
844 // Do not hard code aliases for these, instead create them in defaultXonotic.cfg
845 // also: keep in alphabetical order, please ;)
847 case "autoswitch": ClientCommand_autoswitch(search_request_type, self, argc); break;
848 case "checkfail": ClientCommand_checkfail(search_request_type, self, command); break;
849 case "clientversion": ClientCommand_clientversion(search_request_type, self, argc); break;
850 case "cvar_changes": ClientCommand_cvar_changes(search_request_type, self); break;
851 case "cvar_purechanges": ClientCommand_cvar_purechanges(search_request_type, self); break;
852 case "info": ClientCommand_info(search_request_type, self, argc); break;
853 case "join": ClientCommand_join(search_request_type, self); break;
854 case "ladder": ClientCommand_ladder(search_request_type, self); break;
855 case "lsmaps": ClientCommand_lsmaps(search_request_type, self); break;
856 case "lsnewmaps": ClientCommand_lsnewmaps(search_request_type, self); break;
857 case "maplist": ClientCommand_maplist(search_request_type, self); break;
858 case "rankings": ClientCommand_rankings(search_request_type, self); break;
859 case "ready": ClientCommand_ready(search_request_type, self); break;
860 case "records": ClientCommand_records(search_request_type, self); break;
863 clientcommand(self, command); //print("Invalid command. For a list of supported commands, try cmd help.\n");