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