ds@1952: /** ds@1952: * vim: set ts=4 : ds@1952: * ============================================================================= ds@1952: * SourceMod Mapchooser Plugin ds@1952: * Creates a map vote at appropriate times, setting sm_nextmap to the winning ds@1952: * vote 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 = "MapChooser", ds@1952: author = "AlliedModders LLC", ds@1952: description = "Automated Map Voting", ds@1952: version = SOURCEMOD_VERSION, ds@1952: url = "http://www.sourcemod.net/" ds@1952: }; ds@1952: ds@1952: new Handle:g_Cvar_Winlimit = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_Maxrounds = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_Fraglimit = INVALID_HANDLE; ds@1952: ds@1952: new Handle:g_Cvar_Nextmap = INVALID_HANDLE; ds@1952: ds@1952: new Handle:g_Cvar_StartTime = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_StartRounds = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_StartFrags = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_ExtendTimeStep = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_ExtendRoundStep = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_ExtendFragStep = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_ExcludeMaps = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_IncludeMaps = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_NoVoteMode = INVALID_HANDLE; ds@1952: new Handle:g_Cvar_Extend = INVALID_HANDLE; ds@1952: ds@1952: new Handle:g_VoteTimer = INVALID_HANDLE; ds@1952: new Handle:g_RetryTimer = INVALID_HANDLE; ds@1952: ds@1952: new Handle:g_MapList = INVALID_HANDLE; ds@1952: new Handle:g_OldMapList = INVALID_HANDLE; ds@1952: new Handle:g_NextMapList = INVALID_HANDLE; ds@1952: new Handle:g_TeamScores = INVALID_HANDLE; ds@1952: new Handle:g_VoteMenu = INVALID_HANDLE; ds@1952: ds@1952: new g_Extends; ds@1952: new g_TotalRounds; ds@1952: new bool:g_HasVoteStarted; ds@1952: new g_mapFileSerial = -1; ds@1952: ds@1952: #define VOTE_EXTEND "##extend##" ds@1952: ds@1952: public OnPluginStart() ds@1952: { ds@1952: LoadTranslations("mapchooser.phrases"); ds@1952: ds@1952: new arraySize = ByteCountToCells(33); ds@1952: g_MapList = CreateArray(arraySize); ds@1952: g_OldMapList = CreateArray(arraySize); ds@1952: g_NextMapList = CreateArray(arraySize); 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_ExtendTimeStep = CreateConVar("sm_extendmap_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.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_ExtendFragStep = CreateConVar("sm_extendmap_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0); 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", "0", "Number of extensions allowed each map.", _, true, 0.0); ds@1952: ds@1952: RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now."); ds@1952: 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 (g_Cvar_Winlimit != INVALID_HANDLE || g_Cvar_Maxrounds != INVALID_HANDLE) ds@1952: { ds@1952: HookEvent("round_end", Event_RoundEnd); ds@1952: } ds@1952: ds@1952: if (g_Cvar_Fraglimit != INVALID_HANDLE) ds@1952: { ds@1952: HookEvent("player_death", Event_PlayerDeath); ds@1952: } ds@1952: ds@1952: AutoExecConfig(true, "mapchooser"); ds@1952: } ds@1952: ds@1952: public OnConfigsExecuted() ds@1952: { ds@1952: g_Cvar_Nextmap = FindConVar("sm_nextmap"); ds@1952: ds@1952: if (g_Cvar_Nextmap == INVALID_HANDLE) ds@1952: { ds@1952: LogError("FATAL: Cannot find sm_nextmap cvar. Mapchooser not loaded."); ds@1952: SetFailState("sm_nextmap not found"); ds@1952: } ds@1952: ds@1952: if (ReadMapList(g_MapList, ds@1952: g_mapFileSerial, ds@1952: "mapchooser", ds@1952: MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) ds@1952: != INVALID_HANDLE) ds@1952: ds@1952: { ds@1952: if (g_mapFileSerial == -1) ds@1952: { ds@1952: LogError("Unable to create a valid map list."); ds@1952: } ds@1952: } ds@1952: ds@1952: CreateNextVote(); ds@1952: SetupTimeleftTimer(); ds@1952: SetConVarString(g_Cvar_Nextmap, "Pending Vote"); ds@1952: ds@1952: if (g_TeamScores != INVALID_HANDLE) ds@1952: { ds@1952: CloseHandle(g_TeamScores); ds@1952: } ds@1952: ds@1952: g_TeamScores = CreateArray(2); ds@1952: g_TotalRounds = 0; ds@1952: ds@1952: g_Extends = 0; ds@1952: } ds@1952: ds@1952: public OnMapEnd() ds@1952: { ds@1952: g_HasVoteStarted = 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 OnMapTimeLeftChanged() ds@1952: { ds@1952: if (GetArraySize(g_MapList)) ds@1952: { ds@1952: SetupTimeleftTimer(); ds@1952: } ds@1952: } ds@1952: ds@1952: SetupTimeleftTimer() ds@1952: { ds@1952: new time; ds@1952: if (GetMapTimeLeft(time) && time > 0) ds@1952: { ds@1952: new startTime = GetConVarInt(g_Cvar_StartTime) * 60; ds@1952: if (time - startTime < 0) ds@1952: { ds@1952: InitiateVote(); ds@1952: } ds@1952: else 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: g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVote); ds@1952: } ds@1952: } ds@1952: } ds@1952: ds@1952: public Action:Timer_StartMapVote(Handle:timer) ds@1952: { ds@1952: if (!GetArraySize(g_MapList)) ds@1952: { ds@1952: return Plugin_Stop; ds@1952: } ds@1952: ds@1952: if (timer == g_RetryTimer) ds@1952: { ds@1952: g_RetryTimer = INVALID_HANDLE; ds@1952: } ds@1952: else ds@1952: { ds@1952: g_VoteTimer = INVALID_HANDLE; ds@1952: } ds@1952: ds@1952: InitiateVote(); ds@1952: ds@1952: return Plugin_Stop; ds@1952: } ds@1952: ds@1952: /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */ ds@1952: public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast) ds@1952: { ds@1952: if (!GetArraySize(g_MapList) || g_HasVoteStarted) ds@1952: { ds@1952: return; ds@1952: } ds@1952: ds@1952: new winner = GetEventInt(event, "winner"); ds@1952: ds@1952: if (winner == 0 || winner == 1) ds@1952: { ds@1952: return; ds@1952: } ds@1952: ds@1952: g_TotalRounds++; ds@1952: ds@1952: new team[2], teamPos = -1; ds@1952: for (new i; i < GetArraySize(g_TeamScores); i++) ds@1952: { ds@1952: GetArrayArray(g_TeamScores, i, team); ds@1952: if (team[0] == winner) ds@1952: { ds@1952: teamPos = i; ds@1952: break; ds@1952: } ds@1952: } ds@1952: ds@1952: if (teamPos == -1) ds@1952: { ds@1952: team[0] = winner; ds@1952: team[1] = 1; ds@1952: PushArrayArray(g_TeamScores, team); ds@1952: } ds@1952: else ds@1952: { ds@1952: team[1]++; ds@1952: SetArrayArray(g_TeamScores, teamPos, team); ds@1952: } ds@1952: ds@1952: if (g_Cvar_Winlimit != INVALID_HANDLE) ds@1952: { ds@1952: new winlimit = GetConVarInt(g_Cvar_Winlimit); ds@1952: if (winlimit) ds@1952: { ds@1952: if (team[1] >= (winlimit - GetConVarInt(g_Cvar_StartRounds))) ds@1952: { ds@1952: InitiateVote(); ds@1952: } ds@1952: } ds@1952: } ds@1952: ds@1952: if (g_Cvar_Maxrounds != INVALID_HANDLE) ds@1952: { ds@1952: new maxrounds = GetConVarInt(g_Cvar_Maxrounds); ds@1952: if (maxrounds) ds@1952: { ds@1952: if (g_TotalRounds >= (maxrounds - GetConVarInt(g_Cvar_StartRounds))) ds@1952: { ds@1952: InitiateVote(); ds@1952: } ds@1952: } ds@1952: } ds@1952: } ds@1952: ds@1952: public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast) ds@1952: { ds@1952: if (!GetArraySize(g_MapList) || g_Cvar_Fraglimit == INVALID_HANDLE || g_HasVoteStarted) ds@1952: { ds@1952: return; ds@1952: } ds@1952: ds@1952: if (!GetConVarInt(g_Cvar_Fraglimit)) ds@1952: { ds@1952: return; ds@1952: } ds@1952: ds@1952: new fragger = GetClientOfUserId(GetEventInt(event, "attacker")); ds@1952: if (fragger && GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags))) ds@1952: { ds@1952: InitiateVote(); ds@1952: } ds@1952: } ds@1952: ds@1952: public Action:Command_Mapvote(client, args) ds@1952: { ds@1952: InitiateVote(); ds@1952: ds@1952: return Plugin_Handled; ds@1952: } ds@1952: ds@1952: InitiateVote() ds@1952: { ds@1952: if (g_HasVoteStarted || g_RetryTimer != INVALID_HANDLE) ds@1952: { ds@1952: return; ds@1952: } ds@1952: ds@1952: if (IsVoteInProgress()) ds@1952: { ds@1952: // Can't start a vote, try again in 5 seconds. ds@1952: g_RetryTimer = CreateTimer(5.0, Timer_StartMapVote); ds@1952: return; ds@1952: } ds@1952: ds@1952: g_HasVoteStarted = true; ds@1952: g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction:MENU_ACTIONS_ALL); ds@1952: SetMenuTitle(g_VoteMenu, "Vote Nextmap"); ds@1952: ds@1952: decl String:map[32]; ds@1952: for (new i = 0; i < GetArraySize(g_NextMapList); i++) ds@1952: { ds@1952: GetArrayString(g_NextMapList, i, map, sizeof(map)); ds@1952: AddMenuItem(g_VoteMenu, map, map); ds@1952: } ds@1952: ds@1952: if (GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend)) ds@1952: { ds@1952: AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map"); ds@1952: } ds@1952: ds@1952: SetMenuExitButton(g_VoteMenu, false); ds@1952: VoteMenuToAll(g_VoteMenu, 20); ds@1952: ds@1952: LogMessage("Voting for next map has started."); ds@1952: PrintToChatAll("[SM] %t", "Nextmap Voting Started"); ds@1952: } ds@1952: ds@1952: public Handler_MapVoteMenu(Handle:menu, MenuAction:action, param1, param2) ds@1952: { ds@1952: switch (action) ds@1952: { ds@1952: case MenuAction_End: ds@1952: { ds@1952: g_VoteMenu = INVALID_HANDLE; ds@1952: CloseHandle(menu); ds@1952: } ds@1952: ds@1952: case MenuAction_Display: ds@1952: { ds@1952: decl String:buffer[255]; ds@1952: Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1); ds@1952: ds@1952: new Handle:panel = Handle:param2; ds@1952: SetPanelTitle(panel, buffer); ds@1952: } ds@1952: ds@1952: case MenuAction_DisplayItem: ds@1952: { ds@1952: if (GetMenuItemCount(menu) - 1 == param2) ds@1952: { ds@1952: decl String:map[64], String:buffer[255]; ds@1952: GetMenuItem(menu, param2, map, sizeof(map)); ds@1952: if (strcmp(map, VOTE_EXTEND, false) == 0) ds@1952: { ds@1952: Format(buffer, sizeof(buffer), "%T", "Extend Map", param1); ds@1952: return RedrawMenuItem(buffer); ds@1952: } ds@1952: } ds@1952: } ds@1952: ds@1952: // Why am I commented out? Because BAIL hasn't decided yet if ds@1952: // vote notification will be built into the Vote API. ds@1952: /*case MenuAction_Select: ds@1952: { ds@1952: decl String:Name[32], String:Map[32]; ds@1952: GetClientName(param1, Name, sizeof(Name)); ds@1952: GetMenuItem(menu, param2, Map, sizeof(Map)); ds@1952: ds@1952: PrintToChatAll("[SM] %s has voted for map '%s'", Name, Map); ds@1952: }*/ ds@1952: ds@1952: case MenuAction_VoteCancel: ds@1952: { ds@1952: // If we receive 0 votes, pick at random. ds@1952: if (param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode)) ds@1952: { ds@1952: new count = GetMenuItemCount(menu); ds@1952: new item = GetRandomInt(0, count - 1); ds@1952: decl String:map[32]; ds@1952: GetMenuItem(menu, item, map, sizeof(map)); ds@1952: ds@1952: while (strcmp(map, VOTE_EXTEND, false) == 0) ds@1952: { ds@1952: item = GetRandomInt(0, count - 1); ds@1952: GetMenuItem(menu, item, map, sizeof(map)); ds@1952: } ds@1952: ds@1952: SetNextMap(map); ds@1952: } ds@1952: else ds@1952: { ds@1952: // We were actually cancelled. What should we do? ds@1952: } ds@1952: } ds@1952: ds@1952: case MenuAction_VoteEnd: ds@1952: { ds@1952: decl String:map[32]; ds@1952: GetMenuItem(menu, param1, map, sizeof(map)); ds@1952: ds@1952: if (strcmp(map, VOTE_EXTEND, false) == 0) ds@1952: { ds@1952: g_Extends++; ds@1952: ds@1952: new time; ds@1952: if (GetMapTimeLimit(time)) ds@1952: { ds@1952: if (time > 0) ds@1952: { ds@1952: ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60); ds@1952: } ds@1952: } ds@1952: ds@1952: if (g_Cvar_Winlimit != INVALID_HANDLE) ds@1952: { ds@1952: new winlimit = GetConVarInt(g_Cvar_Winlimit); ds@1952: if (winlimit) ds@1952: { ds@1952: SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep)); ds@1952: } ds@1952: } ds@1952: ds@1952: if (g_Cvar_Maxrounds != INVALID_HANDLE) ds@1952: { ds@1952: new maxrounds = GetConVarInt(g_Cvar_Maxrounds); ds@1952: if (maxrounds) ds@1952: { ds@1952: SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep)); ds@1952: } ds@1952: } ds@1952: ds@1952: if (g_Cvar_Fraglimit != INVALID_HANDLE) ds@1952: { ds@1952: new fraglimit = GetConVarInt(g_Cvar_Fraglimit); ds@1952: if (fraglimit) ds@1952: { ds@1952: SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep)); ds@1952: } ds@1952: } ds@1952: ds@1952: PrintToChatAll("[SM] %t", "Current Map Extended"); ds@1952: LogMessage("Voting for next map has finished. The current map has been extended."); ds@1952: ds@1952: // We extended, so we'll have to vote again. ds@1952: g_HasVoteStarted = false; ds@1952: CreateNextVote(); ds@1952: SetupTimeleftTimer(); ds@1952: } ds@1952: else ds@1952: { ds@1952: SetNextMap(map); ds@1952: } ds@1952: } ds@1952: } ds@1952: ds@1952: return 0; ds@1952: } ds@1952: ds@1952: SetNextMap(const String:map[]) ds@1952: { ds@1952: SetConVarString(g_Cvar_Nextmap, map); ds@1952: PushArrayString(g_OldMapList, map); ds@1952: ds@1952: if (GetArraySize(g_OldMapList) > GetConVarInt(g_Cvar_ExcludeMaps)) ds@1952: { ds@1952: RemoveFromArray(g_OldMapList, 0); ds@1952: } ds@1952: ds@1952: PrintToChatAll("[SM] %t", "Nextmap Voting Finished", map); ds@1952: LogMessage("Voting for next map has finished. Nextmap: %s.", map); ds@1952: } ds@1952: ds@1952: CreateNextVote() ds@1952: { ds@1952: if(g_NextMapList != INVALID_HANDLE) ds@1952: { ds@1952: ClearArray(g_NextMapList); ds@1952: } ds@1952: ds@1952: decl String:map[32]; ds@1952: new index, Handle:tempMaps = CloneArray(g_MapList); ds@1952: ds@1952: GetCurrentMap(map, sizeof(map)); ds@1952: index = FindStringInArray(tempMaps, map); ds@1952: if (index != -1) ds@1952: { ds@1952: RemoveFromArray(tempMaps, index); ds@1952: } ds@1952: ds@1952: if (GetConVarInt(g_Cvar_ExcludeMaps) && GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps)) ds@1952: { ds@1952: for (new i = 0; i < GetArraySize(g_OldMapList); i++) ds@1952: { ds@1952: GetArrayString(g_OldMapList, i, map, sizeof(map)); ds@1952: index = FindStringInArray(tempMaps, map); ds@1952: if (index != -1) ds@1952: { ds@1952: RemoveFromArray(tempMaps, index); ds@1952: } ds@1952: } ds@1952: } ds@1952: ds@1952: new limit = (GetConVarInt(g_Cvar_IncludeMaps) < GetArraySize(tempMaps) ? GetConVarInt(g_Cvar_IncludeMaps) : GetArraySize(tempMaps)); ds@1952: for (new i = 0; i < limit; i++) ds@1952: { ds@1952: new b = GetRandomInt(0, GetArraySize(tempMaps) - 1); ds@1952: GetArrayString(tempMaps, b, map, sizeof(map)); ds@1952: PushArrayString(g_NextMapList, map); ds@1952: RemoveFromArray(tempMaps, b); ds@1952: } ds@1952: ds@1952: CloseHandle(tempMaps); ds@1952: }