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>.
40 public Plugin:myinfo =
43 author = "AlliedModders LLC",
44 description = "Automated Map Voting",
45 version = SOURCEMOD_VERSION,
46 url = "http://www.sourcemod.net/"
50 new Handle:g_Cvar_Winlimit = INVALID_HANDLE;
51 new Handle:g_Cvar_Maxrounds = INVALID_HANDLE;
52 new Handle:g_Cvar_Fraglimit = INVALID_HANDLE;
53 new Handle:g_Cvar_Bonusroundtime = INVALID_HANDLE;
56 new Handle:g_Cvar_StartTime = INVALID_HANDLE;
57 new Handle:g_Cvar_StartRounds = INVALID_HANDLE;
58 new Handle:g_Cvar_StartFrags = INVALID_HANDLE;
59 new Handle:g_Cvar_ExtendTimeStep = INVALID_HANDLE;
60 new Handle:g_Cvar_ExtendRoundStep = INVALID_HANDLE;
61 new Handle:g_Cvar_ExtendFragStep = INVALID_HANDLE;
62 new Handle:g_Cvar_ExcludeMaps = INVALID_HANDLE;
63 new Handle:g_Cvar_IncludeMaps = INVALID_HANDLE;
64 new Handle:g_Cvar_NoVoteMode = INVALID_HANDLE;
65 new Handle:g_Cvar_Extend = INVALID_HANDLE;
66 new Handle:g_Cvar_DontChange = INVALID_HANDLE;
67 new Handle:g_Cvar_EndOfMapVote = INVALID_HANDLE;
68 new Handle:g_Cvar_VoteDuration = INVALID_HANDLE;
70 new Handle:g_VoteTimer = INVALID_HANDLE;
71 new Handle:g_RetryTimer = INVALID_HANDLE;
74 new Handle:g_MapList = INVALID_HANDLE;
75 new Handle:g_NominateList = INVALID_HANDLE;
76 new Handle:g_NominateOwners = INVALID_HANDLE;
77 new Handle:g_OldMapList = INVALID_HANDLE;
78 new Handle:g_NextMapList = INVALID_HANDLE;
79 new Handle:g_VoteMenu = INVALID_HANDLE;
83 new bool:g_HasVoteStarted;
84 new bool:g_WaitingForVote;
85 new bool:g_MapVoteCompleted;
86 new bool:g_ChangeMapAtRoundEnd;
87 new bool:g_ChangeMapInProgress;
88 new g_mapFileSerial = -1;
90 new g_NominateCount = 0;
91 new MapChange:g_ChangeTime;
93 new Handle:g_NominationsResetForward = INVALID_HANDLE;
95 /* Upper bound of how many team there could be */
97 new g_winCount[MAXTEAMS];
99 #define VOTE_EXTEND "##extend##"
100 #define VOTE_DONTCHANGE "##dontchange##"
102 public OnPluginStart()
104 LoadTranslations("mapchooser.phrases");
105 LoadTranslations("common.phrases");
107 new arraySize = ByteCountToCells(33);
108 g_MapList = CreateArray(arraySize);
109 g_NominateList = CreateArray(arraySize);
110 g_NominateOwners = CreateArray(1);
111 g_OldMapList = CreateArray(arraySize);
112 g_NextMapList = CreateArray(arraySize);
114 g_Cvar_EndOfMapVote = CreateConVar("sm_mapvote_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0);
116 g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0);
117 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);
118 g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0);
119 g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
120 g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0);
121 g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0);
122 g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
123 g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0);
124 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);
125 g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0);
126 g_Cvar_DontChange = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0);
127 g_Cvar_VoteDuration = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0);
129 RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now.");
130 RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap <map>");
132 g_Cvar_Winlimit = FindConVar("mp_winlimit");
133 g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
134 g_Cvar_Fraglimit = FindConVar("mp_fraglimit");
135 g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime");
137 if (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE)
139 HookEvent("round_end", Event_RoundEnd);
140 HookEventEx("teamplay_win_panel", Event_TeamPlayWinPanel);
141 HookEventEx("teamplay_restart_round", Event_TFRestartRound);
142 HookEventEx("arena_win_panel", Event_TeamPlayWinPanel);
145 if (g_Cvar_Fraglimit != INVALID_HANDLE)
147 HookEvent("player_death", Event_PlayerDeath);
150 AutoExecConfig(true, "mapchooser");
152 //Change the mp_bonusroundtime max so that we have time to display the vote
153 //If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime
154 if (g_Cvar_Bonusroundtime != INVALID_HANDLE)
156 SetConVarBounds(g_Cvar_Bonusroundtime, ConVarBound_Upper, true, 30.0);
159 g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell);
162 public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
164 RegPluginLibrary("mapchooser");
166 CreateNative("NominateMap", Native_NominateMap);
167 CreateNative("InitiateMapChooserVote", Native_InitiateVote);
168 CreateNative("CanMapChooserStartVote", Native_CanVoteStart);
169 CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone);
170 CreateNative("GetExcludeMapList", Native_GetExcludeMapList);
171 CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled);
173 return APLRes_Success;
176 public OnConfigsExecuted()
178 if (ReadMapList(g_MapList,
181 MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER)
185 if (g_mapFileSerial == -1)
187 LogError("Unable to create a valid map list.");
192 SetupTimeleftTimer();
198 g_MapVoteCompleted = false;
201 ClearArray(g_NominateList);
202 ClearArray(g_NominateOwners);
204 for (new i=0; i<MAXTEAMS; i++)
210 /* Check if mapchooser will attempt to start mapvote during bonus round time - TF2 Only */
211 if ((g_Cvar_Bonusroundtime != INVALID_HANDLE) && !GetConVarInt(g_Cvar_StartRounds))
213 if (GetConVarFloat(g_Cvar_Bonusroundtime) <= GetConVarFloat(g_Cvar_VoteDuration))
215 LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete");
222 g_HasVoteStarted = false;
223 g_WaitingForVote = false;
224 g_ChangeMapAtRoundEnd = false;
225 g_ChangeMapInProgress = false;
227 g_VoteTimer = INVALID_HANDLE;
228 g_RetryTimer = INVALID_HANDLE;
231 GetCurrentMap(map, sizeof(map));
232 PushArrayString(g_OldMapList, map);
234 if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
236 RemoveFromArray(g_OldMapList, 0);
240 public OnClientDisconnect(client)
242 new index = FindValueInArray(g_NominateOwners, client);
249 new String:oldmap[33];
250 GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
251 Call_StartForward(g_NominationsResetForward);
252 Call_PushString(oldmap);
253 Call_PushCell(GetArrayCell(g_NominateOwners, index));
256 RemoveFromArray(g_NominateOwners, index);
257 RemoveFromArray(g_NominateList, index);
261 public Action:Command_SetNextmap(client, args)
265 ReplyToCommand(client, "[SM] Usage: sm_setnextmap <map>");
266 return Plugin_Handled;
270 GetCmdArg(1, map, sizeof(map));
272 if (!IsMapValid(map))
274 ReplyToCommand(client, "[SM] %t", "Map was not found", map);
275 return Plugin_Handled;
278 ShowActivity(client, "%t", "Changed Next Map", map);
279 LogMessage("\"%L\" changed nextmap to \"%s\"", client, map);
282 g_MapVoteCompleted = true;
284 return Plugin_Handled;
287 public OnMapTimeLeftChanged()
289 if (GetArraySize(g_MapList))
291 SetupTimeleftTimer();
298 if (GetMapTimeLeft(time) && time > 0)
300 new startTime = GetConVarInt(g_Cvar_StartTime) * 60;
301 if (time - startTime < 0 && GetConVarBool(g_Cvar_EndOfMapVote) && !g_MapVoteCompleted && !g_HasVoteStarted)
303 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
307 if (g_VoteTimer != INVALID_HANDLE)
309 KillTimer(g_VoteTimer);
310 g_VoteTimer = INVALID_HANDLE;
313 //g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
315 g_VoteTimer = CreateDataTimer(float(time - startTime), Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
316 WritePackCell(data, _:MapChange_MapEnd);
317 WritePackCell(data, _:INVALID_HANDLE);
323 public Action:Timer_StartMapVote(Handle:timer, Handle:data)
325 if (timer == g_RetryTimer)
327 g_WaitingForVote = false;
328 g_RetryTimer = INVALID_HANDLE;
332 g_VoteTimer = INVALID_HANDLE;
335 if (!GetArraySize(g_MapList) || !GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted)
340 new MapChange:mapChange = MapChange:ReadPackCell(data);
341 new Handle:hndl = Handle:ReadPackCell(data);
343 InitiateVote(mapChange, hndl);
348 public Event_TFRestartRound(Handle:event, const String:name[], bool:dontBroadcast)
350 /* Game got restarted - reset our round count tracking */
354 public Event_TeamPlayWinPanel(Handle:event, const String:name[], bool:dontBroadcast)
356 if (g_ChangeMapAtRoundEnd)
358 g_ChangeMapAtRoundEnd = false;
359 CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
360 g_ChangeMapInProgress = true;
363 new bluescore = GetEventInt(event, "blue_score");
364 new redscore = GetEventInt(event, "red_score");
366 if(GetEventInt(event, "round_complete") == 1 || StrEqual(name, "arena_win_panel"))
370 if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted || !GetConVarBool(g_Cvar_EndOfMapVote))
375 CheckMaxRounds(g_TotalRounds);
377 switch(GetEventInt(event, "winning_team"))
381 CheckWinLimit(bluescore);
385 CheckWinLimit(redscore);
387 //We need to do nothing on winning_team == 0 this indicates stalemate.
395 /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
396 public Event_RoundEnd(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 winner = GetEventInt(event, "winner");
407 if (winner == 0 || winner == 1 || !GetConVarBool(g_Cvar_EndOfMapVote))
412 if (winner >= MAXTEAMS)
414 SetFailState("Mod exceed maximum team count - Please file a bug report.");
419 g_winCount[winner]++;
421 if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted)
426 CheckWinLimit(g_winCount[winner]);
427 CheckMaxRounds(g_TotalRounds);
430 public CheckWinLimit(winner_score)
432 if (g_Cvar_Winlimit != INVALID_HANDLE)
434 new winlimit = GetConVarInt(g_Cvar_Winlimit);
437 if (winner_score >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
439 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
445 public CheckMaxRounds(roundcount)
447 if (g_Cvar_Maxrounds != INVALID_HANDLE)
449 new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
452 if (roundcount >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
454 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
460 public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
462 if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted)
467 if (!GetConVarInt(g_Cvar_Fraglimit) || !GetConVarBool(g_Cvar_EndOfMapVote))
472 if (g_MapVoteCompleted)
477 new fragger = GetClientOfUserId(GetEventInt(event, "attacker"));
484 if (GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
486 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
490 public Action:Command_Mapvote(client, args)
492 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
494 return Plugin_Handled;
498 * Starts a new map vote
500 * @param when When the resulting map change should occur.
501 * @param inputlist Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used.
503 InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE)
505 g_WaitingForVote = true;
507 if (IsVoteInProgress())
509 // Can't start a vote, try again in 5 seconds.
510 //g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
513 g_RetryTimer = CreateDataTimer(5.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
514 WritePackCell(data, _:when);
515 WritePackCell(data, _:inputlist);
520 /* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */
521 if (g_MapVoteCompleted && g_ChangeMapInProgress)
528 g_WaitingForVote = false;
530 g_HasVoteStarted = true;
531 g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
532 SetMenuTitle(g_VoteMenu, "Vote Nextmap");
533 SetVoteResultCallback(g_VoteMenu, Handler_MapVoteFinished);
536 * TODO: Make a proper decision on when to clear the nominations list.
537 * Currently it clears when used, and stays if an external list is provided.
538 * Is this the right thing to do? External lists will probably come from places
539 * like sm_mapvote from the adminmenu in the future.
544 /* No input given - User our internal nominations and maplist */
545 if (inputlist == INVALID_HANDLE)
547 new nominateCount = GetArraySize(g_NominateList);
548 new voteSize = GetConVarInt(g_Cvar_IncludeMaps);
550 /* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */
551 new nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount;
554 for (new i=0; i<nominationsToAdd; i++)
556 GetArrayString(g_NominateList, i, map, sizeof(map));
557 AddMenuItem(g_VoteMenu, map, map);
559 /* Notify Nominations that this map is now free */
560 Call_StartForward(g_NominationsResetForward);
561 Call_PushString(map);
562 Call_PushCell(GetArrayCell(g_NominateOwners, i));
566 /* Clear out the rest of the nominations array */
567 for (new i=nominationsToAdd; i<nominateCount; i++)
569 GetArrayString(g_NominateList, i, map, sizeof(map));
571 /* Notify Nominations that this map is now free */
572 Call_StartForward(g_NominationsResetForward);
573 Call_PushString(map);
574 Call_PushCell(GetArrayCell(g_NominateOwners, i));
578 /* There should currently be 'nominationsToAdd' unique maps in the vote */
580 new i = nominationsToAdd;
582 new availableMaps = GetArraySize(g_NextMapList);
586 GetArrayString(g_NextMapList, count, map, sizeof(map));
589 //Check if this map is in the nominate list (and thus already in the vote) */
590 if (FindStringInArray(g_NominateList, map) == -1)
592 /* Insert the map and increment our count */
593 AddMenuItem(g_VoteMenu, map, map);
597 if (count >= availableMaps)
599 //Run out of maps, this will have to do.
604 /* Wipe out our nominations list - Nominations have already been informed of this */
605 ClearArray(g_NominateOwners);
606 ClearArray(g_NominateList);
608 else //We were given a list of maps to start the vote with
610 new size = GetArraySize(inputlist);
612 for (new i=0; i<size; i++)
614 GetArrayString(inputlist, i, map, sizeof(map));
618 AddMenuItem(g_VoteMenu, map, map);
623 /* Do we add any special items? */
624 if ((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange))
626 AddMenuItem(g_VoteMenu, VOTE_DONTCHANGE, "Don't Change");
628 else if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend))
630 AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
633 new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
635 SetMenuExitButton(g_VoteMenu, false);
636 VoteMenuToAll(g_VoteMenu, voteDuration);
638 LogMessage("Voting for next map has started.");
639 PrintToChatAll("[SM] %t", "Nextmap Voting Started");
642 public Handler_MapVoteFinished(Handle:menu,
645 const client_info[][2],
647 const item_info[][2])
651 LogError("No Votes recorded yet Advanced callback fired - Tell pRED* to fix this");
656 GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map));
658 if (strcmp(map, VOTE_EXTEND, false) == 0)
663 if (GetMapTimeLimit(time))
667 ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);
671 if (g_Cvar_Winlimit != INVALID_HANDLE)
673 new winlimit = GetConVarInt(g_Cvar_Winlimit);
676 SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
680 if (g_Cvar_Maxrounds != INVALID_HANDLE)
682 new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
685 SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
689 if (g_Cvar_Fraglimit != INVALID_HANDLE)
691 new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
694 SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));
698 PrintToChatAll("[SM] %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
699 LogMessage("Voting for next map has finished. The current map has been extended.");
701 // We extended, so we'll have to vote again.
702 g_HasVoteStarted = false;
704 SetupTimeleftTimer();
707 else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
709 PrintToChatAll("[SM] %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
710 LogMessage("Voting for next map has finished. 'No Change' was the winner");
712 g_HasVoteStarted = false;
714 SetupTimeleftTimer();
718 if (g_ChangeTime == MapChange_MapEnd)
722 else if (g_ChangeTime == MapChange_Instant)
725 CreateDataTimer(2.0, Timer_ChangeMap, data);
726 WritePackString(data, map);
727 g_ChangeMapInProgress = false;
729 else // MapChange_RoundEnd
732 g_ChangeMapAtRoundEnd = true;
735 g_HasVoteStarted = false;
736 g_MapVoteCompleted = true;
738 PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
739 LogMessage("Voting for next map has finished. Nextmap: %s.", map);
743 public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
749 g_VoteMenu = INVALID_HANDLE;
753 case MenuAction_Display:
755 decl String:buffer[255];
756 Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
758 new Handle:panel = Handle:param2;
759 SetPanelTitle(panel, buffer);
762 case MenuAction_DisplayItem:
764 if (GetMenuItemCount(menu) - 1 == param2)
766 decl String:map[64], String:buffer[255];
767 GetMenuItem(menu, param2, map, sizeof(map));
768 if (strcmp(map, VOTE_EXTEND, false) == 0)
770 Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
771 return RedrawMenuItem(buffer);
773 else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
775 Format(buffer, sizeof(buffer), "%T", "Dont Change", param1);
776 return RedrawMenuItem(buffer);
781 case MenuAction_VoteCancel:
783 // If we receive 0 votes, pick at random.
784 if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode))
786 new count = GetMenuItemCount(menu);
787 new item = GetRandomInt(0, count - 1);
789 GetMenuItem(menu, item, map, sizeof(map));
791 while (strcmp(map, VOTE_EXTEND, false) == 0)
793 item = GetRandomInt(0, count - 1);
794 GetMenuItem(menu, item, map, sizeof(map));
801 // We were actually cancelled. I guess we do nothing.
804 g_HasVoteStarted = false;
805 g_MapVoteCompleted = true;
812 public Action:Timer_ChangeMap(Handle:hTimer, Handle:dp)
814 g_ChangeMapInProgress = false;
818 if (dp == INVALID_HANDLE)
820 if (!GetNextMap(map, sizeof(map)))
822 //No passed map and no set nextmap. fail!
829 ReadPackString(dp, map, sizeof(map));
832 ForceChangeLevel(map, "Map Vote");
839 if(g_NextMapList != INVALID_HANDLE)
841 ClearArray(g_NextMapList);
845 new index, Handle:tempMaps = CloneArray(g_MapList);
847 GetCurrentMap(map, sizeof(map));
848 index = FindStringInArray(tempMaps, map);
851 RemoveFromArray(tempMaps, index);
854 if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps))
856 for (new i = 0; i < GetArraySize(g_OldMapList); i++)
858 GetArrayString(g_OldMapList, i, map, sizeof(map));
859 index = FindStringInArray(tempMaps, map);
862 RemoveFromArray(tempMaps, index);
867 new limit = (GetConVarInt(g_Cvar_IncludeMaps) < GetArraySize(tempMaps) ? GetConVarInt(g_Cvar_IncludeMaps) : GetArraySize(tempMaps));
868 for (new i = 0; i < limit; i++)
870 new b = GetRandomInt(0, GetArraySize(tempMaps) - 1);
871 GetArrayString(tempMaps, b, map, sizeof(map));
872 PushArrayString(g_NextMapList, map);
873 RemoveFromArray(tempMaps, b);
876 CloseHandle(tempMaps);
881 if (g_WaitingForVote || g_HasVoteStarted)
889 NominateResult:InternalNominateMap(String:map[], bool:force, owner)
891 if (!IsMapValid(map))
893 return Nominate_InvalidMap;
898 /* Look to replace an existing nomination by this client - Nominations made with owner = 0 aren't replaced */
899 if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1))
901 new String:oldmap[33];
902 GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
903 Call_StartForward(g_NominationsResetForward);
904 Call_PushString(oldmap);
905 Call_PushCell(owner);
908 SetArrayString(g_NominateList, index, map);
909 return Nominate_Replaced;
912 /* Too many nominated maps. */
913 if (g_NominateCount >= GetConVarInt(g_Cvar_IncludeMaps) && !force)
915 return Nominate_VoteFull;
918 /* Map already in the vote */
919 if (FindStringInArray(g_NominateList, map) != -1)
921 return Nominate_AlreadyInVote;
925 PushArrayString(g_NominateList, map);
926 PushArrayCell(g_NominateOwners, owner);
929 while (GetArraySize(g_NominateList) > GetConVarInt(g_Cvar_IncludeMaps))
931 new String:oldmap[33];
932 GetArrayString(g_NominateList, 0, oldmap, sizeof(oldmap));
933 Call_StartForward(g_NominationsResetForward);
934 Call_PushString(oldmap);
935 Call_PushCell(GetArrayCell(g_NominateOwners, 0));
938 RemoveFromArray(g_NominateList, 0);
939 RemoveFromArray(g_NominateOwners, 0);
942 return Nominate_Added;
945 /* Add natives to allow nominate and initiate vote to be call */
947 /* native bool:NominateMap(const String:map[], bool:force, &NominateError:error); */
948 public Native_NominateMap(Handle:plugin, numParams)
951 GetNativeStringLength(1, len);
958 new String:map[len+1];
959 GetNativeString(1, map, len+1);
961 return _:InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3));
964 /* native InitiateMapChooserVote(); */
965 public Native_InitiateVote(Handle:plugin, numParams)
967 new MapChange:when = MapChange:GetNativeCell(1);
968 new Handle:inputarray = Handle:GetNativeCell(2);
970 LogMessage("Starting map vote because outside request");
971 InitiateVote(when, inputarray);
974 public Native_CanVoteStart(Handle:plugin, numParams)
976 return CanVoteStart();
979 public Native_CheckVoteDone(Handle:plugin, numParams)
981 return g_MapVoteCompleted;
984 public Native_EndOfMapVoteEnabled(Handle:plugin, numParams)
986 return GetConVarBool(g_Cvar_EndOfMapVote);
989 public Native_GetExcludeMapList(Handle:plugin, numParams)
991 new Handle:array = Handle:GetNativeCell(1);
993 if (array == INVALID_HANDLE)
999 new size = GetArraySize(g_OldMapList);
1000 decl String:map[33];
1002 for (new i=0; i<size; i++)
1004 GetArrayString(g_OldMapList, i, map, sizeof(map));
1005 PushArrayString(array, map);