X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fcommand%2Fvote.qc;h=795ea09e5242f12fc0297a500e2b8a29efc8d601;hb=3b5e884b5e67ad05b088c0f0d6af589a6c39be3a;hp=6b39ab7b6d90ffd4d4e8ea90a0dc5962d531af33;hpb=f22b9f83b24cae4a7e31ef6e310214be7d735a4d;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/command/vote.qc b/qcsrc/server/command/vote.qc index 6b39ab7b6..795ea09e5 100644 --- a/qcsrc/server/command/vote.qc +++ b/qcsrc/server/command/vote.qc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -344,7 +345,11 @@ void reset_map(bool dorespawn, bool is_fake_round_start) return; if (!is_fake_round_start) + { + Score_ClearAll(); PlayerStats_GameReport_Reset_All(); + } + if (round_handler_IsActive()) round_handler_Reset(game_starttime); } @@ -414,8 +419,8 @@ void reset_map(bool dorespawn, bool is_fake_round_start) // Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown is set) void ReadyRestart_think(entity this) { - reset_map(true, false); - Score_ClearAll(); + if (!warmup_stage) // if the countdown was not aborted + reset_map(true, false); delete(this); } @@ -425,17 +430,19 @@ void ReadyRestart_force(bool is_fake_round_start) if (time <= game_starttime && game_stopped) return; if (!is_fake_round_start) - bprint("^1Match is restarting...\n"); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_COUNTDOWN_RESTART); VoteReset(); // clear overtime, we have to decrease timelimit to its original value again. if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) cvar_set("timelimit", ftos(autocvar_timelimit - (checkrules_overtimesadded * autocvar_timelimit_overtime))); - checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0; + checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = overtimes = 0; if(warmup_stage) game_starttime = time; // Warmup: No countdown in warmup + else if (autocvar_g_campaign) + game_starttime = time + 3; else game_starttime = time + RESTART_COUNTDOWN; // Go into match mode @@ -472,44 +479,85 @@ void ReadyRestart_force(bool is_fake_round_start) FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { CS(it).allowed_timeouts = autocvar_sv_timeout_number; }); } - if (!sv_ready_restart_after_countdown || warmup_stage) reset_map(true, is_fake_round_start); + if (!sv_ready_restart_after_countdown || warmup_stage) + reset_map(true, is_fake_round_start); + if (autocvar_sv_eventlog) GameLogEcho(":restart"); } void ReadyRestart(bool forceWarmupEnd) { - if (MUTATOR_CALLHOOK(ReadyRestart_Deny) || intermission_running || race_completing) localcmd("restart\n"); + if (MUTATOR_CALLHOOK(ReadyRestart_Deny) || intermission_running || race_completing) + { + // NOTE: ReadyRestart support is mandatory in campaign + if (autocvar_g_campaign) + error("ReadyRestart must be supported in campaign mode!"); + localcmd("restart\n"); // if ReadyRestart is denied, restart the server + } else localcmd("\nsv_hook_readyrestart\n"); - // Reset ALL scores, but only do that at the beginning of the countdown if sv_ready_restart_after_countdown is off! - // Otherwise scores could be manipulated during the countdown. - if (!sv_ready_restart_after_countdown) Score_ClearAll(); - - if(forceWarmupEnd) + if(forceWarmupEnd || autocvar_g_campaign) warmup_stage = 0; // forcefully end warmup and go to match stage else - warmup_stage = cvar("g_warmup"); // go into warmup if it's enabled, otherwise restart into match stage + warmup_stage = autocvar_g_warmup; // go into warmup if it's enabled, otherwise restart into match stage ReadyRestart_force(false); } -// Count the players who are ready and determine whether or not to restart the match +/* Count the players who are ready and determine whether or not to restart the match when: + * a player presses F4 server/command/cmd.qc ClientCommand_ready() + * a player switches from players to specs server/client.qc PutObserverInServer() + * a player joins (from specs or directly) server/client.qc PutPlayerInServer() + * a player disconnects server/client.qc ClientDisconnect() */ void ReadyCount() { + // cannot reset the game while a timeout is active or pending + if (timeout_status) return; + float ready_needed_factor, ready_needed_count; - float t_ready = 0, t_players = 0; + float t_players = 0; + readycount = 0; FOREACH_CLIENT(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME_JOINED(it)), { ++t_players; - if (it.ready) ++t_ready; + if (it.ready) ++readycount; }); - readycount = t_ready; - Nagger_ReadyCounted(); - ready_needed_factor = bound(0.5, cvar("g_warmup_majority_factor"), 0.999); - ready_needed_count = floor(t_players * ready_needed_factor) + 1; + // can't read warmup_stage here as it could have been set to 0 by ReadyRestart() + // and we need to use this when checking if we should abort the countdown + // map_minplayers can only be > 0 if g_warmup was -1 at worldspawn + int minplayers = autocvar_g_warmup > 1 ? autocvar_g_warmup : map_minplayers; + + if (t_players < minplayers) + { + if (game_starttime > time) // someone bailed during countdown, back to warmup + { + warmup_stage = autocvar_g_warmup; // CAN change it AFTER calling Nagger_ReadyCounted() this frame + game_starttime = time; + Send_Notification(NOTIF_ALL, NULL, MSG_MULTI, COUNTDOWN_STOP, minplayers); + if (!sv_ready_restart_after_countdown) // if we ran reset_map() at start of countdown + FOREACH_CLIENT(IS_PLAYER(it), { GiveWarmupResources(it); }); + } + if (warmup_limit > 0) + warmup_limit = -1; + return; // don't ReadyRestart if players are ready but too few + } + else if (minplayers && warmup_limit <= 0) + { + // there's enough players now but we're still in infinite warmup + warmup_limit = cvar("g_warmup_limit"); + if (warmup_limit == 0) + warmup_limit = autocvar_timelimit * 60; + if (warmup_limit > 0) + game_starttime = time; + // implicit else: g_warmup -1 && g_warmup_limit -1 means + // warmup continues until enough players AND enough RUPs (no time limit) + } + + ready_needed_factor = bound(0.5, cvar("g_warmup_majority_factor"), 1); + ready_needed_count = ceil(t_players * ready_needed_factor); if (readycount >= ready_needed_count) ReadyRestart(true); } @@ -519,9 +567,9 @@ void ReadyCount() // Supporting functions for VoteCommand // ====================================== -float Votecommand_check_assignment(entity caller, float assignment) +bool Votecommand_check_assignment(entity caller, float assignment) { - float from_server = (!caller); + bool from_server = (!caller); if ((assignment == VC_ASGNMNT_BOTH) || ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY) @@ -540,33 +588,32 @@ string VoteCommand_extractcommand(string input, float startpos, int argc) return output; } -float VoteCommand_checknasty(string vote_command) +bool VoteCommand_checknasty(string vote_command) { - if ((strstrofs(vote_command, ";", 0) >= 0) + return !((strstrofs(vote_command, ";", 0) >= 0) || (strstrofs(vote_command, "\n", 0) >= 0) || (strstrofs(vote_command, "\r", 0) >= 0) - || (strstrofs(vote_command, "$", 0) >= 0)) return false; - - return true; + || (strstrofs(vote_command, "$", 0) >= 0)); } // NOTE: requires input to be surrounded by spaces string VoteCommand_checkreplacements(string input) { - string output = input; + // add a space around the input so the start and end of the list is captured + string output = strcat(" ", input, " "); // allow gotomap replacements output = strreplace(" map ", " gotomap ", output); output = strreplace(" chmap ", " gotomap ", output); return output; } -float VoteCommand_checkinlist(string vote_command, string list) +bool VoteCommand_checkinlist(string vote_command, string list) { - string l = VoteCommand_checkreplacements(strcat(" ", list, " ")); - - if (strstrofs(l, VoteCommand_checkreplacements(strcat(" ", vote_command, " ")), 0) >= 0) return true; + if (vote_command == "" || list == "") + return false; - return false; + string l = VoteCommand_checkreplacements(list); + return (strstrofs(l, VoteCommand_checkreplacements(vote_command), 0) >= 0); } string ValidateMap(string validated_map, entity caller) @@ -761,10 +808,11 @@ int VoteCommand_parse(entity caller, string vote_command, string vote_list, floa } case "restart": - case "resetmatch": // re-direct all match restarting to resetmatch { - vote_parsed_command = "resetmatch"; - vote_parsed_display = strzone("^1resetmatch"); + // add a delay so that vote result can be seen and announcer can be heard + // if the vote is accepted + vote_parsed_command = strcat("defer 1 ", vote_command); + vote_parsed_display = strzone(strcat("^1", vote_command)); break; }