plugins/mapchooser.sp
author Peace-Maker <januk@freenet.de>
Mon Aug 12 00:58:43 2013 +0100 (2013-08-12)
changeset 3999 02b80e8be3e3
parent 3863 8645c29348dc
child 4101 ca16c2bde907
permissions -rw-r--r--
Fixed sm_cancelvote not resetting rtv state. (bug 5808, r=asherkin)
     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 
    65 /* Plugin ConVars */
    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;
    81 
    82 new Handle:g_VoteTimer = INVALID_HANDLE;
    83 new Handle:g_RetryTimer = INVALID_HANDLE;
    84 
    85 /* Data Handles */
    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;
    92 
    93 new g_Extends;
    94 new g_TotalRounds;
    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;
   101 
   102 new g_NominateCount = 0;
   103 new MapChange:g_ChangeTime;
   104 
   105 new Handle:g_NominationsResetForward = INVALID_HANDLE;
   106 new Handle:g_MapVoteStartedForward = INVALID_HANDLE;
   107 
   108 /* Upper bound of how many team there could be */
   109 #define MAXTEAMS 10
   110 new g_winCount[MAXTEAMS];
   111 
   112 #define VOTE_EXTEND "##extend##"
   113 #define VOTE_DONTCHANGE "##dontchange##"
   114 
   115 public OnPluginStart()
   116 {
   117 	LoadTranslations("mapchooser.phrases");
   118 	LoadTranslations("common.phrases");
   119 	
   120 	new arraySize = ByteCountToCells(PLATFORM_MAX_PATH);
   121 	g_MapList = CreateArray(arraySize);
   122 	g_NominateList = CreateArray(arraySize);
   123 	g_NominateOwners = CreateArray(1);
   124 	g_OldMapList = CreateArray(arraySize);
   125 	g_NextMapList = CreateArray(arraySize);
   126 	
   127 	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 
   129 	g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0);
   130 	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);
   131 	g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0);
   132 	g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
   133 	g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0);
   134 	g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0);	
   135 	g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
   136 	g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0);
   137 	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);
   138 	g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0);
   139 	g_Cvar_DontChange = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0);
   140 	g_Cvar_VoteDuration = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0);
   141 	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);
   142 	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 	
   144 	RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now.");
   145 	RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap <map>");
   146 
   147 	g_Cvar_Winlimit = FindConVar("mp_winlimit");
   148 	g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
   149 	g_Cvar_Fraglimit = FindConVar("mp_fraglimit");
   150 	g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime");
   151 	
   152 	if (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE)
   153 	{
   154 		decl String:folder[64];
   155 		GetGameFolderName(folder, sizeof(folder));
   156 
   157 		if (strcmp(folder, "tf") == 0)
   158 		{
   159 			HookEvent("teamplay_win_panel", Event_TeamPlayWinPanel);
   160 			HookEvent("teamplay_restart_round", Event_TFRestartRound);
   161 			HookEvent("arena_win_panel", Event_TeamPlayWinPanel);
   162 		}
   163 		else if (strcmp(folder, "nucleardawn") == 0)
   164 		{
   165 			HookEvent("round_win", Event_RoundEnd);
   166 		}
   167 		else
   168 		{
   169 			HookEvent("round_end", Event_RoundEnd);
   170 		}
   171 	}
   172 	
   173 	if (g_Cvar_Fraglimit != INVALID_HANDLE)
   174 	{
   175 		HookEvent("player_death", Event_PlayerDeath);		
   176 	}
   177 	
   178 	AutoExecConfig(true, "mapchooser");
   179 	
   180 	//Change the mp_bonusroundtime max so that we have time to display the vote
   181 	//If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime
   182 	if (g_Cvar_Bonusroundtime != INVALID_HANDLE)
   183 	{
   184 		SetConVarBounds(g_Cvar_Bonusroundtime, ConVarBound_Upper, true, 30.0);		
   185 	}
   186 	
   187 	g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell);
   188 	g_MapVoteStartedForward = CreateGlobalForward("OnMapVoteStarted", ET_Ignore);
   189 }
   190 
   191 public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
   192 {
   193 	RegPluginLibrary("mapchooser");	
   194 	
   195 	CreateNative("NominateMap", Native_NominateMap);
   196 	CreateNative("RemoveNominationByMap", Native_RemoveNominationByMap);
   197 	CreateNative("RemoveNominationByOwner", Native_RemoveNominationByOwner);
   198 	CreateNative("InitiateMapChooserVote", Native_InitiateVote);
   199 	CreateNative("CanMapChooserStartVote", Native_CanVoteStart);
   200 	CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone);
   201 	CreateNative("GetExcludeMapList", Native_GetExcludeMapList);
   202 	CreateNative("GetNominatedMapList", Native_GetNominatedMapList);
   203 	CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled);
   204 
   205 	return APLRes_Success;
   206 }
   207 
   208 public OnConfigsExecuted()
   209 {
   210 	if (ReadMapList(g_MapList,
   211 					 g_mapFileSerial, 
   212 					 "mapchooser",
   213 					 MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER)
   214 		!= INVALID_HANDLE)
   215 		
   216 	{
   217 		if (g_mapFileSerial == -1)
   218 		{
   219 			LogError("Unable to create a valid map list.");
   220 		}
   221 	}
   222 	
   223 	CreateNextVote();
   224 	SetupTimeleftTimer();
   225 	
   226 	g_TotalRounds = 0;
   227 	
   228 	g_Extends = 0;
   229 	
   230 	g_MapVoteCompleted = false;
   231 	
   232 	g_NominateCount = 0;
   233 	ClearArray(g_NominateList);
   234 	ClearArray(g_NominateOwners);
   235 	
   236 	for (new i=0; i<MAXTEAMS; i++)
   237 	{
   238 		g_winCount[i] = 0;	
   239 	}
   240 	
   241 
   242 	/* Check if mapchooser will attempt to start mapvote during bonus round time - TF2 Only */
   243 	if ((g_Cvar_Bonusroundtime != INVALID_HANDLE) && !GetConVarInt(g_Cvar_StartRounds))
   244 	{
   245 		if (GetConVarFloat(g_Cvar_Bonusroundtime) <= GetConVarFloat(g_Cvar_VoteDuration))
   246 		{
   247 			LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete");
   248 		}
   249 	}
   250 }
   251 
   252 public OnMapEnd()
   253 {
   254 	g_HasVoteStarted = false;
   255 	g_WaitingForVote = false;
   256 	g_ChangeMapAtRoundEnd = false;
   257 	g_ChangeMapInProgress = false;
   258 	
   259 	g_VoteTimer = INVALID_HANDLE;
   260 	g_RetryTimer = INVALID_HANDLE;
   261 	
   262 	decl String:map[PLATFORM_MAX_PATH];
   263 	GetCurrentMap(map, sizeof(map));
   264 	PushArrayString(g_OldMapList, map);
   265 				
   266 	if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
   267 	{
   268 		RemoveFromArray(g_OldMapList, 0);
   269 	}	
   270 }
   271 
   272 public OnClientDisconnect(client)
   273 {
   274 	new index = FindValueInArray(g_NominateOwners, client);
   275 	
   276 	if (index == -1)
   277 	{
   278 		return;
   279 	}
   280 	
   281 	new String:oldmap[PLATFORM_MAX_PATH];
   282 	GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
   283 	Call_StartForward(g_NominationsResetForward);
   284 	Call_PushString(oldmap);
   285 	Call_PushCell(GetArrayCell(g_NominateOwners, index));
   286 	Call_Finish();
   287 	
   288 	RemoveFromArray(g_NominateOwners, index);
   289 	RemoveFromArray(g_NominateList, index);
   290 	g_NominateCount--;
   291 }
   292 
   293 public Action:Command_SetNextmap(client, args)
   294 {
   295 	if (args < 1)
   296 	{
   297 		ReplyToCommand(client, "[SM] Usage: sm_setnextmap <map>");
   298 		return Plugin_Handled;
   299 	}
   300 
   301 	decl String:map[PLATFORM_MAX_PATH];
   302 	GetCmdArg(1, map, sizeof(map));
   303 
   304 	if (!IsMapValid(map))
   305 	{
   306 		ReplyToCommand(client, "[SM] %t", "Map was not found", map);
   307 		return Plugin_Handled;
   308 	}
   309 
   310 	ShowActivity(client, "%t", "Changed Next Map", map);
   311 	LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map);
   312 
   313 	SetNextMap(map);
   314 	g_MapVoteCompleted = true;
   315 
   316 	return Plugin_Handled;
   317 }
   318 
   319 public OnMapTimeLeftChanged()
   320 {
   321 	if (GetArraySize(g_MapList))
   322 	{
   323 		SetupTimeleftTimer();
   324 	}
   325 }
   326 
   327 SetupTimeleftTimer()
   328 {
   329 	new time;
   330 	if (GetMapTimeLeft(time) && time > 0)
   331 	{
   332 		new startTime = GetConVarInt(g_Cvar_StartTime) * 60;
   333 		if (time - startTime < 0 && GetConVarBool(g_Cvar_EndOfMapVote) && !g_MapVoteCompleted && !g_HasVoteStarted)
   334 		{
   335 			InitiateVote(MapChange_MapEnd, INVALID_HANDLE);		
   336 		}
   337 		else
   338 		{
   339 			if (g_VoteTimer != INVALID_HANDLE)
   340 			{
   341 				KillTimer(g_VoteTimer);
   342 				g_VoteTimer = INVALID_HANDLE;
   343 			}	
   344 			
   345 			//g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
   346 			new Handle:data;
   347 			g_VoteTimer = CreateDataTimer(float(time - startTime), Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
   348 			WritePackCell(data, _:MapChange_MapEnd);
   349 			WritePackCell(data, _:INVALID_HANDLE);
   350 			ResetPack(data);
   351 		}		
   352 	}
   353 }
   354 
   355 public Action:Timer_StartMapVote(Handle:timer, Handle:data)
   356 {
   357 	if (timer == g_RetryTimer)
   358 	{
   359 		g_WaitingForVote = false;
   360 		g_RetryTimer = INVALID_HANDLE;
   361 	}
   362 	else
   363 	{
   364 		g_VoteTimer = INVALID_HANDLE;
   365 	}
   366 	
   367 	if (!GetArraySize(g_MapList) || !GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted)
   368 	{
   369 		return Plugin_Stop;
   370 	}
   371 	
   372 	new MapChange:mapChange = MapChange:ReadPackCell(data);
   373 	new Handle:hndl = Handle:ReadPackCell(data);
   374 
   375 	InitiateVote(mapChange, hndl);
   376 
   377 	return Plugin_Stop;
   378 }
   379 
   380 public Event_TFRestartRound(Handle:event, const String:name[], bool:dontBroadcast)
   381 {
   382 	/* Game got restarted - reset our round count tracking */
   383 	g_TotalRounds = 0;	
   384 }
   385 
   386 public Event_TeamPlayWinPanel(Handle:event, const String:name[], bool:dontBroadcast)
   387 {
   388 	if (g_ChangeMapAtRoundEnd)
   389 	{
   390 		g_ChangeMapAtRoundEnd = false;
   391 		CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
   392 		g_ChangeMapInProgress = true;
   393 	}
   394 	
   395 	new bluescore = GetEventInt(event, "blue_score");
   396 	new redscore = GetEventInt(event, "red_score");
   397 		
   398 	if(GetEventInt(event, "round_complete") == 1 || StrEqual(name, "arena_win_panel"))
   399 	{
   400 		g_TotalRounds++;
   401 		
   402 		if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted || !GetConVarBool(g_Cvar_EndOfMapVote))
   403 		{
   404 			return;
   405 		}
   406 		
   407 		CheckMaxRounds(g_TotalRounds);
   408 		
   409 		switch(GetEventInt(event, "winning_team"))
   410 		{
   411 			case 3:
   412 			{
   413 				CheckWinLimit(bluescore);
   414 			}
   415 			case 2:
   416 			{
   417 				CheckWinLimit(redscore);				
   418 			}			
   419 			//We need to do nothing on winning_team == 0 this indicates stalemate.
   420 			default:
   421 			{
   422 				return;
   423 			}			
   424 		}
   425 	}
   426 }
   427 /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
   428 public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
   429 {
   430 	if (g_ChangeMapAtRoundEnd)
   431 	{
   432 		g_ChangeMapAtRoundEnd = false;
   433 		CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
   434 		g_ChangeMapInProgress = true;
   435 	}
   436 	
   437 	new winner;
   438 	if (strcmp(name, "round_win") == 0)
   439 	{
   440 		// Nuclear Dawn
   441 		winner = GetEventInt(event, "team");
   442 	}
   443 	else
   444 	{
   445 		winner = GetEventInt(event, "winner");
   446 	}
   447 	
   448 	if (winner == 0 || winner == 1 || !GetConVarBool(g_Cvar_EndOfMapVote))
   449 	{
   450 		return;
   451 	}
   452 	
   453 	if (winner >= MAXTEAMS)
   454 	{
   455 		SetFailState("Mod exceed maximum team count - Please file a bug report.");	
   456 	}
   457 
   458 	g_TotalRounds++;
   459 	
   460 	g_winCount[winner]++;
   461 	
   462 	if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted)
   463 	{
   464 		return;
   465 	}
   466 	
   467 	CheckWinLimit(g_winCount[winner]);
   468 	CheckMaxRounds(g_TotalRounds);
   469 }
   470 
   471 public CheckWinLimit(winner_score)
   472 {	
   473 	if (g_Cvar_Winlimit != INVALID_HANDLE)
   474 	{
   475 		new winlimit = GetConVarInt(g_Cvar_Winlimit);
   476 		if (winlimit)
   477 		{			
   478 			if (winner_score >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
   479 			{
   480 				InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   481 			}
   482 		}
   483 	}
   484 }
   485 
   486 public CheckMaxRounds(roundcount)
   487 {		
   488 	if (g_Cvar_Maxrounds != INVALID_HANDLE)
   489 	{
   490 		new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   491 		if (maxrounds)
   492 		{
   493 			if (roundcount >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
   494 			{
   495 				InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   496 			}			
   497 		}
   498 	}
   499 }
   500 
   501 public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
   502 {
   503 	if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted)
   504 	{
   505 		return;
   506 	}
   507 	
   508 	if (!GetConVarInt(g_Cvar_Fraglimit) || !GetConVarBool(g_Cvar_EndOfMapVote))
   509 	{
   510 		return;
   511 	}
   512 
   513 	if (g_MapVoteCompleted)
   514 	{
   515 		return;
   516 	}
   517 
   518 	new fragger = GetClientOfUserId(GetEventInt(event, "attacker"));
   519 
   520 	if (!fragger)
   521 	{
   522 		return;
   523 	}
   524 
   525 	if (GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
   526 	{
   527 		InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   528 	}
   529 }
   530 
   531 public Action:Command_Mapvote(client, args)
   532 {
   533 	InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   534 
   535 	return Plugin_Handled;	
   536 }
   537 
   538 /**
   539  * Starts a new map vote
   540  *
   541  * @param when			When the resulting map change should occur.
   542  * @param inputlist		Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used.
   543  * @param noSpecials	Block special vote options like extend/nochange (upgrade this to bitflags instead?)
   544  */
   545 InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE)
   546 {
   547 	g_WaitingForVote = true;
   548 	
   549 	if (IsVoteInProgress())
   550 	{
   551 		// Can't start a vote, try again in 5 seconds.
   552 		//g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
   553 		
   554 		new Handle:data;
   555 		g_RetryTimer = CreateDataTimer(5.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
   556 		WritePackCell(data, _:when);
   557 		WritePackCell(data, _:inputlist);
   558 		ResetPack(data);
   559 		return;
   560 	}
   561 	
   562 	/* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */
   563 	if (g_MapVoteCompleted && g_ChangeMapInProgress)
   564 	{
   565 		return;
   566 	}
   567 	
   568 	g_ChangeTime = when;
   569 	
   570 	g_WaitingForVote = false;
   571 		
   572 	g_HasVoteStarted = true;
   573 	g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
   574 	SetMenuTitle(g_VoteMenu, "Vote Nextmap");
   575 	SetVoteResultCallback(g_VoteMenu, Handler_MapVoteFinished);
   576 
   577 	/* Call OnMapVoteStarted() Forward */
   578 	Call_StartForward(g_MapVoteStartedForward);
   579 	Call_Finish();
   580 	
   581 	/**
   582 	 * TODO: Make a proper decision on when to clear the nominations list.
   583 	 * Currently it clears when used, and stays if an external list is provided.
   584 	 * Is this the right thing to do? External lists will probably come from places
   585 	 * like sm_mapvote from the adminmenu in the future.
   586 	 */
   587 	 
   588 	decl String:map[PLATFORM_MAX_PATH];
   589 	
   590 	/* No input given - User our internal nominations and maplist */
   591 	if (inputlist == INVALID_HANDLE)
   592 	{
   593 		new nominateCount = GetArraySize(g_NominateList);
   594 		new voteSize = GetConVarInt(g_Cvar_IncludeMaps);
   595 		
   596 		/* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */
   597 		new nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount;
   598 		
   599 		
   600 		for (new i=0; i<nominationsToAdd; i++)
   601 		{
   602 			GetArrayString(g_NominateList, i, map, sizeof(map));
   603 			AddMenuItem(g_VoteMenu, map, map);
   604 			RemoveStringFromArray(g_NextMapList, map);
   605 			
   606 			/* Notify Nominations that this map is now free */
   607 			Call_StartForward(g_NominationsResetForward);
   608 			Call_PushString(map);
   609 			Call_PushCell(GetArrayCell(g_NominateOwners, i));
   610 			Call_Finish();
   611 		}
   612 		
   613 		/* Clear out the rest of the nominations array */
   614 		for (new i=nominationsToAdd; i<nominateCount; i++)
   615 		{
   616 			GetArrayString(g_NominateList, i, map, sizeof(map));
   617 			/* These maps shouldn't be excluded from the vote as they weren't really nominated at all */
   618 			
   619 			/* Notify Nominations that this map is now free */
   620 			Call_StartForward(g_NominationsResetForward);
   621 			Call_PushString(map);
   622 			Call_PushCell(GetArrayCell(g_NominateOwners, i));
   623 			Call_Finish();			
   624 		}
   625 		
   626 		/* There should currently be 'nominationsToAdd' unique maps in the vote */
   627 		
   628 		new i = nominationsToAdd;
   629 		new count = 0;
   630 		new availableMaps = GetArraySize(g_NextMapList);
   631 		
   632 		while (i < voteSize)
   633 		{
   634 			GetArrayString(g_NextMapList, count, map, sizeof(map));
   635 			count++;
   636 			
   637 			/* Insert the map and increment our count */
   638 			AddMenuItem(g_VoteMenu, map, map);
   639 			i++;
   640 			
   641 			if (count >= availableMaps)
   642 			{
   643 				//Run out of maps, this will have to do.
   644 				break;	
   645 			}
   646 		}
   647 		
   648 		/* Wipe out our nominations list - Nominations have already been informed of this */
   649 		ClearArray(g_NominateOwners);
   650 		ClearArray(g_NominateList);
   651 	}
   652 	else //We were given a list of maps to start the vote with
   653 	{
   654 		new size = GetArraySize(inputlist);
   655 		
   656 		for (new i=0; i<size; i++)
   657 		{
   658 			GetArrayString(inputlist, i, map, sizeof(map));
   659 			
   660 			if (IsMapValid(map))
   661 			{
   662 				AddMenuItem(g_VoteMenu, map, map);
   663 			}	
   664 		}
   665 	}
   666 	
   667 	/* Do we add any special items? */
   668 	if ((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange))
   669 	{
   670 		AddMenuItem(g_VoteMenu, VOTE_DONTCHANGE, "Don't Change");
   671 	}
   672 	else if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend))
   673 	{
   674 		AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
   675 	}
   676 	
   677 	new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
   678 
   679 	SetMenuExitButton(g_VoteMenu, false);
   680 	VoteMenuToAll(g_VoteMenu, voteDuration);
   681 
   682 	LogAction(-1, -1, "Voting for next map has started.");
   683 	PrintToChatAll("[SM] %t", "Nextmap Voting Started");
   684 }
   685 
   686 public Handler_VoteFinishedGeneric(Handle:menu,
   687 						   num_votes, 
   688 						   num_clients,
   689 						   const client_info[][2], 
   690 						   num_items,
   691 						   const item_info[][2])
   692 {
   693 	decl String:map[PLATFORM_MAX_PATH];
   694 	GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map));
   695 
   696 	if (strcmp(map, VOTE_EXTEND, false) == 0)
   697 	{
   698 		g_Extends++;
   699 		
   700 		new time;
   701 		if (GetMapTimeLimit(time))
   702 		{
   703 			if (time > 0)
   704 			{
   705 				ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);						
   706 			}
   707 		}
   708 		
   709 		if (g_Cvar_Winlimit != INVALID_HANDLE)
   710 		{
   711 			new winlimit = GetConVarInt(g_Cvar_Winlimit);
   712 			if (winlimit)
   713 			{
   714 				SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
   715 			}					
   716 		}
   717 		
   718 		if (g_Cvar_Maxrounds != INVALID_HANDLE)
   719 		{
   720 			new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   721 			if (maxrounds)
   722 			{
   723 				SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
   724 			}
   725 		}
   726 		
   727 		if (g_Cvar_Fraglimit != INVALID_HANDLE)
   728 		{
   729 			new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
   730 			if (fraglimit)
   731 			{
   732 				SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));						
   733 			}
   734 		}
   735 
   736 		PrintToChatAll("[SM] %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
   737 		LogAction(-1, -1, "Voting for next map has finished. The current map has been extended.");
   738 		
   739 		// We extended, so we'll have to vote again.
   740 		g_HasVoteStarted = false;
   741 		CreateNextVote();
   742 		SetupTimeleftTimer();
   743 		
   744 	}
   745 	else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
   746 	{
   747 		PrintToChatAll("[SM] %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
   748 		LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner");
   749 		
   750 		g_HasVoteStarted = false;
   751 		CreateNextVote();
   752 		SetupTimeleftTimer();
   753 	}
   754 	else
   755 	{
   756 		if (g_ChangeTime == MapChange_MapEnd)
   757 		{
   758 			SetNextMap(map);
   759 		}
   760 		else if (g_ChangeTime == MapChange_Instant)
   761 		{
   762 			new Handle:data;
   763 			CreateDataTimer(2.0, Timer_ChangeMap, data);
   764 			WritePackString(data, map);
   765 			g_ChangeMapInProgress = false;
   766 		}
   767 		else // MapChange_RoundEnd
   768 		{
   769 			SetNextMap(map);
   770 			g_ChangeMapAtRoundEnd = true;
   771 		}
   772 		
   773 		g_HasVoteStarted = false;
   774 		g_MapVoteCompleted = true;
   775 		
   776 		PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
   777 		LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map);
   778 	}	
   779 }
   780 
   781 public Handler_MapVoteFinished(Handle:menu,
   782 						   num_votes, 
   783 						   num_clients,
   784 						   const client_info[][2], 
   785 						   num_items,
   786 						   const item_info[][2])
   787 {
   788 	if (GetConVarBool(g_Cvar_RunOff) && num_items > 1)
   789 	{
   790 		new Float:winningvotes = float(item_info[0][VOTEINFO_ITEM_VOTES]);
   791 		new Float:required = num_votes * (GetConVarFloat(g_Cvar_RunOffPercent) / 100.0);
   792 		
   793 		if (winningvotes <= required)
   794 		{
   795 			/* Insufficient Winning margin - Lets do a runoff */
   796 			g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
   797 			SetMenuTitle(g_VoteMenu, "Runoff Vote Nextmap");
   798 			SetVoteResultCallback(g_VoteMenu, Handler_VoteFinishedGeneric);
   799 
   800 			decl String:map[PLATFORM_MAX_PATH];
   801 			decl String:info1[PLATFORM_MAX_PATH];
   802 			decl String:info2[PLATFORM_MAX_PATH];
   803 			
   804 			GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info1, sizeof(info1));
   805 			AddMenuItem(g_VoteMenu, map, info1);
   806 			GetMenuItem(menu, item_info[1][VOTEINFO_ITEM_INDEX], map, sizeof(map), _, info2, sizeof(info2));
   807 			AddMenuItem(g_VoteMenu, map, info2);
   808 			
   809 			new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
   810 			SetMenuExitButton(g_VoteMenu, false);
   811 			VoteMenuToAll(g_VoteMenu, voteDuration);
   812 			
   813 			/* Notify */
   814 			new Float:map1percent = float(item_info[0][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100;
   815 			new Float:map2percent = float(item_info[1][VOTEINFO_ITEM_VOTES])/ float(num_votes) * 100;
   816 			
   817 			
   818 			PrintToChatAll("[SM] %t", "Starting Runoff", GetConVarFloat(g_Cvar_RunOffPercent), info1, map1percent, info2, map2percent);
   819 			LogMessage("Voting for next map was indecisive, beginning runoff vote");
   820 					
   821 			return;
   822 		}
   823 	}
   824 	
   825 	Handler_VoteFinishedGeneric(menu, num_votes, num_clients, client_info, num_items, item_info);
   826 }
   827 
   828 public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
   829 {
   830 	switch (action)
   831 	{
   832 		case MenuAction_End:
   833 		{
   834 			g_VoteMenu = INVALID_HANDLE;
   835 			CloseHandle(menu);
   836 		}
   837 		
   838 		case MenuAction_Display:
   839 		{
   840 	 		decl String:buffer[255];
   841 			Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
   842 
   843 			new Handle:panel = Handle:param2;
   844 			SetPanelTitle(panel, buffer);
   845 		}		
   846 		
   847 		case MenuAction_DisplayItem:
   848 		{
   849 			if (GetMenuItemCount(menu) - 1 == param2)
   850 			{
   851 				decl String:map[PLATFORM_MAX_PATH], String:buffer[255];
   852 				GetMenuItem(menu, param2, map, sizeof(map));
   853 				if (strcmp(map, VOTE_EXTEND, false) == 0)
   854 				{
   855 					Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
   856 					return RedrawMenuItem(buffer);
   857 				}
   858 				else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
   859 				{
   860 					Format(buffer, sizeof(buffer), "%T", "Dont Change", param1);
   861 					return RedrawMenuItem(buffer);					
   862 				}
   863 			}
   864 		}		
   865 	
   866 		case MenuAction_VoteCancel:
   867 		{
   868 			// If we receive 0 votes, pick at random.
   869 			if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode))
   870 			{
   871 				new count = GetMenuItemCount(menu);
   872 				new item = GetRandomInt(0, count - 1);
   873 				decl String:map[PLATFORM_MAX_PATH];
   874 				GetMenuItem(menu, item, map, sizeof(map));
   875 				
   876 				while (strcmp(map, VOTE_EXTEND, false) == 0)
   877 				{
   878 					item = GetRandomInt(0, count - 1);
   879 					GetMenuItem(menu, item, map, sizeof(map));
   880 				}
   881 				
   882 				SetNextMap(map);
   883 				g_MapVoteCompleted = true;
   884 			}
   885 			else
   886 			{
   887 				// We were actually cancelled. I guess we do nothing.
   888 			}
   889 			
   890 			g_HasVoteStarted = false;
   891 		}
   892 	}
   893 	
   894 	return 0;
   895 }
   896 
   897 public Action:Timer_ChangeMap(Handle:hTimer, Handle:dp)
   898 {
   899 	g_ChangeMapInProgress = false;
   900 	
   901 	new String:map[PLATFORM_MAX_PATH];
   902 	
   903 	if (dp == INVALID_HANDLE)
   904 	{
   905 		if (!GetNextMap(map, sizeof(map)))
   906 		{
   907 			//No passed map and no set nextmap. fail!
   908 			return Plugin_Stop;	
   909 		}
   910 	}
   911 	else
   912 	{
   913 		ResetPack(dp);
   914 		ReadPackString(dp, map, sizeof(map));		
   915 	}
   916 	
   917 	ForceChangeLevel(map, "Map Vote");
   918 	
   919 	return Plugin_Stop;
   920 }
   921 
   922 bool:RemoveStringFromArray(Handle:array, String:str[])
   923 {
   924 	new index = FindStringInArray(array, str);
   925 	if (index != -1)
   926 	{
   927 		RemoveFromArray(array, index);
   928 		return true;
   929 	}
   930 	
   931 	return false;
   932 }
   933 
   934 CreateNextVote()
   935 {
   936 	assert(g_NextMapList)
   937 	ClearArray(g_NextMapList);
   938 	
   939 	decl String:map[PLATFORM_MAX_PATH];
   940 	new Handle:tempMaps  = CloneArray(g_MapList);
   941 	
   942 	GetCurrentMap(map, sizeof(map));
   943 	RemoveStringFromArray(tempMaps, map);
   944 	
   945 	if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps))
   946 	{
   947 		for (new i = 0; i < GetArraySize(g_OldMapList); i++)
   948 		{
   949 			GetArrayString(g_OldMapList, i, map, sizeof(map));
   950 			RemoveStringFromArray(tempMaps, map);
   951 		}	
   952 	}
   953 
   954 	new limit = (GetConVarInt(g_Cvar_IncludeMaps) < GetArraySize(tempMaps) ? GetConVarInt(g_Cvar_IncludeMaps) : GetArraySize(tempMaps));
   955 	for (new i = 0; i < limit; i++)
   956 	{
   957 		new b = GetRandomInt(0, GetArraySize(tempMaps) - 1);
   958 		GetArrayString(tempMaps, b, map, sizeof(map));		
   959 		PushArrayString(g_NextMapList, map);
   960 		RemoveFromArray(tempMaps, b);
   961 	}
   962 	
   963 	CloseHandle(tempMaps);
   964 }
   965 
   966 bool:CanVoteStart()
   967 {
   968 	if (g_WaitingForVote || g_HasVoteStarted)
   969 	{
   970 		return false;	
   971 	}
   972 	
   973 	return true;
   974 }
   975 
   976 NominateResult:InternalNominateMap(String:map[], bool:force, owner)
   977 {
   978 	if (!IsMapValid(map))
   979 	{
   980 		return Nominate_InvalidMap;
   981 	}
   982 	
   983 	new index;
   984 
   985 	/* Look to replace an existing nomination by this client - Nominations made with owner = 0 aren't replaced */
   986 	if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1))
   987 	{
   988 		new String:oldmap[PLATFORM_MAX_PATH];
   989 		GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
   990 		Call_StartForward(g_NominationsResetForward);
   991 		Call_PushString(oldmap);
   992 		Call_PushCell(owner);
   993 		Call_Finish();
   994 		
   995 		SetArrayString(g_NominateList, index, map);
   996 		return Nominate_Replaced;
   997 	}
   998 	
   999 	/* Too many nominated maps. */
  1000 	if (g_NominateCount >= GetConVarInt(g_Cvar_IncludeMaps) && !force)
  1001 	{
  1002 		return Nominate_VoteFull;
  1003 	}
  1004 	
  1005 	/* Map already in the vote */
  1006 	if (FindStringInArray(g_NominateList, map) != -1)
  1007 	{
  1008 		return Nominate_AlreadyInVote;	
  1009 	}
  1010 	
  1011 	
  1012 	PushArrayString(g_NominateList, map);
  1013 	PushArrayCell(g_NominateOwners, owner);
  1014 	g_NominateCount++;
  1015 	
  1016 	while (GetArraySize(g_NominateList) > GetConVarInt(g_Cvar_IncludeMaps))
  1017 	{
  1018 		new String:oldmap[PLATFORM_MAX_PATH];
  1019 		GetArrayString(g_NominateList, 0, oldmap, sizeof(oldmap));
  1020 		Call_StartForward(g_NominationsResetForward);
  1021 		Call_PushString(oldmap);
  1022 		Call_PushCell(GetArrayCell(g_NominateOwners, 0));
  1023 		Call_Finish();
  1024 		
  1025 		RemoveFromArray(g_NominateList, 0);
  1026 		RemoveFromArray(g_NominateOwners, 0);
  1027 	}
  1028 	
  1029 	return Nominate_Added;
  1030 }
  1031 
  1032 /* Add natives to allow nominate and initiate vote to be call */
  1033 
  1034 /* native  bool:NominateMap(const String:map[], bool:force, &NominateError:error); */
  1035 public Native_NominateMap(Handle:plugin, numParams)
  1036 {
  1037 	new len;
  1038 	GetNativeStringLength(1, len);
  1039 	
  1040 	if (len <= 0)
  1041 	{
  1042 	  return false;
  1043 	}
  1044 	
  1045 	new String:map[len+1];
  1046 	GetNativeString(1, map, len+1);
  1047 	
  1048 	return _:InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3));
  1049 }
  1050 
  1051 bool:InternalRemoveNominationByMap(String:map[])
  1052 {	
  1053 	for (new i = 0; i < GetArraySize(g_NominateList); i++)
  1054 	{
  1055 		new String:oldmap[PLATFORM_MAX_PATH];
  1056 		GetArrayString(g_NominateList, i, oldmap, sizeof(oldmap));
  1057 
  1058 		if(strcmp(map, oldmap, false) == 0)
  1059 		{
  1060 			Call_StartForward(g_NominationsResetForward);
  1061 			Call_PushString(oldmap);
  1062 			Call_PushCell(GetArrayCell(g_NominateOwners, i));
  1063 			Call_Finish();
  1064 
  1065 			RemoveFromArray(g_NominateList, i);
  1066 			RemoveFromArray(g_NominateOwners, i);
  1067 			g_NominateCount--;
  1068 
  1069 			return true;
  1070 		}
  1071 	}
  1072 	
  1073 	return false;
  1074 }
  1075 
  1076 /* native  bool:RemoveNominationByMap(const String:map[]); */
  1077 public Native_RemoveNominationByMap(Handle:plugin, numParams)
  1078 {
  1079 	new len;
  1080 	GetNativeStringLength(1, len);
  1081 	
  1082 	if (len <= 0)
  1083 	{
  1084 	  return false;
  1085 	}
  1086 	
  1087 	new String:map[len+1];
  1088 	GetNativeString(1, map, len+1);
  1089 	
  1090 	return _:InternalRemoveNominationByMap(map);
  1091 }
  1092 
  1093 bool:InternalRemoveNominationByOwner(owner)
  1094 {	
  1095 	new index;
  1096 
  1097 	if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1))
  1098 	{
  1099 		new String:oldmap[PLATFORM_MAX_PATH];
  1100 		GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
  1101 
  1102 		Call_StartForward(g_NominationsResetForward);
  1103 		Call_PushString(oldmap);
  1104 		Call_PushCell(owner);
  1105 		Call_Finish();
  1106 
  1107 		RemoveFromArray(g_NominateList, index);
  1108 		RemoveFromArray(g_NominateOwners, index);
  1109 		g_NominateCount--;
  1110 
  1111 		return true;
  1112 	}
  1113 	
  1114 	return false;
  1115 }
  1116 
  1117 /* native  bool:RemoveNominationByOwner(owner); */
  1118 public Native_RemoveNominationByOwner(Handle:plugin, numParams)
  1119 {	
  1120 	return _:InternalRemoveNominationByOwner(GetNativeCell(1));
  1121 }
  1122 
  1123 /* native InitiateMapChooserVote(); */
  1124 public Native_InitiateVote(Handle:plugin, numParams)
  1125 {
  1126 	new MapChange:when = MapChange:GetNativeCell(1);
  1127 	new Handle:inputarray = Handle:GetNativeCell(2);
  1128 	
  1129 	LogAction(-1, -1, "Starting map vote because outside request");
  1130 	InitiateVote(when, inputarray);
  1131 }
  1132 
  1133 public Native_CanVoteStart(Handle:plugin, numParams)
  1134 {
  1135 	return CanVoteStart();	
  1136 }
  1137 
  1138 public Native_CheckVoteDone(Handle:plugin, numParams)
  1139 {
  1140 	return g_MapVoteCompleted;
  1141 }
  1142 
  1143 public Native_EndOfMapVoteEnabled(Handle:plugin, numParams)
  1144 {
  1145 	return GetConVarBool(g_Cvar_EndOfMapVote);
  1146 }
  1147 
  1148 public Native_GetExcludeMapList(Handle:plugin, numParams)
  1149 {
  1150 	new Handle:array = Handle:GetNativeCell(1);
  1151 	
  1152 	if (array == INVALID_HANDLE)
  1153 	{
  1154 		return;	
  1155 	}
  1156 	new size = GetArraySize(g_OldMapList);
  1157 	decl String:map[PLATFORM_MAX_PATH];
  1158 	
  1159 	for (new i=0; i<size; i++)
  1160 	{
  1161 		GetArrayString(g_OldMapList, i, map, sizeof(map));
  1162 		PushArrayString(array, map);	
  1163 	}
  1164 	
  1165 	return;
  1166 }
  1167 
  1168 public Native_GetNominatedMapList(Handle:plugin, numParams)
  1169 {
  1170 	new Handle:maparray = Handle:GetNativeCell(1);
  1171 	new Handle:ownerarray = Handle:GetNativeCell(2);
  1172 	
  1173 	if (maparray == INVALID_HANDLE)
  1174 		return;
  1175 
  1176 	decl String:map[PLATFORM_MAX_PATH];
  1177 
  1178 	for (new i = 0; i < GetArraySize(g_NominateList); i++)
  1179 	{
  1180 		GetArrayString(g_NominateList, i, map, sizeof(map));
  1181 		PushArrayString(maparray, map);
  1182 
  1183 		// If the optional parameter for an owner list was passed, then we need to fill that out as well
  1184 		if(ownerarray != INVALID_HANDLE)
  1185 		{
  1186 			new index = GetArrayCell(g_NominateOwners, i);
  1187 			PushArrayCell(ownerarray, index);
  1188 		}
  1189 	}
  1190 
  1191 	return;
  1192 }