]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/command/vote.qc
Allow chmap and map shorthands for gotomap to be votable (if available) without needi...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / command / vote.qc
index 1a826c6f142daf0164d83a7a27f59b37c09d9820..1869a15a898dc51a15ccbe25f8abe9d0714fee64 100644 (file)
 
 #include "../g_damage.qh"
 #include "../g_world.qh"
+#include "../teamplay.qh"
 #include "../race.qh"
 #include "../round_handler.qh"
 #include "../scores.qh"
 
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
+#include <common/gamemodes/_mod.qh>
 
 #include <common/constants.qh>
 #include <common/net_linked.qh>
@@ -128,19 +130,15 @@ void VoteReset()
 
        if (vote_called)
        {
-               strunzone(vote_called_command);
-               strunzone(vote_called_display);
-               strunzone(vote_caller_name);
+               strfree(vote_called_command);
+               strfree(vote_called_display);
+               strfree(vote_caller_name);
        }
 
        vote_called = VOTE_NULL;
        vote_caller = NULL;
-       vote_caller_name = string_null;
        vote_endtime = 0;
 
-       vote_called_command = string_null;
-       vote_called_display = string_null;
-
        vote_parsed_command = string_null;
        vote_parsed_display = string_null;
 
@@ -347,6 +345,11 @@ void reset_map(bool dorespawn)
                        round_handler_Reset(game_starttime);
        }
 
+       if (shuffleteams_on_reset_map)
+       {
+               shuffleteams();
+               shuffleteams_on_reset_map = false;
+       }
        MUTATOR_CALLHOOK(reset_map_global);
 
        FOREACH_ENTITY_FLOAT_ORDERED(pure_data, false,
@@ -367,7 +370,7 @@ void reset_map(bool dorespawn)
                if (it.reset2) it.reset2(it);
        });
 
-       FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), { Unfreeze(it); });
+       FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), { Unfreeze(it, false); });
 
        // Moving the player reset code here since the player-reset depends
        // on spawnpoint entities which have to be reset first --blub
@@ -437,6 +440,8 @@ void ReadyRestart_force()
        restart_mapalreadyrestarted = false; // reset this var, needed when cvar sv_ready_restart_repeatable is in use
 
        // disable the warmup global for the server
+       if(warmup_stage)
+               localcmd("\nsv_hook_warmupend\n");
        warmup_stage = 0;                // once the game is restarted the game is in match stage
 
        // reset the .ready status of all players (also spectators)
@@ -452,7 +457,7 @@ void ReadyRestart_force()
        }
 
        // initiate the restart-countdown-announcer entity
-       if (autocvar_sv_ready_restart_after_countdown)
+       if (sv_ready_restart_after_countdown)
        {
                entity restart_timer = new_pure(restart_timer);
                setthink(restart_timer, ReadyRestart_think);
@@ -464,19 +469,19 @@ void ReadyRestart_force()
        {
                FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { CS(it).allowed_timeouts = autocvar_sv_timeout_number; });
        }
-    // reset map immediately if this cvar is not set
-    if (!autocvar_sv_ready_restart_after_countdown) reset_map(true);
+
+       if (!sv_ready_restart_after_countdown) reset_map(true);
        if (autocvar_sv_eventlog) GameLogEcho(":restart");
 }
 
 void ReadyRestart()
 {
        if (MUTATOR_CALLHOOK(ReadyRestart_Deny) || game_stopped || race_completing) localcmd("restart\n");
-       else localcmd("\nsv_hook_gamerestart\n");
+       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 (!autocvar_sv_ready_restart_after_countdown) Score_ClearAll();
+       if (!sv_ready_restart_after_countdown) Score_ClearAll();
        ReadyRestart_force();
 }
 
@@ -517,7 +522,7 @@ float Votecommand_check_assignment(entity caller, float assignment)
        return false;
 }
 
-string VoteCommand_extractcommand(string input, float startpos, float argc)
+string VoteCommand_extractcommand(string input, float startpos, int argc)
 {
        string output;
 
@@ -537,11 +542,21 @@ float VoteCommand_checknasty(string vote_command)
        return true;
 }
 
