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