Update CS:GO team score gamedata.
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;
66 new Handle:g_Cvar_StartTime = INVALID_HANDLE;
67 new Handle:g_Cvar_StartRounds = INVALID_HANDLE;
68 new Handle:g_Cvar_StartFrags = INVALID_HANDLE;
69 new Handle:g_Cvar_ExtendTimeStep = INVALID_HANDLE;
70 new Handle:g_Cvar_ExtendRoundStep = INVALID_HANDLE;
71 new Handle:g_Cvar_ExtendFragStep = INVALID_HANDLE;
72 new Handle:g_Cvar_ExcludeMaps = INVALID_HANDLE;
73 new Handle:g_Cvar_IncludeMaps = INVALID_HANDLE;
74 new Handle:g_Cvar_NoVoteMode = INVALID_HANDLE;
75 new Handle:g_Cvar_Extend = INVALID_HANDLE;
76 new Handle:g_Cvar_DontChange = INVALID_HANDLE;
77 new Handle:g_Cvar_EndOfMapVote = INVALID_HANDLE;
78 new Handle:g_Cvar_VoteDuration = INVALID_HANDLE;
79 new Handle:g_Cvar_RunOff = INVALID_HANDLE;
80 new Handle:g_Cvar_RunOffPercent = INVALID_HANDLE;
82 new Handle:g_VoteTimer = INVALID_HANDLE;
83 new Handle:g_RetryTimer = INVALID_HANDLE;
86 new Handle:g_MapList = INVALID_HANDLE;
87 new Handle:g_NominateList = INVALID_HANDLE;
88 new Handle:g_NominateOwners = INVALID_HANDLE;
89 new Handle:g_OldMapList = INVALID_HANDLE;
90 new Handle:g_NextMapList = INVALID_HANDLE;
91 new Handle:g_VoteMenu = INVALID_HANDLE;
95 new bool:g_HasVoteStarted;
96 new bool:g_WaitingForVote;
97 new bool:g_MapVoteCompleted;
98 new bool:g_ChangeMapAtRoundEnd;
99 new bool:g_ChangeMapInProgress;
100 new g_mapFileSerial = -1;
102 new MapChange:g_ChangeTime;
104 new Handle:g_NominationsResetForward = INVALID_HANDLE;
105 new Handle:g_MapVoteStartedForward = INVALID_HANDLE;
107 /* Upper bound of how many team there could be */
109 new g_winCount[MAXTEAMS];
111 #define VOTE_EXTEND "##extend##"
112 #define VOTE_DONTCHANGE "##dontchange##"
114 public OnPluginStart()
116 LoadTranslations("mapchooser.phrases");
117 LoadTranslations("common.phrases");
119 new arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
120 g_MapList = CreateArray(arraySize);
121 g_NominateList = CreateArray(arraySize);
122 g_NominateOwners = CreateArray(1);
123 g_OldMapList = CreateArray(arraySize);
124 g_NextMapList = CreateArray(arraySize);
126 g_Cvar_EndOfMapVote = CreateConVar("sm_mapvote_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0);
128 g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0);
129 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);
130 g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0);
131 g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
132 g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0);
133 g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0);
134 g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
135 g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0);
136 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);
137 g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0);
138 g_Cvar_DontChange = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0);
139 g_Cvar_VoteDuration = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0);
140 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);
141 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);
143 RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now.");
144 RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap <map>");
146 g_Cvar_Winlimit = FindConVar("mp_winlimit");
147 g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
148 g_Cvar_Fraglimit = FindConVar("mp_fraglimit");
149 g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime");
151 if (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE)
153 decl String:folder[64];
154 GetGameFolderName(folder, sizeof(folder));
156 if (strcmp(folder, "tf") == 0)
158 HookEvent("teamplay_win_panel", Event_TeamPlayWinPanel);
159 HookEvent("teamplay_restart_round", Event_TFRestartRound);
160 HookEvent("arena_win_panel", Event_TeamPlayWinPanel);
162 else if (strcmp(folder, "nucleardawn") == 0)
164 HookEvent("round_win", Event_RoundEnd);
168 HookEvent("round_end", Event_RoundEnd);
172 if (g_Cvar_Fraglimit != INVALID_HANDLE)
174 HookEvent("player_death", Event_PlayerDeath);
177 AutoExecConfig(true, "mapchooser");
179 //Change the mp_bonusroundtime max so that we have time to display the vote
180 //If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime
181 if (g_Cvar_Bonusroundtime != INVALID_HANDLE)
183 SetConVarBounds(g_Cvar_Bonusroundtime, ConVarBound_Upper, true, 30.0);
186 g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell);
187 g_MapVoteStartedForward = CreateGlobalForward("OnMapVoteStarted", ET_Ignore);
190 public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
192 RegPluginLibrary("mapchooser");
194 CreateNative("NominateMap", Native_NominateMap);
195 CreateNative("RemoveNominationByMap", Native_RemoveNominationByMap);
196 CreateNative("RemoveNominationByOwner", Native_RemoveNominationByOwner);
197 CreateNative("InitiateMapChooserVote", Native_InitiateVote);
198 CreateNative("CanMapChooserStartVote", Native_CanVoteStart);
199 CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone);
200 CreateNative("GetExcludeMapList", Native_GetExcludeMapList);
201 CreateNative("GetNominatedMapList", Native_GetNominatedMapList);
202 CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled);
204 return APLRes_Success;
207 public OnConfigsExecuted()
209 if (ReadMapList(g_MapList,
212 MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER)
216 if (g_mapFileSerial == -1)
218 LogError("Unable to create a valid map list.");
223 SetupTimeleftTimer();
229 g_MapVoteCompleted = false;
231 ClearArray(g_NominateList);
232 ClearArray(g_NominateOwners);
234 for (new i=0; i<MAXTEAMS; i++)
240 /* Check if mapchooser will attempt to start mapvote during bonus round time - TF2 Only */
241 if ((g_Cvar_Bonusroundtime != INVALID_HANDLE) && !GetConVarInt(g_Cvar_StartRounds))
243 if (GetConVarFloat(g_Cvar_Bonusroundtime) <= GetConVarFloat(g_Cvar_VoteDuration))
245 LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete");
252 g_HasVoteStarted = false;
253 g_WaitingForVote = false;
254 g_ChangeMapAtRoundEnd = false;
255 g_ChangeMapInProgress = false;
257 g_VoteTimer = INVALID_HANDLE;
258 g_RetryTimer = INVALID_HANDLE;
260 decl String:map[PLATFORM_MAX_PATH];
261 GetCurrentMap(map, sizeof(map));
262 PushArrayString(g_OldMapList, map);
264 if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
266 RemoveFromArray(g_OldMapList, 0);
270 public OnClientDisconnect(client)
272 new index = FindValueInArray(g_NominateOwners, client);
279 new String:oldmap[PLATFORM_MAX_PATH];
280 GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
281 Call_StartForward(g_NominationsResetForward);
282 Call_PushString(oldmap);
283 Call_PushCell(GetArrayCell(g_NominateOwners, index));
286 RemoveFromArray(g_NominateOwners, index);
287 RemoveFromArray(g_NominateList, index);
290 public Action:Command_SetNextmap(client, args)
294 ReplyToCommand(client, "[SM] Usage: sm_setnextmap <map>");
295 return Plugin_Handled;
298 decl String:map[PLATFORM_MAX_PATH];
299 GetCmdArg(1, map, sizeof(map));
301 if (!IsMapValid(map))
303 ReplyToCommand(client, "[SM] %t", "Map was not found", map);
304 return Plugin_Handled;
307 ShowActivity(client, "%t", "Changed Next Map", map);
308 LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map);
311 g_MapVoteCompleted = true;
313 return Plugin_Handled;
316 public OnMapTimeLeftChanged()
318 if (GetArraySize(g_MapList))
320 SetupTimeleftTimer();
327 if (GetMapTimeLeft(time) && time > 0)
329 new startTime = GetConVarInt(g_Cvar_StartTime) * 60;
330 if (time - startTime < 0 && GetConVarBool(g_Cvar_EndOfMapVote) && !g_MapVoteCompleted && !g_HasVoteStarted)
332 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
336 if (g_VoteTimer != INVALID_HANDLE)
338 KillTimer(g_VoteTimer);
339 g_VoteTimer = INVALID_HANDLE;
342 //g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
344 g_VoteTimer = CreateDataTimer(float(time - startTime), Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
345 WritePackCell(data, _:MapChange_MapEnd);
346 WritePackCell(data, _:INVALID_HANDLE);
352 public Action:Timer_StartMapVote(Handle:timer, Handle:data)
354 if (timer == g_RetryTimer)
356 g_WaitingForVote = false;
357 g_RetryTimer = INVALID_HANDLE;
361 g_VoteTimer = INVALID_HANDLE;
364 if (!GetArraySize(g_MapList) || !GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted)
369 new MapChange:mapChange = MapChange:ReadPackCell(data);
370 new Handle:hndl = Handle:ReadPackCell(data);
372 InitiateVote(mapChange, hndl);
377 public Event_TFRestartRound(Handle:event, const String:name[], bool:dontBroadcast)
379 /* Game got restarted - reset our round count tracking */
383 public Event_TeamPlayWinPanel(Handle:event, const String:name[], bool:dontBroadcast)
385 if (g_ChangeMapAtRoundEnd)
387 g_ChangeMapAtRoundEnd = false;
388 CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
389 g_ChangeMapInProgress = true;
392 new bluescore = GetEventInt(event, "blue_score");
393 new redscore = GetEventInt(event, "red_score");
395 if(GetEventInt(event, "round_complete") == 1 || StrEqual(name, "arena_win_panel"))
399 if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted || !GetConVarBool(g_Cvar_EndOfMapVote))
404 CheckMaxRounds(g_TotalRounds);
406 switch(GetEventInt(event, "winning_team"))
410 CheckWinLimit(bluescore);
414 CheckWinLimit(redscore);
416 //We need to do nothing on winning_team == 0 this indicates stalemate.
424 /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
425 public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
427 if (g_ChangeMapAtRoundEnd)
429 g_ChangeMapAtRoundEnd = false;
430 CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
431 g_ChangeMapInProgress = true;
435 if (strcmp(name, "round_win") == 0)
438 winner = GetEventInt(event, "team");
442 winner = GetEventInt(event, "winner");
445 if (winner == 0 || winner == 1 || !GetConVarBool(g_Cvar_EndOfMapVote))
450 if (winner >= MAXTEAMS)
452 SetFailState("Mod exceed maximum team count - Please file a bug report.");
457 g_winCount[winner]++;
459 if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted)
464 CheckWinLimit(g_winCount[winner]);
465 CheckMaxRounds(g_TotalRounds);
468 public CheckWinLimit(winner_score)
470 if (g_Cvar_Winlimit != INVALID_HANDLE)
472 new winlimit = GetConVarInt(g_Cvar_Winlimit);
475 if (winner_score >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
477 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
483 public CheckMaxRounds(roundcount)
485 if (g_Cvar_Maxrounds != INVALID_HANDLE)
487 new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
490 if (roundcount >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
492 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
498 public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
500 if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted)
505 if (!GetConVarInt(g_Cvar_Fraglimit) || !GetConVarBool(g_Cvar_EndOfMapVote))
510 if (g_MapVoteCompleted)
515 new fragger = GetClientOfUserId(GetEventInt(event, "attacker"));
522 if (GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
524 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
528 public Action:Command_Mapvote(client, args)
530 InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
532 return Plugin_Handled;
536 * Starts a new map vote
538 * @param when When the resulting map change should occur.
539 * @param inputlist Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used.
540 * @param noSpecials Block special vote options like extend/nochange (upgrade this to bitflags instead?)
542 InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE)
544 g_WaitingForVote = true;
546 if (IsVoteInProgress())
548 // Can't start a vote, try again in 5 seconds.
549 //g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
552 g_RetryTimer = CreateDataTimer(5.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
553 WritePackCell(data, _:when);
554 WritePackCell(data, _:inputlist);
559 /* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */
560 if (g_MapVoteCompleted && g_ChangeMapInProgress)
567 g_WaitingForVote = false;
569 g_HasVoteStarted = true;
570 g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
571 SetMenuTitle(g_VoteMenu, "Vote Nextmap");
572 SetVoteResultCallback(g_VoteMenu, Handler_MapVoteFinished);
574 /* Call OnMapVoteStarted() Forward */
575 Call_StartForward(g_MapVoteStartedForward);
579 * TODO: Make a proper decision on when to clear the nominations list.
580 * Currently it clears when used, and stays if an external list is provided.
581 * Is this the right thing to do? External lists will probably come from places
582 * like sm_mapvote from the adminmenu in the future.
585 decl String:map[PLATFORM_MAX_PATH];
587 /* No input given - User our internal nominations and maplist */
588 if (inputlist == INVALID_HANDLE)
590 new nominateCount = GetArraySize(g_NominateList);
591 new voteSize = GetConVarInt(g_Cvar_IncludeMaps);
593 /* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */
594 new nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount;
597 for (new i=0; i<nominationsToAdd; i++)
599 GetArrayString(g_NominateList, i, map, sizeof(map));
600 AddMenuItem(g_VoteMenu, map, map);
601 RemoveStringFromArray(g_NextMapList, map);
603 /* Notify Nominations that this map is now free */
604 Call_StartForward(g_NominationsResetForward);
605 Call_PushString(map);
606 Call_PushCell(GetArrayCell(g_NominateOwners, i));
610 /* Clear out the rest of the nominations array */
611 for (new i=nominationsToAdd; i<nominateCount; i++)
613 GetArrayString(g_NominateList, i, map, sizeof(map));
614 /* These maps shouldn't be excluded from the vote as they weren't really nominated at all */
616 /* Notify Nominations that this map is now free */
617 Call_StartForward(g_NominationsResetForward);
618 Call_PushString(map);
619 Call_PushCell(GetArrayCell(g_NominateOwners, i));
623 /* There should currently be 'nominationsToAdd' unique maps in the vote */
625 new i = nominationsToAdd;
627 new availableMaps = GetArraySize(g_NextMapList);
631 if (count >= availableMaps)
633 //Run out of maps, this will have to do.
637 GetArrayString(g_NextMapList, count, map, sizeof(map));
640 /* Insert the map and increment our count */
641 AddMenuItem(g_VoteMenu, map, map);
645 /* Wipe out our nominations list - Nominations have already been informed of this */
646 ClearArray(g_NominateOwners);
647 ClearArray(g_NominateList);
649 else //We were given a list of maps to start the vote with
651 new size = GetArraySize(inputlist);
653 for (new i=0; i<size; i++)
655 GetArrayString(inputlist, i, map, sizeof(map));
659 AddMenuItem(g_VoteMenu, map, map);
664 /* Do we add any special items? */
665 if ((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange))
667 AddMenuItem(g_VoteMenu, VOTE_DONTCHANGE, "Don't Change");
669 else if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend))
671 AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
674 /* There are no maps we could vote for. Don't show anything. */
675 if (GetMenuItemCount(g_VoteMenu) == 0)
677 g_HasVoteStarted = false;
678 CloseHandle(g_VoteMenu);
679 g_VoteMenu = INVALID_HANDLE;
683 new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
685 SetMenuExitButton(g_VoteMenu, false);
686 VoteMenuToAll(g_VoteMenu, voteDuration);
688 LogAction(-1, -1, "Voting for next map has started.");
689 PrintToChatAll("[SM] %t", "Nextmap Voting Started");
692 public Handler_VoteFinishedGeneric(Handle:menu,
695 const client_info[][2],
697 const item_info[][2])
699 decl String:map[PLATFORM_MAX_PATH];
700 GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map));
702 if (strcmp(map, VOTE_EXTEND, false) == 0)
707 if (GetMapTimeLimit(time))
711 ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);
715 if (g_Cvar_Winlimit != INVALID_HANDLE)
717 new winlimit = GetConVarInt(g_Cvar_Winlimit);
720 SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
724 if (g_Cvar_Maxrounds != INVALID_HANDLE)
726 new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
729 SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
733 if (g_Cvar_Fraglimit != INVALID_HANDLE)
735 new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
738 SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));
742 PrintToChatAll("[SM] %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
743 LogAction(-1, -1, "Voting for next map has finished. The current map has been extended.");
745 // We extended, so we'll have to vote again.
746 g_HasVoteStarted = false;
748 SetupTimeleftTimer();
751 else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
753 PrintToChatAll("[SM] %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
754 LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner");
756 g_HasVoteStarted = false;
758 SetupTimeleftTimer();
762 if (g_ChangeTime == MapChange_MapEnd)
766 else if (g_ChangeTime == MapChange_Instant)
769 CreateDataTimer(2.0, Timer_ChangeMap, data);
770 WritePackString(data, map);
771 g_ChangeMapInProgress = false;
773 else // MapChange_RoundEnd
776 g_ChangeMapAtRoundEnd = true;
779 g_HasVoteStarted = false;
780 g_MapVoteCompleted = true;
782 PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
783 LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map);
787 public Handler_MapVoteFinished(Handle:menu,
790 const client_info[][2],
792 const item_info[][2])
794 if (GetConVarBool(g_Cvar_RunOff) && num_items > 1)
796 new Float:winningvotes = float(item_info[0][VOTEINFO_ITEM_VOTES]);
797 new Float:required = num_votes * (GetConVarFloat(g_Cvar_RunOffPercent) / 100.0);
799 if (winningvotes < required)
801 /* Insufficient Winning margin - Lets do a runoff */
802 g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
803 SetMenuTitle(g_VoteMenu, "Runoff Vote Nextmap");
804 SetVoteResultCallback(g_VoteMenu, Handler_VoteFinishedGeneric);
806 decl String:map[PLATFORM_MAX_PATH];
807 decl String:info1[PLATFORM_MAX_PATH];
808 decl String:info2[PLATFORM_MAX_PATH];
810 GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info1, sizeof(info1));
811 AddMenuItem(g_VoteMenu, map, info1);
812 GetMenuItem(menu, item_info[1][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info2, sizeof(info2));
813 AddMenuItem(g_VoteMenu, map, info2);
815 new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
816 SetMenuExitButton(g_VoteMenu, false);
817 VoteMenuToAll(g_VoteMenu, voteDuration);
820 new Float:map1percent = float(item_info[0][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100;
821 new Float:map2percent = float(item_info[1][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100;
824 PrintToChatAll("[SM] %t", "Starting Runoff", GetConVarFloat(g_Cvar_RunOffPercent), info1, map1percent, info2, map2percent);
825 LogMessage("Voting for next map was indecisive, beginning runoff vote");
831 Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info);
834 public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
840 g_VoteMenu = INVALID_HANDLE;
844 case MenuAction_Display:
846 decl String:buffer[255];
847 Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
849 new Handle:panel = Handle:param2;
850 SetPanelTitle(panel, buffer);
853 case MenuAction_DisplayItem:
855 if (GetMenuItemCount(menu) - 1 == param2)
857 decl String:map[PLATFORM_MAX_PATH], String:buffer[255];
858 GetMenuItem(menu, param2, map, sizeof(map));
859 if (strcmp(map, VOTE_EXTEND, false) == 0)
861 Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
862 return RedrawMenuItem(buffer);
864 else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
866 Format(buffer, sizeof(buffer), "%T", "Dont Change", param1);
867 return RedrawMenuItem(buffer);
872 case MenuAction_VoteCancel:
874 // If we receive 0 votes, pick at random.
875 if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode))
877 new count = GetMenuItemCount(menu);
878 decl String:map[PLATFORM_MAX_PATH];
879 GetMenuItem(menu, 0, map, sizeof(map));
881 // Make sure the first map in the menu isn't one of the special items.
882 // This would mean there are no real maps in the menu, because the special items are added after all maps. Don't do anything if that's the case.
883 if (strcmp(map, VOTE_EXTEND, false) != 0 && strcmp(map, VOTE_DONTCHANGE, false) != 0)
885 // Get a random map from the list.
886 new item = GetRandomInt(0, count - 1);
887 GetMenuItem(menu, item, map, sizeof(map));
889 // Make sure it's not one of the special items.
890 while (strcmp(map, VOTE_EXTEND, false) == 0 || strcmp(map, VOTE_DONTCHANGE, false) == 0)
892 item = GetRandomInt(0, count - 1);
893 GetMenuItem(menu, item, map, sizeof(map));
897 g_MapVoteCompleted = true;
902 // We were actually cancelled. I guess we do nothing.
905 g_HasVoteStarted = false;
912 public Action:Timer_ChangeMap(Handle:hTimer, Handle:dp)
914 g_ChangeMapInProgress = false;
916 new String:map[PLATFORM_MAX_PATH];
918 if (dp == INVALID_HANDLE)
920 if (!GetNextMap(map, sizeof(map)))
922 //No passed map and no set nextmap. fail!
929 ReadPackString(dp, map, sizeof(map));
932 ForceChangeLevel(map, "Map Vote");
937 bool:RemoveStringFromArray(Handle:array, String:str[])
939 new index = FindStringInArray(array, str);
942 RemoveFromArray(array, index);
951 assert(g_NextMapList)
952 ClearArray(g_NextMapList);
954 decl String:map[PLATFORM_MAX_PATH];
955 new Handle:tempMaps = CloneArray(g_MapList);
957 GetCurrentMap(map, sizeof(map));
958 RemoveStringFromArray(tempMaps, map);
960 if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps))
962 for (new i = 0; i < GetArraySize(g_OldMapList); i++)
964 GetArrayString(g_OldMapList, i, map, sizeof(map));
965 RemoveStringFromArray(tempMaps, map);
969 new limit = (GetConVarInt(g_Cvar_IncludeMaps) < GetArraySize(tempMaps) ? GetConVarInt(g_Cvar_IncludeMaps) : GetArraySize(tempMaps));
970 for (new i = 0; i < limit; i++)
972 new b = GetRandomInt(0, GetArraySize(tempMaps) - 1);
973 GetArrayString(tempMaps, b, map, sizeof(map));
974 PushArrayString(g_NextMapList, map);
975 RemoveFromArray(tempMaps, b);
978 CloseHandle(tempMaps);
983 if (g_WaitingForVote || g_HasVoteStarted)
991 NominateResult:InternalNominateMap(String:map[], bool:force, owner)
993 if (!IsMapValid(map))
995 return Nominate_InvalidMap;
998 /* Map already in the vote */
999 if (FindStringInArray(g_NominateList, map) != -1)
1001 return Nominate_AlreadyInVote;
1006 /* Look to replace an existing nomination by this client - Nominations made with owner = 0 aren't replaced */
1007 if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1))
1009 new String:oldmap[PLATFORM_MAX_PATH];
1010 GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
1011 Call_StartForward(g_NominationsResetForward);
1012 Call_PushString(oldmap);
1013 Call_PushCell(owner);
1016 SetArrayString(g_NominateList, index, map);
1017 return Nominate_Replaced;
1020 /* Too many nominated maps. */
1021 if (GetArraySize(g_NominateList) >= GetConVarInt(g_Cvar_IncludeMaps) && !force)
1023 return Nominate_VoteFull;
1026 PushArrayString(g_NominateList, map);
1027 PushArrayCell(g_NominateOwners, owner);
1029 while (GetArraySize(g_NominateList) > GetConVarInt(g_Cvar_IncludeMaps))
1031 new String:oldmap[PLATFORM_MAX_PATH];
1032 GetArrayString(g_NominateList, 0, oldmap, sizeof(oldmap));
1033 Call_StartForward(g_NominationsResetForward);
1034 Call_PushString(oldmap);
1035 Call_PushCell(GetArrayCell(g_NominateOwners, 0));
1038 RemoveFromArray(g_NominateList, 0);
1039 RemoveFromArray(g_NominateOwners, 0);
1042 return Nominate_Added;
1045 /* Add natives to allow nominate and initiate vote to be call */
1047 /* native bool:NominateMap(const String:map[], bool:force, &NominateError:error); */
1048 public Native_NominateMap(Handle:plugin, numParams)
1051 GetNativeStringLength(1, len);
1058 new String:map[len+1];
1059 GetNativeString(1, map, len+1);
1061 return _:InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3));
1064 bool:InternalRemoveNominationByMap(String:map[])
1066 for (new i = 0; i < GetArraySize(g_NominateList); i++)
1068 new String:oldmap[PLATFORM_MAX_PATH];
1069 GetArrayString(g_NominateList, i, oldmap, sizeof(oldmap));
1071 if(strcmp(map, oldmap, false) == 0)
1073 Call_StartForward(g_NominationsResetForward);
1074 Call_PushString(oldmap);
1075 Call_PushCell(GetArrayCell(g_NominateOwners, i));
1078 RemoveFromArray(g_NominateList, i);
1079 RemoveFromArray(g_NominateOwners, i);
1088 /* native bool:RemoveNominationByMap(const String:map[]); */
1089 public Native_RemoveNominationByMap(Handle:plugin, numParams)
1092 GetNativeStringLength(1, len);
1099 new String:map[len+1];
1100 GetNativeString(1, map, len+1);
1102 return _:InternalRemoveNominationByMap(map);
1105 bool:InternalRemoveNominationByOwner(owner)
1109 if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1))
1111 new String:oldmap[PLATFORM_MAX_PATH];
1112 GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
1114 Call_StartForward(g_NominationsResetForward);
1115 Call_PushString(oldmap);
1116 Call_PushCell(owner);
1119 RemoveFromArray(g_NominateList, index);
1120 RemoveFromArray(g_NominateOwners, index);
1128 /* native bool:RemoveNominationByOwner(owner); */
1129 public Native_RemoveNominationByOwner(Handle:plugin, numParams)
1131 return _:InternalRemoveNominationByOwner(GetNativeCell(1));
1134 /* native InitiateMapChooserVote(); */
1135 public Native_InitiateVote(Handle:plugin, numParams)
1137 new MapChange:when = MapChange:GetNativeCell(1);
1138 new Handle:inputarray = Handle:GetNativeCell(2);
1140 LogAction(-1, -1, "Starting map vote because outside request");
1141 InitiateVote(when, inputarray);
1144 public Native_CanVoteStart(Handle:plugin, numParams)
1146 return CanVoteStart();
1149 public Native_CheckVoteDone(Handle:plugin, numParams)
1151 return g_MapVoteCompleted;
1154 public Native_EndOfMapVoteEnabled(Handle:plugin, numParams)
1156 return GetConVarBool(g_Cvar_EndOfMapVote);
1159 public Native_GetExcludeMapList(Handle:plugin, numParams)
1161 new Handle:array = Handle:GetNativeCell(1);
1163 if (array == INVALID_HANDLE)
1167 new size = GetArraySize(g_OldMapList);
1168 decl String:map[PLATFORM_MAX_PATH];
1170 for (new i=0; i<size; i++)
1172 GetArrayString(g_OldMapList, i, map, sizeof(map));
1173 PushArrayString(array, map);
1179 public Native_GetNominatedMapList(Handle:plugin, numParams)
1181 new Handle:maparray = Handle:GetNativeCell(1);
1182 new Handle:ownerarray = Handle:GetNativeCell(2);
1184 if (maparray == INVALID_HANDLE)
1187 decl String:map[PLATFORM_MAX_PATH];
1189 for (new i = 0; i < GetArraySize(g_NominateList); i++)
1191 GetArrayString(g_NominateList, i, map, sizeof(map));
1192 PushArrayString(maparray, map);
1194 // If the optional parameter for an owner list was passed, then we need to fill that out as well
1195 if(ownerarray != INVALID_HANDLE)
1197 new index = GetArrayCell(g_NominateOwners, i);
1198 PushArrayCell(ownerarray, index);