3 * =============================================================================
4 * SourceMod Mapchooser Plugin
5 * Creates a map vote at appropriate times, setting sm_nextmap to the winning
8 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
9 * =============================================================================
11 * This program is free software; you can redistribute it and/or modify it under
12 * the terms of the GNU General Public License, version 3.0, as published by the
13 * Free Software Foundation.
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20 * You should have received a copy of the GNU General Public License along with
21 * this program. If not, see <http://www.gnu.org/licenses/>.
23 * As a special exception, AlliedModders LLC gives you permission to link the
24 * code of this program (as well as its derivative works) to "Half-Life 2," the
25 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
26 * by the Valve Corporation. You must obey the GNU General Public License in
27 * all respects for all other code used. Additionally, AlliedModders LLC grants
28 * this exception to all derivative works. AlliedModders LLC defines further
29 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
30 * or <http://www.sourcemod.net/license.php>.
38 #define assert(%1) if (!(%1)) ThrowError("Debug Assertion Failed");
39 #define assert_msg(%1,%2) if (!(%1)) ThrowError(%2);
42 #define assert_msg(%1,%2)
50 public Plugin:myinfo =
53 author = "AlliedModders LLC",
54 description = "Automated Map Voting",
55 version = SOURCEMOD_VERSION,
56 url = "http://www.sourcemod.net/"
60 new Handle:g_Cvar_Winlimit = INVALID_HANDLE;
61 new Handle:g_Cvar_Maxrounds = INVALID_HANDLE;
62 new Handle:g_Cvar_Fraglimit = INVALID_HANDLE;
63 new Handle:g_Cvar_Bonusroundtime = INVALID_HANDLE;
64 new Handle:g_Cvar_MatchClinch = INVALID_HANDLE;
67 new Handle:g_Cvar_StartTime = INVALID_HANDLE;
68 new Handle:g_Cvar_StartRounds = INVALID_HANDLE;
69 new Handle:g_Cvar_StartFrags = INVALID_HANDLE;
70 new Handle:g_Cvar_ExtendTimeStep = INVALID_HANDLE;
71 new Handle:g_Cvar_ExtendRoundStep = INVALID_HANDLE;
72 new Handle:g_Cvar_ExtendFragStep = INVALID_HANDLE;
73 new Handle:g_Cvar_ExcludeMaps = INVALID_HANDLE;
74 new Handle:g_Cvar_IncludeMaps = INVALID_HANDLE;
75 new Handle:g_Cvar_NoVoteMode = INVALID_HANDLE;
76 new Handle:g_Cvar_Extend = INVALID_HANDLE;
77 new Handle:g_Cvar_DontChange = INVALID_HANDLE;
78 new Handle:g_Cvar_EndOfMapVote = INVALID_HANDLE;
79 new Handle:g_Cvar_VoteDuration = INVALID_HANDLE;
80 new Handle:g_Cvar_RunOff = INVALID_HANDLE;
81 new Handle:g_Cvar_RunOffPercent = INVALID_HANDLE;
83 new Handle:g_VoteTimer = INVALID_HANDLE;
84 new Handle:g_RetryTimer = INVALID_HANDLE;
87 new Handle:g_MapList = INVALID_HANDLE;
88 new Handle:g_NominateList = INVALID_HANDLE;
89 new Handle:g_NominateOwners = INVALID_HANDLE;
90 new Handle:g_OldMapList = INVALID_HANDLE;
91 new Handle:g_NextMapList = INVALID_HANDLE;
92 new Handle:g_VoteMenu = INVALID_HANDLE;
96 new bool:g_HasVoteStarted;
97 new bool:g_WaitingForVote;
98 new bool:g_MapVoteCompleted;
99 new bool:g_ChangeMapAtRoundEnd;
100 new bool:g_ChangeMapInProgress;
101 new bool:g_HasIntermissionStarted = false;
102 new g_mapFileSerial = -1;
104 new g_NominateCount = 0;
105 new MapChange:g_ChangeTime;
107 new Handle:g_NominationsResetForward = INVALID_HANDLE;
108 new Handle:g_MapVoteStartedForward = INVALID_HANDLE;
110 /* Upper bound of how many team there could be */
112 new g_winCount[MAXTEAMS];
114 #define VOTE_EXTEND "##extend##"
115 #define VOTE_DONTCHANGE "##dontchange##"
117 public OnPluginStart()
119 LoadTranslations("mapchooser.phrases");
120 LoadTranslations("common.phrases");
122 new arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
123 g_MapList = CreateArray(arraySize);
124 g_NominateList = CreateArray(arraySize);
125 g_NominateOwners = CreateArray(1);
126 g_OldMapList = CreateArray(arraySize);
127 g_NextMapList = CreateArray(arraySize);
129 g_Cvar_EndOfMapVote = CreateConVar("sm_mapvote_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0);
131 g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0);
132 g_Cvar_StartRounds = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining. Use 0 on TF2 to start vote during bonus round time", _, true, 0.0);
133 g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0);
134 g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
135 g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0);
136 g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0);
137 g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
138 g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0);
139 g_Cvar_NoVoteMode = CreateConVar("sm_mapvote_novote", "1", "Specifies whether or not MapChooser should pick a map if no votes are received.", _, true, 0.0, true, 1.0);
140 g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0);
141 g_Cvar_DontChange = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0);
142 g_Cvar_VoteDuration = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0);
143 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);
144 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);
146 RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now.");
147 RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap <map>");
149 g_Cvar_Winlimit = FindConVar("mp_winlimit");
150 g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
151 g_Cvar_Fraglimit = FindConVar("mp_fraglimit");
152 g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime");
153 g_Cvar_MatchClinch = FindConVar("mp_match_can_clinch");
155 if (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE)
157 decl String:folder[64];
158 GetGameFolderName(folder, sizeof(folder));
160 if (strcmp(folder, "tf") == 0)
162 HookEvent("teamplay_win_panel", Event_TeamPlayWinPanel);
163 HookEvent("teamplay_restart_round", Event_TFRestartRound);
164 HookEvent("arena_win_panel", Event_TeamPlayWinPanel);
166 else if (strcmp(folder, "nucleardawn") == 0)
168 HookEvent("round_win", Event_RoundEnd);
170 else if (strcmp(folder, "csgo") == 0)
172 HookEvent("round_end", Event_RoundEnd);
173 HookEvent("cs_intermission", Event_Intermission);
174 HookEvent("announce_phase_end", Event_PhaseEnd);
178 HookEvent("round_end", Event_RoundEnd);
182 if (g_Cvar_Fraglimit != INVALID_HANDLE)
184 HookEvent("player_death", Event_PlayerDeath);
187 AutoExecConfig(true, "mapchooser");
189 //Change the mp_bonusroundtime max so that we have time to display the vote
190 //If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime
191 if (g_Cvar_Bonusroundtime != INVALID_HANDLE)
193 SetConVarBounds(g_Cvar_Bonusroundtime, ConVarBound_Upper, true, 30.0);
196 g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell);
197 g_MapVoteStartedForward = CreateGlobalForward("OnMapVoteStarted", ET_Ignore);
200 public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
202 RegPluginLibrary("mapchooser");
204 CreateNative("NominateMap", Native_NominateMap);
205 CreateNative("RemoveNominationByMap", Native_RemoveNominationByMap);
206 CreateNative("RemoveNominationByOwner", Native_RemoveNominationByOwner);
207 CreateNative("InitiateMapChooserVote", Native_InitiateVote);
208 CreateNative("CanMapChooserStartVote", Native_CanVoteStart);
209 CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone);
210 CreateNative("GetExcludeMapList", Native_GetExcludeMapList);
211 CreateNative("GetNominatedMapList", Native_GetNominatedMapList);
212 CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled);
214 return APLRes_Success;
217 public OnConfigsExecuted()
219 if (ReadMapList(g_MapList,
222 MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER)
226 if (g_mapFileSerial == -1)
228 LogError("Unable to create a valid map list.");
233 SetupTimeleftTimer();
239 g_MapVoteCompleted = false;
242 ClearArray(g_NominateList);
243 ClearArray(g_NominateOwners);
245 for (new i=0; i<MAXTEAMS; i++)
251 /* Check if mapchooser will attempt to start mapvote during bonus round time - TF2 Only */
252 if ((g_Cvar_Bonusroundtime != INVALID_HANDLE) && !GetConVarInt(g_Cvar_StartRounds))
254 if (GetConVarFloat(g_Cvar_Bonusroundtime) <= GetConVarFloat(g_Cvar_VoteDuration))
256 LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete");
263 g_HasVoteStarted = false;
264 g_WaitingForVote = false;
265 g_ChangeMapAtRoundEnd = false;
266 g_ChangeMapInProgress = false;
267 g_HasIntermissionStarted = false;
269 g_VoteTimer = INVALID_HANDLE;
270 g_RetryTimer = INVALID_HANDLE;
272 decl String:map[PLATFORM_MAX_PATH];
273 GetCurrentMap(map, sizeof(map));
274 PushArrayString(g_OldMapList, map);
276 if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
278 RemoveFromArray(g_OldMapList, 0);
282 public OnClientDisconnect(client)
284 new index = FindValueInArray(g_NominateOwners, client);
291 new String:oldmap[PLATFORM_MAX_PATH];
292 GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
293 Call_StartForward(g_NominationsResetForward);
294 Call_PushString(oldmap);
295 Call_PushCell(GetArrayCell(g_NominateOwners, index));
298 RemoveFromArray(g_NominateOwners, index);
299 RemoveFromArray(g_NominateList, index);
303 public Action:Command_SetNextmap(client, args)
307 ReplyToCommand(client, "[SM] Usage: sm_setnextmap <map>");
308 return Plugin_Handled;
311 decl String:map[PLATFORM_MAX_PATH];
312 GetCmdArg(1, map, sizeof(map));
314 if (!IsMapValid(map))
316 ReplyToCommand(client, "[SM] %t", "Map was not found", map);
317 return Plugin_Handled;
320 ShowActivity(client, "%t", "Changed Next Map", map);
321 LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map);
324 g_MapVoteCompleted = true;
326 return Plugin_Handled;
329 public OnMapTimeLeftChanged()
331 if (GetArraySize(g_MapList))
333 SetupTimeleftTimer();
340 if (GetMapTimeLeft(time) && time > 0)
342 new startTime = GetConVarInt(g_Cvar_StartTime) * 60;
343 if (time - startTime < 0 && GetConVarBool(g_Cvar_EndOfMapVote) && !g_MapVoteCompleted && !g_HasVoteStarted)
345 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
349 if (g_VoteTimer != INVALID_HANDLE)
351 KillTimer(g_VoteTimer);
352 g_VoteTimer = INVALID_HANDLE;
355 //g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
357 g_VoteTimer = CreateDataTimer(float(time - startTime), Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
358 WritePackCell(data, _:MapChange_MapEnd);
359 WritePackCell(data, _:INVALID_HANDLE);
365 public Action:Timer_StartMapVote(Handle:timer, Handle:data)
367 if (timer == g_RetryTimer)
369 g_WaitingForVote = false;
370 g_RetryTimer = INVALID_HANDLE;
374 g_VoteTimer = INVALID_HANDLE;
377 if (!GetArraySize(g_MapList) || !GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted)
382 new MapChange:mapChange = MapChange:ReadPackCell(data);
383 new Handle:hndl = Handle:ReadPackCell(data);
385 InitiateVote(mapChange, hndl);
390 public Event_TFRestartRound(Handle:event, const String:name[], bool:dontBroadcast)
392 /* Game got restarted - reset our round count tracking */
396 public Event_TeamPlayWinPanel(Handle:event, const String:name[], bool:dontBroadcast)
398 if (g_ChangeMapAtRoundEnd)
400 g_ChangeMapAtRoundEnd = false;
401 CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
402 g_ChangeMapInProgress = true;
405 new bluescore = GetEventInt(event, "blue_score");
406 new redscore = GetEventInt(event, "red_score");
408 if(GetEventInt(event, "round_complete") == 1 || StrEqual(name, "arena_win_panel"))
412 if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted || !GetConVarBool(g_Cvar_EndOfMapVote))
417 CheckMaxRounds(g_TotalRounds);
419 switch(GetEventInt(event, "winning_team"))
423 CheckWinLimit(bluescore);
427 CheckWinLimit(redscore);
429 //We need to do nothing on winning_team == 0 this indicates stalemate.
438 public Event_Intermission(Handle:event, const String:name[], bool:dontBroadcast)
440 g_HasIntermissionStarted = true;
443 public Event_PhaseEnd(Handle:event, const String:name[], bool:dontBroadcast)
445 /* announce_phase_end fires for both half time and the end of the map, but intermission fires first for end of the map. */
446 if (g_HasIntermissionStarted)
451 /* No intermission yet, so this must be half time. Swap the score counters. */
452 new t_score = g_winCount[2];
453 g_winCount[2] = g_winCount[3];
454 g_winCount[3] = t_score;
457 /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
458 public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
460 if (g_ChangeMapAtRoundEnd)
462 g_ChangeMapAtRoundEnd = false;
463 CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
464 g_ChangeMapInProgress = true;
468 if (strcmp(name, "round_win") == 0)
471 winner = GetEventInt(event, "team");
475 winner = GetEventInt(event, "winner");
478 if (winner == 0 || winner == 1 || !GetConVarBool(g_Cvar_EndOfMapVote))
483 if (winner >= MAXTEAMS)
485 SetFailState("Mod exceed maximum team count - Please file a bug report.");
490 g_winCount[winner]++;
492 if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted)
497 CheckWinLimit(g_winCount[winner]);
498 CheckMaxRounds(g_TotalRounds);
501 public CheckWinLimit(winner_score)
503 if (g_Cvar_Winlimit != INVALID_HANDLE)
505 new winlimit = GetConVarInt(g_Cvar_Winlimit);
508 if (winner_score >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
510 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
515 if (g_Cvar_MatchClinch != INVALID_HANDLE && g_Cvar_Maxrounds != INVALID_HANDLE)
517 new clinch = GetConVarInt(g_Cvar_MatchClinch);
521 new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
522 new winlimit = maxrounds / 2;
524 if(winner_score > (winlimit - GetConVarInt(g_Cvar_StartRounds)))
526 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
532 public CheckMaxRounds(roundcount)
534 if (g_Cvar_Maxrounds != INVALID_HANDLE)
536 new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
539 if (roundcount >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
541 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
547 public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
549 if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted)
554 if (!GetConVarInt(g_Cvar_Fraglimit) || !GetConVarBool(g_Cvar_EndOfMapVote))
559 if (g_MapVoteCompleted)
564 new fragger = GetClientOfUserId(GetEventInt(event, "attacker"));
571 if (GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
573 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
577 public Action:Command_Mapvote(client, args)
579 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
581 return Plugin_Handled;
585 * Starts a new map vote
587 * @param when When the resulting map change should occur.
588 * @param inputlist Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used.
589 * @param noSpecials Block special vote options like extend/nochange (upgrade this to bitflags instead?)
591 InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE)
593 g_WaitingForVote = true;
595 if (IsVoteInProgress())
597 // Can't start a vote, try again in 5 seconds.
598 //g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
601 g_RetryTimer = CreateDataTimer(5.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
602 WritePackCell(data, _:when);
603 WritePackCell(data, _:inputlist);
608 /* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */
609 if (g_MapVoteCompleted && g_ChangeMapInProgress)
616 g_WaitingForVote = false;
618 g_HasVoteStarted = true;
619 g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
620 SetMenuTitle(g_VoteMenu, "Vote Nextmap");
621 SetVoteResultCallback(g_VoteMenu, Handler_MapVoteFinished);
623 /* Call OnMapVoteStarted() Forward */
624 Call_StartForward(g_MapVoteStartedForward);
628 * TODO: Make a proper decision on when to clear the nominations list.
629 * Currently it clears when used, and stays if an external list is provided.
630 * Is this the right thing to do? External lists will probably come from places
631 * like sm_mapvote from the adminmenu in the future.
634 decl String:map[PLATFORM_MAX_PATH];
636 /* No input given - User our internal nominations and maplist */
637 if (inputlist == INVALID_HANDLE)
639 new nominateCount = GetArraySize(g_NominateList);
640 new voteSize = GetConVarInt(g_Cvar_IncludeMaps);
642 /* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */
643 new nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount;
646 for (new i=0; i<nominationsToAdd; i++)
648 GetArrayString(g_NominateList, i, map, sizeof(map));
649 AddMenuItem(g_VoteMenu, map, map);
650 RemoveStringFromArray(g_NextMapList, map);
652 /* Notify Nominations that this map is now free */
653 Call_StartForward(g_NominationsResetForward);
654 Call_PushString(map);
655 Call_PushCell(GetArrayCell(g_NominateOwners, i));
659 /* Clear out the rest of the nominations array */
660 for (new i=nominationsToAdd; i<nominateCount; i++)
662 GetArrayString(g_NominateList, i, map, sizeof(map));
663 /* These maps shouldn't be excluded from the vote as they weren't really nominated at all */
665 /* Notify Nominations that this map is now free */
666 Call_StartForward(g_NominationsResetForward);
667 Call_PushString(map);
668 Call_PushCell(GetArrayCell(g_NominateOwners, i));
672 /* There should currently be 'nominationsToAdd' unique maps in the vote */
674 new i = nominationsToAdd;
676 new availableMaps = GetArraySize(g_NextMapList);
680 GetArrayString(g_NextMapList, count, map, sizeof(map));
683 /* Insert the map and increment our count */
684 AddMenuItem(g_VoteMenu, map, map);
687 if (count >= availableMaps)
689 //Run out of maps, this will have to do.
694 /* Wipe out our nominations list - Nominations have already been informed of this */
695 ClearArray(g_NominateOwners);
696 ClearArray(g_NominateList);
698 else //We were given a list of maps to start the vote with
700 new size = GetArraySize(inputlist);
702 for (new i=0; i<size; i++)
704 GetArrayString(inputlist, i, map, sizeof(map));
708 AddMenuItem(g_VoteMenu, map, map);
713 /* Do we add any special items? */
714 if ((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange))
716 AddMenuItem(g_VoteMenu, VOTE_DONTCHANGE, "Don't Change");
718 else if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend))
720 AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
723 new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
725 SetMenuExitButton(g_VoteMenu, false);
726 VoteMenuToAll(g_VoteMenu, voteDuration);
728 LogAction(-1, -1, "Voting for next map has started.");
729 PrintToChatAll("[SM] %t", "Nextmap Voting Started");
732 public Handler_VoteFinishedGeneric(Handle:menu,
735 const client_info[][2],
737 const item_info[][2])
739 decl String:map[PLATFORM_MAX_PATH];
740 GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map));
742 if (strcmp(map, VOTE_EXTEND, false) == 0)
747 if (GetMapTimeLimit(time))
751 ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);
755 if (g_Cvar_Winlimit != INVALID_HANDLE)
757 new winlimit = GetConVarInt(g_Cvar_Winlimit);
760 SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
764 if (g_Cvar_Maxrounds != INVALID_HANDLE)
766 new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
769 SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
773 if (g_Cvar_Fraglimit != INVALID_HANDLE)
775 new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
778 SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));
782 PrintToChatAll("[SM] %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
783 LogAction(-1, -1, "Voting for next map has finished. The current map has been extended.");
785 // We extended, so we'll have to vote again.
786 g_HasVoteStarted = false;
788 SetupTimeleftTimer();
791 else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
793 PrintToChatAll("[SM] %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
794 LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner");
796 g_HasVoteStarted = false;
798 SetupTimeleftTimer();
802 if (g_ChangeTime == MapChange_MapEnd)
806 else if (g_ChangeTime == MapChange_Instant)
809 CreateDataTimer(2.0, Timer_ChangeMap, data);
810 WritePackString(data, map);
811 g_ChangeMapInProgress = false;
813 else // MapChange_RoundEnd
816 g_ChangeMapAtRoundEnd = true;
819 g_HasVoteStarted = false;
820 g_MapVoteCompleted = true;
822 PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
823 LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map);
827 public Handler_MapVoteFinished(Handle:menu,
830 const client_info[][2],
832 const item_info[][2])
834 if (GetConVarBool(g_Cvar_RunOff) && num_items > 1)
836 new Float:winningvotes = float(item_info[0][VOTEINFO_ITEM_VOTES]);
837 new Float:required = num_votes * (GetConVarFloat(g_Cvar_RunOffPercent) / 100.0);
839 if (winningvotes <= required)
841 /* Insufficient Winning margin - Lets do a runoff */
842 g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
843 SetMenuTitle(g_VoteMenu, "Runoff Vote Nextmap");
844 SetVoteResultCallback(g_VoteMenu, Handler_VoteFinishedGeneric);
846 decl String:map[PLATFORM_MAX_PATH];
847 decl String:info1[PLATFORM_MAX_PATH];
848 decl String:info2[PLATFORM_MAX_PATH];
850 GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info1, sizeof(info1));
851 AddMenuItem(g_VoteMenu, map, info1);
852 GetMenuItem(menu, item_info[1][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info2, sizeof(info2));
853 AddMenuItem(g_VoteMenu, map, info2);
855 new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
856 SetMenuExitButton(g_VoteMenu, false);
857 VoteMenuToAll(g_VoteMenu, voteDuration);
860 new Float:map1percent = float(item_info[0][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100;
861 new Float:map2percent = float(item_info[1][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100;
864 PrintToChatAll("[SM] %t", "Starting Runoff", GetConVarFloat(g_Cvar_RunOffPercent), info1, map1percent, info2, map2percent);
865 LogMessage("Voting for next map was indecisive, beginning runoff vote");
871 Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info);
874 public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
880 g_VoteMenu = INVALID_HANDLE;
884 case MenuAction_Display:
886 decl String:buffer[255];
887 Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
889 new Handle:panel = Handle:param2;
890 SetPanelTitle(panel, buffer);
893 case MenuAction_DisplayItem:
895 if (GetMenuItemCount(menu) - 1 == param2)
897 decl String:map[PLATFORM_MAX_PATH], String:buffer[255];
898 GetMenuItem(menu, param2, map, sizeof(map));
899 if (strcmp(map, VOTE_EXTEND, false) == 0)
901 Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
902 return RedrawMenuItem(buffer);
904 else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
906 Format(buffer, sizeof(buffer), "%T", "Dont Change", param1);
907 return RedrawMenuItem(buffer);
912 case MenuAction_VoteCancel:
914 // If we receive 0 votes, pick at random.
915 if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode))
917 new count = GetMenuItemCount(menu);
918 new item = GetRandomInt(0, count - 1);
919 decl String:map[PLATFORM_MAX_PATH];
920 GetMenuItem(menu, item, map, sizeof(map));
922 while (strcmp(map, VOTE_EXTEND, false) == 0)
924 item = GetRandomInt(0, count - 1);
925 GetMenuItem(menu, item, map, sizeof(map));
932 // We were actually cancelled. I guess we do nothing.
935 g_HasVoteStarted = false;
936 g_MapVoteCompleted = true;
943 public Action:Timer_ChangeMap(Handle:hTimer, Handle:dp)
945 g_ChangeMapInProgress = false;
947 new String:map[PLATFORM_MAX_PATH];
949 if (dp == INVALID_HANDLE)
951 if (!GetNextMap(map, sizeof(map)))
953 //No passed map and no set nextmap. fail!
960 ReadPackString(dp, map, sizeof(map));
963 ForceChangeLevel(map, "Map Vote");
968 bool:RemoveStringFromArray(Handle:array, String:str[])
970 new index = FindStringInArray(array, str);
973 RemoveFromArray(array, index);
982 assert(g_NextMapList)
983 ClearArray(g_NextMapList);
985 decl String:map[PLATFORM_MAX_PATH];
986 new Handle:tempMaps = CloneArray(g_MapList);
988 GetCurrentMap(map, sizeof(map));
989 RemoveStringFromArray(tempMaps, map);
991 if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps))
993 for (new i = 0; i < GetArraySize(g_OldMapList); i++)
995 GetArrayString(g_OldMapList, i, map, sizeof(map));
996 RemoveStringFromArray(tempMaps, map);
1000 new limit = (GetConVarInt(g_Cvar_IncludeMaps) < GetArraySize(tempMaps) ? GetConVarInt(g_Cvar_IncludeMaps) : GetArraySize(tempMaps));
1001 for (new i = 0; i < limit; i++)
1003 new b = GetRandomInt(0, GetArraySize(tempMaps) - 1);
1004 GetArrayString(tempMaps, b, map, sizeof(map));
1005 PushArrayString(g_NextMapList, map);
1006 RemoveFromArray(tempMaps, b);
1009 CloseHandle(tempMaps);
1014 if (g_WaitingForVote || g_HasVoteStarted)
1022 NominateResult:InternalNominateMap(String:map[], bool:force, owner)
1024 if (!IsMapValid(map))
1026 return Nominate_InvalidMap;
1031 /* Look to replace an existing nomination by this client - Nominations made with owner = 0 aren't replaced */
1032 if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1))
1034 new String:oldmap[PLATFORM_MAX_PATH];
1035 GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
1036 Call_StartForward(g_NominationsResetForward);
1037 Call_PushString(oldmap);
1038 Call_PushCell(owner);
1041 SetArrayString(g_NominateList, index, map);
1042 return Nominate_Replaced;
1045 /* Too many nominated maps. */
1046 if (g_NominateCount >= GetConVarInt(g_Cvar_IncludeMaps) && !force)
1048 return Nominate_VoteFull;
1051 /* Map already in the vote */
1052 if (FindStringInArray(g_NominateList, map) != -1)
1054 return Nominate_AlreadyInVote;
1058 PushArrayString(g_NominateList, map);
1059 PushArrayCell(g_NominateOwners, owner);
1062 while (GetArraySize(g_NominateList) > GetConVarInt(g_Cvar_IncludeMaps))
1064 new String:oldmap[PLATFORM_MAX_PATH];
1065 GetArrayString(g_NominateList, 0, oldmap, sizeof(oldmap));
1066 Call_StartForward(g_NominationsResetForward);
1067 Call_PushString(oldmap);
1068 Call_PushCell(GetArrayCell(g_NominateOwners, 0));
1071 RemoveFromArray(g_NominateList, 0);
1072 RemoveFromArray(g_NominateOwners, 0);
1075 return Nominate_Added;
1078 /* Add natives to allow nominate and initiate vote to be call */
1080 /* native bool:NominateMap(const String:map[], bool:force, &NominateError:error); */
1081 public Native_NominateMap(Handle:plugin, numParams)
1084 GetNativeStringLength(1, len);
1091 new String:map[len+1];
1092 GetNativeString(1, map, len+1);
1094 return _:InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3));
1097 bool:InternalRemoveNominationByMap(String:map[])
1099 for (new i = 0; i < GetArraySize(g_NominateList); i++)
1101 new String:oldmap[PLATFORM_MAX_PATH];
1102 GetArrayString(g_NominateList, i, oldmap, sizeof(oldmap));
1104 if(strcmp(map, oldmap, false) == 0)
1106 Call_StartForward(g_NominationsResetForward);
1107 Call_PushString(oldmap);
1108 Call_PushCell(GetArrayCell(g_NominateOwners, i));
1111 RemoveFromArray(g_NominateList, i);
1112 RemoveFromArray(g_NominateOwners, i);
1122 /* native bool:RemoveNominationByMap(const String:map[]); */
1123 public Native_RemoveNominationByMap(Handle:plugin, numParams)
1126 GetNativeStringLength(1, len);
1133 new String:map[len+1];
1134 GetNativeString(1, map, len+1);
1136 return _:InternalRemoveNominationByMap(map);
1139 bool:InternalRemoveNominationByOwner(owner)
1143 if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1))
1145 new String:oldmap[PLATFORM_MAX_PATH];
1146 GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
1148 Call_StartForward(g_NominationsResetForward);
1149 Call_PushString(oldmap);
1150 Call_PushCell(owner);
1153 RemoveFromArray(g_NominateList, index);
1154 RemoveFromArray(g_NominateOwners, index);
1163 /* native bool:RemoveNominationByOwner(owner); */
1164 public Native_RemoveNominationByOwner(Handle:plugin, numParams)
1166 return _:InternalRemoveNominationByOwner(GetNativeCell(1));
1169 /* native InitiateMapChooserVote(); */
1170 public Native_InitiateVote(Handle:plugin, numParams)
1172 new MapChange:when = MapChange:GetNativeCell(1);
1173 new Handle:inputarray = Handle:GetNativeCell(2);
1175 LogAction(-1, -1, "Starting map vote because outside request");
1176 InitiateVote(when, inputarray);
1179 public Native_CanVoteStart(Handle:plugin, numParams)
1181 return CanVoteStart();
1184 public Native_CheckVoteDone(Handle:plugin, numParams)
1186 return g_MapVoteCompleted;
1189 public Native_EndOfMapVoteEnabled(Handle:plugin, numParams)
1191 return GetConVarBool(g_Cvar_EndOfMapVote);
1194 public Native_GetExcludeMapList(Handle:plugin, numParams)
1196 new Handle:array = Handle:GetNativeCell(1);
1198 if (array == INVALID_HANDLE)
1202 new size = GetArraySize(g_OldMapList);
1203 decl String:map[PLATFORM_MAX_PATH];
1205 for (new i=0; i<size; i++)
1207 GetArrayString(g_OldMapList, i, map, sizeof(map));
1208 PushArrayString(array, map);
1214 public Native_GetNominatedMapList(Handle:plugin, numParams)
1216 new Handle:maparray = Handle:GetNativeCell(1);
1217 new Handle:ownerarray = Handle:GetNativeCell(2);
1219 if (maparray == INVALID_HANDLE)
1222 decl String:map[PLATFORM_MAX_PATH];
1224 for (new i = 0; i < GetArraySize(g_NominateList); i++)
1226 GetArrayString(g_NominateList, i, map, sizeof(map));
1227 PushArrayString(maparray, map);
1229 // If the optional parameter for an owner list was passed, then we need to fill that out as well
1230 if(ownerarray != INVALID_HANDLE)
1232 new index = GetArrayCell(g_NominateOwners, i);
1233 PushArrayCell(ownerarray, index);