]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/command/vote.qc
Merge branch 'master' into terencehill/bot_waypoints
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / command / vote.qc
index b91a8f4df3e208633e21e0d7ee40ef12fcddffc1..638dbb1565eabf86df69239c58be3c40f77e9516 100644 (file)
@@ -1,4 +1,8 @@
 #include "vote.qh"
+
+#include <server/defs.qh>
+#include <server/miscfunctions.qh>
+
 #include <common/command/_mod.qh>
 #include "vote.qh"
 
@@ -77,8 +81,9 @@ bool Nagger_SendEntity(entity this, entity to, float sendflags)
        {
                for (i = 1; i <= maxclients; i += 8)
                {
-                       for (f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
-                               if (!IS_REAL_CLIENT(e) || e.ready) f |= b;
+                       for (f = 0, e = edict_num(i), b = BIT(0); b < BIT(8); b <<= 1, e = nextent(e))
+                               if (!IS_REAL_CLIENT(e) || e.ready)
+                                       f |= b;
                        WriteByte(MSG_ENTITY, f);
                }
        }
@@ -109,7 +114,7 @@ void Nagger_ReadyCounted()
 // If the vote_caller is still here, return their name, otherwise vote_caller_name
 string OriginalCallerName()
 {
-       if (IS_REAL_CLIENT(vote_caller)) return vote_caller.netname;
+       if (IS_REAL_CLIENT(vote_caller)) return playername(vote_caller, false);
        return vote_caller_name;
 }
 
@@ -119,23 +124,19 @@ string OriginalCallerName()
 
 void VoteReset()
 {
-       FOREACH_CLIENT(true, LAMBDA(it.vote_selection = 0));
+       FOREACH_CLIENT(true, { it.vote_selection = 0; });
 
        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;
 
@@ -198,13 +199,13 @@ void VoteSpam(float notvoters, float mincount, string result)
        }
 }
 
+#define spectators_allowed (!autocvar_sv_vote_nospectators || (autocvar_sv_vote_nospectators == 1 && (warmup_stage || intermission_running)))
+
 void VoteCount(float first_count)
 {
        // declarations
        vote_accept_count = vote_reject_count = vote_abstain_count = 0;
 
-       bool spectators_allowed = (!autocvar_sv_vote_nospectators || (autocvar_sv_vote_nospectators == 1 && (warmup_stage || gameover)));
-
        float vote_player_count = 0, notvoters = 0;
        float vote_real_player_count = 0, vote_real_accept_count = 0;
        float vote_real_reject_count = 0, vote_real_abstain_count = 0;
@@ -214,7 +215,7 @@ void VoteCount(float first_count)
        Nagger_VoteCountChanged();
 
        // add up all the votes from each connected client
-       FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_CLIENT(it), LAMBDA(
+       FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_CLIENT(it), {
                ++vote_player_count;
                if (IS_PLAYER(it))   ++vote_real_player_count;
                switch (it.vote_selection)
@@ -233,7 +234,7 @@ void VoteCount(float first_count)
                        }
                        default: break;
                }
-       ));
+       });
 
        // Check to see if there are enough players on the server to allow master voting... otherwise, vote master could be used for evil.
        if ((vote_called == VOTE_MASTER) && autocvar_sv_vote_master_playerlimit > vote_player_count)
@@ -336,7 +337,7 @@ void reset_map(bool dorespawn)
 {
        if (time <= game_starttime)
        {
-               if (gameover)
+               if (game_stopped)
                        return;
                if (round_handler_IsActive())
                        round_handler_Reset(game_starttime);
@@ -344,7 +345,10 @@ void reset_map(bool dorespawn)
 
        MUTATOR_CALLHOOK(reset_map_global);
 
-       FOREACH_ENTITY_ORDERED(IS_NOT_A_CLIENT(it), {
+       FOREACH_ENTITY_FLOAT_ORDERED(pure_data, false,
+       {
+               if(IS_CLIENT(it))
+                       continue;
                if (it.reset)
                {
                        it.reset(it);
@@ -359,7 +363,7 @@ void reset_map(bool dorespawn)
                if (it.reset2) it.reset2(it);
        });
 
-       FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), LAMBDA(Unfreeze(it)));
+       FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), { Unfreeze(it); });
 
        // Moving the player reset code here since the player-reset depends
        // on spawnpoint entities which have to be reset first --blub
@@ -367,28 +371,26 @@ void reset_map(bool dorespawn)
        {
                if (!MUTATOR_CALLHOOK(reset_map_players))
                {
-                       FOREACH_CLIENT(true, LAMBDA(
-                               /*
-                               only reset players if a restart countdown is active
-                               this can either be due to cvar sv_ready_restart_after_countdown having set
-                               restart_mapalreadyrestarted to 1 after the countdown ended or when
-                               sv_ready_restart_after_countdown is not used and countdown is still running
-                               */
-                               if (restart_mapalreadyrestarted || (time < game_starttime))
+                       if (restart_mapalreadyrestarted || (time < game_starttime))
+                       {
+                               FOREACH_CLIENT(IS_PLAYER(it),
                                {
+                                       /*
+                                       only reset players if a restart countdown is active
+                                       this can either be due to cvar sv_ready_restart_after_countdown having set
+                                       restart_mapalreadyrestarted to 1 after the countdown ended or when
+                                       sv_ready_restart_after_countdown is not used and countdown is still running
+                                       */
                                        // NEW: changed behaviour so that it prevents that previous spectators/observers suddenly spawn as players
-                                       if (IS_PLAYER(it))
-                                       {
-                                               // PlayerScore_Clear(it);
-                                               it.killcount = 0;
-                                               // stop the player from moving so that he stands still once he gets respawned
-                                               it.velocity = '0 0 0';
-                                               it.avelocity = '0 0 0';
-                                               it.movement = '0 0 0';
-                                               PutClientInServer(it);
-                                       }
-                               }
-                       ));
+                                       // PlayerScore_Clear(it);
+                                       CS(it).killcount = 0;
+                                       // stop the player from moving so that he stands still once he gets respawned
+                                       it.velocity = '0 0 0';
+                                       it.avelocity = '0 0 0';
+                                       CS(it).movement = '0 0 0';
+                                       PutClientInServer(it);
+                               });
+                       }
                }
        }
 }
