X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fvote.qc;h=d25ae95132a771b06d8214f870247f15f3d9a050;hb=63cd9ca069e838bf30a785dfc3500c50fdf94340;hp=55570c6f2bcc806a7dcafd2a10b1848db4a5679d;hpb=1b2f61baeeff5a7d004fafd3cfdd8269a0338a1e;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/vote.qc b/qcsrc/server/vote.qc index 55570c6f2..d25ae9513 100644 --- a/qcsrc/server/vote.qc +++ b/qcsrc/server/vote.qc @@ -1,3 +1,258 @@ +// ============================================= +// Server side voting code, reworked by Samual +// Last updated: December 4th, 2011 +// ============================================= + +#define VC_REQUEST_COMMAND 1 +#define VC_REQUEST_USAGE 2 + +#define VC_ASGNMNT_BOTH 1 +#define VC_ASGNMNT_CLIENTONLY 2 +#define VC_ASGNMNT_SERVERONLY 3 + + +// ============================ +// Misc. Supporting Functions +// ============================ + +float Votecommand_check_assignment(entity caller, float assignment) +{ + float from_server = (!caller); + + if((assignment == VC_ASGNMNT_BOTH) + || ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY) + || (from_server && assignment == VC_ASGNMNT_SERVERONLY))) + { + print("check_assignment returned true\n"); + return TRUE; + } + + print("check_assignment returned false\n"); + return FALSE; +} + +string VoteCommand_getprefix(entity caller) +{ + if(caller) + return "cmd"; + else + return "sv_cmd"; +} + +float Nagger_SendEntity(entity to, float sendflags) +{ + float nags, i, f, b; + entity e; + WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER); + + // bits: + // 1 = ready + // 2 = player needs to ready up + // 4 = vote + // 8 = player needs to vote + // 16 = warmup + // sendflags: + // 64 = vote counts + // 128 = vote string + + nags = 0; + if(readycount) + { + nags |= 1; + if(to.ready == 0) + nags |= 2; + } + if(votecalled) + { + nags |= 4; + if(to.vote_vote == 0) + nags |= 8; + } + if(inWarmupStage) + nags |= 16; + + if(sendflags & 64) + nags |= 64; + + if(sendflags & 128) + nags |= 128; + + if(!(nags & 4)) // no vote called? send no string + nags &~= (64 | 128); + + WriteByte(MSG_ENTITY, nags); + + if(nags & 64) + { + WriteByte(MSG_ENTITY, vote_yescount); + WriteByte(MSG_ENTITY, vote_nocount); + WriteByte(MSG_ENTITY, vote_needed_absolute); + WriteChar(MSG_ENTITY, to.vote_vote); + } + + if(nags & 128) + WriteString(MSG_ENTITY, votecalledvote_display); + + if(nags & 1) + { + for(i = 1; i <= maxclients; i += 8) + { + for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e)) + if(clienttype(e) != CLIENTTYPE_REAL || e.ready) + f |= b; + WriteByte(MSG_ENTITY, f); + } + } + + return TRUE; +} + +void Nagger_Init() +{ + Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity); +} + +void Nagger_VoteChanged() +{ + if(nagger) + nagger.SendFlags |= 128; +} + +void Nagger_VoteCountChanged() +{ + if(nagger) + nagger.SendFlags |= 64; +} + +void Nagger_ReadyCounted() +{ + if(nagger) + nagger.SendFlags |= 1; +} + +void ReadyRestartForce() +{ + local entity e; + + bprint("^1Server is restarting...\n"); + + VoteReset(); + + // clear overtime + if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) { + //we have to decrease timelimit to its original value again!! + float newTL; + newTL = autocvar_timelimit; + newTL -= checkrules_overtimesadded * autocvar_timelimit_overtime; + cvar_set("timelimit", ftos(newTL)); + } + + checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0; + + + readyrestart_happened = 1; + game_starttime = time; + if(!g_ca && !g_arena) + game_starttime += RESTART_COUNTDOWN; + restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use + + 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(e) + e.ready = 0; + readycount = 0; + Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client + + 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) + { + restartTimer = spawn(); + restartTimer.think = restartTimer_Think; + restartTimer.nextthink = game_starttime; + } + + //after a restart every players number of allowed timeouts gets reset, too + if(autocvar_sv_timeout) + { + FOR_EACH_REALPLAYER(e) + e.allowedTimeouts = autocvar_sv_timeout_number; + } + + //reset map immediately if this cvar is not set + if (!autocvar_sv_ready_restart_after_countdown) + reset_map(TRUE); + + if(autocvar_sv_eventlog) + GameLogEcho(":restart"); +} + +void ReadyRestart() +{ + // no arena, assault support yet... + if(g_arena | g_assault | gameover | intermission_running | race_completing) + localcmd("restart\n"); + else + localcmd("\nsv_hook_gamerestart\n"); + + ReadyRestartForce(); + + // 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(); +} + +/** + * Counts how many players are ready. If not enough players are ready, the function + * does nothing. If all players are ready, the timelimit will be extended and the + * restart_countdown variable is set to allow other functions like PlayerPostThink + * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown + * is not set the map will be resetted. + * + * Function is called after the server receives a 'ready' sign from a player. + */ +void ReadyCount() +{ + local entity e; + local float r, p; + + r = p = 0; + + FOR_EACH_REALPLAYER(e) + { + p += 1; + if(e.ready) + r += 1; + } + + readycount = r; + + Nagger_ReadyCounted(); + + if(r) // at least one is ready + if(r == p) // and, everyone is ready + ReadyRestart(); +} + +/** + * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown + * is set) + */ +void restartTimer_Think() { + restart_mapalreadyrestarted = 1; + reset_map(TRUE); + Score_ClearAll(); + remove(self); + return; +} + float VoteCheckNasty(string cmd) { if(strstrofs(cmd, ";", 0) >= 0) @@ -116,219 +371,165 @@ float RemapVote(string vote, string cmd, entity e) return TRUE; } -float GameCommand_Vote(string s, entity e) { - local float playercount; - float argc; - argc = tokenize_console(s); - if(argv(0) == "help") { - print_to(e, " vote COMMANDS ARGUMENTS. See 'vhelp' for more info."); - return TRUE; - } else if(argv(0) == "vote") { - if(argv(1) == "") { - print_to(e, "^1You have to supply a vote command. See 'vhelp' for more info."); - } else if(argv(1) == "help") { - VoteHelp(e); - } else if(argv(1) == "status") { - if(votecalled) { - print_to(e, strcat("^7Vote for ", votecalledvote_display, "^7 called by ^7", VoteNetname(votecaller), "^7.")); - } else { - print_to(e, "^1No vote called."); - } - } else if(argv(1) == "call") { - if(!e || autocvar_sv_vote_call) { - if(autocvar_sv_vote_nospectators && e && e.classname != "player") { - print_to(e, "^1Error: Only players can call a vote."); // TODO invent a cvar name for allowing votes by spectators during warmup anyway - } - else if(timeoutStatus) { //don't allow a vote call during a timeout - print_to(e, "^1Error: You can not call a vote while a timeout is active."); - } - else if(votecalled) { - print_to(e, "^1There is already a vote called."); - } else { - local string vote; - vote = VoteParse(s, argc); - if(vote == "") { - print_to(e, "^1Your vote is empty. See 'vhelp' for more info."); - } else if(e - && time < e.vote_next) { - print_to(e, strcat("^1You have to wait ^2", ftos(ceil(e.vote_next - time)), "^1 seconds before you can again call a vote.")); - } else if(VoteCheckNasty(vote)) { - print_to(e, "Syntax error in command. See 'vhelp' for more info."); - } else if(RemapVote(vote, "vcall", e)) { - votecalledvote = strzone(RemapVote_vote); - votecalledvote_display = strzone(RemapVote_display); - votecalled = TRUE; - votecalledmaster = FALSE; - votefinished = time + autocvar_sv_vote_timeout; - votecaller = e; // remember who called the vote - if(e) { - e.vote_vote = 1; // of course you vote yes - e.vote_next = time + autocvar_sv_vote_wait; - } - bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2 calls a vote for ", votecalledvote_display, "\n"); - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display)); - Nagger_VoteChanged(); - VoteCount(); // needed if you are the only one - msg_entity = e; - - local entity player; - FOR_EACH_REALCLIENT(player) - { - ++playercount; - } - if(playercount > 1) // don't announce a "vote now" sound if player is alone - Announce("votecall"); - } else { - print_to(e, "^1This vote is not ok. See 'vhelp' for more info."); - } - } - } else { - print_to(e, "^1Vote calling is NOT allowed."); - } - } else if(argv(1) == "stop") { - if(!votecalled) { - print_to(e, "^1No vote called."); - } else if(e == votecaller) { // the votecaller can stop a vote - VoteStop(e); - } else if(!e) { // server admin / console can too - VoteStop(e); - } else if(e.vote_master) { // masters can too - VoteStop(e); - } else { - print_to(e, "^1You are not allowed to stop that Vote."); - } - } else if(argv(1) == "master") { - if(autocvar_sv_vote_master) { - if(votecalled) { - print_to(e, "^1There is already a vote called."); - } else { - votecalled = TRUE; - votecalledmaster = TRUE; - votecalledvote = strzone("XXX"); - votecalledvote_display = strzone("^3master"); - votefinished = time + autocvar_sv_vote_timeout; - votecaller = e; // remember who called the vote - if(e) { - e.vote_vote = 1; // of course you vote yes - e.vote_next = time + autocvar_sv_vote_wait; - } - bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2 calls a vote to become ^3master^2.\n"); - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display)); - Nagger_VoteChanged(); - VoteCount(); // needed if you are the only one - } - } else { - print_to(e, "^1Vote to become master is NOT allowed."); - } - } else if(argv(1) == "do") { - if(!e || e.vote_master) { - local string dovote; - dovote = VoteParse(s, argc); - if(dovote == "") { - print_to(e, "^1Your command was empty. See 'vhelp' for more info."); - } else if(VoteCheckNasty(dovote)) { - print_to(e, "Syntax error in command. See 'vhelp' for more info."); - } else if(RemapVote(dovote, "vdo", e)) { // strcat seems to be necessary - bprint("\{1}^2* ^3", VoteNetname(e), "^2 used their ^3master^2 status to do \"^2", RemapVote_display, "^2\".\n"); - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":vote:vdo:", ftos(e.playerid), ":", RemapVote_display)); - localcmd(strcat(RemapVote_vote, "\n")); - } else { - print_to(e, "^1This command is not ok. See 'vhelp' for more info."); - } - } else { - print_to(e, "^1You are NOT a master. You might need to login or vote to become master first. See 'vhelp' for more info."); - } - } else if(argv(1) == "login") { - local string masterpwd; - masterpwd = autocvar_sv_vote_master_password; - if(masterpwd != "") { - local float granted; - granted = (masterpwd == argv(2)); - if (e) - e.vote_master = granted; - if(granted) { - print("Accepted master login from ", VoteNetname(e), "\n"); - bprint("\{1}^2* ^3", VoteNetname(e), "^2 logged in as ^3master^2\n"); - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":vote:vlogin:", ftos(e.playerid))); - } - else - print("REJECTED master login from ", VoteNetname(e), "\n"); - } - else - print_to(e, "^1Login to become master is NOT allowed."); - } else if(argv(1) == "yes") { - if(!votecalled) { - print_to(e, "^1No vote called."); - } else if (!e) { - print_to(e, "^1You can't vote from the server console."); - } else if(e.vote_vote == 0 - || autocvar_sv_vote_change) { - msg_entity = e; - print_to(e, "^1You accepted the vote."); - e.vote_vote = 1; - if(!autocvar_sv_vote_singlecount) { - VoteCount(); - } - } else { - print_to(e, "^1You have already voted."); - } - } else if(argv(1) == "no") { - if(!votecalled) { - print_to(e, "^1No vote called."); - } else if (!e) { - print_to(e, "^1You can't vote from the server console."); - } else if(e.vote_vote == 0 - || autocvar_sv_vote_change) { - msg_entity = e; - print_to(e, "^1You rejected the vote."); - e.vote_vote = -1; - if(!autocvar_sv_vote_singlecount) { - VoteCount(); - } - } else { - print_to(e, "^1You have already voted."); - } - } else if(argv(1) == "abstain" || argv(1) == "dontcare") { - if(!votecalled) { - print_to(e, "^1No vote called."); - } else if (!e) { - print_to(e, "^1You can't vote from the server console."); - } else if(e.vote_vote == 0 - || autocvar_sv_vote_change) { - msg_entity = e; - print_to(e, "^1You abstained from your vote."); - e.vote_vote = -2; - if(!autocvar_sv_vote_singlecount) { - VoteCount(); - } - } else { - print_to(e, "^1You have already voted."); - } - } else { - // ignore this? - print_to(e, "^1Unknown vote command."); + +// ======================= +// Command Sub-Functions +// ======================= + +void VoteCommand_abstain(float request, entity caller) +{ + switch(request) + { + case VC_REQUEST_COMMAND: + { + + return; + } + + default: + case VC_REQUEST_USAGE: + { + print("\nUsage:^3 vote \n"); + print(" No arguments required.\n"); + return; } - return TRUE; } +} + +void VoteCommand_stop(float request, entity caller) +{ + switch(request) + { + case VC_REQUEST_COMMAND: + { + + return; + } + + default: + case VC_REQUEST_USAGE: + { + print("\nUsage:^3 vote \n"); + print(" No arguments required.\n"); + return; + } + } +} + +/* use this when creating a new command, making sure to place it in alphabetical order. +void VoteCommand_(float request) +{ + switch(request) + { + case VC_REQUEST_COMMAND: + { + + return; + } + + default: + case VC_REQUEST_USAGE: + { + print("\nUsage:^3 vote \n"); + print(" No arguments required.\n"); + return; + } + } +} +*/ + + +// ================================== +// Macro system for server commands +// ================================== + +// Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;) +#define VOTE_COMMANDS(request,caller,arguments) \ + VOTE_COMMAND("abstain", VoteCommand_abstain(request, caller), "Abstain your vote in current poll", VC_ASGNMNT_CLIENTONLY) \ + VOTE_COMMAND("force", VoteCommand_force(request, caller), "Force a result of a vote", VC_ASGNMNT_SERVERONLY) \ + VOTE_COMMAND("help", VoteCommand_macro_help(caller), "Shows this information", VC_ASGNMNT_BOTH) \ + VOTE_COMMAND("no", VoteCommand_no(request, caller), "Vote no in current poll", VC_ASGNMNT_CLIENTONLY) \ + VOTE_COMMAND("status", VoteCommand_status(request, caller), "Prints information about current poll", VC_ASGNMNT_BOTH) \ + VOTE_COMMAND("stop", VoteCommand_stop(request, caller), "Immediately end a vote", VC_ASGNMNT_BOTH) \ + VOTE_COMMAND("yes", VoteCommand_yes(request, caller), "Vote yes in current poll", VC_ASGNMNT_CLIENTONLY) \ + //VOTE_COMMAND("", VoteCommand_(request, caller, arguments), "", ) \ + /* nothing */ + +void VoteCommand_macro_help(entity caller) +{ + print("\nUsage:^3 ", VoteCommand_getprefix(caller), " vote COMMAND...^7, where possible commands are:\n"); + + #define VOTE_COMMAND(name,function,description,assignment) \ + { if(Votecommand_check_assignment(caller, assignment)) { print(" ^2", name, "^7: ", description, "\n"); } } + + VOTE_COMMANDS(0, caller, 0) + #undef VOTE_COMMAND + + return; +} + +float VoteCommand_macro_command(entity caller, float argc) +{ + #define VOTE_COMMAND(name,function,description,assignment) \ + { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(0))) { function; return TRUE; } } } + + VOTE_COMMANDS(VC_REQUEST_COMMAND, caller, argc) + #undef VOTE_COMMAND + + return FALSE; +} + +float VoteCommand_macro_usage(entity caller, float argc) +{ + #define VOTE_COMMAND(name,function,description,assignment) \ + { if(Votecommand_check_assignment(caller, assignment)) { if(name == strtolower(argv(1))) { function; return TRUE; } } } + + VOTE_COMMANDS(VC_REQUEST_USAGE, caller, argc) + #undef VOTE_COMMAND + return FALSE; } + +// ====================================== +// Main function handling vote commands +// ====================================== + +void VoteCommand(entity caller, float argc) +{ + if(strtolower(argv(0)) == "help") + { + if(argc == 1) + { + VoteCommand_macro_help(caller); + return; + } + else if(VoteCommand_macro_usage(caller, argc)) + { + return; + } + } + else if(VoteCommand_macro_command(caller, argc)) + { + return; + } + + // nothing above caught the command, must be invalid + //print("Unknown server command", ((command != "") ? strcat(" \"", command, "\"") : ""), ". For a list of supported commands, try sv_cmd help.\n"); +} + void VoteHelp(entity e) { - local string vmasterdis; + string vmasterdis; if(!autocvar_sv_vote_master) { vmasterdis = " ^1(disabled)"; } - local string vlogindis; + string vlogindis; if("" == autocvar_sv_vote_master_password) { vlogindis = " ^1(disabled)"; } - local string vcalldis; + string vcalldis; if(!autocvar_sv_vote_call) { vcalldis = " ^1(disabled)"; } @@ -439,7 +640,7 @@ float VoteAllowed(string votecommand, string cmd) { } void VoteReset() { - local entity player; + entity player; FOR_EACH_CLIENT(player) { @@ -535,17 +736,17 @@ void VoteSpam(float notvoters, float mincount, string result) } void VoteCount() { - local float playercount; + float playercount; playercount = 0; vote_yescount = 0; vote_nocount = 0; vote_abstaincount = 0; - local entity player; + entity player; //same for real players - local float realplayercount; - local float realplayeryescount; - local float realplayernocount; - local float realplayerabstaincount; + float realplayercount; + float realplayeryescount; + float realplayernocount; + float realplayerabstaincount; realplayercount = realplayernocount = realplayerabstaincount = realplayeryescount = 0; Nagger_VoteCountChanged();