From 3b12883a1bdd84da7184bc6c10f276c5368ea3b8 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Sat, 5 Nov 2011 20:36:20 -0400 Subject: [PATCH] Added support for runoff voting in mapchooser (bug 4218, r=dvander). --- plugins/mapchooser.sp | 120 ++++++++++++++++++++-------- translations/mapchooser.phrases.txt | 14 +++- 2 files changed, 98 insertions(+), 36 deletions(-) diff --git a/plugins/mapchooser.sp b/plugins/mapchooser.sp index 7ad497a2..e6f9e35d 100644 --- a/plugins/mapchooser.sp +++ b/plugins/mapchooser.sp @@ -31,6 +31,16 @@ * * Version: $Id$ */ + +//#define DEBUG + +#if defined DEBUG + #define assert(%1) if (!(%1)) ThrowError("Debug Assertion Failed"); + #define assert_msg(%1,%2) if (!(%1)) ThrowError(%2); +#else + #define assert(%1) + #define assert_msg(%1,%2) +#endif #pragma semicolon 1 #include @@ -66,6 +76,8 @@ new Handle:g_Cvar_Extend = INVALID_HANDLE; new Handle:g_Cvar_DontChange = INVALID_HANDLE; new Handle:g_Cvar_EndOfMapVote = INVALID_HANDLE; new Handle:g_Cvar_VoteDuration = INVALID_HANDLE; +new Handle:g_Cvar_RunOff = INVALID_HANDLE; +new Handle:g_Cvar_RunOffPercent = INVALID_HANDLE; new Handle:g_VoteTimer = INVALID_HANDLE; new Handle:g_RetryTimer = INVALID_HANDLE; @@ -126,6 +138,8 @@ public OnPluginStart() g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0); g_Cvar_DontChange = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0); g_Cvar_VoteDuration = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0); + g_Cvar_RunOff = CreateConVar("sm_mapvote_runoff", "0", "Hold run of votes if winning choice is less than a certain margin", _, true, 0.0, true, 1.0); + g_Cvar_RunOffPercent = CreateConVar("sm_mapvote_runoffpercent", "50", "If winning choice has less than this percent of votes, hold a runoff", _, true, 0.0, true, 100.0); RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now."); RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap "); @@ -526,6 +540,7 @@ public Action:Command_Mapvote(client, args) * * @param when When the resulting map change should occur. * @param inputlist Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used. + * @param noSpecials Block special vote options like extend/nochange (upgrade this to bitflags instead?) */ InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE) { @@ -586,6 +601,7 @@ InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE) { GetArrayString(g_NominateList, i, map, sizeof(map)); AddMenuItem(g_VoteMenu, map, map); + RemoveStringFromArray(g_NextMapList, map); /* Notify Nominations that this map is now free */ Call_StartForward(g_NominationsResetForward); @@ -598,6 +614,7 @@ InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE) for (new i=nominationsToAdd; i= availableMaps) { @@ -670,19 +683,13 @@ InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE) PrintToChatAll("[SM] %t", "Nextmap Voting Started"); } -public Handler_MapVoteFinished(Handle:menu, +public Handler_VoteFinishedGeneric(Handle:menu, num_votes, num_clients, const client_info[][2], num_items, const item_info[][2]) { - if (num_votes == 0) - { - LogError("No Votes recorded yet Advanced callback fired - Tell pRED* to fix this"); - return; - } - decl String:map[32]; GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map)); @@ -771,6 +778,53 @@ public Handler_MapVoteFinished(Handle:menu, } } +public Handler_MapVoteFinished(Handle:menu, + num_votes, + num_clients, + const client_info[][2], + num_items, + const item_info[][2]) +{ + if (GetConVarBool(g_Cvar_RunOff) && num_items > 1) + { + new Float:winningvotes = float(item_info[0][VOTEINFO_ITEM_VOTES]); + new Float:required = num_votes * (GetConVarFloat(g_Cvar_RunOffPercent) / 100.0); + + if (winningvotes <= required) + { + /* Insufficient Winning margin - Lets do a runoff */ + g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL); + SetMenuTitle(g_VoteMenu, "Runoff Vote Nextmap"); + SetVoteResultCallback(g_VoteMenu, Handler_VoteFinishedGeneric); + + decl String:map[32]; + decl String:info1[32]; + decl String:info2[32]; + + GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info1, sizeof(info1)); + AddMenuItem(g_VoteMenu, map, info1); + GetMenuItem(menu, item_info[1][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info2, sizeof(info2)); + AddMenuItem(g_VoteMenu, map, info2); + + new voteDuration = GetConVarInt(g_Cvar_VoteDuration); + SetMenuExitButton(g_VoteMenu, false); + VoteMenuToAll(g_VoteMenu, voteDuration); + + /* Notify */ + new Float:map1percent = float(item_info[0][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100; + new Float:map2percent = float(item_info[1][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100; + + + PrintToChatAll("[SM] %t", "Starting Runoff", GetConVarFloat(g_Cvar_RunOffPercent), info1, map1percent, info2, map2percent); + LogMessage("Voting for next map was indecisive, beginning runoff vote"); + + return; + } + } + + Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info); +} + public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2) { switch (action) @@ -865,33 +919,35 @@ public Action:Timer_ChangeMap(Handle:hTimer, Handle:dp) return Plugin_Stop; } -CreateNextVote() +bool:RemoveStringFromArray(Handle:array, String:str[]) { - if(g_NextMapList != INVALID_HANDLE) - { - ClearArray(g_NextMapList); - } - - decl String:map[32]; - new index, Handle:tempMaps = CloneArray(g_MapList); - - GetCurrentMap(map, sizeof(map)); - index = FindStringInArray(tempMaps, map); + new index = FindStringInArray(array, str); if (index != -1) { - RemoveFromArray(tempMaps, index); - } + RemoveFromArray(array, index); + return true; + } + + return false; +} + +CreateNextVote() +{ + assert(g_NextMapList) + ClearArray(g_NextMapList); + + decl String:map[32]; + new Handle:tempMaps = CloneArray(g_MapList); + + GetCurrentMap(map, sizeof(map)); + RemoveStringFromArray(tempMaps, map); if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps)) { for (new i = 0; i < GetArraySize(g_OldMapList); i++) { GetArrayString(g_OldMapList, i, map, sizeof(map)); - index = FindStringInArray(tempMaps, map); - if (index != -1) - { - RemoveFromArray(tempMaps, index); - } + RemoveStringFromArray(tempMaps, map); } } diff --git a/translations/mapchooser.phrases.txt b/translations/mapchooser.phrases.txt index 1b9a8e26..fdbcee89 100644 --- a/translations/mapchooser.phrases.txt +++ b/translations/mapchooser.phrases.txt @@ -12,13 +12,13 @@ "Nextmap Voting Finished" { - "#format" "{1:s},{2:i},{3:i}" + "#format" "{1:s},{2:i},{3:i}" "en" "Map voting has finished. The next map will be {1}. (Received {2}%% of {3} votes)" } "Current Map Extended" { - "#format" "{1:i},{2:i}" + "#format" "{1:i},{2:i}" "en" "The current map has been extended. (Received {1}%% of {2} votes)" } @@ -34,13 +34,19 @@ "Current Map Stays" { - "#format" "{1:i},{2:i}" + "#format" "{1:i},{2:i}" "en" "Current map continues! The Vote has spoken! (Received {1}%% of {2} votes)" } "Changed Next Map" { "#format" "{1:s}" - "en" "Changed nextmap to \"{1}\"." + "en" "Changed nextmap to \"{1}\"." + } + + "Starting Runoff" + { + "#format" "{1:.0f},{2:s},{3:.0f},{4:s},{5:.0f}" + "en" "No map got over {1}%% votes ({2} [{3}%%] & {4} [{5}%%]), starting runoff vote" } }