X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fcommand%2Fvote.qc;h=abdae75d5e73f62c6feed4bbf25ba8580e948a05;hb=278f5440b4272f696c6b6e5bb9bb3139b8b53a11;hp=0f0d431d10f0db132ce39ec9343e1c4d8c153c3d;hpb=7a090f8a8bfc50bc065e572a17c77a153678dd6b;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/command/vote.qc b/qcsrc/server/command/vote.qc index 0f0d431d1..abdae75d5 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; @@ -367,7 +374,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,7 +404,7 @@ 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) { @@ -469,15 +476,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 +483,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 +507,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 +662,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 +671,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 +688,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 +723,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 +732,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 +757,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 +789,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 +812,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 +827,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 +850,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 +859,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 +883,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 +906,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 +920,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,7 +936,7 @@ 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; } @@ -880,7 +958,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; } @@ -898,7 +976,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) \ @@ -911,17 +989,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 { @@ -974,4 +1051,4 @@ void VoteCommand(float request, entity caller, float argc, string vote_command) return; } } -} \ No newline at end of file +}