@@ -405,7 +407,7 @@ void ReadyRestart_think(entity this)
 // Forces a restart of the game without actually reloading the map // this is a mess...
 void ReadyRestart_force()
 {
-       if (time <= game_starttime && gameover)
+       if (time <= game_starttime && game_stopped)
                return;
 
        bprint("^1Server is restarting...\n");
@@ -421,11 +423,12 @@ void ReadyRestart_force()
        game_starttime = time + RESTART_COUNTDOWN;
 
        // clear player attributes
-       FOREACH_CLIENT(true, LAMBDA(
+       FOREACH_CLIENT(IS_PLAYER(it), {
                it.alivetime = 0;
-               it.killcount = 0;
-               PS_GR_P_ADDVAL(it, PLAYERSTATS_ALIVETIME, -PS_GR_P_ADDVAL(it, PLAYERSTATS_ALIVETIME, 0));
-       ));
+               CS(it).killcount = 0;
+               float val = PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_ALIVETIME, 0);
+               PlayerStats_GameReport_Event_Player(it, PLAYERSTATS_ALIVETIME, -val);
+       });
 
        restart_mapalreadyrestarted = false; // reset this var, needed when cvar sv_ready_restart_repeatable is in use
 
@@ -433,7 +436,7 @@ void ReadyRestart_force()
        warmup_stage = 0;                // once the game is restarted the game is in match stage
 
        // reset the .ready status of all players (also spectators)
-       FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(it.ready = false));
+       FOREACH_CLIENT(IS_REAL_CLIENT(it), { it.ready = false; });
        readycount = 0;
        Nagger_ReadyCounted();  // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
 
@@ -445,7 +448,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);
@@ -455,21 +458,21 @@ void ReadyRestart_force()
        // after a restart every players number of allowed timeouts gets reset, too
        if (autocvar_sv_timeout)
        {
-               FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(it.allowed_timeouts = autocvar_sv_timeout_number));
+               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) || gameover || race_completing) localcmd("restart\n");
+       if (MUTATOR_CALLHOOK(ReadyRestart_Deny) || game_stopped || race_completing) localcmd("restart\n");
        else localcmd("\nsv_hook_gamerestart\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();
 }
 
@@ -479,10 +482,10 @@ void ReadyCount()
        float ready_needed_factor, ready_needed_count;
        float t_ready = 0, t_players = 0;
 
-       FOREACH_CLIENT(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || it.caplayer == 1), LAMBDA(
+       FOREACH_CLIENT(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || it.caplayer == 1), {
                ++t_players;
                if (it.ready) ++t_ready;
-       ));
+       });
 
        readycount = t_ready;
 
@@ -578,7 +581,7 @@ float VoteCommand_checkargs(float startpos, float argc)
        if(cvar_type(cvarname) & CVAR_TYPEFLAG_EXISTS)
                cmdrestriction = cvar_string(cvarname);
        else
-               LOG_INFO("NOTE: ", cvarname, " does not exist, no restrictions will be applied.\n");
+               LOG_INFO("NOTE: ", cvarname, " does not exist, no restrictions will be applied.");
 
        if (cmdrestriction == "") return true;
 
@@ -641,26 +644,25 @@ float VoteCommand_checkargs(float startpos, float argc)
        return true;
 }
 