+// NOTE: requires input to be surrounded by spaces
+string VoteCommand_checkreplacements(string input)
+{
+       string output = input;
+       // allow gotomap replacements
+       output = strreplace(" map ", " gotomap ", output);
+       output = strreplace(" chmap ", " gotomap ", output);
+       return output;
+}
+
 float VoteCommand_checkinlist(string vote_command, string list)
 {
-       string l = strcat(" ", list, " ");
+       string l = VoteCommand_checkreplacements(strcat(" ", list, " "));
 
-       if (strstrofs(l, strcat(" ", vote_command, " "), 0) >= 0) return true;
+       if (strstrofs(l, VoteCommand_checkreplacements(strcat(" ", vote_command, " ")), 0) >= 0) return true;
 
        return false;
 }
@@ -574,7 +589,7 @@ string ValidateMap(string validated_map, entity caller)
        return validated_map;
 }
 
-float VoteCommand_checkargs(float startpos, float argc)
+float VoteCommand_checkargs(float startpos, int argc)
 {
        float p, q, check, minargs;
        string cvarname = strcat("sv_vote_command_restriction_", argv(startpos));
@@ -648,7 +663,7 @@ float VoteCommand_checkargs(float startpos, float argc)
        return true;
 }
 
-int VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc)
+int VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, int argc)
 {
        string first_command = argv(startpos);
        int missing_chars = argv_start_index(startpos);
@@ -660,6 +675,14 @@ int VoteCommand_parse(entity caller, string vote_command, string vote_list, floa
 
        if (!VoteCommand_checkargs(startpos, argc)) return 0;
 
+       switch (MUTATOR_CALLHOOK(VoteCommand_Parse, caller, first_command, vote_command, startpos, argc))
+       {
+               case MUT_VOTEPARSE_CONTINUE: { break; }
+               case MUT_VOTEPARSE_SUCCESS: { return 1; }
+               case MUT_VOTEPARSE_INVALID: { return -1; }
+               case MUT_VOTEPARSE_UNACCEPTABLE: { return 0; }
+       }
+
        switch (first_command) // now go through and parse the proper commands to adjust as needed.
        {
                case "kick":
@@ -698,6 +721,43 @@ int VoteCommand_parse(entity caller, string vote_command, string vote_list, floa
                        break;
                }
 
+               // TODO: replicate the old behaviour of being able to vote for maps from different modes on multimode servers (possibly support it in gotomap too)
+               // maybe fallback instead of aborting if map name is invalid?
+               case "nextmap":
+               {
+                       vote_command = ValidateMap(argv(startpos + 1), caller);
+                       if (!vote_command)  return -1;
+                       vote_parsed_command = strcat("nextmap ", vote_command);
+                       vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
+
+                       break;
+               }
+
+               case "timelimit": // include restrictions on the maximum votable time limit
+               {
+                       float timelimit_vote = stof(argv(startpos + 1));
+                       if(timelimit_vote > autocvar_timelimit_max || timelimit_vote < autocvar_timelimit_min)
+                       {
+                               print_to(caller, strcat("Invalid timelimit vote, accepted values are between ", ftos(autocvar_timelimit_min), " and ", ftos(autocvar_timelimit_max), "."));
+                               return -1;
+                       }
+                       timelimit_vote = bound(autocvar_timelimit_min, timelimit_vote, autocvar_timelimit_max);
+                       vote_parsed_command = strcat("timelimit ", ftos(timelimit_vote));
+                       vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
+
+                       break;
+               }
+
+               case "restart":
+               {
+                       // 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;
+               }
+
                default:
                {
                        vote_parsed_command = vote_command;
@@ -715,7 +775,7 @@ int VoteCommand_parse(entity caller, string vote_command, string vote_list, floa
 //  Command Sub-Functions
 // =======================
 
-void VoteCommand_abstain(float request, entity caller)  // CLIENT ONLY
+void VoteCommand_abstain(int request, entity caller)  // CLIENT ONLY
 {
        switch (request)
        {
@@ -747,7 +807,7 @@ void VoteCommand_abstain(float request, entity caller)  // CLIENT ONLY
        }
 }
 
-void VoteCommand_call(float request, entity caller, float argc, string vote_command)  // BOTH
+void VoteCommand_call(int request, entity caller, int argc, string vote_command)  // BOTH
 {
        switch (request)
        {
@@ -809,14 +869,15 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm
                                }
 
                                FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++tmp_playercount; });
-                               if (tmp_playercount > 1)
-                                       Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL);
 
                                bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote for ", vote_called_display, "\n");
                                if (autocvar_sv_eventlog)
                                        GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
                                Nagger_VoteChanged();
                                VoteCount(true);  // needed if you are the only one
+
+                               if (tmp_playercount > 1 && vote_called != VOTE_NULL)
+                                       Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL);
                        }
 
                        return;
@@ -834,7 +895,7 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm
        }
 }
 
