plugins/mapchooser.sp
changeset 2133 cad0a5ea6742
parent 2032 444fe6d52e25
child 2156 186f2f16564c
     1.1 --- a/plugins/mapchooser.sp	Mon Jun 30 23:18:26 2008 +0000
     1.2 +++ b/plugins/mapchooser.sp	Tue Jul 01 01:18:52 2008 +0000
     1.3 @@ -34,6 +34,11 @@
     1.4  
     1.5  #pragma semicolon 1
     1.6  #include <sourcemod>
     1.7 +#include <mapchooser>
     1.8 +#include <nextmap>
     1.9 +#undef REQUIRE_EXTENSIONS
    1.10 +#include <tf2>
    1.11 +#define REQUIRE_EXTENSIONS
    1.12  
    1.13  public Plugin:myinfo =
    1.14  {
    1.15 @@ -44,12 +49,13 @@
    1.16  	url = "http://www.sourcemod.net/"
    1.17  };
    1.18  
    1.19 +/* Valve ConVars */
    1.20  new Handle:g_Cvar_Winlimit = INVALID_HANDLE;
    1.21  new Handle:g_Cvar_Maxrounds = INVALID_HANDLE;
    1.22  new Handle:g_Cvar_Fraglimit = INVALID_HANDLE;
    1.23 +new Handle:g_Cvar_Bonusroundtime = INVALID_HANDLE;
    1.24  
    1.25 -new Handle:g_Cvar_Nextmap = INVALID_HANDLE;
    1.26 -
    1.27 +/* Plugin ConVars */
    1.28  new Handle:g_Cvar_StartTime = INVALID_HANDLE;
    1.29  new Handle:g_Cvar_StartRounds = INVALID_HANDLE;
    1.30  new Handle:g_Cvar_StartFrags = INVALID_HANDLE;
    1.31 @@ -60,22 +66,39 @@
    1.32  new Handle:g_Cvar_IncludeMaps = INVALID_HANDLE;
    1.33  new Handle:g_Cvar_NoVoteMode = INVALID_HANDLE;
    1.34  new Handle:g_Cvar_Extend = INVALID_HANDLE;
    1.35 -
    1.36 +new Handle:g_Cvar_DontChange = INVALID_HANDLE;
    1.37 +new Handle:g_Cvar_EndOfMapVote = INVALID_HANDLE;
    1.38 +new Handle:g_Cvar_VoteDuration = INVALID_HANDLE;
    1.39  new Handle:g_VoteTimer = INVALID_HANDLE;
    1.40  new Handle:g_RetryTimer = INVALID_HANDLE;
    1.41  
    1.42 +/* Data Handles */
    1.43  new Handle:g_MapList = INVALID_HANDLE;
    1.44 +new Handle:g_NominateList = INVALID_HANDLE;
    1.45 +new Handle:g_NominateOwners = INVALID_HANDLE;
    1.46  new Handle:g_OldMapList = INVALID_HANDLE;
    1.47  new Handle:g_NextMapList = INVALID_HANDLE;
    1.48 -new Handle:g_TeamScores = INVALID_HANDLE;
    1.49  new Handle:g_VoteMenu = INVALID_HANDLE;
    1.50  
    1.51  new g_Extends;
    1.52  new g_TotalRounds;
    1.53  new bool:g_HasVoteStarted;
    1.54 +new bool:g_WaitingForVote;
    1.55 +new bool:g_MapVoteCompleted;
    1.56 +new bool:g_ChangeMapAtRoundEnd;
    1.57  new g_mapFileSerial = -1;
    1.58  
    1.59 +new g_NominateCount = 0;
    1.60 +new MapChange:g_ChangeTime;
    1.61 +
    1.62 +new Handle:g_NominationsResetForward = INVALID_HANDLE;
    1.63 +
    1.64 +/* Upper bound of how many team there could be */
    1.65 +#define MAXTEAMS 10
    1.66 +new g_winCount[MAXTEAMS];
    1.67 +
    1.68  #define VOTE_EXTEND "##extend##"
    1.69 +#define VOTE_DONTCHANGE "##dontchange##"
    1.70  
    1.71  public OnPluginStart()
    1.72  {
    1.73 @@ -83,30 +106,38 @@
    1.74  	
    1.75  	new arraySize = ByteCountToCells(33);
    1.76  	g_MapList = CreateArray(arraySize);
    1.77 +	g_NominateList = CreateArray(arraySize);
    1.78 +	g_NominateOwners = CreateArray(1);
    1.79  	g_OldMapList = CreateArray(arraySize);
    1.80  	g_NextMapList = CreateArray(arraySize);
    1.81 +	
    1.82 +	g_Cvar_EndOfMapVote = CreateConVar("sm_mapvote_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0);
    1.83 +	HookConVarChange(g_Cvar_EndOfMapVote, EndVoteChanged);
    1.84  
    1.85  	g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0);
    1.86 -	g_Cvar_StartRounds = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining.", _, true, 1.0);
    1.87 +	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);
    1.88  	g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0);
    1.89  	g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
    1.90 -	g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 5.0);
    1.91 +	g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0);
    1.92  	g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0);	
    1.93  	g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
    1.94  	g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0);
    1.95  	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);
    1.96  	g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "0", "Number of extensions allowed each map.", _, true, 0.0);
    1.97 -
    1.98 +	g_Cvar_DontChange = CreateConVar("sm_mapvote_dontchange", "1", "Specifies if a 'Don't Change' option should be added to early votes", _, true, 0.0);
    1.99 +	g_Cvar_VoteDuration = CreateConVar("sm_mapvote_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0, true, 25.0);
   1.100  	RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now.");
   1.101  
   1.102  	g_Cvar_Winlimit = FindConVar("mp_winlimit");
   1.103  	g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
   1.104  	g_Cvar_Fraglimit = FindConVar("mp_fraglimit");
   1.105 +	g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime");
   1.106  	
   1.107  	if (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE)
   1.108  	{
   1.109  		HookEvent("round_end", Event_RoundEnd);
   1.110 -		HookEventEx("teamplay_round_win", Event_TeamPlayRoundWin);
   1.111 +		HookEventEx("teamplay_win_panel", Event_TeamPlayWinPanel);
   1.112 +		HookEventEx("teamplay_restart_round", Event_TFRestartRound);
   1.113  	}
   1.114  	
   1.115  	if (g_Cvar_Fraglimit != INVALID_HANDLE)
   1.116 @@ -115,18 +146,37 @@
   1.117  	}
   1.118  	
   1.119  	AutoExecConfig(true, "mapchooser");
   1.120 +	
   1.121 +	//Change the mp_bonusroundtime max so that we have time to display the vote
   1.122 +	//If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime
   1.123 +	if (g_Cvar_Bonusroundtime != INVALID_HANDLE)
   1.124 +	{
   1.125 +		SetConVarBounds(g_Cvar_Bonusroundtime, ConVarBound_Upper, true, 30.0);		
   1.126 +	}
   1.127 +	
   1.128 +	RegPluginLibrary("mapchooser");
   1.129 +	
   1.130 +	g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell);
   1.131 +}
   1.132 +
   1.133 +public bool:AskPluginLoad(Handle:myself, bool:late, String:error[], err_max)
   1.134 +{
   1.135 +	CreateNative("NominateMap", Native_NominateMap);
   1.136 +	CreateNative("InitiateMapChooserVote", Native_InitiateVote);
   1.137 +	CreateNative("CanMapChooserStartVote", Native_CanVoteStart);
   1.138 +	CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone);
   1.139 +	CreateNative("GetExcludeMapList", Native_GetExcludeMapList);
   1.140 +
   1.141 +	return true;
   1.142 +}
   1.143 +
   1.144 +public OnMapStart()
   1.145 +{
   1.146 +	Call_Cancel();	
   1.147  }
   1.148  
   1.149  public OnConfigsExecuted()
   1.150  {
   1.151 -	g_Cvar_Nextmap = FindConVar("sm_nextmap");
   1.152 -
   1.153 -	if (g_Cvar_Nextmap == INVALID_HANDLE)
   1.154 -	{
   1.155 -		LogError("FATAL: Cannot find sm_nextmap cvar. Mapchooser not loaded.");
   1.156 -		SetFailState("sm_nextmap not found");
   1.157 -	}
   1.158 -	
   1.159  	if (ReadMapList(g_MapList,
   1.160  					 g_mapFileSerial, 
   1.161  					 "mapchooser",
   1.162 @@ -142,22 +192,37 @@
   1.163  	
   1.164  	CreateNextVote();
   1.165  	SetupTimeleftTimer();
   1.166 -	SetConVarString(g_Cvar_Nextmap, "Pending Vote");
   1.167  	
   1.168 -	if (g_TeamScores != INVALID_HANDLE)
   1.169 -	{
   1.170 -		CloseHandle(g_TeamScores);
   1.171 -	}
   1.172 -	
   1.173 -	g_TeamScores = CreateArray(2);
   1.174  	g_TotalRounds = 0;
   1.175  	
   1.176  	g_Extends = 0;
   1.177 +	
   1.178 +	g_MapVoteCompleted = false;
   1.179 +	
   1.180 +	g_NominateCount = 0;
   1.181 +	ClearArray(g_NominateList);
   1.182 +	ClearArray(g_NominateOwners);
   1.183 +	
   1.184 +	for (new i=0; i<MAXTEAMS; i++)
   1.185 +	{
   1.186 +		g_winCount[i] = 0;	
   1.187 +	}
   1.188 +	
   1.189 +
   1.190 +	/* Check if mapchooser will attempt to start mapvote during bonus round time - TF2 Only */
   1.191 +	if ((g_Cvar_Bonusroundtime != INVALID_HANDLE) && !GetConVarInt(g_Cvar_StartRounds))
   1.192 +	{
   1.193 +		if (GetConVarFloat(g_Cvar_Bonusroundtime) <= GetConVarFloat(g_Cvar_VoteDuration))
   1.194 +		{
   1.195 +			LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete");
   1.196 +		}
   1.197 +	}
   1.198  }
   1.199  
   1.200  public OnMapEnd()
   1.201  {
   1.202  	g_HasVoteStarted = false;
   1.203 +	g_WaitingForVote = false;
   1.204  	
   1.205  	if (g_VoteTimer != INVALID_HANDLE)
   1.206  	{
   1.207 @@ -170,6 +235,48 @@
   1.208  		KillTimer(g_RetryTimer);
   1.209  		g_RetryTimer = INVALID_HANDLE;
   1.210  	}
   1.211 +	
   1.212 +	decl String:map[32];
   1.213 +	GetCurrentMap(map, sizeof(map));
   1.214 +	PushArrayString(g_OldMapList, map);
   1.215 +				
   1.216 +	if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
   1.217 +	{
   1.218 +		RemoveFromArray(g_OldMapList, 0);
   1.219 +	}	
   1.220 +}
   1.221 +
   1.222 +public OnClientDisconnect(client)
   1.223 +{
   1.224 +	new index = FindValueInArray(g_NominateOwners, client);
   1.225 +	
   1.226 +	if (index == -1)
   1.227 +	{
   1.228 +		return;
   1.229 +	}
   1.230 +	
   1.231 +	new String:oldmap[33];
   1.232 +	GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
   1.233 +	Call_StartForward(g_NominationsResetForward);
   1.234 +	Call_PushString(oldmap);
   1.235 +	Call_PushCell(GetArrayCell(g_NominateOwners, index));
   1.236 +	Call_Finish();
   1.237 +	
   1.238 +	RemoveFromArray(g_NominateOwners, index);
   1.239 +	RemoveFromArray(g_NominateList, index);
   1.240 +	g_NominateCount--;
   1.241 +}
   1.242 +
   1.243 +public EndVoteChanged(Handle:convar, const String:oldValue[], const String:newValue[])
   1.244 +{
   1.245 +	if (newValue[0] == '1')
   1.246 +	{
   1.247 +		SetNextMap("Pending Vote");
   1.248 +	}
   1.249 +	else
   1.250 +	{
   1.251 +		SetNextMap("");	
   1.252 +	}
   1.253  }
   1.254  
   1.255  public OnMapTimeLeftChanged()
   1.256 @@ -186,9 +293,9 @@
   1.257  	if (GetMapTimeLeft(time) && time > 0)
   1.258  	{
   1.259  		new startTime = GetConVarInt(g_Cvar_StartTime) * 60;
   1.260 -		if (time - startTime < 0)
   1.261 +		if (time - startTime < 0 && GetConVarBool(g_Cvar_EndOfMapVote) && !g_MapVoteCompleted && !g_HasVoteStarted)
   1.262  		{
   1.263 -			InitiateVote();		
   1.264 +			InitiateVote(MapChange_MapEnd, INVALID_HANDLE);		
   1.265  		}
   1.266  		else
   1.267  		{
   1.268 @@ -205,7 +312,7 @@
   1.269  
   1.270  public Action:Timer_StartMapVote(Handle:timer)
   1.271  {
   1.272 -	if (!GetArraySize(g_MapList))
   1.273 +	if (!GetArraySize(g_MapList) || !GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted)
   1.274  	{
   1.275  		return Plugin_Stop;
   1.276  	}		
   1.277 @@ -218,87 +325,116 @@
   1.278  	{
   1.279  		g_VoteTimer = INVALID_HANDLE;
   1.280  	}
   1.281 -
   1.282 -	InitiateVote();
   1.283 +	
   1.284 +	InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   1.285  
   1.286  	return Plugin_Stop;
   1.287  }
   1.288  
   1.289 -public Event_TeamPlayRoundWin(Handle:event, const String:name[], bool:dontBroadcast)
   1.290 +public Event_TFRestartRound(Handle:event, const String:name[], bool:dontBroadcast)
   1.291 +{
   1.292 +	/* Game got restarted - reset our round count tracking */
   1.293 +	g_TotalRounds = 0;	
   1.294 +}
   1.295 +
   1.296 +public Event_TeamPlayWinPanel(Handle:event, const String:name[], bool:dontBroadcast)
   1.297  {
   1.298 -	new isFull = GetEventInt(event, "full_round");
   1.299 -	new winningTeam = GetEventInt(event, "team");
   1.300 +	if (g_ChangeMapAtRoundEnd)
   1.301 +	{
   1.302 +		g_ChangeMapAtRoundEnd = false;
   1.303 +		CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE);
   1.304 +	}
   1.305 +	
   1.306 +	if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted || !GetConVarBool(g_Cvar_EndOfMapVote))
   1.307 +	{
   1.308 +		return;
   1.309 +	}
   1.310 +	
   1.311 +	new bluescore = GetEventInt(event, "blue_score");
   1.312 +	new redscore = GetEventInt(event, "red_score");
   1.313  	
   1.314 -	if (isFull == 1)
   1.315 -	{
   1.316 -		RoundCompleted(winningTeam);
   1.317 +	if(GetEventInt(event, "round_complete") == 1)
   1.318 +	{	
   1.319 +		switch(GetEventInt(event, "winning_team"))
   1.320 +		{
   1.321 +			case TFTeam_Blue:
   1.322 +			{
   1.323 +				CheckWinLimit(bluescore);
   1.324 +			}
   1.325 +			case TFTeam_Red:
   1.326 +			{
   1.327 +				CheckWinLimit(redscore);				
   1.328 +			}			
   1.329 +			//We need to do nothing on winning_team == 0 this indicates stalemate.
   1.330 +			default:
   1.331 +			{
   1.332 +				return;
   1.333 +			}			
   1.334 +		}
   1.335 +		
   1.336 +		g_TotalRounds++;
   1.337 +		CheckMaxRounds(g_TotalRounds);
   1.338  	}
   1.339  }
   1.340  /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
   1.341  public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
   1.342  {
   1.343 -	if (!GetArraySize(g_MapList) || g_HasVoteStarted)
   1.344 +	if (g_ChangeMapAtRoundEnd)
   1.345 +	{
   1.346 +		g_ChangeMapAtRoundEnd = false;
   1.347 +		CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE);
   1.348 +	}
   1.349 +	
   1.350 +	if (!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted)
   1.351  	{
   1.352  		return;
   1.353  	}
   1.354  
   1.355  	new winner = GetEventInt(event, "winner");
   1.356  	
   1.357 -	RoundCompleted(winner);
   1.358 -}
   1.359 -	
   1.360 -public RoundCompleted(winner)
   1.361 -{
   1.362 -	if (winner == 0 || winner == 1)
   1.363 +	if (winner == 0 || winner == 1 || !GetConVarBool(g_Cvar_EndOfMapVote))
   1.364  	{
   1.365  		return;
   1.366  	}
   1.367 +	
   1.368 +	if (winner >= MAXTEAMS)
   1.369 +	{
   1.370 +		SetFailState("Mod exceed maximum team count - Please file a bug report.");	
   1.371 +	}
   1.372  
   1.373  	g_TotalRounds++;
   1.374  	
   1.375 -	new team[2], teamPos = -1;
   1.376 -	for (new i; i < GetArraySize(g_TeamScores); i++)
   1.377 -	{
   1.378 -		GetArrayArray(g_TeamScores, i, team);
   1.379 -		if (team[0] == winner)
   1.380 -		{
   1.381 -			teamPos = i;
   1.382 -			break;
   1.383 -		}		
   1.384 -	}
   1.385 +	g_winCount[winner]++;
   1.386  	
   1.387 -	if (teamPos == -1)
   1.388 -	{
   1.389 -		team[0] = winner;
   1.390 -		team[1] = 1;
   1.391 -		PushArrayArray(g_TeamScores, team);
   1.392 -	}
   1.393 -	else
   1.394 -	{
   1.395 -		team[1]++;
   1.396 -		SetArrayArray(g_TeamScores, teamPos, team);
   1.397 -	}	
   1.398 -	
   1.399 +	CheckWinLimit(g_winCount[winner]);
   1.400 +	CheckMaxRounds(g_TotalRounds);
   1.401 +}
   1.402 +
   1.403 +public CheckWinLimit(winner_score)
   1.404 +{	
   1.405  	if (g_Cvar_Winlimit != INVALID_HANDLE)
   1.406  	{
   1.407  		new winlimit = GetConVarInt(g_Cvar_Winlimit);
   1.408  		if (winlimit)
   1.409 -		{
   1.410 -			if (team[1] >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
   1.411 +		{			
   1.412 +			if (winner_score >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
   1.413  			{
   1.414 -				InitiateVote();
   1.415 +				InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   1.416  			}
   1.417  		}
   1.418  	}
   1.419 -	
   1.420 +}
   1.421 +
   1.422 +public CheckMaxRounds(roundcount)
   1.423 +{		
   1.424  	if (g_Cvar_Maxrounds != INVALID_HANDLE)
   1.425  	{
   1.426  		new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   1.427  		if (maxrounds)
   1.428  		{
   1.429 -			if (g_TotalRounds >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
   1.430 +			if (roundcount >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
   1.431  			{
   1.432 -				InitiateVote();
   1.433 +				InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   1.434  			}			
   1.435  		}
   1.436  	}
   1.437 @@ -311,7 +447,7 @@
   1.438  		return;
   1.439  	}
   1.440  	
   1.441 -	if (!GetConVarInt(g_Cvar_Fraglimit))
   1.442 +	if (!GetConVarInt(g_Cvar_Fraglimit) || !GetConVarBool(g_Cvar_EndOfMapVote))
   1.443  	{
   1.444  		return;
   1.445  	}
   1.446 @@ -319,54 +455,249 @@
   1.447  	new fragger = GetClientOfUserId(GetEventInt(event, "attacker"));
   1.448  	if (fragger && GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
   1.449  	{
   1.450 -		InitiateVote();
   1.451 +		InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   1.452  	}
   1.453  }
   1.454  
   1.455  public Action:Command_Mapvote(client, args)
   1.456  {
   1.457 -	InitiateVote();
   1.458 +	InitiateVote(MapChange_MapEnd, INVALID_HANDLE);
   1.459  
   1.460  	return Plugin_Handled;	
   1.461  }
   1.462  
   1.463 -InitiateVote()
   1.464 +InitiateVote(MapChange:when, Handle:inputlist)
   1.465  {
   1.466 -	if (g_HasVoteStarted || g_RetryTimer != INVALID_HANDLE)
   1.467 +	if (!CanVoteStart())
   1.468  	{
   1.469  		return;
   1.470  	}
   1.471  	
   1.472 +	g_WaitingForVote = true;
   1.473 +	
   1.474  	if (IsVoteInProgress())
   1.475  	{
   1.476  		// Can't start a vote, try again in 5 seconds.
   1.477  		g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote);
   1.478  		return;
   1.479 -	}		
   1.480 +	}
   1.481  	
   1.482 +	g_ChangeTime = when;
   1.483 +	
   1.484 +	g_WaitingForVote = false;
   1.485 +		
   1.486  	g_HasVoteStarted = true;
   1.487  	g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
   1.488  	SetMenuTitle(g_VoteMenu, "Vote Nextmap");
   1.489 -
   1.490 +	SetVoteResultCallback(g_VoteMenu, Handler_MapVoteFinished);
   1.491 +	
   1.492 +	/**
   1.493 +	 * TODO: Make a proper decision on when to clear the nominations list.
   1.494 +	 * Currently it clears when used, and stays if an external list is provided.
   1.495 +	 * Is this the right thing to do? External lists will probably come from places
   1.496 +	 * like sm_mapvote from the adminmenu in the future.
   1.497 +	 */
   1.498 +	 
   1.499  	decl String:map[32];
   1.500 -	for (new i = 0; i < GetArraySize(g_NextMapList); i++)
   1.501 +	
   1.502 +	/* No input given - User our internal nominations and maplist */
   1.503 +	if (inputlist == INVALID_HANDLE)
   1.504  	{
   1.505 -		GetArrayString(g_NextMapList, i, map, sizeof(map));
   1.506 -		AddMenuItem(g_VoteMenu, map, map);
   1.507 +		new nominateCount = GetArraySize(g_NominateList);
   1.508 +		new voteSize = GetConVarInt(g_Cvar_IncludeMaps);
   1.509 +		
   1.510 +		/* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */
   1.511 +		new nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount;
   1.512 +		
   1.513 +		
   1.514 +		for (new i=0; i<nominationsToAdd; i++)
   1.515 +		{
   1.516 +			GetArrayString(g_NominateList, i, map, sizeof(map));
   1.517 +			AddMenuItem(g_VoteMenu, map, map);
   1.518 +			
   1.519 +			/* Notify Nominations that this map is now free */
   1.520 +			Call_StartForward(g_NominationsResetForward);
   1.521 +			Call_PushString(map);
   1.522 +			Call_PushCell(GetArrayCell(g_NominateOwners, i));
   1.523 +			Call_Finish();
   1.524 +		}
   1.525 +		
   1.526 +		/* Clear out the rest of the nominations array */
   1.527 +		for (new i=nominationsToAdd; i<nominateCount; i++)
   1.528 +		{
   1.529 +			GetArrayString(g_NominateList, i, map, sizeof(map));
   1.530 +			
   1.531 +			/* Notify Nominations that this map is now free */
   1.532 +			Call_StartForward(g_NominationsResetForward);
   1.533 +			Call_PushString(map);
   1.534 +			Call_PushCell(GetArrayCell(g_NominateOwners, i));
   1.535 +			Call_Finish();			
   1.536 +		}
   1.537 +		
   1.538 +		/* There should currently be 'nominationsToAdd' unique maps in the vote */
   1.539 +		
   1.540 +		new i = nominationsToAdd;
   1.541 +		new count = 0;
   1.542 +		new availableMaps = GetArraySize(g_NextMapList);
   1.543 +		
   1.544 +		while (i < voteSize)
   1.545 +		{
   1.546 +			GetArrayString(g_NextMapList, count, map, sizeof(map));
   1.547 +			count++;
   1.548 +			
   1.549 +			//Check if this map is in the nominate list (and thus already in the vote) */
   1.550 +			if (FindStringInArray(g_NominateList, map) == -1)
   1.551 +			{
   1.552 +				/* Insert the map and increment our count */
   1.553 +				AddMenuItem(g_VoteMenu, map, map);
   1.554 +				i++;
   1.555 +			}
   1.556 +			
   1.557 +			if (count >= availableMaps)
   1.558 +			{
   1.559 +				//Run out of maps, this will have to do.
   1.560 +				break;	
   1.561 +			}
   1.562 +		}
   1.563 +		
   1.564 +		/* Wipe out our nominations list - Nominations have already been informed of this */
   1.565 +		ClearArray(g_NominateOwners);
   1.566 +		ClearArray(g_NominateList);
   1.567  	}
   1.568 -
   1.569 -	if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend))
   1.570 +	else //We were given a list of maps to start the vote with
   1.571 +	{
   1.572 +		new size = GetArraySize(inputlist);
   1.573 +		
   1.574 +		for (new i=0; i<size; i++)
   1.575 +		{
   1.576 +			GetArrayString(inputlist, i, map, sizeof(map));
   1.577 +			
   1.578 +			if (IsMapValid(map))
   1.579 +			{
   1.580 +				AddMenuItem(g_VoteMenu, map, map);
   1.581 +			}	
   1.582 +		}
   1.583 +	}
   1.584 +	
   1.585 +	/* Do we add any special items? */
   1.586 +	if ((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange))
   1.587 +	{
   1.588 +		AddMenuItem(g_VoteMenu, VOTE_DONTCHANGE, "Don't Change");
   1.589 +	}
   1.590 +	else if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend))
   1.591  	{
   1.592  		AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
   1.593  	}
   1.594 +	
   1.595 +	new voteDuration = GetConVarInt(g_Cvar_VoteDuration);
   1.596  
   1.597  	SetMenuExitButton(g_VoteMenu, false);
   1.598 -	VoteMenuToAll(g_VoteMenu, 20);
   1.599 +	VoteMenuToAll(g_VoteMenu, voteDuration);
   1.600  
   1.601  	LogMessage("Voting for next map has started.");
   1.602  	PrintToChatAll("[SM] %t", "Nextmap Voting Started");
   1.603  }
   1.604  
   1.605 +public Handler_MapVoteFinished(Handle:menu,
   1.606 +						   num_votes, 
   1.607 +						   num_clients,
   1.608 +						   const client_info[][2], 
   1.609 +						   num_items,
   1.610 +						   const item_info[][2])
   1.611 +{
   1.612 +	if (num_votes == 0)
   1.613 +	{
   1.614 +		LogError("No Votes recorded yet Advanced callback fired - Tell pRED* to fix this");
   1.615 +		return;	
   1.616 +	}
   1.617 +	
   1.618 +	decl String:map[32];
   1.619 +	GetMenuItem(menu, item_info[0][VOTEINFO_ITEM_INDEX], map, sizeof(map));
   1.620 +
   1.621 +	if (strcmp(map, VOTE_EXTEND, false) == 0)
   1.622 +	{
   1.623 +		g_Extends++;
   1.624 +		
   1.625 +		new time;
   1.626 +		if (GetMapTimeLimit(time))
   1.627 +		{
   1.628 +			if (time > 0)
   1.629 +			{
   1.630 +				ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);						
   1.631 +			}
   1.632 +		}
   1.633 +		
   1.634 +		if (g_Cvar_Winlimit != INVALID_HANDLE)
   1.635 +		{
   1.636 +			new winlimit = GetConVarInt(g_Cvar_Winlimit);
   1.637 +			if (winlimit)
   1.638 +			{
   1.639 +				SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
   1.640 +			}					
   1.641 +		}
   1.642 +		
   1.643 +		if (g_Cvar_Maxrounds != INVALID_HANDLE)
   1.644 +		{
   1.645 +			new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   1.646 +			if (maxrounds)
   1.647 +			{
   1.648 +				SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
   1.649 +			}
   1.650 +		}
   1.651 +		
   1.652 +		if (g_Cvar_Fraglimit != INVALID_HANDLE)
   1.653 +		{
   1.654 +			new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
   1.655 +			if (fraglimit)
   1.656 +			{
   1.657 +				SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));						
   1.658 +			}
   1.659 +		}
   1.660 +
   1.661 +		PrintToChatAll("[SM] %t", "Current Map Extended", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
   1.662 +		LogMessage("Voting for next map has finished. The current map has been extended.");
   1.663 +		
   1.664 +		// We extended, so we'll have to vote again.
   1.665 +		g_HasVoteStarted = false;
   1.666 +		CreateNextVote();
   1.667 +		SetupTimeleftTimer();
   1.668 +		
   1.669 +	}
   1.670 +	else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
   1.671 +	{
   1.672 +		PrintToChatAll("[SM] %t", "Current Map Stays", RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
   1.673 +		LogMessage("Voting for next map has finished. 'No Change' was the winner");
   1.674 +		
   1.675 +		g_HasVoteStarted = false;
   1.676 +		CreateNextVote();
   1.677 +		SetupTimeleftTimer();
   1.678 +	}
   1.679 +	else
   1.680 +	{
   1.681 +		if (g_ChangeTime == MapChange_MapEnd)
   1.682 +		{
   1.683 +			SetNextMap(map);
   1.684 +		}
   1.685 +		else if (g_ChangeTime == MapChange_Instant)
   1.686 +		{
   1.687 +			new Handle:data;
   1.688 +			CreateDataTimer(2.0, Timer_ChangeMap, data);
   1.689 +			WritePackString(data, map);
   1.690 +		}
   1.691 +		else // MapChange_RoundEnd
   1.692 +		{
   1.693 +			SetNextMap(map);
   1.694 +			g_ChangeMapAtRoundEnd = true;
   1.695 +		}
   1.696 +		
   1.697 +		g_HasVoteStarted = false;
   1.698 +		g_MapVoteCompleted = true;
   1.699 +		
   1.700 +		PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map, RoundToFloor(float(item_info[0][VOTEINFO_ITEM_VOTES])/float(num_votes)*100), num_votes);
   1.701 +		LogMessage("Voting for next map has finished. Nextmap: %s.", map);
   1.702 +	}	
   1.703 +}
   1.704 +
   1.705  public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
   1.706  {
   1.707  	switch (action)
   1.708 @@ -397,20 +728,14 @@
   1.709  					Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
   1.710  					return RedrawMenuItem(buffer);
   1.711  				}
   1.712 +				else if (strcmp(map, VOTE_DONTCHANGE, false) == 0)
   1.713 +				{
   1.714 +					Format(buffer, sizeof(buffer), "%T", "Dont Change", param1);
   1.715 +					return RedrawMenuItem(buffer);					
   1.716 +				}
   1.717  			}
   1.718  		}		
   1.719 -
   1.720 -		// Why am I commented out? Because BAIL hasn't decided yet if
   1.721 -		// vote notification will be built into the Vote API.
   1.722 -		/*case MenuAction_Select:
   1.723 -		{
   1.724 -			decl String:Name[32], String:Map[32];
   1.725 -			GetClientName(param1, Name, sizeof(Name));
   1.726 -			GetMenuItem(menu, param2, Map, sizeof(Map));
   1.727 -
   1.728 -			PrintToChatAll("[SM] %s has voted for map '%s'", Name, Map);
   1.729 -		}*/
   1.730 -		
   1.731 +	
   1.732  		case MenuAction_VoteCancel:
   1.733  		{
   1.734  			// If we receive 0 votes, pick at random.
   1.735 @@ -432,84 +757,37 @@
   1.736  			else
   1.737  			{
   1.738  				// We were actually cancelled. What should we do?
   1.739 -			}
   1.740 -		}
   1.741 -
   1.742 -		case MenuAction_VoteEnd:
   1.743 -		{
   1.744 -			decl String:map[32];
   1.745 -			GetMenuItem(menu, param1, map, sizeof(map));
   1.746 -
   1.747 -			if (strcmp(map, VOTE_EXTEND, false) == 0)
   1.748 -			{
   1.749 -				g_Extends++;
   1.750 -				
   1.751 -				new time;
   1.752 -				if (GetMapTimeLimit(time))
   1.753 -				{
   1.754 -					if (time > 0)
   1.755 -					{
   1.756 -						ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);						
   1.757 -					}
   1.758 -				}
   1.759 -				
   1.760 -				if (g_Cvar_Winlimit != INVALID_HANDLE)
   1.761 -				{
   1.762 -					new winlimit = GetConVarInt(g_Cvar_Winlimit);
   1.763 -					if (winlimit)
   1.764 -					{
   1.765 -						SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
   1.766 -					}					
   1.767 -				}
   1.768 -				
   1.769 -				if (g_Cvar_Maxrounds != INVALID_HANDLE)
   1.770 -				{
   1.771 -					new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   1.772 -					if (maxrounds)
   1.773 -					{
   1.774 -						SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
   1.775 -					}
   1.776 -				}
   1.777 -				
   1.778 -				if (g_Cvar_Fraglimit != INVALID_HANDLE)
   1.779 -				{
   1.780 -					new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
   1.781 -					if (fraglimit)
   1.782 -					{
   1.783 -						SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));						
   1.784 -					}
   1.785 -				}
   1.786 -
   1.787 -				PrintToChatAll("[SM] %t", "Current Map Extended");
   1.788 -				LogMessage("Voting for next map has finished. The current map has been extended.");
   1.789 -				
   1.790 -				// We extended, so we'll have to vote again.
   1.791  				g_HasVoteStarted = false;
   1.792  				CreateNextVote();
   1.793  				SetupTimeleftTimer();
   1.794  			}
   1.795 -			else
   1.796 -			{
   1.797 -				SetNextMap(map);
   1.798 -			}
   1.799  		}
   1.800  	}
   1.801  	
   1.802  	return 0;
   1.803  }
   1.804  
   1.805 -SetNextMap(const String:map[])
   1.806 +public Action:Timer_ChangeMap(Handle:hTimer, Handle:dp)
   1.807  {
   1.808 -	SetConVarString(g_Cvar_Nextmap, map);
   1.809 -	PushArrayString(g_OldMapList, map);
   1.810 -				
   1.811 -	if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
   1.812 +	new String:map[65];
   1.813 +	
   1.814 +	if (dp == INVALID_HANDLE)
   1.815  	{
   1.816 -		RemoveFromArray(g_OldMapList, 0);
   1.817 +		if (!GetNextMap(map, sizeof(map)))
   1.818 +		{
   1.819 +			//No passed map and no set nextmap. fail!
   1.820 +			return Plugin_Stop;	
   1.821 +		}
   1.822  	}
   1.823 -		
   1.824 -	PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map);
   1.825 -	LogMessage("Voting for next map has finished. Nextmap: %s.", map);	
   1.826 +	else
   1.827 +	{
   1.828 +		ResetPack(dp);
   1.829 +		ReadPackString(dp, map, sizeof(map));		
   1.830 +	}
   1.831 +	
   1.832 +	ServerCommand("changelevel \"%s\"", map);
   1.833 +	
   1.834 +	return Plugin_Stop;
   1.835  }
   1.836  
   1.837  CreateNextVote()
   1.838 @@ -552,4 +830,131 @@
   1.839  	}
   1.840  	
   1.841  	CloseHandle(tempMaps);
   1.842 +}
   1.843 +
   1.844 +bool:CanVoteStart()
   1.845 +{
   1.846 +	if (g_WaitingForVote || g_HasVoteStarted)
   1.847 +	{
   1.848 +		return false;	
   1.849 +	}
   1.850 +	
   1.851 +	return true;
   1.852 +}
   1.853 +
   1.854 +NominateResult:InternalNominateMap(String:map[], bool:force, owner)
   1.855 +{
   1.856 +	if (!IsMapValid(map))
   1.857 +	{
   1.858 +		return Nominate_InvalidMap;
   1.859 +	}
   1.860 +	
   1.861 +	new index;
   1.862 +
   1.863 +	if ((index = FindValueInArray(g_NominateOwners, owner)) != -1)
   1.864 +	{
   1.865 +		new String:oldmap[33];
   1.866 +		GetArrayString(g_NominateList, index, oldmap, sizeof(oldmap));
   1.867 +		Call_StartForward(g_NominationsResetForward);
   1.868 +		Call_PushString(oldmap);
   1.869 +		Call_PushCell(owner);
   1.870 +		Call_Finish();
   1.871 +		
   1.872 +		
   1.873 +		SetArrayString(g_NominateList, index, map);
   1.874 +		return Nominate_Replaced;
   1.875 +	}
   1.876 +	
   1.877 +	/* Too many nominated maps. */
   1.878 +	if (g_NominateCount >= GetConVarInt(g_Cvar_IncludeMaps) && !force)
   1.879 +	{
   1.880 +		return Nominate_VoteFull;
   1.881 +	}
   1.882 +	
   1.883 +	/* Map already in the vote */
   1.884 +	if (FindStringInArray(g_NominateList, map) != -1)
   1.885 +	{
   1.886 +		return Nominate_AlreadyInVote;	
   1.887 +	}
   1.888 +	
   1.889 +	
   1.890 +	PushArrayString(g_NominateList, map);
   1.891 +	PushArrayCell(g_NominateOwners, owner);
   1.892 +	g_NominateCount++;
   1.893 +	
   1.894 +	while (GetArraySize(g_NominateList) > GetConVarInt(g_Cvar_IncludeMaps))
   1.895 +	{
   1.896 +		new String:oldmap[33];
   1.897 +		GetArrayString(g_NominateList, 0, oldmap, sizeof(oldmap));
   1.898 +		Call_StartForward(g_NominationsResetForward);
   1.899 +		Call_PushString(oldmap);
   1.900 +		Call_PushCell(GetArrayCell(g_NominateOwners, 0));
   1.901 +		Call_Finish();
   1.902 +		
   1.903 +		RemoveFromArray(g_NominateList, 0);
   1.904 +		RemoveFromArray(g_NominateOwners, 0);
   1.905 +	}
   1.906 +	
   1.907 +	return Nominate_Added;
   1.908 +}
   1.909 +
   1.910 +/* Add natives to allow nominate and initiate vote to be call */
   1.911 +
   1.912 +/* native  bool:NominateMap(const String:map[], bool:force, &NominateError:error); */
   1.913 +public Native_NominateMap(Handle:plugin, numParams)
   1.914 +{
   1.915 +	new len;
   1.916 +	GetNativeStringLength(1, len);
   1.917 +	
   1.918 +	if (len <= 0)
   1.919 +	{
   1.920 +	  return false;
   1.921 +	}
   1.922 +	
   1.923 +	new String:map[len+1];
   1.924 +	GetNativeString(1, map, len+1);
   1.925 +	
   1.926 +	return _:InternalNominateMap(map, GetNativeCell(2), GetNativeCell(3));
   1.927 +}
   1.928 +
   1.929 +/* native InitiateMapChooserVote(); */
   1.930 +public Native_InitiateVote(Handle:plugin, numParams)
   1.931 +{
   1.932 +	new MapChange:when = MapChange:GetNativeCell(1);
   1.933 +	new Handle:inputarray = Handle:GetNativeCell(2);
   1.934 +	
   1.935 +	LogMessage("Starting map vote because outside request");
   1.936 +	InitiateVote(when, inputarray);
   1.937 +}
   1.938 +
   1.939 +public Native_CanVoteStart(Handle:plugin, numParams)
   1.940 +{
   1.941 +	return CanVoteStart();	
   1.942 +}
   1.943 +
   1.944 +public Native_CheckVoteDone(Handle:plugin, numParams)
   1.945 +{
   1.946 +	return g_MapVoteCompleted;
   1.947 +}
   1.948 +
   1.949 +public Native_GetExcludeMapList(Handle:plugin, numParams)
   1.950 +{
   1.951 +	new Handle:array = Handle:GetNativeCell(1);
   1.952 +	
   1.953 +	if (array == INVALID_HANDLE)
   1.954 +	{
   1.955 +		return;	
   1.956 +	}
   1.957 +	
   1.958 +	
   1.959 +	new size = GetArraySize(g_OldMapList);
   1.960 +	decl String:map[33];
   1.961 +	
   1.962 +	for (new i=0; i<size; i++)
   1.963 +	{
   1.964 +		GetArrayString(g_OldMapList, i, map, sizeof(map));
   1.965 +		PushArrayString(array, map);	
   1.966 +	}
   1.967 +	
   1.968 +	return;
   1.969  }
   1.970 \ No newline at end of file