X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fcommand%2Fvote.qc;h=277720369bef3465542d76bb3650f97761e74885;hb=388d75de0d7edf1406d0275014dacc08e54779a5;hp=f5c7f0ccb75da68c6dfcb616aebaf0e906f4bd23;hpb=774174402459b33ecb4c8c62448c1d34a97d80b3;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/command/vote.qc b/qcsrc/server/command/vote.qc index f5c7f0ccb..42d729caf 100644 --- a/qcsrc/server/command/vote.qc +++ b/qcsrc/server/command/vote.qc @@ -1,6 +1,6 @@ // ============================================= // Server side voting code, reworked by Samual -// Last updated: December 14th, 2011 +// Last updated: December 27th, 2011 // ============================================= // Nagger for players to know status of voting @@ -172,7 +172,7 @@ void VoteSpam(float notvoters, float mincount, string result) strcat("^2:^1", ftos(vote_reject_count)), ((mincount >= 0) ? strcat("^2 (^1", ftos(mincount), "^2 needed)") : "^2"), strcat(", ^1", ftos(vote_abstain_count), "^2 didn't care"), - strcat(", ^1", ftos(notvoters), strcat("^2 didn't ", ((mincount >= 0) ? string_null : "have to "), "vote\n")))); + strcat(", ^1", ftos(notvoters), strcat("^2 didn't ", ((mincount >= 0) ? "" : "have to "), "vote\n")))); if(autocvar_sv_eventlog) { @@ -185,18 +185,18 @@ void VoteSpam(float notvoters, float mincount, string result) } } -void VoteCount() +void VoteCount(float first_count) { // declarations vote_accept_count = vote_reject_count = vote_abstain_count = 0; float spectators_allowed = ((autocvar_sv_vote_nospectators != 2) - || ((autocvar_sv_vote_nospectators == 1) && inWarmupStage) + || ((autocvar_sv_vote_nospectators == 1) && (inWarmupStage || gameover)) || (autocvar_sv_vote_nospectators == 0)); - float vote_player_count, is_player, notvoters; - float vote_real_player_count, vote_real_accept_count; - float vote_real_reject_count, vote_real_abstain_count; + float vote_player_count = 0, is_player, notvoters = 0; + float vote_real_player_count = 0, vote_real_accept_count = 0; + float vote_real_reject_count = 0, vote_real_abstain_count = 0; float vote_needed_of_voted, final_needed_votes; float vote_factor_overall, vote_factor_of_voted; @@ -250,8 +250,15 @@ void VoteCount() vote_factor_of_voted = bound(0.5, autocvar_sv_vote_majority_factor_of_voted, 0.999); vote_needed_of_voted = floor((vote_accept_count + vote_reject_count) * vote_factor_of_voted) + 1; + // are there any players at all on the server? it could be an admin vote + if(vote_player_count == 0 && first_count) + { + VoteSpam(0, -1, "yes"); // no players at all, just accept it + VoteAccept(); + return; + } - // finally calculate the result of the vote + // since there ARE players, finally calculate the result of the vote if(vote_accept_count >= vote_needed_overall) { VoteSpam(notvoters, -1, "yes"); // there is enough acceptions to pass the vote @@ -301,7 +308,7 @@ void VoteThink() if(vote_endtime > 0) // a vote was called if(time > vote_endtime) // time is up { - VoteCount(); + VoteCount(FALSE); } return; @@ -312,6 +319,84 @@ void VoteThink() // Game logic for warmup // ======================= +// Resets the state of all clients, items, flags, runes, keys, weapons, waypoints, ... of the map. +void reset_map(float dorespawn) +{ + entity oldself; + oldself = self; + + if(time <= game_starttime && round_handler_IsActive()) + round_handler_Reset(game_starttime + 1); + + if(g_race || g_cts) + race_ReadyRestart(); + else MUTATOR_CALLHOOK(reset_map_global); + + lms_lowest_lives = 999; + lms_next_place = player_count; + + for(self = world; (self = nextent(self)); ) + if(clienttype(self) == CLIENTTYPE_NOTACLIENT) + { + if(self.reset) + { + self.reset(); + continue; + } + + if(self.team_saved) + self.team = self.team_saved; + + if(self.flags & FL_PROJECTILE) // remove any projectiles left + remove(self); + } + + // Waypoints and assault start come LAST + for(self = world; (self = nextent(self)); ) + if(clienttype(self) == CLIENTTYPE_NOTACLIENT) + { + if(self.reset2) + { + self.reset2(); + continue; + } + } + + // Moving the player reset code here since the player-reset depends + // on spawnpoint entities which have to be reset first --blub + if(dorespawn) + if(!MUTATOR_CALLHOOK(reset_map_players)) + FOR_EACH_CLIENT(self) // reset all players + { + /* + 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)) + { + //NEW: changed behaviour so that it prevents that previous spectators/observers suddenly spawn as players + if (self.classname == "player") { + //PlayerScore_Clear(self); + if(g_lms) + PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()); + self.killcount = 0; + //stop the player from moving so that he stands still once he gets respawned + self.velocity = '0 0 0'; + self.avelocity = '0 0 0'; + self.movement = '0 0 0'; + PutClientInServer(); + } + } + } + + if(g_keyhunt) + kh_Controller_SetThink_NoMsg(autocvar_g_balance_keyhunt_delay_round+(game_starttime - time), kh_StartRound); + + self = oldself; +} + // Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown is set) void ReadyRestart_think() { @@ -338,28 +423,34 @@ void ReadyRestart_force() checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0; readyrestart_happened = 1; - game_starttime = time; - if(!g_ca && !g_arena) { game_starttime += RESTART_COUNTDOWN; } - + game_starttime = time + RESTART_COUNTDOWN; + + // clear alivetime + FOR_EACH_CLIENT(tmp_player) + { + tmp_player.alivetime = 0; + PlayerStats_Event(tmp_player, PLAYERSTATS_ALIVETIME, -PlayerStats_Event(tmp_player, PLAYERSTATS_ALIVETIME, 0)); + } + restart_mapalreadyrestarted = 0; // reset this var, needed when cvar sv_ready_restart_repeatable is in use // disable the warmup global for the server inWarmupStage = 0; // once the game is restarted the game is in match stage // reset the .ready status of all players (also spectators) - FOR_EACH_CLIENTSLOT(tmp_player) { tmp_player.ready = 0; } + FOR_EACH_REALCLIENT(tmp_player) { tmp_player.ready = 0; } readycount = 0; Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client // lock teams with lockonrestart - if(autocvar_teamplay_lockonrestart && teamplay) + if(autocvar_teamplay_lockonrestart && teamplay) { lockteams = 1; bprint("^1The teams are now locked.\n"); } //initiate the restart-countdown-announcer entity - if(autocvar_sv_ready_restart_after_countdown && !g_ca && !g_arena) + if(autocvar_sv_ready_restart_after_countdown) { restart_timer = spawn(); restart_timer.think = ReadyRestart_think; @@ -367,7 +458,7 @@ void ReadyRestart_force() } // after a restart every players number of allowed timeouts gets reset, too - if(autocvar_sv_timeout) { FOR_EACH_REALPLAYER(tmp_player) { tmp_player.allowedTimeouts = autocvar_sv_timeout_number; } } + if(autocvar_sv_timeout) { FOR_EACH_REALPLAYER(tmp_player) { tmp_player.allowed_timeouts = autocvar_sv_timeout_number; } } //reset map immediately if this cvar is not set if not(autocvar_sv_ready_restart_after_countdown) { reset_map(TRUE); } @@ -397,12 +488,15 @@ void ReadyCount() { entity tmp_player; float ready_needed_factor, ready_needed_count; - float t_ready, t_players; + float t_ready = 0, t_players = 0; - FOR_EACH_REALPLAYER(tmp_player) + FOR_EACH_REALCLIENT(tmp_player) { - ++t_players; - if(tmp_player.ready) { ++t_ready; } + if(tmp_player.classname == "player" || tmp_player.caplayer == 1) + { + ++t_players; + if(tmp_player.ready) { ++t_ready; } + } } readycount = t_ready; @@ -469,15 +563,6 @@ float VoteCommand_checkinlist(string vote_command, string list) if(strstrofs(l, strcat(" ", vote_command, " "), 0) >= 0) return TRUE; - // if gotomap is allowed, chmap is too, and vice versa - if(vote_command == "gotomap") - if(strstrofs(l, " chmap ", 0) >= 0) - return TRUE; - - if(vote_command == "chmap") - if(strstrofs(l, " gotomap ", 0) >= 0) - return TRUE; - return FALSE; } @@ -485,7 +570,7 @@ string ValidateMap(string validated_map, entity caller) { validated_map = MapInfo_FixName(validated_map); - if(!validated_map) + if not(validated_map) { print_to(caller, "This map is not available on this server."); return string_null; @@ -509,41 +594,114 @@ string ValidateMap(string validated_map, entity caller) return validated_map; } -float VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc) +float VoteCommand_checkargs(float startpos, float argc) { - string first_command; - entity victim; - - first_command = argv(startpos); + float p, q, check, minargs; + string cvarname = strcat("sv_vote_command_restriction_", argv(startpos)); + string cmdrestriction = cvar_string(cvarname); // note: this warns on undefined cvar. We want that. + string charlist, arg; + float checkmate; - if not(VoteCommand_checkinlist(first_command, vote_list)) + if(cmdrestriction == "") + return TRUE; + + ++startpos; // skip command name + + // check minimum arg count + + // 0 args: argc == startpos + // 1 args: argc == startpos + 1 + // ... + + minargs = stof(cmdrestriction); + if(argc - startpos < minargs) return FALSE; - if(argc < startpos) // These commands won't work without arguments + p = strstrofs(cmdrestriction, ";", 0); // find first semicolon + + for(;;) { - switch(first_command) + // we know that at any time, startpos <= argc - minargs + // so this means: argc-minargs >= startpos >= argc, thus + // argc-minargs >= argc, thus minargs <= 0, thus all minargs + // have been seen already + + if(startpos >= argc) // all args checked? GOOD + break; + + if(p < 0) // no more args? FAIL { - case "map": - case "chmap": - case "gotomap": - case "kick": - case "kickban": - return FALSE; - - default: { break; } + // exception: exactly minargs left, this one included + if(argc - startpos == minargs) + break; + + // otherwise fail + return FALSE; } + + // cut to next semicolon + q = strstrofs(cmdrestriction, ";", p+1); // find next semicolon + if(q < 0) + charlist = substring(cmdrestriction, p+1, -1); + else + charlist = substring(cmdrestriction, p+1, q - (p+1)); + + // in case we ever want to allow semicolons in VoteCommand_checknasty + // charlist = strreplace("^^", ";", charlist); + + if(charlist != "") + { + // verify the arg only contains allowed chars + arg = argv(startpos); + checkmate = strlen(arg); + for(check = 0; check < checkmate; ++check) + if(strstrofs(charlist, substring(arg, check, 1), 0) < 0) + return FALSE; // not allowed character + // all characters are allowed. FINE. + } + + ++startpos; + --minargs; + p = q; } + + return TRUE; +} + +float VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc) +{ + string first_command; + first_command = argv(startpos); + + if not(VoteCommand_checkinlist(first_command, vote_list)) + return FALSE; + + if not(VoteCommand_checkargs(startpos, argc)) + return FALSE; + switch(first_command) // now go through and parse the proper commands to adjust as needed. { case "kick": case "kickban": // catch all kick/kickban commands { - victim = GetFilteredEntity(argv(startpos + 1)); - if not(victim) { return FALSE; } - // TODO: figure out how kick/kickban/ban commands work and re-write this to fit around them - vote_parsed_command = vote_command; - vote_parsed_display = strcat("^1", vote_command, " (^7", victim.netname, "^1): ", "todo"); + entity victim = GetIndexedEntity(argc, (startpos + 1)); + float accepted = VerifyClientEntity(victim, TRUE, FALSE); + + if(accepted > 0) + { + string reason = ((argc > next_token) ? substring(vote_command, argv_start_index(next_token), argv_end_index(-1) - argv_start_index(next_token)) : "No reason provided"); + string command_arguments; + + if(first_command == "kickban") + command_arguments = strcat(ftos(autocvar_g_ban_default_bantime), " ", ftos(autocvar_g_ban_default_masksize), " ~"); + else + command_arguments = reason; + + vote_parsed_command = strcat(first_command, " # ", ftos(num_for_edict(victim)), " ", command_arguments); + vote_parsed_display = strcat("^1", vote_command, " (^7", victim.netname, "^1): ", reason); + } + else { print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n")); return FALSE; } break; } @@ -591,7 +749,7 @@ void VoteCommand_abstain(float request, entity caller) // CLIENT ONLY print_to(caller, "^1You abstained from your vote."); caller.vote_selection = VOTE_SELECT_ABSTAIN; msg_entity = caller; - if(!autocvar_sv_vote_singlecount) { VoteCount(); } + if(!autocvar_sv_vote_singlecount) { VoteCount(FALSE); } } return; @@ -600,7 +758,7 @@ void VoteCommand_abstain(float request, entity caller) // CLIENT ONLY default: case CMD_REQUEST_USAGE: { - print_to(caller, "\nUsage:^3 vote abstain"); + print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote abstain")); print_to(caller, " No arguments required."); return; } @@ -617,15 +775,16 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm || ((autocvar_sv_vote_nospectators == 1) && inWarmupStage) || (autocvar_sv_vote_nospectators == 0)); - float tmp_playercount; + float tmp_playercount = 0; entity tmp_player; vote_command = VoteCommand_extractcommand(vote_command, 2, argc); if not(autocvar_sv_vote_call || !caller) { print_to(caller, "^1Vote calling is not allowed."); } + else if(!autocvar_sv_vote_gamestart && time < game_starttime) { print_to(caller, "^1Vote calling is not allowed before the match has started."); } else if(vote_called) { print_to(caller, "^1There is already a vote called."); } else if(!spectators_allowed && (caller && (caller.classname != "player"))) { print_to(caller, "^1Only players can call a vote."); } - else if(timeoutStatus) { print_to(caller, "^1You can not call a vote while a timeout is active."); } + else if(timeout_status) { print_to(caller, "^1You can not call a vote while a timeout is active."); } else if(caller && (time < caller.vote_waittime)) { print_to(caller, strcat("^1You have to wait ^2", ftos(ceil(caller.vote_waittime - time)), "^1 seconds before you can again call a vote.")); } else if not(VoteCommand_checknasty(vote_command)) { print_to(caller, "^1Syntax error in command, see 'vhelp' for more info."); } else if not(VoteCommand_parse(caller, vote_command, autocvar_sv_vote_commands, 2, argc)) { print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info."); } @@ -651,7 +810,7 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm bprint("\{1}^2* ^3", GetCallerName(vote_caller), "^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(); // needed if you are the only one + VoteCount(TRUE); // needed if you are the only one } return; @@ -660,10 +819,10 @@ void VoteCommand_call(float request, entity caller, float argc, string vote_comm default: case CMD_REQUEST_USAGE: { - print_to(caller, "\nUsage:^3 vote call command"); + print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote call command")); print_to(caller, " Where 'command' is the command to request a vote upon."); - print_to(caller, "Examples: call gotomap dance"); - print_to(caller, " call endmatch"); + print_to(caller, strcat("Examples: ", GetCommandPrefix(caller), " vote call gotomap dance")); + print_to(caller, strcat(" ", GetCommandPrefix(caller), " vote call endmatch")); return; } } @@ -685,7 +844,7 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co if not(caller.vote_master) { print_to(caller, "^1You do not have vote master privelages."); } else if not(VoteCommand_checknasty(vote_command)) { print_to(caller, "^1Syntax error in command, see 'vhelp' for more info."); } - else if not(VoteCommand_parse(caller, vote_command, autocvar_sv_vote_master_commands, 3, argc)) { print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info."); } + else if not(VoteCommand_parse(caller, vote_command, strcat(autocvar_sv_vote_commands, " ", autocvar_sv_vote_master_commands), 3, argc)) { print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info."); } else // everything went okay, proceed with command { @@ -717,8 +876,14 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co default: // calling a vote for master { + float spectators_allowed = ((autocvar_sv_vote_nospectators != 2) + || ((autocvar_sv_vote_nospectators == 1) && inWarmupStage) + || (autocvar_sv_vote_nospectators == 0)); + if not(autocvar_sv_vote_master_callable) { print_to(caller, "^1Vote to become vote master is not allowed."); } else if(vote_called) { print_to(caller, "^1There is already a vote called."); } + else if(!spectators_allowed && (caller && (caller.classname != "player"))) { print_to(caller, "^1Only players can call a vote."); } + else if(timeout_status) { print_to(caller, "^1You can not call a vote while a timeout is active."); } else // everything went okay, continue with creating vote { @@ -734,7 +899,7 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co bprint("\{1}^2* ^3", GetCallerName(vote_caller), "^2 calls a vote to become ^3master^2.\n"); if(autocvar_sv_eventlog) { GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display)); } Nagger_VoteChanged(); - VoteCount(); // needed if you are the only one + VoteCount(TRUE); // needed if you are the only one } return; @@ -749,7 +914,7 @@ void VoteCommand_master(float request, entity caller, float argc, string vote_co default: case CMD_REQUEST_USAGE: { - print_to(caller, "\nUsage:^3 vote master [action [command | password]]"); + print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote master [action [command | password]]")); print_to(caller, " If action is left blank, it calls a vote for you to become master."); print_to(caller, " Otherwise the actions are either 'do' a command or 'login' as master."); return; @@ -772,7 +937,7 @@ 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(); } + if(!autocvar_sv_vote_singlecount) { VoteCount(FALSE); } } return; @@ -781,7 +946,7 @@ void VoteCommand_no(float request, entity caller) // CLIENT ONLY default: case CMD_REQUEST_USAGE: { - print_to(caller, "\nUsage:^3 vote no"); + print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote no")); print_to(caller, " No arguments required."); return; } @@ -805,7 +970,7 @@ void VoteCommand_status(float request, entity caller) // BOTH default: case CMD_REQUEST_USAGE: { - print_to(caller, "\nUsage:^3 vote status"); + print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote status")); print_to(caller, " No arguments required."); return; } @@ -828,7 +993,7 @@ void VoteCommand_stop(float request, entity caller) // BOTH default: case CMD_REQUEST_USAGE: { - print_to(caller, "\nUsage:^3 vote stop"); + print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote stop")); print_to(caller, " No arguments required."); return; } @@ -842,14 +1007,14 @@ void VoteCommand_yes(float request, entity caller) // CLIENT ONLY case CMD_REQUEST_COMMAND: { if not(vote_called) { print_to(caller, "^1No vote called."); } - if not(caller.vote_selection == VOTE_SELECT_NULL || autocvar_sv_vote_change) { print_to(caller, "^1You have already voted."); } + else if not(caller.vote_selection == VOTE_SELECT_NULL || autocvar_sv_vote_change) { 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(); } + if(!autocvar_sv_vote_singlecount) { VoteCount(FALSE); } } return; @@ -858,14 +1023,15 @@ void VoteCommand_yes(float request, entity caller) // CLIENT ONLY default: case CMD_REQUEST_USAGE: { - print_to(caller, "\nUsage:^3 vote yes"); + print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote yes")); print_to(caller, " No arguments required."); return; } } } -/* use this when creating a new command, making sure to place it in alphabetical order. +/* 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) { switch(request) @@ -879,7 +1045,7 @@ void VoteCommand_(float request) default: case CMD_REQUEST_USAGE: { - print_to(caller, "\nUsage:^3 vote "); + print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote "); print_to(caller, " No arguments required."); return; } @@ -897,7 +1063,7 @@ void VoteCommand_(float request) VOTE_COMMAND("abstain", VoteCommand_abstain(request, caller), "Abstain your vote in current vote", VC_ASGNMNT_CLIENTONLY) \ VOTE_COMMAND("call", VoteCommand_call(request, caller, arguments, command), "Create a new vote for players to decide on", VC_ASGNMNT_BOTH) \ VOTE_COMMAND("help", VoteCommand_macro_help(caller, arguments), "Shows this information", VC_ASGNMNT_BOTH) \ - VOTE_COMMAND("master", VoteCommand_master(request, caller, arguments, command), "", VC_ASGNMNT_CLIENTONLY) \ + VOTE_COMMAND("master", VoteCommand_master(request, caller, arguments, command), "Full control over all voting and vote commands", VC_ASGNMNT_CLIENTONLY) \ VOTE_COMMAND("no", VoteCommand_no(request, caller), "Select no in current vote", VC_ASGNMNT_CLIENTONLY) \ VOTE_COMMAND("status", VoteCommand_status(request, caller), "Prints information about current vote", VC_ASGNMNT_BOTH) \ VOTE_COMMAND("stop", VoteCommand_stop(request, caller), "Immediately end a vote", VC_ASGNMNT_BOTH) \ @@ -910,17 +1076,16 @@ void VoteCommand_macro_help(entity caller, float argc) if(argc == 2) // help display listing all commands { - print_to(caller, strcat("\nUsage:^3 ", command_origin, " vote COMMAND...^7, where possible commands are:")); - + print_to(caller, "\nVoting commands:\n"); #define VOTE_COMMAND(name,function,description,assignment) \ { if(Votecommand_check_assignment(caller, assignment)) { print_to(caller, strcat(" ^2", name, "^7: ", description)); } } VOTE_COMMANDS(0, caller, 0, "") #undef VOTE_COMMAND - print_to(caller, strcat("For help about specific commands, type ", command_origin, " vote help COMMAND")); - print_to(caller, "^7You can call a vote for or execute these commands:"); - print_to(caller, strcat("^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7")); + print_to(caller, strcat("\nUsage:^3 ", command_origin, " vote COMMAND...^7, where possible commands are listed above.\n")); + print_to(caller, strcat("For help about a specific command, type ", command_origin, " vote help COMMAND")); + print_to(caller, strcat("\n^7You can call a vote for or execute these commands: ^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7")); } else // usage for individual command { @@ -973,4 +1138,4 @@ void VoteCommand(float request, entity caller, float argc, string vote_command) return; } } -} \ No newline at end of file +}