plugins/mapchooser.sp
author Borja Ferrer <faluco@alliedmods.net>
Fri Nov 23 23:24:40 2007 +0000 (2007-11-23)
changeset 1718 2655287f48dd
parent 1698 7caa46a2eb2c
child 1719 c9d5e55f0899
permissions -rw-r--r--
gah forgot this
     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 g_TotalRounds;
    78 new bool:g_HasVoteStarted;
    79 new g_mapFileTime;
    80 
    81 #define VOTE_EXTEND "##extend##"
    82 
    83 public OnPluginStart()
    84 {
    85 	LoadTranslations("mapchooser.phrases");
    86 	
    87 	new arraySize = ByteCountToCells(33);
    88 	g_MapList = CreateArray(arraySize);
    89 	g_OldMapList = CreateArray(arraySize);
    90 	g_NextMapList = CreateArray(arraySize);
    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 	if (g_TeamScores != INVALID_HANDLE)
   144 	{
   145 		CloseHandle(g_TeamScores);
   146 	}
   147 	
   148 	g_TeamScores = CreateArray(2);
   149 	g_TotalRounds = 0;
   150 }
   151 
   152 public OnMapEnd()
   153 {
   154 	g_HasVoteStarted = false;
   155 	
   156 	if (g_VoteTimer != INVALID_HANDLE)
   157 	{
   158 		KillTimer(g_VoteTimer);
   159 		g_VoteTimer = INVALID_HANDLE;
   160 	}
   161 	
   162 	if (g_RetryTimer != INVALID_HANDLE)
   163 	{
   164 		KillTimer(g_RetryTimer);
   165 		g_RetryTimer = INVALID_HANDLE;
   166 	}
   167 }
   168 
   169 public OnMapTimeLeftChanged()
   170 {
   171 	if (GetArraySize(g_MapList))
   172 	{
   173 		SetupTimeleftTimer();
   174 	}
   175 }
   176 
   177 SetupTimeleftTimer()
   178 {
   179 	new time;
   180 	if (GetMapTimeLeft(time) && time > 0)
   181 	{
   182 		new startTime = GetConVarInt(g_Cvar_StartTime) * 60;
   183 		if (time - startTime < 0)
   184 		{
   185 			InitiateVote();		
   186 		}
   187 		else
   188 		{
   189 			if (g_VoteTimer != INVALID_HANDLE)
   190 			{
   191 				KillTimer(g_VoteTimer);
   192 				g_VoteTimer = INVALID_HANDLE;
   193 			}	
   194 			
   195 			g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote);
   196 		}		
   197 	}
   198 }
   199 
   200 public Action:Timer_StartMapVote(Handle:timer)
   201 {
   202 	if (!GetArraySize(g_MapList))
   203 	{
   204 		return Plugin_Stop;
   205 	}		
   206 	
   207 	if (timer == g_RetryTimer)
   208 	{
   209 		g_RetryTimer = INVALID_HANDLE;
   210 	}
   211 	else
   212 	{
   213 		g_VoteTimer = INVALID_HANDLE;
   214 	}
   215 
   216 	InitiateVote();
   217 
   218 	return Plugin_Stop;
   219 }
   220 
   221 /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */
   222 public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
   223 {
   224 	if (!GetArraySize(g_MapList) || g_HasVoteStarted)
   225 	{
   226 		return;
   227 	}
   228 
   229 	new winner = GetEventInt(event, "winner");
   230 	
   231 	if (winner == 0 || winner == 1)
   232 	{
   233 		return;
   234 	}
   235 
   236 	g_TotalRounds++;
   237 	
   238 	new team[2], teamPos = -1;
   239 	for (new i; i < GetArraySize(g_TeamScores); i++)
   240 	{
   241 		GetArrayArray(g_TeamScores, i, team);
   242 		if (team[0] == winner)
   243 		{
   244 			teamPos = i;
   245 			break;
   246 		}		
   247 	}
   248 	
   249 	if (teamPos == -1)
   250 	{
   251 		team[0] = winner;
   252 		team[1] = 1;
   253 		PushArrayArray(g_TeamScores, team);
   254 	}
   255 	else
   256 	{
   257 		team[1]++;
   258 		SetArrayArray(g_TeamScores, teamPos, team);
   259 	}	
   260 	
   261 	if (g_Cvar_Winlimit != INVALID_HANDLE)
   262 	{
   263 		new winlimit = GetConVarInt(g_Cvar_Winlimit);
   264 		if (winlimit)
   265 		{
   266 			if (team[1] >= (winlimit - GetConVarInt(g_Cvar_StartRounds)))
   267 			{
   268 				InitiateVote();
   269 			}
   270 		}
   271 	}
   272 	
   273 	if (g_Cvar_Maxrounds != INVALID_HANDLE)
   274 	{
   275 		new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   276 		if (maxrounds)
   277 		{
   278 			if (g_TotalRounds >= (maxrounds - GetConVarInt(g_Cvar_StartRounds)))
   279 			{
   280 				InitiateVote();
   281 			}			
   282 		}
   283 	}
   284 }
   285 
   286 public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
   287 {
   288 	if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted)
   289 	{
   290 		return;
   291 	}
   292 	
   293 	if (!GetConVarInt(g_Cvar_Fraglimit))
   294 	{
   295 		return;
   296 	}
   297 
   298 	new fragger = GetClientOfUserId(GetEventInt(event, "attacker"));
   299 	if (fragger && GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags)))
   300 	{
   301 		InitiateVote();
   302 	}
   303 }
   304 
   305 public Action:Command_Mapvote(client, args)
   306 {
   307 	InitiateVote();
   308 
   309 	return Plugin_Handled;	
   310 }
   311 
   312 InitiateVote()
   313 {
   314 	if (g_HasVoteStarted || g_RetryTimer != INVALID_HANDLE)
   315 	{
   316 		return;
   317 	}
   318 	
   319 	if (IsVoteInProgress())
   320 	{
   321 		// Can't start a vote, try again in 5 seconds.
   322 		g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote);
   323 		return;
   324 	}		
   325 	
   326 	g_HasVoteStarted = true;
   327 	g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
   328 	SetMenuTitle(g_VoteMenu, "Vote Nextmap");
   329 
   330 	decl String:map[32];
   331 	for (new i = 0; i < GetArraySize(g_NextMapList); i++)
   332 	{
   333 		GetArrayString(g_NextMapList, i, map, sizeof(map));
   334 		AddMenuItem(g_VoteMenu, map, map);
   335 	}
   336 
   337 	if (GetConVarBool(g_Cvar_Extend))
   338 	{
   339 		new bool:allowExtend, time;
   340 		if (GetMapTimeLimit(time) && time > 0 && time < GetConVarInt(g_Cvar_ExtendTimeMax))
   341 		{
   342 			allowExtend = true;
   343 		}
   344 
   345 		if (g_Cvar_Winlimit != INVALID_HANDLE && GetConVarInt(g_Cvar_Winlimit) < GetConVarInt(g_Cvar_ExtendRoundMax))
   346 		{
   347 			allowExtend = true;
   348 		}	
   349 	
   350 		if (g_Cvar_Maxrounds != INVALID_HANDLE && GetConVarInt(g_Cvar_Maxrounds) < GetConVarInt(g_Cvar_ExtendRoundMax))
   351 		{
   352 			allowExtend = true;
   353 		}
   354 	
   355 		if (g_Cvar_Fraglimit != INVALID_HANDLE && GetConVarInt(g_Cvar_Fraglimit) < GetConVarInt(g_Cvar_ExtendFragMax))
   356 		{
   357 			allowExtend = true;
   358 		}
   359 
   360 		if (allowExtend)
   361 		{
   362 			AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map");
   363 		}
   364 	}
   365 
   366 	SetMenuExitButton(g_VoteMenu, false);
   367 	VoteMenuToAll(g_VoteMenu, 20);
   368 
   369 	LogMessage("Voting for next map has started.");
   370 	PrintToChatAll("[SM] %t", "Nextmap Voting Started");
   371 }
   372 
   373 public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
   374 {
   375 	switch (action)
   376 	{
   377 		case MenuAction_End:
   378 		{
   379 			g_VoteMenu = INVALID_HANDLE;
   380 			CloseHandle(menu);
   381 		}
   382 		
   383 		case MenuAction_Display:
   384 		{
   385 	 		decl String:buffer[255];
   386 			Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
   387 
   388 			new Handle:panel = Handle:param2;
   389 			SetPanelTitle(panel, buffer);
   390 		}		
   391 		
   392 		case MenuAction_DisplayItem:
   393 		{
   394 			if (GetMenuItemCount(menu) - 1 == param2)
   395 			{
   396 				decl String:map[64], String:buffer[255];
   397 				GetMenuItem(menu, param2, map, sizeof(map));
   398 				if (strcmp(map, VOTE_EXTEND, false) == 0)
   399 				{
   400 					Format(buffer, sizeof(buffer), "%T", "Extend Map", param1);
   401 					return RedrawMenuItem(buffer);
   402 				}
   403 			}
   404 		}		
   405 
   406 		// Why am I commented out? Because BAIL hasn't decided yet if
   407 		// vote notification will be built into the Vote API.
   408 		/*case MenuAction_Select:
   409 		{
   410 			decl String:Name[32], String:Map[32];
   411 			GetClientName(param1, Name, sizeof(Name));
   412 			GetMenuItem(menu, param2, Map, sizeof(Map));
   413 
   414 			PrintToChatAll("[SM] %s has voted for map '%s'", Name, Map);
   415 		}*/
   416 		
   417 		case MenuAction_VoteCancel:
   418 		{
   419 			// If we receive 0 votes, pick at random.
   420 			if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode))
   421 			{
   422 				new count = GetMenuItemCount(menu);
   423 				new item = GetRandomInt(0, count - 1);
   424 				decl String:map[32];
   425 				GetMenuItem(menu, item, map, sizeof(map));
   426 				
   427 				while (strcmp(map, VOTE_EXTEND, false) == 0)
   428 				{
   429 					item = GetRandomInt(0, count - 1);
   430 					GetMenuItem(menu, item, map, sizeof(map));
   431 				}
   432 				
   433 				SetNextMap(map);			
   434 			}
   435 			else
   436 			{
   437 				// We were actually cancelled. What should we do?
   438 			}
   439 		}
   440 
   441 		case MenuAction_VoteEnd:
   442 		{
   443 			decl String:map[32];
   444 			GetMenuItem(menu, param1, map, sizeof(map));
   445 
   446 			if (strcmp(map, VOTE_EXTEND, false) == 0)
   447 			{
   448 				new time;
   449 				if (GetMapTimeLimit(time))
   450 				{
   451 					if (time > 0 && time < GetConVarInt(g_Cvar_ExtendTimeMax))
   452 					{
   453 						ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60);						
   454 					}
   455 				}
   456 				
   457 				if (g_Cvar_Winlimit != INVALID_HANDLE)
   458 				{
   459 					new winlimit = GetConVarInt(g_Cvar_Winlimit);
   460 					if (winlimit && winlimit < GetConVarInt(g_Cvar_ExtendRoundMax))
   461 					{
   462 						SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep));
   463 					}					
   464 				}
   465 				
   466 				if (g_Cvar_Maxrounds != INVALID_HANDLE)
   467 				{
   468 					new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
   469 					if (maxrounds && maxrounds < GetConVarInt(g_Cvar_ExtendRoundMax))
   470 					{
   471 						SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep));
   472 					}
   473 				}
   474 				
   475 				if (g_Cvar_Fraglimit != INVALID_HANDLE)
   476 				{
   477 					new fraglimit = GetConVarInt(g_Cvar_Fraglimit);
   478 					if (fraglimit && fraglimit < GetConVarInt(g_Cvar_ExtendFragMax))
   479 					{
   480 						SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep));						
   481 					}
   482 				}
   483 
   484 				PrintToChatAll("[SM] %t", "Current Map Extended");
   485 				LogMessage("Voting for next map has finished. The current map has been extended.");
   486 				
   487 				// We extended, so we'll have to vote again.
   488 				g_HasVoteStarted = false;
   489 				CreateNextVote();
   490 				SetupTimeleftTimer();
   491 			}
   492 			else
   493 			{
   494 				SetNextMap(map);
   495 			}
   496 		}
   497 	}
   498 	
   499 	return 0;
   500 }
   501 
   502 SetNextMap(const String:map[])
   503 {
   504 	SetConVarString(g_Cvar_Nextmap, map);
   505 	PushArrayString(g_OldMapList, map);
   506 				
   507 	if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps))
   508 	{
   509 		RemoveFromArray(g_OldMapList, 0);
   510 	}
   511 		
   512 	PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map);
   513 	LogMessage("Voting for next map has finished. Nextmap: %s.", map);	
   514 }
   515 
   516 CreateNextVote()
   517 {
   518 	if(g_NextMapList != INVALID_HANDLE)
   519 	{
   520 		ClearArray(g_NextMapList);
   521 	}
   522 	
   523 	decl String:map[32];
   524 	new Handle:tempMaps  = CloneArray(g_MapList);
   525 	
   526 	if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps))
   527 	{
   528 		for (new i = 0; i < GetArraySize(g_OldMapList); i++)
   529 		{
   530 			GetArrayString(g_OldMapList, i, map, sizeof(map));
   531 			new index = FindStringInArray(tempMaps, map);
   532 			if (index != -1)
   533 			{
   534 				RemoveFromArray(tempMaps, index);
   535 			}
   536 		}	
   537 	}
   538 	else
   539 	{
   540 		// If we didn't check against ExcludeMaps, we have to remove the current map.
   541 		GetCurrentMap(map, sizeof(map));
   542 		new index = FindStringInArray(tempMaps, map);
   543 		if (index != -1)
   544 		{
   545 			RemoveFromArray(tempMaps, index);			
   546 		}	
   547 	}
   548 
   549 	new limit = (GetConVarInt(g_Cvar_IncludeMaps) < GetArraySize(tempMaps) ? GetConVarInt(g_Cvar_IncludeMaps) : GetArraySize(tempMaps));
   550 	for (new i = 0; i < limit; i++)
   551 	{
   552 		new b = GetRandomInt(0, GetArraySize(tempMaps) - 1);
   553 		GetArrayString(tempMaps, b, map, sizeof(map));		
   554 		PushArrayString(g_NextMapList, map);
   555 		RemoveFromArray(tempMaps, b);
   556 	}
   557 	
   558 	CloseHandle(tempMaps);
   559 }