plugins/mapchooser.sp
author Michael McKoy <ferret99gt@gmail.com>
Mon Mar 18 20:10:31 2013 -0400 (2013-03-18)
changeset 3851 40e1814dd3d6
parent 3826 cdb77388037e
child 3863 8645c29348dc
permissions -rw-r--r--
Fixed mapchooser interaction with CS:GO winlimits (bug 5484, r=psychonic).
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod Mapchooser Plugin
     5  * Creates a map vote at appropriate times, setting sm_nextmap to the winning
     6  * vote
     7  *
     8  * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
     9  * =============================================================================
    10  *
    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.
    14  * 
    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
    18  * details.
    19  *
    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/>.
    22  *
    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>.
    31  *
    32  * Version: $Id$
    33  */
    34  
    35 //#define DEBUG
    36 
    37 #if defined DEBUG
    38 	#define assert(%1) if (!(%1)) ThrowError("Debug Assertion Failed");
    39 	#define assert_msg(%1,%2) if (!(%1)) ThrowError(%2);
    40 #else
    41 	#define assert(%1)
    42 	#define assert_msg(%1,%2)
    43 #endif
    44 
    45 #pragma semicolon 1
    46 #include <sourcemod>
    47 #include <mapchooser>
    48 #include <nextmap>
    49 
    50 public Plugin:myinfo =
    51 {
    52 	name = "MapChooser",
    53 	author = "AlliedModders LLC",
    54 	description = "Automated Map Voting",
    55 	version = SOURCEMOD_VERSION,
    56 	url = "http://www.sourcemod.net/"
    57 };
    58 
    59 /* Valve ConVars */
    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;
    65 
    66 /* Plugin ConVars */
    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;
    82 
    83 new Handle:g_VoteTimer = INVALID_HANDLE;
    84 new Handle:g_RetryTimer = INVALID_HANDLE;
    85 
    86 /* Data Handles */
    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;
    93 
    94 new g_Extends;
    95 new g_TotalRounds;
    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;
   103 
   104 new g_NominateCount = 0;
   105 new MapChange:g_ChangeTime;
   106 
   107 new Handle:g_NominationsResetForward = INVALID_HANDLE;
   108 new Handle:g_MapVoteStartedForward = INVALID_HANDLE;
   109 
   110 /* Upper bound of how many team there could be */
   111 #define MAXTEAMS 10
   112 new g_winCount[MAXTEAMS];
   113 
   114 #define VOTE_EXTEND "##extend##"
   115 #define VOTE_DONTCHANGE "##dontchange##"
   116 
   117 public OnPluginStart()
   118 {
   119 	LoadTranslations("mapchooser.phrases");
   120 	LoadTranslations("common.phrases");
   121 	
   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);
   128 	
   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);
   130 
   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);
   145 	
   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>");
   148 
   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");
   154 	
   155 	if (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE)
   156 	{
   157 		decl String:folder[64];
   158 		GetGameFolderName(folder, sizeof(folder));
   159 
   160 		if (strcmp(folder, "tf") == 0)
   161 		{
   162 			HookEvent("teamplay_win_panel", Event_TeamPlayWinPanel);
   163 			HookEvent("teamplay_restart_round", Event_TFRestartRound);
   164 			HookEvent("arena_win_panel", Event_TeamPlayWinPanel);
   165 		}
   166 		else if (strcmp(folder, "nucleardawn") == 0)
   167 		{
   168 			HookEvent("round_win", Event_RoundEnd);
   169 		}
   170 		else if (strcmp(folder, "csgo") == 0)
   171 		{
   172 			HookEvent("round_end", Event_RoundEnd);
   173 			HookEvent("cs_intermission", Event_Intermission);
   174 			HookEvent("announce_phase_end", Event_PhaseEnd);
   175 		}
   176 		else
   177 		{
   178 			HookEvent("round_end", Event_RoundEnd);
   179 		}
   180 	}
   181 	
   182 	if (g_Cvar_Fraglimit != INVALID_HANDLE)
   183 	{
   184 		HookEvent("player_death", Event_PlayerDeath);		
   185 	}
   186 	
   187 	AutoExecConfig(true, "mapchooser");
   188 	
   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)
   192 	{
   193 		SetConVarBounds(g_Cvar_Bonusroundtime, ConVarBound_Upper, true, 30.0);		
   194 	}
   195 	
   196 	g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell);
   197 	g_MapVoteStartedForward = CreateGlobalForward("OnMapVoteStarted", ET_Ignore);
   198 }
   199 
   200 public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
   201 {
   202 	RegPluginLibrary("mapchooser");	
   203 	
   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);
   213 
   214 	return APLRes_Success;
   215 }
   216 
   217 public OnConfigsExecuted()
   218 {
   219 	if (ReadMapList(g_MapList,
   220 					 g_mapFileSerial, 
   221 					 "mapchooser",
   222 					 MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER)
   223 		!= INVALID_HANDLE)
   224 		
   225 	{
   226 		if (g_mapFileSerial == -1)
   227 		{
   228 			LogError("Unable to create a valid map list.");
   229 		}
   230 	}
   231 	
   232 	CreateNextVote();
   233 	SetupTimeleftTimer();
   234 	
   235 	g_TotalRounds = 0;
   236 	
   237 	g_Extends = 0;
   238 	
   239 	g_MapVoteCompleted = false;
   240 	
   241 	g_NominateCount = 0;
   242 	ClearArray(g_NominateList);
   243 	ClearArray(g_NominateOwners);
   244 	
   245 	for (new i=0; i<MAXTEAMS; i++)
   246 	{
   247 		g_winCount[i] = 0;	
   248 	}
   249 	
   250 
   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))
   253 	{
   254 		if (GetConVarFloat(g_Cvar_Bonusroundtime) <= GetConVarFloat(g_Cvar_VoteDuration))
   255 		{
   256 			LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete");
   257 		}
   258 	}
   259 }
   260 
   261 public OnMapEnd()
   262 {
   263 	g_HasVoteStarted = false;
   264 	g_WaitingForVote = false;
   265 	g_ChangeMapAtRoundEnd = false;
   266 	g_ChangeMapInProgress = false;
   267 	g_HasIntermissionStarted = false;
   268 	
   269 	g_VoteTimer = INVALID_HANDLE;
   270 	g_RetryTimer = INVALID_HANDLE;
   271 	
   272 	decl String:map[PLATFORM_MAX_PATH];
   273 	GetCurrentMap(map, sizeof(map));
   274 	PushArrayString(g_OldMapList, map);
   275 				
   276 	if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
   277 	{
   278 		RemoveFromArray(g_OldMapList, 0);
   279 	}	
   280 }
   281 
   282 public OnClientDisconnect(client)
   283 {
   284 	new index = FindValueInArray(g_NominateOwners, client);
   285 	
   286 	if (index == -1)
   287 	{
   288 		return;
   289 	}
   290 	
   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));
   296 	Call_Finish();
   297 	
   298 	RemoveFromArray(g_NominateOwners, index);
   299 	RemoveFromArray(g_NominateList, index);
   300 	g_NominateCount--;
   301 }
   302 
   303 public Action:Command_SetNextmap(client, args)
   304 {
   305 	if (args < 1)
   306 	{
   307 		ReplyToCommand(client, "[SM] Usage: sm_setnextmap <map>");
   308 		return Plugin_Handled;
   309 	}
   310 
   311 	decl String:map[PLATFORM_MAX_PATH];
   312 	GetCmdArg(1, map, sizeof(map));
   313 
   314 	if (!IsMapValid(map))
   315 	{
   316 		ReplyToCommand(client, "[SM] %t", "Map was not found", map);
   317 		return Plugin_Handled;
   318 	}
   319 
   320 	ShowActivity(client, "%t", "Changed Next Map", map);
   321 	LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map);
   322 
   323 	SetNextMap(map);
   324 	g_MapVoteCompleted = true;
   325 
   326 	return Plugin_Handled;
   327 }
   328 
   329 public OnMapTimeLeftChanged()
   330 {
   331 	if (GetArraySize(g_MapList))
   332 	{
   333 		SetupTimeleftTimer();
   334 	}
   335 }
   336 
   337 SetupTimeleftTimer()
   338 {
   339 	new time;
   340 	if (GetMapTimeLeft(time) && time > 0)
   341 	{
   342 		new startTime = GetConVarInt(g_Cvar_StartTime) * 60;
   343 		if (time - startTime < 0 && GetConVarBool(g_Cvar_EndOfMapVote) && !g_MapVoteCompleted && !g_HasVoteStarted)
   344 		{
   345 			InitiateVote(MapChange_MapEnd, INVALID_HANDLE);		
   346 		}
   347 		else
   348 		{
   349 			if (g_VoteTimer != INVALID_HANDLE)
   350 			{
   351 				KillTimer(g_VoteTimer);
   352 				g_VoteTimer = INVALID_HANDLE;
   353 			}	
   354 			
   355 			//g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
   356 			new Handle:data;
   357 			g_VoteTimer = CreateDataTimer(float(time - startTime), Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
   358 			WritePackCell(data, _:MapChange_MapEnd);
   359 			WritePackCell(data, _:INVALID_HANDLE);
   360 			ResetPack(data);
   361 		}		
   362 	}
   363 }
   364 
   365 public Action:Timer_StartMapVote(Handle:timer, Handle:data)
   366 {
   367 	if (timer == g_RetryTimer)
   368 	{
   369 		g_WaitingForVote = false;
   370 		g_RetryTimer = INVALID_HANDLE;
   371 	}
   372 	else
   373 	{
   374 		g_VoteTimer = INVALID_HANDLE;
   375 	}
   376 	
   377 	if (!GetArraySize(g_MapList) || !GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted)
   378 	{
   379 		return Plugin_Stop;
   380 	}
   381 	
   382 	new MapChange:mapChange = MapChange:ReadPackCell(data);
   383 	new Handle:hndl = Handle:ReadPackCell(data);
   384 
   385 	InitiateVote(mapChange, hndl);
   386 
   387 	return Plugin_Stop;
   388 }
   389 
   390 public Event_TFRestartRound(Handle:event, const String:name[], bool:dontBroadcast)
   391 {
   392 	/* Game got restarted - reset our round count tracking */
   393 	g_TotalRounds = 0;	
   394 }
   395 
   396 public Event_TeamPlayWinPanel(Handle:event, const String:name[], bool:dontBroadcast)
   397 {
   398 	if (g_ChangeMapAtRoundEnd)
   399 	{
   400 		g_ChangeMapAtRoundEnd = false;
   401 		CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
   402 		g_ChangeMapInProgress = true;
   403 	}
   404 	
   405 	new bluescore = GetEventInt(event, "blue_score");
   406 	new redscore = GetEventInt(event, "red_score");
   407 		
   408 	if(GetEventInt(event, "round_complete") == 1 || StrEqual(name, "arena_win_panel"))
   409 	{
   410 		g_TotalRounds++;
   411 		
   412 		if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted || !GetConVarBool(g_Cvar_EndOfMapVote))
   413 		{
   414 			return;
   415 		}
   416 		
   417 		CheckMaxRounds(g_TotalRounds);
   418 		
   419 		switch(GetEventInt(event, "winning_team"))
   420 		{
   421 			case 3:
   422 			{
   423 				CheckWinLimit(bluescore);
   424 			}
   425 			case 2:
   426 			{
   427 				CheckWinLimit(redscore);				
   428 			}			
   429 			//We need to do nothing on winning_team == 0 this indicates stalemate.
   430 			default:
   431 			{
   432 				return;
   433 			}			
   434 		}
   435 	}
   436 }
   437 
   438 public Event_Intermission(Handle:event, const String:name[], bool:dontBroadcast)
   439 {
   440 	g_HasIntermissionStarted = true;
   441 }
   442 
   443 public Event_PhaseEnd(Handle:event, const String:name[], bool:dontBroadcast)
   444 {
   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)
   447 	{
   448 		return;
   449 	}
   450 
   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;
   455 }
   456 
   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)
   459 {
   460 	if (g_ChangeMapAtRoundEnd)
   461 	{
   462 		g_ChangeMapAtRoundEnd = false;
   463 		CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
   464 		g_ChangeMapInProgress = true;
   465 	}
   466 	
   467 	new winner;
   468 	if (strcmp(name, "round_win") == 0)
   469 	{
   470 		// Nuclear Dawn
   471 		winner = GetEventInt(event, "team");
   472 	}
   473 	else
   474 	{
   475 		winner = GetEventInt(event, "winner");
   476 	}
   477 	
   478 	if (winner == 0 || winner == 1 || !GetConVarBool(g_Cvar_EndOfMapVote))
   479 	{
   480 		return;
   481 	}
   482 	
   483 	if (winner >= MAXTEAMS)
   484 	{
   485 		SetFailState("Mod exceed maximum team count - Please file a bug report.");	
   486 	}
   487 
   488 	g_TotalRounds++;
   489 	
   490 	g_winCount[winner]++;
   491 	
   492 	if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted)
   493 	{
   494 		return;
   495 	}
   496 	
   497 	CheckWinLimit(g_winCount[winner]);
   498 	CheckMaxRounds(g_TotalRounds);
   499 }
   500 
   501 public CheckWinLimit(winner_score)
   502 {	
   503 	if (g_Cvar_Winlimit != INVALID_HANDLE)
   504 	{
   505 		new winlimit = GetConVarInt(g_Cvar_Winlimit);
   506 		if (winlimit)
   507 		{			
   508 			if (winner_score >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
   509 			{
   510 				InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   511 			}
   512 		}
   513 	}
   514 
   515 	if (g_Cvar_MatchClinch != INVALID_HANDLE && g_Cvar_Maxrounds != INVALID_HANDLE)
   516 	{
   517 		new clinch = GetConVarInt(g_Cvar_MatchClinch);
   518 
   519 		if (clinch)
   520 		{
   521 			new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   522 			new winlimit = maxrounds / 2;
   523 
   524 			if(winner_score > (winlimit - GetConVarInt(g_Cvar_StartRounds)))
   525 			{
   526 				InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   527 			}
   528 		}
   529 	}
   530 }
   531 
   532 public CheckMaxRounds(roundcount)
   533 {		
   534 	if (g_Cvar_Maxrounds != INVALID_HANDLE)
   535 	{
   536 		new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   537 		if (maxrounds)
   538 		{
   539 			if (roundcount >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
   540 			{
   541 				InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   542 			}			
   543 		}
   544 	}
   545 }
   546 
   547 public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
   548 {
   549 	if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted)
   550 	{
   551 		return;
   552 	}
   553 	
   554 	if (!GetConVarInt(g_Cvar_Fraglimit) || !GetConVarBool(g_Cvar_EndOfMapVote))
   555 	{
   556 		return;
   557 	}
   558 
   559 	if (g_MapVoteCompleted)
   560 	{
   561 		return;
   562 	}
   563 
   564 	new fragger = GetClientOfUserId(GetEventInt(event, "attacker"));
   565 
   566 	if (!fragger)
   567 	{
   568 		return;
   569 	}
   570 
   571 	if (GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
   572 	{
   573 		InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   574 	}
   575 }
   576 
   577 public Action:Command_Mapvote(client, args)
   578 {
   579 	InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   580 
   581 	return Plugin_Handled;	
   582 }
   583 
   584 /**
   585  * Starts a new map vote
   586  *
   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?)
   590  */
   591 InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE)
   592 {
   593 	g_WaitingForVote = true;
   594 	
   595 	if (IsVoteInProgress())
   596 	{
   597 		// Can't start a vote, try again in 5 seconds.
   598 		//g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
   599 		
   600 		new Handle:data;
   601 		g_RetryTimer = CreateDataTimer(5.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
   602 		WritePackCell(data, _:when);
   603 		WritePackCell(data, _:inputlist);
   604 		ResetPack(data);
   605 		return;
   606 	}
   607 	
   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)
   610 	{
   611 		return;
   612 	}
   613 	
   614 	g_ChangeTime = when;
   615 	
   616 	g_WaitingForVote = false;
   617 		
   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);
   622 
   623 	/* Call OnMapVoteStarted() Forward */
   624 	Call_StartForward(g_MapVoteStartedForward);
   625 	Call_Finish();
   626 	
   627 	/**
   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.
   632 	 */
   633 	 
   634 	decl String:map[PLATFORM_MAX_PATH];
   635 	
   636 	/* No input given - User our internal nominations and maplist */
   637 	if (inputlist == INVALID_HANDLE)
   638 	{
   639 		new nominateCount = GetArraySize(g_NominateList);
   640 		new voteSize = GetConVarInt(g_Cvar_IncludeMaps);
   641 		
   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;
   644 		
   645 		
   646 		for (new i=0; i<nominationsToAdd; i++)
   647 		{
   648 			GetArrayString(g_NominateList, i, map, sizeof(map));
   649 			AddMenuItem(g_VoteMenu, map, map);
   650 			RemoveStringFromArray(g_NextMapList, map);
   651 			
   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));
   656 			Call_Finish();
   657 		}
   658 		
   659 		/* Clear out the rest of the nominations array */
   660 		for (new i=nominationsToAdd; i<nominateCount; i++)
   661 		{
   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 */
   664 			
   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));
   669 			Call_Finish();			
   670 		}
   671 		
   672 		/* There should currently be 'nominationsToAdd' unique maps in the vote */
   673 		
   674 		new i = nominationsToAdd;
   675 		new count = 0;
   676 		new availableMaps = GetArraySize(g_NextMapList);
   677 		
   678 		while (i < voteSize)
   679 		{
   680 			GetArrayString(g_NextMapList, count, map, sizeof(map));
   681 			count++;
   682 			
   683 			/* Insert the map and increment our count */
   684 			AddMenuItem(g_VoteMenu, map, map);
   685 			i++;
   686 			
   687 			if (count >= availableMaps)
   688 			{
   689 				//Run out of maps, this will have to do.
   690 				break;	
   691 			}
   692 		}
   693 		
   694 		/* Wipe out our nominations list - Nominations have already been informed of this */
   695 		ClearArray(g_NominateOwners);
   696 		ClearArray(g_NominateList);
   697 	}
   698 	else //We were given a list of maps to start the vote with
   699 	{
   700 		new size = GetArraySize(inputlist);
   701 		
   702 		for (new i=0; i<size; i++)
   703 		{
   704 			GetArrayString(inputlist, i, map, sizeof(map));
   705 			
   706 			if (IsMapValid(map))
   707 			{
   708 				AddMenuItem(g_VoteMenu, map, map);
   709 			}	
   710 		}
   711 	}
   712 	
   713 	/* Do we add any special items? */
   714 	if ((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange))
   715 	{
   716 		AddMenuItem(g_VoteMenu, VOTE_DONTCHANGE, "Don't Change");
   717 	}
   718 	else if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend))
   719 	{
   720 		AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
   721 	}
   722 	
   723 	new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
   724 
   725 	SetMenuExitButton(g_VoteMenu, false);
   726 	VoteMenuToAll(g_VoteMenu, voteDuration);
   727 
   728 	LogAction(-1, -1, "Voting for next map has started.");
   729 	PrintToChatAll("[SM] %t", "Nextmap Voting Started");
   730 }
   731 
   732 public Handler_VoteFinishedGeneric(Handle:menu,
   733 						   num_votes, 
   734 						   num_clients,
   735 						   const client_info[][2], 
   736 						   num_items,
   737 						   const item_info[][2])
   738 {
   739 	decl String:map[PLATFORM_MAX_PATH];
   740 	GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map));
   741 
   742 	if (strcmp(map, VOTE_EXTEND, false) == 0)
   743 	{
   744 		g_Extends++;
   745 		
   746 		new time;
   747 		if (GetMapTimeLimit(time))
   748 		{
   749 			if (time > 0)
   750 			{
   751 				ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);						
   752 			}
   753 		}
   754 		
   755 		if (g_Cvar_Winlimit != INVALID_HANDLE)
   756 		{
   757 			new winlimit = GetConVarInt(g_Cvar_Winlimit);
   758 			if (winlimit)
   759 			{
   760 				SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
   761 			}					
   762 		}
   763 		
   764 		if (g_Cvar_Maxrounds != INVALID_HANDLE)
   765 		{
   766 			new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   767 			if (maxrounds)
   768 			{
   769 				SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
   770 			}
   771 		}
   772 		
   773 		if (g_Cvar_Fraglimit != INVALID_HANDLE)
   774 		{
   775 			new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
   776 			if (fraglimit)
   777 			{
   778 				SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));						
   779 			}
   780 		}
   781 
   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.");
   784 		
   785 		// We extended, so we'll have to vote again.
   786 		g_HasVoteStarted = false;
   787 		CreateNextVote();
   788 		SetupTimeleftTimer();
   789 		
   790 	}
   791 	else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
   792 	{
   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");
   795 		
   796 		g_HasVoteStarted = false;
   797 		CreateNextVote();
   798 		SetupTimeleftTimer();
   799 	}
   800 	else
   801 	{
   802 		if (g_ChangeTime == MapChange_MapEnd)
   803 		{
   804 			SetNextMap(map);
   805 		}
   806 		else if (g_ChangeTime == MapChange_Instant)
   807 		{
   808 			new Handle:data;
   809 			CreateDataTimer(2.0, Timer_ChangeMap, data);
   810 			WritePackString(data, map);
   811 			g_ChangeMapInProgress = false;
   812 		}
   813 		else // MapChange_RoundEnd
   814 		{
   815 			SetNextMap(map);
   816 			g_ChangeMapAtRoundEnd = true;
   817 		}
   818 		
   819 		g_HasVoteStarted = false;
   820 		g_MapVoteCompleted = true;
   821 		
   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);
   824 	}	
   825 }
   826 
   827 public Handler_MapVoteFinished(Handle:menu,
   828 						   num_votes, 
   829 						   num_clients,
   830 						   const client_info[][2], 
   831 						   num_items,
   832 						   const item_info[][2])
   833 {
   834 	if (GetConVarBool(g_Cvar_RunOff) && num_items > 1)
   835 	{
   836 		new Float:winningvotes = float(item_info[0][VOTEINFO_ITEM_VOTES]);
   837 		new Float:required = num_votes * (GetConVarFloat(g_Cvar_RunOffPercent) / 100.0);
   838 		
   839 		if (winningvotes <= required)
   840 		{
   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);
   845 
   846 			decl String:map[PLATFORM_MAX_PATH];
   847 			decl String:info1[PLATFORM_MAX_PATH];
   848 			decl String:info2[PLATFORM_MAX_PATH];
   849 			
   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);
   854 			
   855 			new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
   856 			SetMenuExitButton(g_VoteMenu, false);
   857 			VoteMenuToAll(g_VoteMenu, voteDuration);
   858 			
   859 			/* Notify */
   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;
   862 			
   863 			
   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");
   866 					
   867 			return;
   868 		}
   869 	}
   870 	
   871 	Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info);
   872 }
   873 
   874 public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
   875 {
   876 	switch (action)
   877 	{
   878 		case MenuAction_End:
   879 		{
   880 			g_VoteMenu = INVALID_HANDLE;
   881 			CloseHandle(menu);
   882 		}
   883 		
   884 		case MenuAction_Display:
   885 		{
   886 	 		decl String:buffer[255];
   887 			Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
   888 
   889 			new Handle:panel = Handle:param2;
   890 			SetPanelTitle(panel, buffer);
   891 		}		
   892 		
   893 		case MenuAction_DisplayItem:
   894 		{
   895 			if (GetMenuItemCount(menu) - 1 == param2)
   896 			{
   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)
   900 				{
   901 					Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
   902 					return RedrawMenuItem(buffer);
   903 				}
   904 				else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
   905 				{
   906 					Format(buffer, sizeof(buffer), "%T", "Dont Change", param1);
   907 					return RedrawMenuItem(buffer);					
   908 				}
   909 			}
   910 		}		
   911 	
   912 		case MenuAction_VoteCancel:
   913 		{
   914 			// If we receive 0 votes, pick at random.
   915 			if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode))
   916 			{
   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));
   921 				
   922 				while (strcmp(map, VOTE_EXTEND, false) == 0)
   923 				{
   924 					item = GetRandomInt(0, count - 1);
   925 					GetMenuItem(menu, item, map, sizeof(map));
   926 				}
   927 				
   928 				SetNextMap(map);			
   929 			}
   930 			else
   931 			{
   932 				// We were actually cancelled. I guess we do nothing.
   933 			}
   934 			
   935 			g_HasVoteStarted = false;
   936 			g_MapVoteCompleted = true;
   937 		}
   938 	}
   939 	
   940 	return 0;
   941 }
   942 
   943 public Action:Timer_ChangeMap(Handle:hTimer, Handle:dp)
   944 {
   945 	g_ChangeMapInProgress = false;
   946 	
   947 	new String:map[PLATFORM_MAX_PATH];
   948 	
   949 	if (dp == INVALID_HANDLE)
   950 	{
   951 		if (!GetNextMap(map, sizeof(map)))
   952 		{
   953 			//No passed map and no set nextmap. fail!
   954 			return Plugin_Stop;	
   955 		}
   956 	}
   957 	else
   958 	{
   959 		ResetPack(dp);
   960 		ReadPackString(dp, map, sizeof(map));		
   961 	}
   962 	
   963 	ForceChangeLevel(map, "Map Vote");
   964 	
   965 	return Plugin_Stop;
   966 }
   967 
   968 bool:RemoveStringFromArray(Handle:array, String:str[])
   969 {
   970 	new index = FindStringInArray(array, str);
   971 	if (index != -1)
   972 	{
   973 		RemoveFromArray(array, index);
   974 		return true;
   975 	}
   976 	
   977 	return false;
   978 }
   979 
   980 CreateNextVote()
   981 {
   982 	assert(g_NextMapList)
   983 	ClearArray(g_NextMapList);
   984 	
   985 	decl String:map[PLATFORM_MAX_PATH];
   986 	new Handle:tempMaps  = CloneArray(g_MapList);
   987 	
   988 	GetCurrentMap(map, sizeof(map));
   989 	RemoveStringFromArray(tempMaps, map);
   990 	
   991 	if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps))
   992 	{
   993 		for (new i = 0; i < GetArraySize(g_OldMapList); i++)
   994 		{
   995 			GetArrayString(g_OldMapList, i, map, sizeof(map));
   996 			RemoveStringFromArray(tempMaps, map);
   997 		}	
   998 	}
   999 
  1000 	new limit = (GetConVarInt(g_Cvar_IncludeMaps) < GetArraySize(tempMaps) ? GetConVarInt(g_Cvar_IncludeMaps) : GetArraySize(tempMaps));
  1001 	for (new i = 0; i < limit; i++)
  1002 	{
  1003 		new b = GetRandomInt(0, GetArraySize(tempMaps) - 1);
  1004 		GetArrayString(tempMaps, b, map, sizeof(map));		
  1005 		PushArrayString(g_NextMapList, map);
  1006 		RemoveFromArray(tempMaps, b);
  1007 	}
  1008 	
  1009 	CloseHandle(tempMaps);
  1010 }
  1011 
  1012 bool:CanVoteStart()
  1013 {
  1014 	if (g_WaitingForVote || g_HasVoteStarted)
  1015 	{
  1016 		return false;	
  1017 	}
  1018 	
  1019 	return true;
  1020 }
  1021 
  1022 NominateResult:InternalNominateMap(String:map[], bool:force, owner)
  1023 {
  1024 	if (!IsMapValid(map))
  1025 	{
  1026 		return Nominate_InvalidMap;
  1027 	}
  1028 	
  1029 	new index;
  1030 
  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))
  1033 	{
  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);
  1039 		Call_Finish();
  1040 		
  1041 		SetArrayString(g_NominateList, index, map);
  1042 		return Nominate_Replaced;
  1043 	}
  1044 	
  1045 	/* Too many nominated maps. */
  1046 	if (g_NominateCount >= GetConVarInt(g_Cvar_IncludeMaps) && !force)
  1047 	{
  1048 		return Nominate_VoteFull;
  1049 	}
  1050 	
  1051 	/* Map already in the vote */
  1052 	if (FindStringInArray(g_NominateList, map) != -1)
  1053 	{
  1054 		return Nominate_AlreadyInVote;	
  1055 	}
  1056 	
  1057 	
  1058 	PushArrayString(g_NominateList, map);
  1059 	PushArrayCell(g_NominateOwners, owner);
  1060 	g_NominateCount++;
  1061 	
  1062 	while (GetArraySize(g_NominateList) > GetConVarInt(g_Cvar_IncludeMaps))
  1063 	{
  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));
  1069 		Call_Finish();
  1070 		
  1071 		RemoveFromArray(g_NominateList, 0);
  1072 		RemoveFromArray(g_NominateOwners, 0);
  1073 	}
  1074 	
  1075 	return Nominate_Added;
  1076 }
  1077 
  1078 /* Add natives to allow nominate and initiate vote to be call */
  1079 
  1080 /* native  bool:NominateMap(const String:map[], bool:force, &NominateError:error); */
  1081 public Native_NominateMap(Handle:plugin, numParams)
  1082 {
  1083 	new len;
  1084 	GetNativeStringLength(1, len);
  1085 	
  1086 	if (len <= 0)
  1087 	{
  1088 	  return false;
  1089 	}
  1090 	
  1091 	new String:map[len+1];
  1092 	GetNativeString(1, map, len+1);
  1093 	
  1094 	return _:InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3));
  1095 }
  1096 
  1097 bool:InternalRemoveNominationByMap(String:map[])
  1098 {	
  1099 	for (new i = 0; i < GetArraySize(g_NominateList); i++)
  1100 	{
  1101 		new String:oldmap[PLATFORM_MAX_PATH];
  1102 		GetArrayString(g_NominateList, i, oldmap, sizeof(oldmap));
  1103 
  1104 		if(strcmp(map, oldmap, false) == 0)
  1105 		{
  1106 			Call_StartForward(g_NominationsResetForward);
  1107 			Call_PushString(oldmap);
  1108 			Call_PushCell(GetArrayCell(g_NominateOwners, i));
  1109 			Call_Finish();
  1110 
  1111 			RemoveFromArray(g_NominateList, i);
  1112 			RemoveFromArray(g_NominateOwners, i);
  1113 			g_NominateCount--;
  1114 
  1115 			return true;
  1116 		}
  1117 	}
  1118 	
  1119 	return false;
  1120 }
  1121 
  1122 /* native  bool:RemoveNominationByMap(const String:map[]); */
  1123 public Native_RemoveNominationByMap(Handle:plugin, numParams)
  1124 {
  1125 	new len;
  1126 	GetNativeStringLength(1, len);
  1127 	
  1128 	if (len <= 0)
  1129 	{
  1130 	  return false;
  1131 	}
  1132 	
  1133 	new String:map[len+1];
  1134 	GetNativeString(1, map, len+1);
  1135 	
  1136 	return _:InternalRemoveNominationByMap(map);
  1137 }
  1138 
  1139 bool:InternalRemoveNominationByOwner(owner)
  1140 {	
  1141 	new index;
  1142 
  1143 	if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1))
  1144 	{
  1145 		new String:oldmap[PLATFORM_MAX_PATH];
  1146 		GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
  1147 
  1148 		Call_StartForward(g_NominationsResetForward);
  1149 		Call_PushString(oldmap);
  1150 		Call_PushCell(owner);
  1151 		Call_Finish();
  1152 
  1153 		RemoveFromArray(g_NominateList, index);
  1154 		RemoveFromArray(g_NominateOwners, index);
  1155 		g_NominateCount--;
  1156 
  1157 		return true;
  1158 	}
  1159 	
  1160 	return false;
  1161 }
  1162 
  1163 /* native  bool:RemoveNominationByOwner(owner); */
  1164 public Native_RemoveNominationByOwner(Handle:plugin, numParams)
  1165 {	
  1166 	return _:InternalRemoveNominationByOwner(GetNativeCell(1));
  1167 }
  1168 
  1169 /* native InitiateMapChooserVote(); */
  1170 public Native_InitiateVote(Handle:plugin, numParams)
  1171 {
  1172 	new MapChange:when = MapChange:GetNativeCell(1);
  1173 	new Handle:inputarray = Handle:GetNativeCell(2);
  1174 	
  1175 	LogAction(-1, -1, "Starting map vote because outside request");
  1176 	InitiateVote(when, inputarray);
  1177 }
  1178 
  1179 public Native_CanVoteStart(Handle:plugin, numParams)
  1180 {
  1181 	return CanVoteStart();	
  1182 }
  1183 
  1184 public Native_CheckVoteDone(Handle:plugin, numParams)
  1185 {
  1186 	return g_MapVoteCompleted;
  1187 }
  1188 
  1189 public Native_EndOfMapVoteEnabled(Handle:plugin, numParams)
  1190 {
  1191 	return GetConVarBool(g_Cvar_EndOfMapVote);
  1192 }
  1193 
  1194 public Native_GetExcludeMapList(Handle:plugin, numParams)
  1195 {
  1196 	new Handle:array = Handle:GetNativeCell(1);
  1197 	
  1198 	if (array == INVALID_HANDLE)
  1199 	{
  1200 		return;	
  1201 	}
  1202 	new size = GetArraySize(g_OldMapList);
  1203 	decl String:map[PLATFORM_MAX_PATH];
  1204 	
  1205 	for (new i=0; i<size; i++)
  1206 	{
  1207 		GetArrayString(g_OldMapList, i, map, sizeof(map));
  1208 		PushArrayString(array, map);	
  1209 	}
  1210 	
  1211 	return;
  1212 }
  1213 
  1214 public Native_GetNominatedMapList(Handle:plugin, numParams)
  1215 {
  1216 	new Handle:maparray = Handle:GetNativeCell(1);
  1217 	new Handle:ownerarray = Handle:GetNativeCell(2);
  1218 	
  1219 	if (maparray == INVALID_HANDLE)
  1220 		return;
  1221 
  1222 	decl String:map[PLATFORM_MAX_PATH];
  1223 
  1224 	for (new i = 0; i < GetArraySize(g_NominateList); i++)
  1225 	{
  1226 		GetArrayString(g_NominateList, i, map, sizeof(map));
  1227 		PushArrayString(maparray, map);
  1228 
  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)
  1231 		{
  1232 			new index = GetArrayCell(g_NominateOwners, i);
  1233 			PushArrayCell(ownerarray, index);
  1234 		}
  1235 	}
  1236 
  1237 	return;
  1238 }