plugins/mapchooser.sp
author Michael McKoy <ferret99gt@gmail.com>
Fri Nov 09 23:46:05 2007 +0000 (2007-11-09)
changeset 1664 e054be79e28a
parent 1642 4d3ed2d9d077
child 1698 7caa46a2eb2c
permissions -rw-r--r--
New adt_array functions: CloneArray, FindStringInArray, FindCellInArray
Adjusted mapchooser/randomcycle/rockthevote and LoadMaps stock to use new array functions
     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 
    38 public Plugin:myinfo =
    39 {
    40 	name = "MapChooser",
    41 	author = "AlliedModders LLC",
    42 	description = "Automated Map Voting",
    43 	version = SOURCEMOD_VERSION,
    44 	url = "http://www.sourcemod.net/"
    45 };
    46 
    47 new Handle:g_Cvar_Winlimit = INVALID_HANDLE;
    48 new Handle:g_Cvar_Maxrounds = INVALID_HANDLE;
    49 new Handle:g_Cvar_Fraglimit = INVALID_HANDLE;
    50 
    51 new Handle:g_Cvar_Nextmap = INVALID_HANDLE;
    52 
    53 new Handle:g_Cvar_StartTime = INVALID_HANDLE;
    54 new Handle:g_Cvar_StartRounds = INVALID_HANDLE;
    55 new Handle:g_Cvar_StartFrags = INVALID_HANDLE;
    56 new Handle:g_Cvar_ExtendTimeMax = INVALID_HANDLE;
    57 new Handle:g_Cvar_ExtendTimeStep = INVALID_HANDLE;
    58 new Handle:g_Cvar_ExtendRoundMax = INVALID_HANDLE;
    59 new Handle:g_Cvar_ExtendRoundStep = INVALID_HANDLE;
    60 new Handle:g_Cvar_ExtendFragMax = INVALID_HANDLE;
    61 new Handle:g_Cvar_ExtendFragStep = INVALID_HANDLE;
    62 new Handle:g_Cvar_Mapfile = INVALID_HANDLE;
    63 new Handle:g_Cvar_ExcludeMaps = INVALID_HANDLE;
    64 new Handle:g_Cvar_IncludeMaps = INVALID_HANDLE;
    65 new Handle:g_Cvar_NoVoteMode = INVALID_HANDLE;
    66 new Handle:g_Cvar_Extend = INVALID_HANDLE;
    67 
    68 new Handle:g_VoteTimer = INVALID_HANDLE;
    69 new Handle:g_RetryTimer = INVALID_HANDLE;
    70 
    71 new Handle:g_MapList = INVALID_HANDLE;
    72 new Handle:g_OldMapList = INVALID_HANDLE;
    73 new Handle:g_NextMapList = INVALID_HANDLE;
    74 new Handle:g_TeamScores = INVALID_HANDLE;
    75 new Handle:g_VoteMenu = INVALID_HANDLE;
    76 
    77 new bool:g_HasVoteStarted;
    78 new g_mapFileTime;
    79 
    80 #define VOTE_EXTEND "##extend##"
    81 
    82 public OnPluginStart()
    83 {
    84 	LoadTranslations("mapchooser.phrases");
    85 	
    86 	new arraySize = ByteCountToCells(33);
    87 	g_MapList = CreateArray(arraySize);
    88 	g_OldMapList = CreateArray(arraySize);
    89 	g_NextMapList = CreateArray(arraySize);
    90 	g_TeamScores = CreateArray(2);
    91 
    92 	g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0);
    93 	g_Cvar_StartRounds = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining.", _, true, 1.0);
    94 	g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0);
    95 	g_Cvar_ExtendTimeMax = CreateConVar("sm_extendmap_maxtime", "90", "Specifies the maximum amount of time a map can be extended", _, true, 0.0);
    96 	g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
    97 	g_Cvar_ExtendRoundMax = CreateConVar("sm_extendmap_maxrounds", "30", "Specfies the maximum amount of rounds a map can be extended", _, true, 0.0);
    98 	g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 5.0);
    99 	g_Cvar_ExtendFragMax = CreateConVar("sm_extendmap_maxfrags", "150", "Specfies the maximum frags allowed that a map can be extended.", _, true, 0.0);
   100 	g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0);	
   101 	g_Cvar_Mapfile = CreateConVar("sm_mapvote_file", "configs/maps.ini", "Map file to use. (Def sourcemod/configs/maps.ini)");
   102 	g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
   103 	g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0);
   104 	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);
   105 	g_Cvar_Extend = CreateConVar("sm_mapvote_extend", "1", "Specifies whether or not MapChooser will allow the map to be extended.", _, true, 0.0, true, 1.0);
   106 
   107 	RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now.");
   108 
   109 	g_Cvar_Winlimit = FindConVar("mp_winlimit");
   110 	g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
   111 	g_Cvar_Fraglimit = FindConVar("mp_fraglimit");
   112 	
   113 	if (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE)
   114 	{
   115 		HookEvent("round_end", Event_RoundEnd);
   116 	}
   117 	
   118 	if (g_Cvar_Fraglimit != INVALID_HANDLE)
   119 	{
   120 		HookEvent("player_death", Event_PlayerDeath);		
   121 	}
   122 	
   123 	AutoExecConfig(true, "mapchooser");
   124 }
   125 
   126 public OnConfigsExecuted()
   127 {
   128 	g_Cvar_Nextmap = FindConVar("sm_nextmap");
   129 
   130 	if (g_Cvar_Nextmap == INVALID_HANDLE)
   131 	{
   132 		LogError("FATAL: Cannot find sm_nextmap cvar. Mapchooser not loaded.");
   133 		SetFailState("sm_nextmap not found");
   134 	}
   135 	
   136 	if (LoadMaps(g_MapList, g_mapFileTime, g_Cvar_Mapfile))
   137 	{
   138 		CreateNextVote();
   139 		SetupTimeleftTimer();
   140 		SetConVarString(g_Cvar_Nextmap, "Pending Vote");
   141 	}
   142 }
   143 
   144 public OnMapEnd()
   145 {
   146 	g_HasVoteStarted = false;
   147 	
   148 	if (g_VoteTimer != INVALID_HANDLE)
   149 	{
   150 		KillTimer(g_VoteTimer);
   151 		g_VoteTimer = INVALID_HANDLE;
   152 	}
   153 	
   154 	if (g_RetryTimer != INVALID_HANDLE)
   155 	{
   156 		KillTimer(g_RetryTimer);
   157 		g_RetryTimer = INVALID_HANDLE;
   158 	}
   159 }
   160 
   161 public OnMapTimeLeftChanged()
   162 {
   163 	if (GetArraySize(g_MapList))
   164 	{
   165 		SetupTimeleftTimer();
   166 	}
   167 }
   168 
   169 SetupTimeleftTimer()
   170 {
   171 	new time;
   172 	if (GetMapTimeLeft(time) && time > 0)
   173 	{
   174 		new startTime = GetConVarInt(g_Cvar_StartTime) * 60;
   175 		if (time - startTime < 0)
   176 		{
   177 			InitiateVote();		
   178 		}
   179 		else
   180 		{
   181 			if (g_VoteTimer != INVALID_HANDLE)
   182 			{
   183 				KillTimer(g_VoteTimer);
   184 				g_VoteTimer = INVALID_HANDLE;
   185 			}	
   186 			
   187 			g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote);
   188 		}		
   189 	}
   190 }
   191 
   192 public Action:Timer_StartMapVote(Handle:timer)
   193 {
   194 	if (!GetArraySize(g_MapList))
   195 	{
   196 		return Plugin_Stop;
   197 	}		
   198 	
   199 	if (timer == g_RetryTimer)
   200 	{
   201 		g_RetryTimer = INVALID_HANDLE;
   202 	}
   203 	else
   204 	{
   205 		g_VoteTimer = INVALID_HANDLE;
   206 	}
   207 
   208 	InitiateVote();
   209 
   210 	return Plugin_Stop;
   211 }
   212 
   213 /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
   214 public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
   215 {
   216 	if (!GetArraySize(g_MapList) || g_HasVoteStarted)
   217 	{
   218 		return;
   219 	}
   220 
   221 	new winner = GetEventInt(event, "winner");
   222 	
   223 	if (winner == 0 || winner == 1)
   224 	{
   225 		return;
   226 	}
   227 
   228 	static total;
   229 	total++;
   230 	
   231 	new team[2], teamPos = -1;
   232 	for (new i; i < GetArraySize(g_TeamScores); i++)
   233 	{
   234 		GetArrayArray(g_TeamScores, i, team);
   235 		if (team[0] == winner)
   236 		{
   237 			teamPos = i;
   238 			break;
   239 		}		
   240 	}
   241 	
   242 	if (teamPos == -1)
   243 	{
   244 		team[0] = winner;
   245 		team[1] = 1;
   246 		PushArrayArray(g_TeamScores, team);
   247 	}
   248 	else
   249 	{
   250 		team[1]++;
   251 		SetArrayArray(g_TeamScores, teamPos, team);
   252 	}	
   253 	
   254 	if (g_Cvar_Winlimit != INVALID_HANDLE)
   255 	{
   256 		new winlimit = GetConVarInt(g_Cvar_Winlimit);
   257 		if (winlimit)
   258 		{
   259 			if (team[1] >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
   260 			{
   261 				InitiateVote();
   262 			}
   263 		}
   264 	}
   265 	
   266 	if (g_Cvar_Maxrounds != INVALID_HANDLE)
   267 	{
   268 		new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   269 		if (maxrounds)
   270 		{
   271 			if (total >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
   272 			{
   273 				InitiateVote();
   274 			}			
   275 		}
   276 	}
   277 }
   278 
   279 public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
   280 {
   281 	if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted)
   282 	{
   283 		return;
   284 	}
   285 	
   286 	if (!GetConVarInt(g_Cvar_Fraglimit))
   287 	{
   288 		return;
   289 	}
   290 
   291 	new fragger = GetClientOfUserId(GetEventInt(event, "attacker"));
   292 	if (fragger && GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
   293 	{
   294 		InitiateVote();
   295 	}
   296 }
   297 
   298 public Action:Command_Mapvote(client, args)
   299 {
   300 	InitiateVote();
   301 
   302 	return Plugin_Handled;	
   303 }
   304 
   305 InitiateVote()
   306 {
   307 	if (g_HasVoteStarted || g_RetryTimer != INVALID_HANDLE)
   308 	{
   309 		return;
   310 	}
   311 	
   312 	if (IsVoteInProgress())
   313 	{
   314 		// Can't start a vote, try again in 5 seconds.
   315 		g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote);
   316 		return;
   317 	}		
   318 	
   319 	g_HasVoteStarted = true;
   320 	g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
   321 	SetMenuTitle(g_VoteMenu, "Vote Nextmap");
   322 
   323 	decl String:map[32];
   324 	for (new i = 0; i < GetArraySize(g_NextMapList); i++)
   325 	{
   326 		GetArrayString(g_NextMapList, i, map, sizeof(map));
   327 		AddMenuItem(g_VoteMenu, map, map);
   328 	}
   329 
   330 	if (GetConVarBool(g_Cvar_Extend))
   331 	{
   332 		new bool:allowExtend, time;
   333 		if (GetMapTimeLimit(time) && time > 0 && time < GetConVarInt(g_Cvar_ExtendTimeMax))
   334 		{
   335 			allowExtend = true;
   336 		}
   337 
   338 		if (g_Cvar_Winlimit != INVALID_HANDLE && GetConVarInt(g_Cvar_Winlimit) < GetConVarInt(g_Cvar_ExtendRoundMax))
   339 		{
   340 			allowExtend = true;
   341 		}	
   342 	
   343 		if (g_Cvar_Maxrounds != INVALID_HANDLE && GetConVarInt(g_Cvar_Maxrounds) < GetConVarInt(g_Cvar_ExtendRoundMax))
   344 		{
   345 			allowExtend = true;
   346 		}
   347 	
   348 		if (g_Cvar_Fraglimit != INVALID_HANDLE && GetConVarInt(g_Cvar_Fraglimit) < GetConVarInt(g_Cvar_ExtendFragMax))
   349 		{
   350 			allowExtend = true;
   351 		}
   352 
   353 		if (allowExtend)
   354 		{
   355 			AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
   356 		}
   357 	}
   358 
   359 	SetMenuExitButton(g_VoteMenu, false);
   360 	VoteMenuToAll(g_VoteMenu, 20);
   361 
   362 	LogMessage("Voting for next map has started.");
   363 	PrintToChatAll("[SM] %t", "Nextmap Voting Started");
   364 }
   365 
   366 public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
   367 {
   368 	switch (action)
   369 	{
   370 		case MenuAction_End:
   371 		{
   372 			g_VoteMenu = INVALID_HANDLE;
   373 			CloseHandle(menu);
   374 		}
   375 		
   376 		case MenuAction_Display:
   377 		{
   378 	 		decl String:buffer[255];
   379 			Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
   380 
   381 			new Handle:panel = Handle:param2;
   382 			SetPanelTitle(panel, buffer);
   383 		}		
   384 		
   385 		case MenuAction_DisplayItem:
   386 		{
   387 			if (GetMenuItemCount(menu) - 1 == param2)
   388 			{
   389 				decl String:map[64], String:buffer[255];
   390 				GetMenuItem(menu, param2, map, sizeof(map));
   391 				if (strcmp(map, VOTE_EXTEND, false) == 0)
   392 				{
   393 					Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
   394 					return RedrawMenuItem(buffer);
   395 				}
   396 			}
   397 		}		
   398 
   399 		// Why am I commented out? Because BAIL hasn't decided yet if
   400 		// vote notification will be built into the Vote API.
   401 		/*case MenuAction_Select:
   402 		{
   403 			decl String:Name[32], String:Map[32];
   404 			GetClientName(param1, Name, sizeof(Name));
   405 			GetMenuItem(menu, param2, Map, sizeof(Map));
   406 
   407 			PrintToChatAll("[SM] %s has voted for map '%s'", Name, Map);
   408 		}*/
   409 		
   410 		case MenuAction_VoteCancel:
   411 		{
   412 			// If we receive 0 votes, pick at random.
   413 			if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode))
   414 			{
   415 				new count = GetMenuItemCount(menu);
   416 				new item = GetRandomInt(0, count - 1);
   417 				decl String:map[32];
   418 				GetMenuItem(menu, item, map, sizeof(map));
   419 				
   420 				while (strcmp(map, VOTE_EXTEND, false) == 0)
   421 				{
   422 					item = GetRandomInt(0, count - 1);
   423 					GetMenuItem(menu, item, map, sizeof(map));
   424 				}
   425 				
   426 				SetNextMap(map);			
   427 			}
   428 			else
   429 			{
   430 				// We were actually cancelled. What should we do?
   431 			}
   432 		}
   433 
   434 		case MenuAction_VoteEnd:
   435 		{
   436 			decl String:map[32];
   437 			GetMenuItem(menu, param1, map, sizeof(map));
   438 
   439 			if (strcmp(map, VOTE_EXTEND, false) == 0)
   440 			{
   441 				new time;
   442 				if (GetMapTimeLimit(time))
   443 				{
   444 					if (time > 0 && time < GetConVarInt(g_Cvar_ExtendTimeMax))
   445 					{
   446 						ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);						
   447 					}
   448 				}
   449 				
   450 				if (g_Cvar_Winlimit != INVALID_HANDLE)
   451 				{
   452 					new winlimit = GetConVarInt(g_Cvar_Winlimit);
   453 					if (winlimit && winlimit < GetConVarInt(g_Cvar_ExtendRoundMax))
   454 					{
   455 						SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
   456 					}					
   457 				}
   458 				
   459 				if (g_Cvar_Maxrounds != INVALID_HANDLE)
   460 				{
   461 					new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   462 					if (maxrounds && maxrounds < GetConVarInt(g_Cvar_ExtendRoundMax))
   463 					{
   464 						SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
   465 					}
   466 				}
   467 				
   468 				if (g_Cvar_Fraglimit != INVALID_HANDLE)
   469 				{
   470 					new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
   471 					if (fraglimit && fraglimit < GetConVarInt(g_Cvar_ExtendFragMax))
   472 					{
   473 						SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));						
   474 					}
   475 				}
   476 
   477 				PrintToChatAll("[SM] %t", "Current Map Extended");
   478 				LogMessage("Voting for next map has finished. The current map has been extended.");
   479 				
   480 				// We extended, so we'll have to vote again.
   481 				g_HasVoteStarted = false;
   482 				CreateNextVote();
   483 				SetupTimeleftTimer();
   484 			}
   485 			else
   486 			{
   487 				SetNextMap(map);
   488 			}
   489 		}
   490 	}
   491 	
   492 	return 0;
   493 }
   494 
   495 SetNextMap(const String:map[])
   496 {
   497 	SetConVarString(g_Cvar_Nextmap, map);
   498 	PushArrayString(g_OldMapList, map);
   499 				
   500 	if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
   501 	{
   502 		RemoveFromArray(g_OldMapList, 0);
   503 	}
   504 		
   505 	PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map);
   506 	LogMessage("Voting for next map has finished. Nextmap: %s.", map);	
   507 }
   508 
   509 CreateNextVote()
   510 {
   511 	if(g_NextMapList != INVALID_HANDLE)
   512 	{
   513 		ClearArray(g_NextMapList);
   514 	}
   515 	
   516 	decl String:map[32];
   517 	new Handle:tempMaps  = CloneArray(g_MapList);
   518 	
   519 	if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps))
   520 	{
   521 		for (new i = 0; i < GetArraySize(g_OldMapList); i++)
   522 		{
   523 			GetArrayString(g_OldMapList, i, map, sizeof(map));
   524 			new index = FindStringInArray(tempMaps, map);
   525 			if (index != -1)
   526 			{
   527 				RemoveFromArray(tempMaps, index);
   528 			}
   529 		}	
   530 	}
   531 	else
   532 	{
   533 		// If we didn't check against ExcludeMaps, we have to remove the current map.
   534 		GetCurrentMap(map, sizeof(map));
   535 		new index = FindStringInArray(tempMaps, map);
   536 		if (index != -1)
   537 		{
   538 			RemoveFromArray(tempMaps, index);			
   539 		}	
   540 	}
   541 
   542 	new limit = (GetConVarInt(g_Cvar_IncludeMaps) < GetArraySize(tempMaps) ? GetConVarInt(g_Cvar_IncludeMaps) : GetArraySize(tempMaps));
   543 	for (new i = 0; i < limit; i++)
   544 	{
   545 		new b = GetRandomInt(0, GetArraySize(tempMaps) - 1);
   546 		GetArrayString(tempMaps, b, map, sizeof(map));		
   547 		PushArrayString(g_NextMapList, map);
   548 		RemoveFromArray(tempMaps, b);
   549 	}
   550 	
   551 	CloseHandle(tempMaps);
   552 }