ds@1952: /** ds@1952: * vim: set ts=4 : ds@1952: * ============================================================================= ds@1952: * SourceMod Map Management Plugin ds@1952: * Provides all map related functionality, including map changing, map voting, ds@1952: * and nextmap. ds@1952: * ds@1952: * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. ds@1952: * ============================================================================= ds@1952: * ds@1952: * This program is free software; you can redistribute it and/or modify it under ds@1952: * the terms of the GNU General Public License, version 3.0, as published by the ds@1952: * Free Software Foundation. ds@1952: * ds@1952: * This program is distributed in the hope that it will be useful, but WITHOUT ds@1952: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ds@1952: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ds@1952: * details. ds@1952: * ds@1952: * You should have received a copy of the GNU General Public License along with ds@1952: * this program. If not, see . ds@1952: * ds@1952: * As a special exception, AlliedModders LLC gives you permission to link the ds@1952: * code of this program (as well as its derivative works) to "Half-Life 2," the ds@1952: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software ds@1952: * by the Valve Corporation. You must obey the GNU General Public License in ds@1952: * all respects for all other code used. Additionally, AlliedModders LLC grants ds@1952: * this exception to all derivative works. AlliedModders LLC defines further ds@1952: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), ds@1952: * or . ds@1952: * ds@1952: * Version: $Id$ ds@1952: */ ds@1952: ds@1952: #pragma semicolon 1 ds@1952: #include ds@1952: ds@1952: public Plugin:myinfo = ds@1952: { ds@1952: name = "Map Manager", ds@1952: author = "AlliedModders LLC", ds@1952: description = "Map Management", ds@1952: version = SOURCEMOD_VERSION, ds@1952: url = "http://www.sourcemod.net/" ds@1952: }; ds@1952: ds@1952: #include "mapmanagement/globals.sp" ds@1952: #include "mapmanagement/commands.sp" ds@1952: #include "mapmanagement/events.sp" ds@1952: #include "mapmanagement/functions.sp" ds@1952: #include "mapmanagement/menus.sp" ds@1952: #include "mapmanagement/timers.sp" ds@1952: #include "mapmanagement/votes.sp" ds@1952: ds@1952: public OnPluginStart() ds@1952: { ds@1952: LoadTranslations("mapmanager.phrases"); ds@1952: ds@1952: // Prepare nextmap functionality. ds@1952: g_VGUIMenu = GetUserMessageId("VGUIMenu"); ds@1952: if (g_VGUIMenu == INVALID_MESSAGE_ID) ds@1952: { ds@1952: LogError("FATAL: Cannot find VGUIMenu user message id. MapManager crippled."); ds@1952: g_NextMapEnabled = false; ds@1952: } ds@1952: HookUserMessage(g_VGUIMenu, UserMsg_VGUIMenu); ds@1952: ds@1952: // Create all of the arrays, sized for a 64 character string. ds@1952: new arraySize = ByteCountToCells(64); ds@1952: g_MapCycle = CreateArray(arraySize); ds@1952: g_MapList = CreateArray(arraySize); ds@1952: g_MapHistory = CreateArray(arraySize); ds@1952: g_NextVoteMaps = CreateArray(arraySize); ds@1952: g_SelectedMaps = CreateArray(arraySize); ds@1952: g_NominatedMaps = CreateArray(arraySize); ds@1952: ds@1952: g_TeamScores = CreateArray(2); ds@1952: ds@1952: // Hook say ds@1952: RegConsoleCmd("say", Command_Say); ds@1952: RegConsoleCmd("say_team", Command_Say); ds@1952: ds@1952: // Register all commands. ds@1952: RegAdminCmd("sm_map", Command_Map, ADMFLAG_CHANGEMAP, "sm_map [r/e]"); ds@1952: RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap "); ds@1952: RegAdminCmd("sm_votemap", Command_Votemap, ADMFLAG_VOTE|ADMFLAG_CHANGEMAP, "sm_votemap [mapname2] ... [mapname5] "); ds@1952: RegAdminCmd("sm_maplist", Command_List, ADMFLAG_GENERIC, "sm_maplist"); ds@1952: RegAdminCmd("sm_nominate", Command_Addmap, ADMFLAG_CHANGEMAP, "sm_nominate - Nominates a map for RockTheVote and MapChooser. Overrides existing nominations."); ds@1952: RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces the MapChooser vote to occur."); ds@1952: ds@1952: if (GetCommandFlags("nextmap") == INVALID_FCVAR_FLAGS) ds@1952: { ds@1952: RegServerCmd("nextmap", Command_Nextmap); ds@1952: } ds@1952: ds@1952: // Register all convars ds@1952: g_Cvar_Nextmap = CreateConVar("sm_nextmap", "", "Sets the Next Map", FCVAR_NOTIFY); ds@1952: ds@1952: g_Cvar_MapCount = CreateConVar("sm_mm_maps", "4", "Number of maps to be voted on at end of map or RTV. 2 to 6. (Def 4)", 0, true, 2.0, true, 6.0); ds@1952: g_Cvar_Excludes = CreateConVar("sm_mm_exclude", "5", "Specifies how many past maps to exclude from end of map vote and RTV.", _, true, 0.0); ds@1952: ds@1952: g_Cvar_MapChooser = CreateConVar("sm_mm_mapchooser", "0", "Enables MapChooser (End of Map voting)", 0, true, 0.0, true, 1.0); ds@1952: g_Cvar_RockTheVote = CreateConVar("sm_mm_rockthevote", "0", "Enables RockTheVote (Player initiated map votes)", 0, true, 0.0, true, 1.0); ds@1952: g_Cvar_Randomize = CreateConVar("sm_mm_randomize", "0", "Enabled Randomizer (Randomly picks the next map)", 0, true, 0.0, true, 1.0); ds@1952: g_Cvar_Nominate = CreateConVar("sm_mm_nominate", "1", "Enables nomination system.", 0, true, 0.0, true, 1.0); ds@1952: ds@1952: g_Cvar_VoteMap = CreateConVar("sm_mm_votemap", "0.60", "Percentage required for successful sm_votemap.", 0, true, 0.05, true, 1.0); ds@1952: g_Cvar_RTVLimit = CreateConVar("sm_mm_rtvlimit", "0.60", "Required percentage of players needed to rockthevote", 0, true, 0.05, true, 1.0); ds@1952: g_Cvar_MinPlayers = CreateConVar("sm_mm_minplayers", "0", "Number of players required before RTV will be enabled.", 0, true, 0.0, true, float(MAXPLAYERS)); ds@1952: ds@1952: g_Cvar_StartTime = CreateConVar("sm_mapvote_start", "3.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0); ds@1952: g_Cvar_StartRounds = CreateConVar("sm_mapvote_startround", "2.0", "Specifies when to start the vote based on rounds remaining.", _, true, 1.0); ds@1952: g_Cvar_StartFrags = CreateConVar("sm_mapvote_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0); ds@1952: g_Cvar_ExtendTimeMax = CreateConVar("sm_extendmap_maxtime", "90", "Specifies the maximum amount of time a map can be extended", _, true, 0.0); ds@1952: g_Cvar_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0); ds@1952: g_Cvar_ExtendRoundMax = CreateConVar("sm_extendmap_maxrounds", "30", "Specfies the maximum amount of rounds a map can be extended", _, true, 0.0); ds@1952: g_Cvar_ExtendRoundStep = CreateConVar("sm_extendmap_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 5.0); ds@1952: g_Cvar_ExtendFragMax = CreateConVar("sm_extendmap_maxfrags", "150", "Specfies the maximum frags allowed that a map can be extended.", _, true, 0.0); ds@1952: g_Cvar_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0); ds@1952: g_Cvar_Mapfile = CreateConVar("sm_mapvote_file", "configs/maps.ini", "Map file to use. (Def sourcemod/configs/maps.ini)"); ds@1952: g_Cvar_ExcludeMaps = CreateConVar("sm_mapvote_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0); ds@1952: g_Cvar_IncludeMaps = CreateConVar("sm_mapvote_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 6.0); ds@1952: 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); ds@1952: 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); ds@1952: ds@1952: // Find game convars ds@1952: g_Cvar_Chattime = FindConVar("mp_chattime"); ds@1952: g_Cvar_Winlimit = FindConVar("mp_winlimit"); ds@1952: g_Cvar_Maxrounds = FindConVar("mp_maxrounds"); ds@1952: g_Cvar_Fraglimit = FindConVar("mp_fraglimit"); ds@1952: ds@1952: if (GetCommandFlags("nextmap") == INVALID_FCVAR_FLAGS) ds@1952: { ds@1952: RegServerCmd("nextmap", Command_Nextmap); ds@1952: } ds@1952: ds@1952: // Hook events ds@1952: HookEvent("round_end", Event_RoundEnd); // We always require round_end ds@1952: if (g_Cvar_Fraglimit != INVALID_HANDLE) ds@1952: { ds@1952: HookEvent("player_death", Event_PlayerDeath); ds@1952: } ds@1952: ds@1952: // Set to the current map so OnMapStart() will know what to do ds@1952: decl String:currentMap[64]; ds@1952: GetCurrentMap(currentMap, 64); ds@1952: SetNextmap(currentMap); ds@1952: ds@1952: // Create necessary menus for TopMenu ds@1952: g_Menu_Map = CreateMenu(MenuHandler_Map); ds@1952: SetMenuTitle(g_Menu_Map, "Please select a map"); ds@1952: SetMenuExitBackButton(g_Menu_Map, true); ds@1952: ds@1952: g_Menu_Votemap = CreateMenu(MenuHandler_VoteMap, MenuAction_DrawItem); ds@1952: SetMenuTitle(g_Menu_Votemap, "Please select a map"); ds@1952: SetMenuExitBackButton(g_Menu_Votemap, true); ds@1952: ds@1952: // Bind TopMenu commands to adminmenu_maplist.ini, in cases it doesn't exist in maplists.cfg ds@1952: decl String:mapListPath[PLATFORM_MAX_PATH]; ds@1952: BuildPath(Path_SM, mapListPath, sizeof(mapListPath), "configs/adminmenu_maplist.ini"); ds@1952: SetMapListCompatBind("sm_map menu", mapListPath); ds@1952: SetMapListCompatBind("sm_votemap menu", mapListPath); ds@1952: ds@1952: // Account for late loading ds@1952: new Handle:topmenu; ds@1952: if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != INVALID_HANDLE)) ds@1952: { ds@1952: OnAdminMenuReady(topmenu); ds@1952: } ds@1952: ds@1952: AutoExecConfig(true, "mapmanager"); ds@1952: } ds@1952: ds@1952: public OnAdminMenuReady(Handle:topmenu) ds@1952: { ds@1952: /* Block us from being called twice */ ds@1952: if (topmenu == hTopMenu) ds@1952: { ds@1952: return; ds@1952: } ds@1952: ds@1952: /* Save the Handle */ ds@1952: hTopMenu = topmenu; ds@1952: ds@1952: new TopMenuObject:server_commands = FindTopMenuCategory(hTopMenu, ADMINMENU_SERVERCOMMANDS); ds@1952: ds@1952: if (server_commands != INVALID_TOPMENUOBJECT) ds@1952: { ds@1952: AddToTopMenu(hTopMenu, ds@1952: "sm_map", ds@1952: TopMenuObject_Item, ds@1952: AdminMenu_Map, ds@1952: server_commands, ds@1952: "sm_map", ds@1952: ADMFLAG_CHANGEMAP); ds@1952: } ds@1952: ds@1952: new TopMenuObject:voting_commands = FindTopMenuCategory(hTopMenu, ADMINMENU_VOTINGCOMMANDS); ds@1952: ds@1952: if (voting_commands != INVALID_TOPMENUOBJECT) ds@1952: { ds@1952: AddToTopMenu(hTopMenu, ds@1952: "sm_votemap", ds@1952: TopMenuObject_Item, ds@1952: AdminMenu_VoteMap, ds@1952: voting_commands, ds@1952: "sm_votemap", ds@1952: ADMFLAG_VOTE|ADMFLAG_CHANGEMAP); ds@1952: } ds@1952: } ds@1952: ds@1952: public OnLibraryRemoved(const String:name[]) ds@1952: { ds@1952: if (strcmp(name, "adminmenu") == 0) ds@1952: { ds@1952: hTopMenu = INVALID_HANDLE; ds@1952: } ds@1952: } ds@1952: ds@1952: public OnConfigsExecuted() ds@1952: { ds@1952: // Add map logic here ds@1952: ds@1952: // Get the current and last maps. ds@1952: decl String:lastMap[64], String:currentMap[64]; ds@1952: GetConVarString(g_Cvar_Nextmap, lastMap, 64); ds@1952: GetCurrentMap(currentMap, 64); ds@1952: ds@1952: // Why am I doing this? If we switched to a new map, but it wasn't what we expected (Due to sm_map, sm_votemap, or ds@1952: // some other plugin/command), we don't want to scramble the map cycle. Or for example, admin switches to a custom map ds@1952: // not in mapcyclefile. So we keep it set to the last expected nextmap. - ferret ds@1952: if (strcmp(lastMap, currentMap) == 0) ds@1952: { ds@1952: FindAndSetNextMap(); ds@1952: } ds@1952: ds@1952: // Build map menus for sm_map, sm_votemap, and RTV. ds@1952: BuildMapMenu(g_Menu_Map, list); ds@1952: BuildMapMenu(g_Menu_VoteMap, list); ds@1952: ds@1952: // If the Randomize option is on, randomize! ds@1952: if (GetConVarBool(g_Cvar_Randomize)) ds@1952: { ds@1952: CreateTimer(5.0, Timer_RandomizeNextmap); ds@1952: } ds@1952: ds@1952: // If MapChooser is active, start it up! ds@1952: if (GetConVarBool(g_Cvar_MapChooser)) ds@1952: { ds@1952: SetupTimeleftTimer(); ds@1952: SetConVarString(g_Cvar_Nextmap, "Pending Vote"); ds@1952: } ds@1952: ds@1952: // If RockTheVote is active, start it up! ds@1952: if (GetConVarBool(g_Cvar_RockTheVote)) ds@1952: { ds@1952: BuildMapMenu(g_Menu_RTV, list); ds@1952: CreateTimer(30.0, Timer_DelayRTV); ds@1952: } ds@1952: } ds@1952: ds@1952: // Reinitialize all our various globals ds@1952: public OnMapStart() ds@1952: { ds@1952: if (g_Nominate != INVALID_HANDLE) ds@1952: { ds@1952: ClearArray(g_Nominate); ds@1952: } ds@1952: ds@1952: if (g_TeamScores != INVALID_HANDLE) ds@1952: { ds@1952: ClearArray(g_TeamScores); ds@1952: } ds@1952: ds@1952: g_TotalRounds = 0; ds@1952: ds@1952: g_RTV_Voters = 0; ds@1952: g_RTV_Votes = 0; ds@1952: g_RTV_VotesNeeded = 0; ds@1952: g_RTV_Started = false; ds@1952: g_RTV_Ended = false; ds@1952: } ds@1952: ds@1952: // Reset globals as necessary and kill timers ds@1952: public OnMapEnd() ds@1952: { ds@1952: g_IntermissionCalled = false; ds@1952: g_HasVoteStarted = false; ds@1952: ds@1952: g_RTV_Allowed = false; ds@1952: ds@1952: if (g_VoteTimer != INVALID_HANDLE) ds@1952: { ds@1952: KillTimer(g_VoteTimer); ds@1952: g_VoteTimer = INVALID_HANDLE; ds@1952: } ds@1952: ds@1952: if (g_RetryTimer != INVALID_HANDLE) ds@1952: { ds@1952: KillTimer(g_RetryTimer); ds@1952: g_RetryTimer = INVALID_HANDLE; ds@1952: } ds@1952: } ds@1952: ds@1952: public bool:OnClientConnect(client, String:rejectmsg[], maxlen) ds@1952: { ds@1952: if (IsFakeClient(client)) ds@1952: { ds@1952: return true; ds@1952: } ds@1952: ds@1952: // If RTV is active, deal with vote counting. ds@1952: if (GetConVarBool(g_Cvar_RockTheVote)) ds@1952: { ds@1952: g_RTV_Voted[client] = false; ds@1952: g_RTV_Voters++; ds@1952: g_RTV_VotesNeeded = RoundToFloor(float(g_Voters) * GetConVarFloat(g_Cvar_Needed)); ds@1952: } ds@1952: ds@1952: // If Nominate is active, let the new client nominate ds@1952: if (GetConVarBool(g_Cvar_Nominate)) ds@1952: { ds@1952: g_Nominated[client] = false; ds@1952: } ds@1952: ds@1952: return true; ds@1952: } ds@1952: ds@1952: public OnClientDisconnect(client) ds@1952: { ds@1952: if (IsFakeClient(client)) ds@1952: { ds@1952: return; ds@1952: } ds@1952: ds@1952: // If RTV is active, deal with vote counting. ds@1952: if (GetConVarBool(g_Cvar_RockTheVote)) ds@1952: { ds@1952: if(g_RTV_Voted[client]) ds@1952: { ds@1952: g_RTV_Votes--; ds@1952: } ds@1952: ds@1952: g_RTV_Voters--; ds@1952: g_RTV_VotesNeeded = RoundToFloor(float(g_RTV_Voters) * GetConVarFloat(g_Cvar_RTVLimit)); ds@1952: ds@1952: // If this client caused us to fall below the RTV threshold and its allowed be started, start it. ds@1952: if (g_RTV_Votes && g_RTV_Voters && g_RTV_Votes >= g_RTV_VotesNeeded && g_RTV_Allowed && !g_RTV_Started) ds@1952: { ds@1952: g_RTV_Started = true; ds@1952: CreateTimer(2.0, Timer_StartRTV, TIMER_FLAG_NO_MAPCHANGE); ds@1952: } ds@1952: } ds@1952: } ds@1952: ds@1952: public OnMapTimeLeftChanged() ds@1952: { ds@1952: if (GetConVarBool(g_Cvar_MapChooser)) ds@1952: { ds@1952: SetupTimeleftTimer(); ds@1952: } ds@1952: }