Initial import of mapchooser.sp. Untested, not in builds yet, awaiting a change to core
Initial import of mapchooser.sp. Untested, not in builds yet, awaiting a change to core
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/plugins/mapchooser.sp Wed Sep 19 00:24:53 2007 +0000
1.3 @@ -0,0 +1,528 @@
1.4 +/**
1.5 + * vim: set ts=4 :
1.6 + * =============================================================================
1.7 + * SourceMod Mapchooser Plugin
1.8 + * Creates a map vote at appropriate times, setting sm_nextmap to the winning
1.9 + * vote
1.10 + *
1.11 + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
1.12 + * =============================================================================
1.13 + *
1.14 + * This program is free software; you can redistribute it and/or modify it under
1.15 + * the terms of the GNU General Public License, version 3.0, as published by the
1.16 + * Free Software Foundation.
1.17 + *
1.18 + * This program is distributed in the hope that it will be useful, but WITHOUT
1.19 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.20 + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.21 + * details.
1.22 + *
1.23 + * You should have received a copy of the GNU General Public License along with
1.24 + * this program. If not, see <http://www.gnu.org/licenses/>.
1.25 + *
1.26 + * As a special exception, AlliedModders LLC gives you permission to link the
1.27 + * code of this program (as well as its derivative works) to "Half-Life 2," the
1.28 + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
1.29 + * by the Valve Corporation. You must obey the GNU General Public License in
1.30 + * all respects for all other code used. Additionally, AlliedModders LLC grants
1.31 + * this exception to all derivative works. AlliedModders LLC defines further
1.32 + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
1.33 + * or <http://www.sourcemod.net/license.php>.
1.34 + *
1.35 + * Version: $Id$
1.36 + */
1.37 +
1.38 +#pragma semicolon 1
1.39 +#include <sourcemod>
1.40 +
1.41 +#define MAX_MAPS_SELECTION 5
1.42 +
1.43 +new Handle:g_Cvar_Winlimit = INVALID_HANDLE;
1.44 +new Handle:g_Cvar_Maxrounds = INVALID_HANDLE;
1.45 +
1.46 +new Handle:g_Cvar_Nextmap = INVALID_HANDLE;
1.47 +new Handle:g_Cvar_StartTime = INVALID_HANDLE;
1.48 +new Handle:g_Cvar_ExtendTimeMax = INVALID_HANDLE;
1.49 +new Handle:g_Cvar_ExtendTimeStep = INVALID_HANDLE;
1.50 +new Handle:g_Cvar_ExtendRoundsMax = INVALID_HANDLE;
1.51 +new Handle:g_Cvar_ExtendRoundsStep = INVALID_HANDLE;
1.52 +new Handle:g_Cvar_Mapfile = INVALID_HANDLE;
1.53 +new Handle:g_Cvar_ExcludeMaps = INVALID_HANDLE;
1.54 +
1.55 +new Handle:g_VoteTimer = INVALID_HANDLE;
1.56 +new Handle:g_RetryTimer = INVALID_HANDLE;
1.57 +
1.58 +new Handle:g_MapList = INVALID_HANDLE;
1.59 +new Handle:g_OldMapList = INVALID_HANDLE;
1.60 +new Handle:g_NextMapList = INVALID_HANDLE;
1.61 +new Handle:g_VoteMenu = INVALID_HANDLE;
1.62 +
1.63 +new g_MapCount;
1.64 +new g_GameType;
1.65 +new bool:g_HasVoteStarted;
1.66 +new g_mapFileTime;
1.67 +
1.68 +enum
1.69 +{
1.70 + GAME_HL2 = 0,
1.71 + GAME_CSS,
1.72 + GAME_DOD
1.73 +}
1.74 +
1.75 +public OnPluginStart()
1.76 +{
1.77 + LoadTranslations("mapchooser.phrases");
1.78 +
1.79 + g_MapList = CreateArray(33);
1.80 + g_OldMapList = CreateArray(33);
1.81 +
1.82 + g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies the time to start the vote when this much time remains.", _, true, 1.0);
1.83 + g_Cvar_ExtendTimeMax = CreateConVar("sm_extendmap_maxtime", "90", "Specifies the maximum amount of time a map can be extended");
1.84 + g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0);
1.85 + g_Cvar_ExtendRoundsMax = CreateConVar("sm_extendmap_maxrounds", "30", "Specfies the maximum amount of rounds a map can be extended");
1.86 + g_Cvar_ExtendRoundsStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 5.0);
1.87 + g_Cvar_Mapfile = CreateConVar("sm_mapvote_file", "configs/maps.ini", "Map file to use. (Def sourcemod/configs/maps.ini)");
1.88 + g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0);
1.89 +
1.90 + decl String:FolderName[32];
1.91 + GetGameFolderName(FolderName, sizeof(FolderName));
1.92 +
1.93 + if (strcmp(FolderName, "cstrike", false) == 0)
1.94 + {
1.95 + g_GameType = GAME_CSS;
1.96 + g_Cvar_Maxrounds = FindConVar("mp_maxrounds");
1.97 + }
1.98 + else if (strcmp(FolderName, "dod", false) == 0)
1.99 + {
1.100 + g_GameType = GAME_DOD;
1.101 + }
1.102 +
1.103 + if (g_GameType)
1.104 + {
1.105 + g_Cvar_Winlimit = FindConVar("mp_winlimit");
1.106 + HookEvent("team_score", Event_TeamScore);
1.107 + }
1.108 +}
1.109 +
1.110 +public OnMapStart()
1.111 +{
1.112 + g_Cvar_Nextmap = FindConVar("sm_nextmap");
1.113 +
1.114 + if (g_Cvar_Nextmap == INVALID_HANDLE)
1.115 + {
1.116 + LogError("FATAL: Cannot find sm_nextmap cvar. Mapchooser not loaded.");
1.117 + SetFailState("sm_nextmap not found");
1.118 + }
1.119 +
1.120 + if (LoadMaps())
1.121 + {
1.122 + CreateNextVote();
1.123 + SetupTimeleftTimer();
1.124 + }
1.125 +}
1.126 +
1.127 +public OnMapEnd()
1.128 +{
1.129 + g_HasVoteStarted = false;
1.130 +}
1.131 +
1.132 +public OnMapTimeLeftChanged()
1.133 +{
1.134 + SetupTimeleftTimer();
1.135 +}
1.136 +
1.137 +SetupTimeleftTimer()
1.138 +{
1.139 + new time;
1.140 + if (GetMapTimeLeft(time) && time > 0)
1.141 + {
1.142 + new startTime = GetConVarInt(g_Cvar_StartTime);
1.143 + if (time - startTime < 0)
1.144 + {
1.145 + InitiateVote();
1.146 + }
1.147 + else
1.148 + {
1.149 + if (g_VoteTimer != INVALID_HANDLE)
1.150 + {
1.151 + KillTimer(g_VoteTimer);
1.152 + g_VoteTimer = INVALID_HANDLE;
1.153 + }
1.154 +
1.155 + g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote, TIMER_FLAG_NO_MAPCHANGE);
1.156 + }
1.157 + }
1.158 +}
1.159 +
1.160 +public Action:Timer_StartMapVote(Handle:timer)
1.161 +{
1.162 + if (!g_MapCount)
1.163 + {
1.164 + return Plugin_Stop;
1.165 + }
1.166 +
1.167 + if (timer == g_RetryTimer)
1.168 + {
1.169 + g_RetryTimer = INVALID_HANDLE;
1.170 + }
1.171 + else
1.172 + {
1.173 + g_VoteTimer = INVALID_HANDLE;
1.174 + }
1.175 +
1.176 + InitiateVote();
1.177 +
1.178 + return Plugin_Stop;
1.179 +}
1.180 +
1.181 +public Event_TeamScore(Handle:event, const String:name[], bool:dontBroadcast)
1.182 +{
1.183 + if (!g_MapCount)
1.184 + {
1.185 + return;
1.186 + }
1.187 +
1.188 + static Score[2];
1.189 + new Team = GetEventInt(event, "teamid");
1.190 + Score[Team - 2] = GetEventInt(event, "score");
1.191 +
1.192 + new winlimit = GetConVarInt(g_Cvar_Winlimit);
1.193 + if (winlimit)
1.194 + {
1.195 + new Limit = winlimit - 2;
1.196 + if (Score[0] > Limit || Score[1] > Limit)
1.197 + {
1.198 + InitiateVote();
1.199 + }
1.200 + }
1.201 + else if (g_GameType == GAME_CSS)
1.202 + {
1.203 + new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
1.204 + if (maxrounds)
1.205 + {
1.206 + if ((Score[0] + Score[1]) > maxrounds - 2)
1.207 + {
1.208 + InitiateVote();
1.209 + }
1.210 + }
1.211 + }
1.212 +}
1.213 +
1.214 +InitiateVote()
1.215 +{
1.216 + if (g_HasVoteStarted || g_RetryTimer != INVALID_HANDLE)
1.217 + {
1.218 + return;
1.219 + }
1.220 +
1.221 + if (IsVoteInProgress())
1.222 + {
1.223 + // Can't start a vote, try again in 5 seconds.
1.224 + g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote, TIMER_FLAG_NO_MAPCHANGE);
1.225 + return;
1.226 + }
1.227 +
1.228 + g_HasVoteStarted = true;
1.229 + g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL);
1.230 + SetMenuTitle(g_VoteMenu, "Vote Nextmap");
1.231 +
1.232 + KvRewind(g_NextMapList);
1.233 + KvGotoFirstSubKey(g_NextMapList);
1.234 +
1.235 + decl String:map[32];
1.236 + do
1.237 + {
1.238 + KvGetSectionName(g_NextMapList, map, sizeof(map));
1.239 + AddMenuItem(g_VoteMenu, map, map);
1.240 + }
1.241 + while (KvGotoNextKey(g_NextMapList));
1.242 +
1.243 + new bool:AllowExtend, time;
1.244 + if (GetMapTimeLimit(time) && time > 0 && time < GetConVarInt(g_Cvar_ExtendTimeMax))
1.245 + {
1.246 + AllowExtend = true;
1.247 + }
1.248 +
1.249 + if (g_GameType)
1.250 + {
1.251 + // Yes, I could short circuit this above. But I find it cleaner to break
1.252 + // it up into two if's
1.253 + if (GetConVarInt(g_Cvar_Maxrounds) < GetConVarInt(g_Cvar_ExtendRoundsMax)
1.254 + || GetConVarInt(g_Cvar_Winlimit) < GetConVarInt(g_Cvar_ExtendRoundsMax))
1.255 + {
1.256 + AllowExtend = true;
1.257 + }
1.258 + }
1.259 +
1.260 + if (AllowExtend)
1.261 + {
1.262 + AddMenuItem(g_VoteMenu, "##extend##", "Extend");
1.263 + }
1.264 +
1.265 + SetMenuExitButton(g_VoteMenu, false);
1.266 + VoteMenuToAll(g_VoteMenu, 20);
1.267 +
1.268 + LogMessage("Voting for next map has started.");
1.269 + PrintToChatAll("[SM] %t", "Nextmap Voting Started");
1.270 +}
1.271 +
1.272 +public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2)
1.273 +{
1.274 + switch (action)
1.275 + {
1.276 + case MenuAction_End:
1.277 + {
1.278 + g_VoteMenu = INVALID_HANDLE;
1.279 + CloseHandle(menu);
1.280 + }
1.281 +
1.282 + case MenuAction_Display:
1.283 + {
1.284 + decl String:buffer[255];
1.285 + Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1);
1.286 +
1.287 + new Handle:panel = Handle:param2;
1.288 + SetPanelTitle(panel, buffer);
1.289 + }
1.290 +
1.291 + // Why am I commented out? Because BAIL hasn't decided yet if
1.292 + // vote notification will be built into the Vote API.
1.293 + /*case MenuAction_Select:
1.294 + {
1.295 + decl String:Name[32], String:Map[32];
1.296 + GetClientName(param1, Name, sizeof(Name));
1.297 + GetMenuItem(menu, param2, Map, sizeof(Map));
1.298 +
1.299 + PrintToChatAll("[SM] %s has voted for map '%s'", Name, Map);
1.300 + }*/
1.301 +
1.302 + case MenuAction_VoteCancel:
1.303 + {
1.304 + // If we receive 0 votes, pick at random.
1.305 + if (param1 == VoteCancel_NoVotes)
1.306 + {
1.307 + new count = GetMenuItemCount(menu);
1.308 + new item = GetRandomInt(0, count - 1);
1.309 + decl String:map[32];
1.310 + GetMenuItem(menu, item, map, sizeof(map));
1.311 +
1.312 + while (strcmp(map, "##extend##", false) == 0)
1.313 + {
1.314 + item = GetRandomInt(0, count - 1);
1.315 + GetMenuItem(menu, item, map, sizeof(map));
1.316 + }
1.317 +
1.318 + SetNextMap(map);
1.319 + }
1.320 + else
1.321 + {
1.322 + // We were actually cancelled. What should we do?
1.323 + }
1.324 + }
1.325 +
1.326 + case MenuAction_VoteEnd:
1.327 + {
1.328 + decl String:map[32];
1.329 + GetMenuItem(menu, param1, map, sizeof(map));
1.330 +
1.331 + if (strcmp(map, "##extend##", false) == 0)
1.332 + {
1.333 + new time;
1.334 + if (GetMapTimeLimit(time))
1.335 + {
1.336 + if (time > 0 && time < GetConVarInt(g_Cvar_ExtendTimeMax))
1.337 + {
1.338 + ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep));
1.339 + }
1.340 + }
1.341 +
1.342 + if (g_GameType)
1.343 + {
1.344 + new roundstep = GetConVarInt(g_Cvar_ExtendRoundsStep);
1.345 + new winlimit = GetConVarInt(g_Cvar_Winlimit);
1.346 + if (winlimit < GetConVarInt(g_Cvar_ExtendRoundsMax))
1.347 + {
1.348 + SetConVarInt(g_Cvar_Winlimit, winlimit + roundstep);
1.349 + }
1.350 +
1.351 + if (g_GameType == GAME_CSS)
1.352 + {
1.353 + new maxrounds = GetConVarInt(g_Cvar_Maxrounds);
1.354 + if (maxrounds < GetConVarInt(g_Cvar_ExtendRoundsMax))
1.355 + {
1.356 + SetConVarInt(g_Cvar_Maxrounds, maxrounds + roundstep);
1.357 + }
1.358 + }
1.359 + }
1.360 +
1.361 + PrintToChatAll("[SM] %t", "Current Map Extended");
1.362 + LogMessage("Voting for next map has finished. The current map has been extended.");
1.363 +
1.364 + // We extended, so we have to vote again.
1.365 + g_HasVoteStarted = false;
1.366 + }
1.367 + else
1.368 + {
1.369 + SetNextMap(map);
1.370 + }
1.371 + }
1.372 + }
1.373 +}
1.374 +
1.375 +SetNextMap(const String:map[])
1.376 +{
1.377 + SetConVarString(g_Cvar_Nextmap, map);
1.378 + PushArrayString(g_OldMapList, map);
1.379 +
1.380 + if (GetArraySize(g_OldMapList) > 5)
1.381 + {
1.382 + RemoveFromArray(g_OldMapList, 0);
1.383 + }
1.384 +
1.385 + PrintToChatAll("[SM] %t", "Nextmap Voting Finished");
1.386 + LogMessage("Voting for next map has finished. Nextmap: %s.", map);
1.387 +}
1.388 +
1.389 +
1.390 +CreateNextVote()
1.391 +{
1.392 + g_NextMapList = CreateKeyValues("MapChooser");
1.393 +
1.394 + new bool:oldMaps = false;
1.395 + if (g_MapCount > GetConVarInt(g_Cvar_ExcludeMaps))
1.396 + {
1.397 + oldMaps = true;
1.398 + }
1.399 +
1.400 + decl String:map[32];
1.401 + for (new i = 0, b, count; i < g_MapCount; i++)
1.402 + {
1.403 + b = GetRandomInt(0, g_MapCount - 1);
1.404 + GetArrayString(g_MapList, b, map, sizeof(map));
1.405 +
1.406 + if (!IsMapSelected(map) && !(oldMaps && IsMapOld(map)))
1.407 + {
1.408 +
1.409 + KvJumpToKey(g_NextMapList, map, true);
1.410 + count++;
1.411 + }
1.412 +
1.413 + if (count == MAX_MAPS_SELECTION || count >= 9)
1.414 + {
1.415 + break;
1.416 + }
1.417 + }
1.418 +}
1.419 +
1.420 +LoadMaps()
1.421 +{
1.422 + new bool:fileFound;
1.423 +
1.424 + decl String:mapPath[256], String:mapFile[64];
1.425 + GetConVarString(g_Cvar_Mapfile, mapFile, 64);
1.426 + BuildPath(Path_SM, mapPath, sizeof(mapFile), mapFile);
1.427 + fileFound = FileExists(mapPath);
1.428 + if (!fileFound)
1.429 + {
1.430 + new Handle:mapCycleFile = FindConVar("mapcyclefile");
1.431 + GetConVarString(mapCycleFile, mapPath, sizeof(mapPath));
1.432 + fileFound = FileExists(mapPath);
1.433 + }
1.434 +
1.435 + if (!fileFound)
1.436 + {
1.437 + LogError("Unable to locate g_Cvar_Mapfile or mapcyclefile, no maps loaded.");
1.438 +
1.439 + g_MapCount = 0;
1.440 + if (g_MapList != INVALID_HANDLE)
1.441 + {
1.442 + ClearArray(g_MapList);
1.443 + }
1.444 +
1.445 + return 0;
1.446 + }
1.447 +
1.448 + // If the file hasn't changed, there's no reason to reload
1.449 + // all of the maps.
1.450 + new fileTime = GetFileTime(mapPath, FileTime_LastChange);
1.451 + if (g_mapFileTime == fileTime)
1.452 + {
1.453 + return g_MapCount;
1.454 + }
1.455 +
1.456 + g_mapFileTime = fileTime;
1.457 +
1.458 + // Reset the array
1.459 + g_MapCount = 0;
1.460 + if (g_MapList != INVALID_HANDLE)
1.461 + {
1.462 + ClearArray(g_MapList);
1.463 + }
1.464 +
1.465 + PrintToServer("[SM] Loading mapchooser map file [%s]", mapPath);
1.466 +
1.467 + decl String:currentMap[32];
1.468 + GetCurrentMap(currentMap, sizeof(currentMap));
1.469 +
1.470 + new Handle:file = OpenFile(mapPath, "rt");
1.471 + if (file == INVALID_HANDLE)
1.472 + {
1.473 + LogError("[SM] Could not open file: %s", mapPath);
1.474 + return 0;
1.475 + }
1.476 +
1.477 + decl String:buffer[64], len;
1.478 + while (!IsEndOfFile(file) && ReadFileLine(file, buffer, sizeof(buffer)))
1.479 + {
1.480 + TrimString(buffer);
1.481 +
1.482 + if ((len = StrContains(buffer, ".bsp", false)) != -1)
1.483 + {
1.484 + buffer[len] = '\0';
1.485 + }
1.486 +
1.487 + if (buffer[0] == '\0' || !IsValidCvarChar(buffer[0]) || !IsMapValid(buffer)
1.488 + || strcmp(currentMap, buffer, false) == 0)
1.489 + {
1.490 + continue;
1.491 + }
1.492 +
1.493 + PushArrayString(g_MapList, buffer);
1.494 + g_MapCount++;
1.495 + }
1.496 +
1.497 + CloseHandle(file);
1.498 + return g_MapCount;
1.499 +}
1.500 +
1.501 +stock bool:IsValidCvarChar(c)
1.502 +{
1.503 + return (c == '_' || IsCharAlpha(c) || IsCharNumeric(c));
1.504 +}
1.505 +
1.506 +stock bool:IsMapSelected(const String:Map[])
1.507 +{
1.508 + KvRewind(g_NextMapList);
1.509 + if (KvJumpToKey(g_NextMapList, Map))
1.510 + {
1.511 + return true;
1.512 + }
1.513 +
1.514 + return false;
1.515 +}
1.516 +
1.517 +stock bool:IsMapOld(const String:map[])
1.518 +{
1.519 + decl String:oldMap[64];
1.520 +
1.521 + for (new i = 0; i < GetArraySize(g_OldMapList); i++)
1.522 + {
1.523 + GetArrayString(g_OldMapList, i, oldMap, sizeof(oldMap));
1.524 + if(strcmp(map, oldMap, false) == 0)
1.525 + {
1.526 + return false;
1.527 + }
1.528 + }
1.529 +
1.530 + return true;
1.531 +}
1.532 \ No newline at end of file