plugins/mapchooser.sp
author Matt Woodrow <pred@alliedmods.net>
Fri Jul 24 12:43:13 2009 +1200 (2009-07-24)
changeset 2747 791eb96e3a20
parent 2676 e470e7a8734c
child 3150 a7a63f60830b
permissions -rw-r--r--
Please GCC?
     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 #pragma semicolon 1
    36 #include <sourcemod>
    37 #include <mapchooser>
    38 #include <nextmap>
    39 
    40 public Plugin:myinfo =
    41 {
    42 	name = "MapChooser",
    43 	author = "AlliedModders LLC",
    44 	description = "Automated Map Voting",
    45 	version = SOURCEMOD_VERSION,
    46 	url = "http://www.sourcemod.net/"
    47 };
    48 
    49 /* Valve ConVars */
    50 new Handle:g_Cvar_Winlimit = INVALID_HANDLE;
    51 new Handle:g_Cvar_Maxrounds = INVALID_HANDLE;
    52 new Handle:g_Cvar_Fraglimit = INVALID_HANDLE;
    53 new Handle:g_Cvar_Bonusroundtime = INVALID_HANDLE;
    54 
    55 /* Plugin ConVars */
    56 new Handle:g_Cvar_StartTime = INVALID_HANDLE;
    57 new Handle:g_Cvar_StartRounds = INVALID_HANDLE;
    58 new Handle:g_Cvar_StartFrags = INVALID_HANDLE;
    59 new Handle:g_Cvar_ExtendTimeStep = INVALID_HANDLE;
    60 new Handle:g_Cvar_ExtendRoundStep = INVALID_HANDLE;
    61 new Handle:g_Cvar_ExtendFragStep = INVALID_HANDLE;
    62 new Handle:g_Cvar_ExcludeMaps = INVALID_HANDLE;
    63 new Handle:g_Cvar_IncludeMaps = INVALID_HANDLE;
    64 new Handle:g_Cvar_NoVoteMode = INVALID_HANDLE;
    65 new Handle:g_Cvar_Extend = INVALID_HANDLE;
    66 new Handle:g_Cvar_DontChange = INVALID_HANDLE;
    67 new Handle:g_Cvar_EndOfMapVote = INVALID_HANDLE;
    68 new Handle:g_Cvar_VoteDuration = INVALID_HANDLE;
    69 
    70 new Handle:g_VoteTimer = INVALID_HANDLE;
    71 new Handle:g_RetryTimer = INVALID_HANDLE;
    72 
    73 /* Data Handles */
    74 new Handle:g_MapList = INVALID_HANDLE;
    75 new Handle:g_NominateList = INVALID_HANDLE;
    76 new Handle:g_NominateOwners = INVALID_HANDLE;
    77 new Handle:g_OldMapList = INVALID_HANDLE;
    78 new Handle:g_NextMapList = INVALID_HANDLE;
    79 new Handle:g_VoteMenu = INVALID_HANDLE;
    80 
    81 new g_Extends;
    82 new g_TotalRounds;
    83 new bool:g_HasVoteStarted;
    84 new bool:g_WaitingForVote;
    85 new bool:g_MapVoteCompleted;
    86 new bool:g_ChangeMapAtRoundEnd;
    87 new bool:g_ChangeMapInProgress;
    88 new g_mapFileSerial = -1;
    89 
    90 new g_NominateCount = 0;
    91 new MapChange:g_ChangeTime;
    92 
    93 new Handle:g_NominationsResetForward = INVALID_HANDLE;
    94 
    95 /* Upper bound of how many team there could be */
    96 #define MAXTEAMS 10
    97 new g_winCount[MAXTEAMS];
    98 
    99 #define VOTE_EXTEND "##extend##"
   100 #define VOTE_DONTCHANGE "##dontchange##"
   101 
   102 public OnPluginStart()
   103 {
   104 	LoadTranslations("mapchooser.phrases");
   105 	LoadTranslations("common.phrases");
   106 	
   107 	new arraySize = ByteCountToCells(33);
   108 	g_MapList = CreateArray(arraySize);
   109 	g_NominateList = CreateArray(arraySize);
   110 	g_NominateOwners = CreateArray(1);
   111 	g_OldMapList = CreateArray(arraySize);
   112 	g_NextMapList = CreateArray(arraySize);
   113 	
   114 	g_Cvar_EndOfMapVote = CreateConVar("sm_mapvote_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0);
   115 
   116 	g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0);
   117 	g_Cvar_StartRounds = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining. Use 0 on TF2 to start vote during bonus round time", _, true, 0.0);
   118 	g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0);
   119 	g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
   120 	g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0);
   121 	g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0);	
   122 	g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
   123 	g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0);
   124 	g_Cvar_NoVoteMode = CreateConVar("sm_mapvote_novote", "1", "Specifies whether or not MapChooser should pick a map if no votes are received.", _, true, 0.0, true, 1.0);
   125 	g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0);
   126 	g_Cvar_DontChange = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0);
   127 	g_Cvar_VoteDuration = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0);
   128 	
   129 	RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now.");
   130 	RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap <map>");
   131 
   132 	g_Cvar_Winlimit = FindConVar("mp_winlimit");
   133 	g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
   134 	g_Cvar_Fraglimit = FindConVar("mp_fraglimit");
   135 	g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime");
   136 	
   137 	if (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE)
   138 	{
   139 		HookEvent("round_end", Event_RoundEnd);
   140 		HookEventEx("teamplay_win_panel", Event_TeamPlayWinPanel);
   141 		HookEventEx("teamplay_restart_round", Event_TFRestartRound);
   142 		HookEventEx("arena_win_panel", Event_TeamPlayWinPanel);
   143 	}
   144 	
   145 	if (g_Cvar_Fraglimit != INVALID_HANDLE)
   146 	{
   147 		HookEvent("player_death", Event_PlayerDeath);		
   148 	}
   149 	
   150 	AutoExecConfig(true, "mapchooser");
   151 	
   152 	//Change the mp_bonusroundtime max so that we have time to display the vote
   153 	//If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime
   154 	if (g_Cvar_Bonusroundtime != INVALID_HANDLE)
   155 	{
   156 		SetConVarBounds(g_Cvar_Bonusroundtime, ConVarBound_Upper, true, 30.0);		
   157 	}
   158 	
   159 	g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell);
   160 }
   161 
   162 public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
   163 {
   164 	RegPluginLibrary("mapchooser");	
   165 	
   166 	CreateNative("NominateMap", Native_NominateMap);
   167 	CreateNative("InitiateMapChooserVote", Native_InitiateVote);
   168 	CreateNative("CanMapChooserStartVote", Native_CanVoteStart);
   169 	CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone);
   170 	CreateNative("GetExcludeMapList", Native_GetExcludeMapList);
   171 	CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled);
   172 
   173 	return APLRes_Success;
   174 }
   175 
   176 public OnConfigsExecuted()
   177 {
   178 	if (ReadMapList(g_MapList,
   179 					 g_mapFileSerial, 
   180 					 "mapchooser",
   181 					 MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER)
   182 		!= INVALID_HANDLE)
   183 		
   184 	{
   185 		if (g_mapFileSerial == -1)
   186 		{
   187 			LogError("Unable to create a valid map list.");
   188 		}
   189 	}
   190 	
   191 	CreateNextVote();
   192 	SetupTimeleftTimer();
   193 	
   194 	g_TotalRounds = 0;
   195 	
   196 	g_Extends = 0;
   197 	
   198 	g_MapVoteCompleted = false;
   199 	
   200 	g_NominateCount = 0;
   201 	ClearArray(g_NominateList);
   202 	ClearArray(g_NominateOwners);
   203 	
   204 	for (new i=0; i<MAXTEAMS; i++)
   205 	{
   206 		g_winCount[i] = 0;	
   207 	}
   208 	
   209 
   210 	/* Check if mapchooser will attempt to start mapvote during bonus round time - TF2 Only */
   211 	if ((g_Cvar_Bonusroundtime != INVALID_HANDLE) && !GetConVarInt(g_Cvar_StartRounds))
   212 	{
   213 		if (GetConVarFloat(g_Cvar_Bonusroundtime) <= GetConVarFloat(g_Cvar_VoteDuration))
   214 		{
   215 			LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete");
   216 		}
   217 	}
   218 }
   219 
   220 public OnMapEnd()
   221 {
   222 	g_HasVoteStarted = false;
   223 	g_WaitingForVote = false;
   224 	g_ChangeMapAtRoundEnd = false;
   225 	g_ChangeMapInProgress = false;
   226 	
   227 	g_VoteTimer = INVALID_HANDLE;
   228 	g_RetryTimer = INVALID_HANDLE;
   229 	
   230 	decl String:map[32];
   231 	GetCurrentMap(map, sizeof(map));
   232 	PushArrayString(g_OldMapList, map);
   233 				
   234 	if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
   235 	{
   236 		RemoveFromArray(g_OldMapList, 0);
   237 	}	
   238 }
   239 
   240 public OnClientDisconnect(client)
   241 {
   242 	new index = FindValueInArray(g_NominateOwners, client);
   243 	
   244 	if (index == -1)
   245 	{
   246 		return;
   247 	}
   248 	
   249 	new String:oldmap[33];
   250 	GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
   251 	Call_StartForward(g_NominationsResetForward);
   252 	Call_PushString(oldmap);
   253 	Call_PushCell(GetArrayCell(g_NominateOwners, index));
   254 	Call_Finish();
   255 	
   256 	RemoveFromArray(g_NominateOwners, index);
   257 	RemoveFromArray(g_NominateList, index);
   258 	g_NominateCount--;
   259 }
   260 
   261 public Action:Command_SetNextmap(client, args)
   262 {
   263 	if (args < 1)
   264 	{
   265 		ReplyToCommand(client, "[SM] Usage: sm_setnextmap <map>");
   266 		return Plugin_Handled;
   267 	}
   268 
   269 	decl String:map[64];
   270 	GetCmdArg(1, map, sizeof(map));
   271 
   272 	if (!IsMapValid(map))
   273 	{
   274 		ReplyToCommand(client, "[SM] %t", "Map was not found", map);
   275 		return Plugin_Handled;
   276 	}
   277 
   278 	ShowActivity(client, "%t", "Changed Next Map", map);
   279 	LogMessage("\"%L\" changed nextmap to \"%s\"", client, map);
   280 
   281 	SetNextMap(map);
   282 	g_MapVoteCompleted = true;
   283 
   284 	return Plugin_Handled;
   285 }
   286 
   287 public OnMapTimeLeftChanged()
   288 {
   289 	if (GetArraySize(g_MapList))
   290 	{
   291 		SetupTimeleftTimer();
   292 	}
   293 }
   294 
   295 SetupTimeleftTimer()
   296 {
   297 	new time;
   298 	if (GetMapTimeLeft(time) && time > 0)
   299 	{
   300 		new startTime = GetConVarInt(g_Cvar_StartTime) * 60;
   301 		if (time - startTime < 0 && GetConVarBool(g_Cvar_EndOfMapVote) && !g_MapVoteCompleted && !g_HasVoteStarted)
   302 		{
   303 			InitiateVote(MapChange_MapEnd, INVALID_HANDLE);		
   304 		}
   305 		else
   306 		{
   307 			if (g_VoteTimer != INVALID_HANDLE)
   308 			{
   309 				KillTimer(g_VoteTimer);
   310 				g_VoteTimer = INVALID_HANDLE;
   311 			}	
   312 			
   313 			//g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
   314 			new Handle:data;
   315 			g_VoteTimer = CreateDataTimer(float(time - startTime), Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
   316 			WritePackCell(data, _:MapChange_MapEnd);
   317 			WritePackCell(data, _:INVALID_HANDLE);
   318 			ResetPack(data);
   319 		}		
   320 	}
   321 }
   322 
   323 public Action:Timer_StartMapVote(Handle:timer, Handle:data)
   324 {
   325 	if (timer == g_RetryTimer)
   326 	{
   327 		g_WaitingForVote = false;
   328 		g_RetryTimer = INVALID_HANDLE;
   329 	}
   330 	else
   331 	{
   332 		g_VoteTimer = INVALID_HANDLE;
   333 	}
   334 	
   335 	if (!GetArraySize(g_MapList) || !GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted)
   336 	{
   337 		return Plugin_Stop;
   338 	}
   339 	
   340 	new MapChange:mapChange = MapChange:ReadPackCell(data);
   341 	new Handle:hndl = Handle:ReadPackCell(data);
   342 
   343 	InitiateVote(mapChange, hndl);
   344 
   345 	return Plugin_Stop;
   346 }
   347 
   348 public Event_TFRestartRound(Handle:event, const String:name[], bool:dontBroadcast)
   349 {
   350 	/* Game got restarted - reset our round count tracking */
   351 	g_TotalRounds = 0;	
   352 }
   353 
   354 public Event_TeamPlayWinPanel(Handle:event, const String:name[], bool:dontBroadcast)
   355 {
   356 	if (g_ChangeMapAtRoundEnd)
   357 	{
   358 		g_ChangeMapAtRoundEnd = false;
   359 		CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
   360 		g_ChangeMapInProgress = true;
   361 	}
   362 	
   363 	new bluescore = GetEventInt(event, "blue_score");
   364 	new redscore = GetEventInt(event, "red_score");
   365 		
   366 	if(GetEventInt(event, "round_complete") == 1 || StrEqual(name, "arena_win_panel"))
   367 	{
   368 		g_TotalRounds++;
   369 		
   370 		if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted || !GetConVarBool(g_Cvar_EndOfMapVote))
   371 		{
   372 			return;
   373 		}
   374 		
   375 		CheckMaxRounds(g_TotalRounds);
   376 		
   377 		switch(GetEventInt(event, "winning_team"))
   378 		{
   379 			case 3:
   380 			{
   381 				CheckWinLimit(bluescore);
   382 			}
   383 			case 2:
   384 			{
   385 				CheckWinLimit(redscore);				
   386 			}			
   387 			//We need to do nothing on winning_team == 0 this indicates stalemate.
   388 			default:
   389 			{
   390 				return;
   391 			}			
   392 		}
   393 	}
   394 }
   395 /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
   396 public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
   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 winner = GetEventInt(event, "winner");
   406 	
   407 	if (winner == 0 || winner == 1 || !GetConVarBool(g_Cvar_EndOfMapVote))
   408 	{
   409 		return;
   410 	}
   411 	
   412 	if (winner >= MAXTEAMS)
   413 	{
   414 		SetFailState("Mod exceed maximum team count - Please file a bug report.");	
   415 	}
   416 
   417 	g_TotalRounds++;
   418 	
   419 	g_winCount[winner]++;
   420 	
   421 	if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted)
   422 	{
   423 		return;
   424 	}
   425 	
   426 	CheckWinLimit(g_winCount[winner]);
   427 	CheckMaxRounds(g_TotalRounds);
   428 }
   429 
   430 public CheckWinLimit(winner_score)
   431 {	
   432 	if (g_Cvar_Winlimit != INVALID_HANDLE)
   433 	{
   434 		new winlimit = GetConVarInt(g_Cvar_Winlimit);
   435 		if (winlimit)
   436 		{			
   437 			if (winner_score >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
   438 			{
   439 				InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   440 			}
   441 		}
   442 	}
   443 }
   444 
   445 public CheckMaxRounds(roundcount)
   446 {		
   447 	if (g_Cvar_Maxrounds != INVALID_HANDLE)
   448 	{
   449 		new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   450 		if (maxrounds)
   451 		{
   452 			if (roundcount >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
   453 			{
   454 				InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   455 			}			
   456 		}
   457 	}
   458 }
   459 
   460 public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
   461 {
   462 	if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted)
   463 	{
   464 		return;
   465 	}
   466 	
   467 	if (!GetConVarInt(g_Cvar_Fraglimit) || !GetConVarBool(g_Cvar_EndOfMapVote))
   468 	{
   469 		return;
   470 	}
   471 
   472 	if (g_MapVoteCompleted)
   473 	{
   474 		return;
   475 	}
   476 
   477 	new fragger = GetClientOfUserId(GetEventInt(event, "attacker"));
   478 
   479 	if (!fragger)
   480 	{
   481 		return;
   482 	}
   483 
   484 	if (GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
   485 	{
   486 		InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   487 	}
   488 }
   489 
   490 public Action:Command_Mapvote(client, args)
   491 {
   492 	InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   493 
   494 	return Plugin_Handled;	
   495 }
   496 
   497 /**
   498  * Starts a new map vote
   499  *
   500  * @param when			When the resulting map change should occur.
   501  * @param inputlist		Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used.
   502  */
   503 InitiateVote(MapChange:when, Handle:inputlist=INVALID_HANDLE)
   504 {
   505 	g_WaitingForVote = true;
   506 	
   507 	if (IsVoteInProgress())
   508 	{
   509 		// Can't start a vote, try again in 5 seconds.
   510 		//g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE);
   511 		
   512 		new Handle:data;
   513 		g_RetryTimer = CreateDataTimer(5.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE);
   514 		WritePackCell(data, _:when);
   515 		WritePackCell(data, _:inputlist);
   516 		ResetPack(data);
   517 		return;
   518 	}
   519 	
   520 	/* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */
   521 	if (g_MapVoteCompleted && g_ChangeMapInProgress)
   522 	{
   523 		return;
   524 	}
   525 	
   526 	g_ChangeTime = when;
   527 	
   528 	g_WaitingForVote = false;
   529 		
   530 	g_HasVoteStarted = true;
   531 	g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
   532 	SetMenuTitle(g_VoteMenu, "Vote Nextmap");
   533 	SetVoteResultCallback(g_VoteMenu, Handler_MapVoteFinished);
   534 	
   535 	/**
   536 	 * TODO: Make a proper decision on when to clear the nominations list.
   537 	 * Currently it clears when used, and stays if an external list is provided.
   538 	 * Is this the right thing to do? External lists will probably come from places
   539 	 * like sm_mapvote from the adminmenu in the future.
   540 	 */
   541 	 
   542 	decl String:map[32];
   543 	
   544 	/* No input given - User our internal nominations and maplist */
   545 	if (inputlist == INVALID_HANDLE)
   546 	{
   547 		new nominateCount = GetArraySize(g_NominateList);
   548 		new voteSize = GetConVarInt(g_Cvar_IncludeMaps);
   549 		
   550 		/* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */
   551 		new nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount;
   552 		
   553 		
   554 		for (new i=0; i<nominationsToAdd; i++)
   555 		{
   556 			GetArrayString(g_NominateList, i, map, sizeof(map));
   557 			AddMenuItem(g_VoteMenu, map, map);
   558 			
   559 			/* Notify Nominations that this map is now free */
   560 			Call_StartForward(g_NominationsResetForward);
   561 			Call_PushString(map);
   562 			Call_PushCell(GetArrayCell(g_NominateOwners, i));
   563 			Call_Finish();
   564 		}
   565 		
   566 		/* Clear out the rest of the nominations array */
   567 		for (new i=nominationsToAdd; i<nominateCount; i++)
   568 		{
   569 			GetArrayString(g_NominateList, i, map, sizeof(map));
   570 			
   571 			/* Notify Nominations that this map is now free */
   572 			Call_StartForward(g_NominationsResetForward);
   573 			Call_PushString(map);
   574 			Call_PushCell(GetArrayCell(g_NominateOwners, i));
   575 			Call_Finish();			
   576 		}
   577 		
   578 		/* There should currently be 'nominationsToAdd' unique maps in the vote */
   579 		
   580 		new i = nominationsToAdd;
   581 		new count = 0;
   582 		new availableMaps = GetArraySize(g_NextMapList);
   583 		
   584 		while (i < voteSize)
   585 		{
   586 			GetArrayString(g_NextMapList, count, map, sizeof(map));
   587 			count++;
   588 			
   589 			//Check if this map is in the nominate list (and thus already in the vote) */
   590 			if (FindStringInArray(g_NominateList, map) == -1)
   591 			{
   592 				/* Insert the map and increment our count */
   593 				AddMenuItem(g_VoteMenu, map, map);
   594 				i++;
   595 			}
   596 			
   597 			if (count >= availableMaps)
   598 			{
   599 				//Run out of maps, this will have to do.
   600 				break;	
   601 			}
   602 		}
   603 		
   604 		/* Wipe out our nominations list - Nominations have already been informed of this */
   605 		ClearArray(g_NominateOwners);
   606 		ClearArray(g_NominateList);
   607 	}
   608 	else //We were given a list of maps to start the vote with
   609 	{
   610 		new size = GetArraySize(inputlist);
   611 		
   612 		for (new i=0; i<size; i++)
   613 		{
   614 			GetArrayString(inputlist, i, map, sizeof(map));
   615 			
   616 			if (IsMapValid(map))
   617 			{
   618 				AddMenuItem(g_VoteMenu, map, map);
   619 			}	
   620 		}
   621 	}
   622 	
   623 	/* Do we add any special items? */
   624 	if ((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange))
   625 	{
   626 		AddMenuItem(g_VoteMenu, VOTE_DONTCHANGE, "Don't Change");
   627 	}
   628 	else if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend))
   629 	{
   630 		AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
   631 	}
   632 	
   633 	new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
   634 
   635 	SetMenuExitButton(g_VoteMenu, false);
   636 	VoteMenuToAll(g_VoteMenu, voteDuration);
   637 
   638 	LogMessage("Voting for next map has started.");
   639 	PrintToChatAll("[SM] %t", "Nextmap Voting Started");
   640 }
   641 
   642 public Handler_MapVoteFinished(Handle:menu,
   643 						   num_votes, 
   644 						   num_clients,
   645 						   const client_info[][2], 
   646 						   num_items,
   647 						   const item_info[][2])
   648 {
   649 	if (num_votes == 0)
   650 	{
   651 		LogError("No Votes recorded yet Advanced callback fired - Tell pRED* to fix this");
   652 		return;	
   653 	}
   654 	
   655 	decl String:map[32];
   656 	GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map));
   657 
   658 	if (strcmp(map, VOTE_EXTEND, false) == 0)
   659 	{
   660 		g_Extends++;
   661 		
   662 		new time;
   663 		if (GetMapTimeLimit(time))
   664 		{
   665 			if (time > 0)
   666 			{
   667 				ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);						
   668 			}
   669 		}
   670 		
   671 		if (g_Cvar_Winlimit != INVALID_HANDLE)
   672 		{
   673 			new winlimit = GetConVarInt(g_Cvar_Winlimit);
   674 			if (winlimit)
   675 			{
   676 				SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
   677 			}					
   678 		}
   679 		
   680 		if (g_Cvar_Maxrounds != INVALID_HANDLE)
   681 		{
   682 			new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   683 			if (maxrounds)
   684 			{
   685 				SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
   686 			}
   687 		}
   688 		
   689 		if (g_Cvar_Fraglimit != INVALID_HANDLE)
   690 		{
   691 			new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
   692 			if (fraglimit)
   693 			{
   694 				SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));						
   695 			}
   696 		}
   697 
   698 		PrintToChatAll("[SM] %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
   699 		LogMessage("Voting for next map has finished. The current map has been extended.");
   700 		
   701 		// We extended, so we'll have to vote again.
   702 		g_HasVoteStarted = false;
   703 		CreateNextVote();
   704 		SetupTimeleftTimer();
   705 		
   706 	}
   707 	else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
   708 	{
   709 		PrintToChatAll("[SM] %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
   710 		LogMessage("Voting for next map has finished. 'No Change' was the winner");
   711 		
   712 		g_HasVoteStarted = false;
   713 		CreateNextVote();
   714 		SetupTimeleftTimer();
   715 	}
   716 	else
   717 	{
   718 		if (g_ChangeTime == MapChange_MapEnd)
   719 		{
   720 			SetNextMap(map);
   721 		}
   722 		else if (g_ChangeTime == MapChange_Instant)
   723 		{
   724 			new Handle:data;
   725 			CreateDataTimer(2.0, Timer_ChangeMap, data);
   726 			WritePackString(data, map);
   727 			g_ChangeMapInProgress = false;
   728 		}
   729 		else // MapChange_RoundEnd
   730 		{
   731 			SetNextMap(map);
   732 			g_ChangeMapAtRoundEnd = true;
   733 		}
   734 		
   735 		g_HasVoteStarted = false;
   736 		g_MapVoteCompleted = true;
   737 		
   738 		PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
   739 		LogMessage("Voting for next map has finished. Nextmap: %s.", map);
   740 	}	
   741 }
   742 
   743 public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
   744 {
   745 	switch (action)
   746 	{
   747 		case MenuAction_End:
   748 		{
   749 			g_VoteMenu = INVALID_HANDLE;
   750 			CloseHandle(menu);
   751 		}
   752 		
   753 		case MenuAction_Display:
   754 		{
   755 	 		decl String:buffer[255];
   756 			Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
   757 
   758 			new Handle:panel = Handle:param2;
   759 			SetPanelTitle(panel, buffer);
   760 		}		
   761 		
   762 		case MenuAction_DisplayItem:
   763 		{
   764 			if (GetMenuItemCount(menu) - 1 == param2)
   765 			{
   766 				decl String:map[64], String:buffer[255];
   767 				GetMenuItem(menu, param2, map, sizeof(map));
   768 				if (strcmp(map, VOTE_EXTEND, false) == 0)
   769 				{
   770 					Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
   771 					return RedrawMenuItem(buffer);
   772 				}
   773 				else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
   774 				{
   775 					Format(buffer, sizeof(buffer), "%T", "Dont Change", param1);
   776 					return RedrawMenuItem(buffer);					
   777 				}
   778 			}
   779 		}		
   780 	
   781 		case MenuAction_VoteCancel:
   782 		{
   783 			// If we receive 0 votes, pick at random.
   784 			if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode))
   785 			{
   786 				new count = GetMenuItemCount(menu);
   787 				new item = GetRandomInt(0, count - 1);
   788 				decl String:map[32];
   789 				GetMenuItem(menu, item, map, sizeof(map));
   790 				
   791 				while (strcmp(map, VOTE_EXTEND, false) == 0)
   792 				{
   793 					item = GetRandomInt(0, count - 1);
   794 					GetMenuItem(menu, item, map, sizeof(map));
   795 				}
   796 				
   797 				SetNextMap(map);			
   798 			}
   799 			else
   800 			{
   801 				// We were actually cancelled. I guess we do nothing.
   802 			}
   803 			
   804 			g_HasVoteStarted = false;
   805 			g_MapVoteCompleted = true;
   806 		}
   807 	}
   808 	
   809 	return 0;
   810 }
   811 
   812 public Action:Timer_ChangeMap(Handle:hTimer, Handle:dp)
   813 {
   814 	g_ChangeMapInProgress = false;
   815 	
   816 	new String:map[65];
   817 	
   818 	if (dp == INVALID_HANDLE)
   819 	{
   820 		if (!GetNextMap(map, sizeof(map)))
   821 		{
   822 			//No passed map and no set nextmap. fail!
   823 			return Plugin_Stop;	
   824 		}
   825 	}
   826 	else
   827 	{
   828 		ResetPack(dp);
   829 		ReadPackString(dp, map, sizeof(map));		
   830 	}
   831 	
   832 	ForceChangeLevel(map, "Map Vote");
   833 	
   834 	return Plugin_Stop;
   835 }
   836 
   837 CreateNextVote()
   838 {
   839 	if(g_NextMapList != INVALID_HANDLE)
   840 	{
   841 		ClearArray(g_NextMapList);
   842 	}
   843 	
   844 	decl String:map[32];
   845 	new index, Handle:tempMaps  = CloneArray(g_MapList);
   846 	
   847 	GetCurrentMap(map, sizeof(map));
   848 	index = FindStringInArray(tempMaps, map);
   849 	if (index != -1)
   850 	{
   851 		RemoveFromArray(tempMaps, index);
   852 	}	
   853 	
   854 	if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps))
   855 	{
   856 		for (new i = 0; i < GetArraySize(g_OldMapList); i++)
   857 		{
   858 			GetArrayString(g_OldMapList, i, map, sizeof(map));
   859 			index = FindStringInArray(tempMaps, map);
   860 			if (index != -1)
   861 			{
   862 				RemoveFromArray(tempMaps, index);
   863 			}
   864 		}	
   865 	}
   866 
   867 	new limit = (GetConVarInt(g_Cvar_IncludeMaps) < GetArraySize(tempMaps) ? GetConVarInt(g_Cvar_IncludeMaps) : GetArraySize(tempMaps));
   868 	for (new i = 0; i < limit; i++)
   869 	{
   870 		new b = GetRandomInt(0, GetArraySize(tempMaps) - 1);
   871 		GetArrayString(tempMaps, b, map, sizeof(map));		
   872 		PushArrayString(g_NextMapList, map);
   873 		RemoveFromArray(tempMaps, b);
   874 	}
   875 	
   876 	CloseHandle(tempMaps);
   877 }
   878 
   879 bool:CanVoteStart()
   880 {
   881 	if (g_WaitingForVote || g_HasVoteStarted)
   882 	{
   883 		return false;	
   884 	}
   885 	
   886 	return true;
   887 }
   888 
   889 NominateResult:InternalNominateMap(String:map[], bool:force, owner)
   890 {
   891 	if (!IsMapValid(map))
   892 	{
   893 		return Nominate_InvalidMap;
   894 	}
   895 	
   896 	new index;
   897 
   898 	/* Look to replace an existing nomination by this client - Nominations made with owner = 0 aren't replaced */
   899 	if (owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1))
   900 	{
   901 		new String:oldmap[33];
   902 		GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
   903 		Call_StartForward(g_NominationsResetForward);
   904 		Call_PushString(oldmap);
   905 		Call_PushCell(owner);
   906 		Call_Finish();
   907 		
   908 		SetArrayString(g_NominateList, index, map);
   909 		return Nominate_Replaced;
   910 	}
   911 	
   912 	/* Too many nominated maps. */
   913 	if (g_NominateCount >= GetConVarInt(g_Cvar_IncludeMaps) && !force)
   914 	{
   915 		return Nominate_VoteFull;
   916 	}
   917 	
   918 	/* Map already in the vote */
   919 	if (FindStringInArray(g_NominateList, map) != -1)
   920 	{
   921 		return Nominate_AlreadyInVote;	
   922 	}
   923 	
   924 	
   925 	PushArrayString(g_NominateList, map);
   926 	PushArrayCell(g_NominateOwners, owner);
   927 	g_NominateCount++;
   928 	
   929 	while (GetArraySize(g_NominateList) > GetConVarInt(g_Cvar_IncludeMaps))
   930 	{
   931 		new String:oldmap[33];
   932 		GetArrayString(g_NominateList, 0, oldmap, sizeof(oldmap));
   933 		Call_StartForward(g_NominationsResetForward);
   934 		Call_PushString(oldmap);
   935 		Call_PushCell(GetArrayCell(g_NominateOwners, 0));
   936 		Call_Finish();
   937 		
   938 		RemoveFromArray(g_NominateList, 0);
   939 		RemoveFromArray(g_NominateOwners, 0);
   940 	}
   941 	
   942 	return Nominate_Added;
   943 }
   944 
   945 /* Add natives to allow nominate and initiate vote to be call */
   946 
   947 /* native  bool:NominateMap(const String:map[], bool:force, &NominateError:error); */
   948 public Native_NominateMap(Handle:plugin, numParams)
   949 {
   950 	new len;
   951 	GetNativeStringLength(1, len);
   952 	
   953 	if (len <= 0)
   954 	{
   955 	  return false;
   956 	}
   957 	
   958 	new String:map[len+1];
   959 	GetNativeString(1, map, len+1);
   960 	
   961 	return _:InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3));
   962 }
   963 
   964 /* native InitiateMapChooserVote(); */
   965 public Native_InitiateVote(Handle:plugin, numParams)
   966 {
   967 	new MapChange:when = MapChange:GetNativeCell(1);
   968 	new Handle:inputarray = Handle:GetNativeCell(2);
   969 	
   970 	LogMessage("Starting map vote because outside request");
   971 	InitiateVote(when, inputarray);
   972 }
   973 
   974 public Native_CanVoteStart(Handle:plugin, numParams)
   975 {
   976 	return CanVoteStart();	
   977 }
   978 
   979 public Native_CheckVoteDone(Handle:plugin, numParams)
   980 {
   981 	return g_MapVoteCompleted;
   982 }
   983 
   984 public Native_EndOfMapVoteEnabled(Handle:plugin, numParams)
   985 {
   986 	return GetConVarBool(g_Cvar_EndOfMapVote);
   987 }
   988 
   989 public Native_GetExcludeMapList(Handle:plugin, numParams)
   990 {
   991 	new Handle:array = Handle:GetNativeCell(1);
   992 	
   993 	if (array == INVALID_HANDLE)
   994 	{
   995 		return;	
   996 	}
   997 	
   998 	
   999 	new size = GetArraySize(g_OldMapList);
  1000 	decl String:map[33];
  1001 	
  1002 	for (new i=0; i<size; i++)
  1003 	{
  1004 		GetArrayString(g_OldMapList, i, map, sizeof(map));
  1005 		PushArrayString(array, map);	
  1006 	}
  1007 	
  1008 	return;
  1009 }