-void VoteCommand_master(float request, entity caller, float argc, string vote_command)  // CLIENT ONLY
+void VoteCommand_master(int request, entity caller, int argc, string vote_command)  // CLIENT ONLY
 {
        switch (request)
        {
@@ -949,7 +1010,7 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
        }
 }
 
-void VoteCommand_no(float request, entity caller)  // CLIENT ONLY
+void VoteCommand_no(int request, entity caller)  // CLIENT ONLY
 {
        switch (request)
        {
@@ -987,7 +1048,7 @@ void VoteCommand_no(float request, entity caller)  // CLIENT ONLY
        }
 }
 
-void VoteCommand_status(float request, entity caller)  // BOTH
+void VoteCommand_status(int request, entity caller)  // BOTH
 {
        switch (request)
        {
@@ -1009,7 +1070,7 @@ void VoteCommand_status(float request, entity caller)  // BOTH
        }
 }
 
-void VoteCommand_stop(float request, entity caller)  // BOTH
+void VoteCommand_stop(int request, entity caller)  // BOTH
 {
        switch (request)
        {
@@ -1031,7 +1092,7 @@ void VoteCommand_stop(float request, entity caller)  // BOTH
        }
 }
 
-void VoteCommand_yes(float request, entity caller)  // CLIENT ONLY
+void VoteCommand_yes(int request, entity caller)  // CLIENT ONLY
 {
        switch (request)
        {
@@ -1066,7 +1127,7 @@ void VoteCommand_yes(float request, entity caller)  // CLIENT ONLY
 
 /* use this when creating a new command, making sure to place it in alphabetical order... also,
 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
-void VoteCommand_(float request)
+void VoteCommand_(int request)
 {
     switch(request)
     {
@@ -1104,7 +1165,7 @@ void VoteCommand_(float request)
        VOTE_COMMAND("yes", VoteCommand_yes(request, caller), "Select yes in current vote", VC_ASGNMNT_CLIENTONLY) \
        /* nothing */
 
-void VoteCommand_macro_help(entity caller, float argc)
+void VoteCommand_macro_help(entity caller, int argc)
 {
        string command_origin = GetCommandPrefix(caller);
 
@@ -1128,10 +1189,16 @@ void VoteCommand_macro_help(entity caller, float argc)
 
                VOTE_COMMANDS(CMD_REQUEST_USAGE, caller, argc, "");
 #undef VOTE_COMMAND
+
+               string cvarname = strcat("sv_vote_command_help_", argv(2));
+               if(cvar_type(cvarname) & CVAR_TYPEFLAG_EXISTS)
+                       wordwrap_sprint(caller, cvar_string(cvarname), 1000);
+               else
+                       print_to(caller, "No documentation exists for this vote");
        }
 }
 
-float VoteCommand_macro_command(entity caller, float argc, string vote_command)
+float VoteCommand_macro_command(entity caller, int argc, string vote_command)
 {
        #define VOTE_COMMAND(name, function, description, assignment) \
                { if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(1))) { function; return true; } } }
@@ -1147,7 +1214,7 @@ float VoteCommand_macro_command(entity caller, float argc, string vote_command)
 //  Main function handling vote commands
 // ======================================
 
-void VoteCommand(float request, entity caller, float argc, string vote_command)
+void VoteCommand(int request, entity caller, int argc, string vote_command)
 {
        // Guide for working with argc arguments by example:
        // argc:   1    - 2      - 3     - 4