-float 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, float argc)
 {
-       string first_command;
-
-       first_command = argv(startpos);
+       string first_command = argv(startpos);
+       int missing_chars = argv_start_index(startpos);
 
-       /*printf("VoteCommand_parse(): Command: '%s', Length: %f.\n",
-           substring(vote_command, argv_start_index(startpos), strlen(vote_command) - argv_start_index(startpos)),
-           strlen(substring(vote_command, argv_start_index(startpos), strlen(vote_command) - argv_start_index(startpos)))
-       );*/
+       if (autocvar_sv_vote_limit > 0 && strlen(vote_command) > autocvar_sv_vote_limit)
+               return 0;
 
-       if (
-           (autocvar_sv_vote_limit > 0)
-           &&
-           (strlen(substring(vote_command, argv_start_index(startpos), strlen(vote_command) - argv_start_index(startpos))) > autocvar_sv_vote_limit)
-          )   return false;
+       if (!VoteCommand_checkinlist(first_command, vote_list)) return 0;
 
-       if (!VoteCommand_checkinlist(first_command, vote_list)) return false;
+       if (!VoteCommand_checkargs(startpos, argc)) return 0;
 
-       if (!VoteCommand_checkargs(startpos, argc)) return false;
+       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.
        {
@@ -672,16 +674,18 @@ float VoteCommand_parse(entity caller, string vote_command, string vote_list, fl
 
                        if (accepted > 0)
                        {
-                               string reason = ((argc > next_token) ? substring(vote_command, argv_start_index(next_token), strlen(vote_command) - argv_start_index(next_token)) : "No reason provided");
-                               string command_arguments;
+                               string reason = "No reason provided";
+                               if(argc > next_token)
+                                       reason = substring(vote_command, argv_start_index(next_token) - missing_chars, -1);
 
-                               if (first_command == "kickban") command_arguments = strcat(ftos(autocvar_g_ban_default_bantime), " ", ftos(autocvar_g_ban_default_masksize), " ~");
-                               else command_arguments = reason;
+                               string command_arguments = reason;
+                               if (first_command == "kickban")
+                                       command_arguments = strcat(ftos(autocvar_g_ban_default_bantime), " ", ftos(autocvar_g_ban_default_masksize), " ~");
 
                                vote_parsed_command = strcat(first_command, " # ", ftos(etof(victim)), " ", command_arguments);
-                               vote_parsed_display = strcat("^1", vote_command, " (^7", victim.netname, "^1): ", reason);
+                               vote_parsed_display = sprintf("^1%s #%d ^7%s^1 %s", first_command, etof(victim), victim.netname, reason);
                        }
-                       else { print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n")); return false; }
+                       else { print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n")); return 0; }
 
                        break;
                }
@@ -691,13 +695,23 @@ float VoteCommand_parse(entity caller, string vote_command, string vote_list, fl
                case "gotomap":  // re-direct all map selection commands to gotomap
                {
                        vote_command = ValidateMap(argv(startpos + 1), caller);
-                       if (!vote_command)   return false;
+                       if (!vote_command)  return -1;
                        vote_parsed_command = strcat("gotomap ", vote_command);
                        vote_parsed_display = strzone(strcat("^1", vote_parsed_command));
 
                        break;
                }
 
+               case "nextmap": // 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?
+               {
+                       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;
+               }
+
                default:
                {
                        vote_parsed_command = vote_command;
@@ -707,7 +721,7 @@ float VoteCommand_parse(entity caller, string vote_command, string vote_list, fl
                }
        }
 
-       return true;
+       return 1;
 }
 
 
@@ -753,9 +767,8 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm
        {
                case CMD_REQUEST_COMMAND:
                {
-                       bool spectators_allowed = (!autocvar_sv_vote_nospectators || (autocvar_sv_vote_nospectators == 1 && (warmup_stage || gameover)));
-
                        float tmp_playercount = 0;
+                       int parse_error;
 
                        vote_command = VoteCommand_extractcommand(vote_command, 2, argc);
 
@@ -788,11 +801,11 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm
                        {
                                print_to(caller, "^1Syntax error in command, see 'vhelp' for more info.");
                        }
-                       else if (!VoteCommand_parse(caller, vote_command, autocvar_sv_vote_commands, 2, argc))
+                       else if ((parse_error = VoteCommand_parse(caller, vote_command, autocvar_sv_vote_commands, 2, argc)) <= 0)
                        {
-                               print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info.");
+                               if(parse_error == 0)
+                                       print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info.");
                        }
-
                        else  // everything went okay, continue with calling the vote
                        {
                                vote_caller = caller;  // remember who called the vote
@@ -809,11 +822,13 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm
                                        msg_entity = caller;
                                }
 
-                               FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(++tmp_playercount));
-                               if (tmp_playercount > 1)   Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL);  // don't announce a "vote now" sound if player is alone
+                               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));
+                               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
                        }
@@ -845,24 +860,28 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
                                {
                                        case "do":
                                        {
+                                               int parse_error;
                                                vote_command = VoteCommand_extractcommand(vote_command, 3, argc);
 
-                                               if (!caller.vote_master) { print_to(caller, "^1You do not have vote master privelages."); }
+                                               if (!caller.vote_master)
+                                                       print_to(caller, "^1You do not have vote master privileges.");
                                                else if (!VoteCommand_checknasty(vote_command))
                                                {
                                                        print_to(caller, "^1Syntax error in command, see 'vhelp' for more info.");
                                                }
-                                               else if (!VoteCommand_parse(caller, vote_command, strcat(autocvar_sv_vote_commands, " ", autocvar_sv_vote_master_commands), 3, argc))
+                                               else if ((parse_error = VoteCommand_parse(caller, vote_command, strcat(autocvar_sv_vote_commands, " ", autocvar_sv_vote_master_commands), 3, argc)) <= 0)
                                                {
-                                                       print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info.");
+                                                       if(parse_error == 0)
+                                                               print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info.");
                                                }
-
                                                else  // everything went okay, proceed with command
                                                {
                                                        localcmd(strcat(vote_parsed_command, "\n"));
                                                        print_to(caller, strcat("Executing command '", vote_parsed_display, "' on server."));
                                                        bprint("\{1}^2* ^3", GetCallerName(caller), "^2 used their ^3master^2 status to do \"^2", vote_parsed_display, "^2\".\n");
-                                                       if (autocvar_sv_eventlog)   GameLogEcho(strcat(":vote:vdo:", ftos(caller.playerid), ":", vote_parsed_display)); }
+                                                       if (autocvar_sv_eventlog)
+                                                               GameLogEcho(strcat(":vote:vdo:", ftos(caller.playerid), ":", vote_parsed_display));
+                                               }
 
                                                return;
                                        }
@@ -878,21 +897,20 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
                                                {
                                                        print_to(caller, strcat("Rejected vote master login from ", GetCallerName(caller)));
                                                }
-
                                                else  // everything went okay, proceed with giving this player master privilages
                                                {
                                                        caller.vote_master = true;
                                                        print_to(caller, strcat("Accepted vote master login from ", GetCallerName(caller)));
                                                        bprint("\{1}^2* ^3", GetCallerName(caller), "^2 logged in as ^3master^2\n");
-                                                       if (autocvar_sv_eventlog)   GameLogEcho(strcat(":vote:vlogin:", ftos(caller.playerid))); }
+                                                       if (autocvar_sv_eventlog)
+                                                               GameLogEcho(strcat(":vote:vlogin:", ftos(caller.playerid)));
+                                               }
 
                                                return;
                                        }
 
                                        default:  // calling a vote for master
                                        {
-                                               bool spectators_allowed = (!autocvar_sv_vote_nospectators || (autocvar_sv_vote_nospectators == 1 && (warmup_stage || gameover)));
-
                                                if (!autocvar_sv_vote_master_callable) { print_to(caller, "^1Vote to become vote master is not allowed."); }
                                                else if (vote_called)
                                                {
@@ -906,7 +924,6 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
                                                {
                                                        print_to(caller, "^1You can not call a vote while a timeout is active.");
                                                }
-
                                                else  // everything went okay, continue with creating vote
                                                {
                                                        vote_caller = caller;
@@ -920,7 +937,8 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co
                                                        caller.vote_waittime = time + autocvar_sv_vote_wait;
 
                                                        bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote to become ^3master^2.\n");
-                                                       if (autocvar_sv_eventlog)   GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display));
+                                                       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
                                                }
@@ -966,7 +984,9 @@ void VoteCommand_no(float request, entity caller)  // CLIENT ONLY
                                print_to(caller, "^1You rejected the vote.");
                                caller.vote_selection = VOTE_SELECT_REJECT;
                                msg_entity = caller;
-                               if (!autocvar_sv_vote_singlecount)   VoteCount(false); }
+                               if (!autocvar_sv_vote_singlecount)
+                                       VoteCount(false);
+                       }
 
                        return;
                }
@@ -1036,13 +1056,14 @@ void VoteCommand_yes(float request, entity caller)  // CLIENT ONLY
                        {
                                print_to(caller, "^1You have already voted.");
                        }
-
                        else  // everything went okay, continue changing vote
                        {
                                print_to(caller, "^1You accepted the vote.");
                                caller.vote_selection = VOTE_SELECT_ACCEPT;
                                msg_entity = caller;
-                               if (!autocvar_sv_vote_singlecount)   VoteCount(false); }
+                               if (!autocvar_sv_vote_singlecount)
+                                       VoteCount(false);
+                       }
 
                        return;
                }
@@ -1121,6 +1142,12 @@ 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");
        }
 }