/** * vim: set ts=4 : * ============================================================================= * MapChooser Extended * Creates a map vote at appropriate times, setting sm_nextmap to the winning * vote. Includes extra options not present in the SourceMod MapChooser * * MapChooser Extended (C)2011-2013 Powerlord (Ross Bemrose) * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. * ============================================================================= * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 3.0, as published by the * Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . * * As a special exception, AlliedModders LLC gives you permission to link the * code of this program (as well as its derivative works) to "Half-Life 2," the * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software * by the Valve Corporation. You must obey the GNU General Public License in * all respects for all other code used. Additionally, AlliedModders LLC grants * this exception to all derivative works. AlliedModders LLC defines further * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), * or . * * Version: $Id$ */ //#define DEBUG #if defined DEBUG #define assert(%1) if(!(%1)) ThrowError("Debug Assertion Failed"); #define assert_msg(%1,%2) if(!(%1)) ThrowError(%2); #else #define assert(%1) #define assert_msg(%1,%2) #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma semicolon 1 #pragma newdecls required #define MCE_VERSION "1.3.1" enum RoundCounting { RoundCounting_Standard = 0, RoundCounting_MvM, RoundCounting_ArmsRace, } // CSGO requires two cvars to get the game type enum { GameType_Classic = 0, GameType_GunGame = 1, GameType_Training = 2, GameType_Custom = 3, } enum { GunGameMode_ArmsRace = 0, GunGameMode_Demolition = 1, GunGameMode_DeathMatch = 2, } public Plugin myinfo = { name = "MapChooser Extended", author = "Powerlord, Zuko, BotoX and AlliedModders LLC", description = "Automated Map Voting with Extensions", version = MCE_VERSION, url = "" }; /* Valve ConVars */ ConVar g_Cvar_Winlimit; ConVar g_Cvar_Maxrounds; ConVar g_Cvar_Fraglimit; ConVar g_Cvar_Bonusroundtime; ConVar g_Cvar_GameType; ConVar g_Cvar_GameMode; /* Plugin ConVars */ ConVar g_Cvar_StartTime; ConVar g_Cvar_StartRounds; ConVar g_Cvar_StartFrags; ConVar g_Cvar_ExtendTimeStep; ConVar g_Cvar_ExtendRoundStep; ConVar g_Cvar_ExtendFragStep; ConVar g_Cvar_ExcludeMaps; ConVar g_Cvar_ExcludeMapsTime; ConVar g_Cvar_IncludeMaps; ConVar g_Cvar_IncludeMapsReserved; ConVar g_Cvar_NoVoteMode; ConVar g_Cvar_Extend; ConVar g_Cvar_DontChange; ConVar g_Cvar_EndOfMapVote; ConVar g_Cvar_VoteDuration; Handle g_VoteTimer = INVALID_HANDLE; Handle g_RetryTimer = INVALID_HANDLE; Handle g_WarningTimer = INVALID_HANDLE; /* Data Handles */ Handle g_MapList = INVALID_HANDLE; Handle g_NominateList[MAXPLAYERS + 1]; Handle g_NominateOwners = INVALID_HANDLE; StringMap g_OldMapList; StringMap g_TimeMapList; Handle g_NextMapList = INVALID_HANDLE; Handle g_VoteMenu = INVALID_HANDLE; KeyValues g_Config; int g_Extends; int g_TotalRounds; bool g_HasVoteStarted; bool g_WaitingForVote; bool g_MapVoteCompleted; bool g_ChangeMapAtRoundEnd; bool g_ChangeMapInProgress; bool g_HasIntermissionStarted = false; bool g_SaveCDOnMapEnd = true; bool g_DidNotExtend = false; bool g_DidRoundTerminate = false; bool g_IniatedLastVote = false; int g_mapFileSerial = -1; int g_iPlayerCount_excludeSpec; int g_iMapsFromCasualPool; int g_iPlayerAFKTime; int g_NominateCount = 0; int g_NominateReservedCount = 0; int g_iInterval; float player_mapvote_worth[MAXPLAYERS + 1]; char player_mapvote[MAXPLAYERS + 1][PLATFORM_MAX_PATH]; MapChange g_ChangeTime; //check if autismbot bool is_bot_player[MAXPLAYERS + 1]; Handle g_NominationsResetForward = INVALID_HANDLE; Handle g_MapVoteStartedForward = INVALID_HANDLE; /* Mapchooser Extended Plugin ConVars */ ConVar g_Cvar_RunOff; ConVar g_Cvar_RunOffPercent; ConVar g_Cvar_BlockSlots; ConVar g_Cvar_MaxRunOffs; ConVar g_Cvar_StartTimePercent; ConVar g_Cvar_StartTimePercentEnable; ConVar g_Cvar_WarningTime; ConVar g_Cvar_RunOffWarningTime; ConVar g_Cvar_TimerLocation; ConVar g_Cvar_ExtendPosition; ConVar g_Cvar_MarkCustomMaps; ConVar g_Cvar_RandomizeNominations; ConVar g_Cvar_HideTimer; ConVar g_Cvar_NoVoteOption; ConVar g_Cvar_ShufflePerClient; ConVar g_Cvar_NoRestrictionTimeframeEnable; ConVar g_Cvar_NoRestrictionTimeframeMinTime; ConVar g_Cvar_NoRestrictionTimeframeMaxTime; /* Mapchooser Extended Data Handles */ Handle g_OfficialList = INVALID_HANDLE; /* Mapchooser Extended Forwards */ Handle g_MapVoteWarningStartForward = INVALID_HANDLE; Handle g_MapVoteWarningTickForward = INVALID_HANDLE; Handle g_MapVoteStartForward = INVALID_HANDLE; Handle g_MapVoteEndForward = INVALID_HANDLE; Handle g_MapVoteRunoffStartForward = INVALID_HANDLE; /* Mapchooser Extended Globals */ int g_RunoffCount = 0; int g_mapOfficialFileSerial = -1; char g_GameModName[64]; bool g_WarningInProgress = false; bool g_AddNoVote = false; RoundCounting g_RoundCounting = RoundCounting_Standard; /* Upper bound of how many team there could be */ #define MAXTEAMS 10 int g_winCount[MAXTEAMS]; bool g_BlockedSlots = false; int g_ObjectiveEnt = -1; enum TimerLocation { TimerLocation_Hint = 0, TimerLocation_Center = 1, TimerLocation_Chat = 2, } enum WarningType { WarningType_Vote, WarningType_Revote, } #define VOTE_EXTEND "##extend##" #define VOTE_DONTCHANGE "##dontchange##" /* Mapchooser Extended Defines */ #define LINE_ONE "##lineone##" #define LINE_TWO "##linetwo##" #define LINE_SPACER "##linespacer##" #define FAILURE_TIMER_LENGTH 5 //call forward to reset all nominations public void OnPluginEnd() { for (int i = 0; i < MaxClients; i++) { int index = FindValueInArray(g_NominateOwners, i); if (index == -1) continue; for (int j = 0; j < GetArraySize(g_NominateList[i]); j++) { char oldmap[PLATFORM_MAX_PATH]; Call_StartForward(g_NominationsResetForward); GetArrayString(g_NominateList[i], j, oldmap, PLATFORM_MAX_PATH); Call_PushString(oldmap); Call_PushCell(i); Call_Finish(); } } } public void OnPluginStart() { LoadTranslations("mapchooser_extended.phrases"); LoadTranslations("basevotes.phrases"); LoadTranslations("common.phrases"); int arraySize = ByteCountToCells(PLATFORM_MAX_PATH); g_MapList = CreateArray(arraySize); g_NominateOwners = CreateArray(1); g_OldMapList = new StringMap(); g_TimeMapList = new StringMap(); g_NextMapList = CreateArray(arraySize); g_OfficialList = CreateArray(arraySize); for (int i = 0; i < MaxClients; i++) { g_NominateList[i] = CreateArray(arraySize); if (IsValidClient(i)) { OnClientPostAdminCheck(i); } } GetGameFolderName(g_GameModName, sizeof(g_GameModName)); g_Cvar_EndOfMapVote = CreateConVar("mce_endvote", "1", "Specifies if MapChooser should run an end of map vote", _, true, 0.0, true, 1.0); g_Cvar_StartTime = CreateConVar("mce_starttime", "10.0", "Specifies when to start the vote based on time remaining.", _, true, 1.0); g_Cvar_StartRounds = CreateConVar("mce_startround", "2.0", "Specifies when to start the vote based on rounds remaining. Use 0 on DoD:S, CS:S, and TF2 to start vote during bonus round time", _, true, 0.0); g_Cvar_StartFrags = CreateConVar("mce_startfrags", "5.0", "Specifies when to start the vote base on frags remaining.", _, true, 1.0); g_Cvar_ExtendTimeStep = CreateConVar("mce_extend_timestep", "15", "Specifies how much many more minutes each extension makes", _, true, 5.0); g_Cvar_ExtendRoundStep = CreateConVar("mce_extend_roundstep", "5", "Specifies how many more rounds each extension makes", _, true, 1.0); g_Cvar_ExtendFragStep = CreateConVar("mce_extend_fragstep", "10", "Specifies how many more frags are allowed when map is extended.", _, true, 5.0); g_Cvar_ExcludeMaps = CreateConVar("mce_exclude", "5", "Specifies how many past maps to exclude from the vote.", _, true, 0.0); g_Cvar_ExcludeMapsTime = CreateConVar("mce_exclude_time", "5h", "Specifies how long in minutes an old map is excluded from the vote."); g_Cvar_IncludeMaps = CreateConVar("mce_include", "5", "Specifies how many maps to include in the vote.", _, true, 2.0, true, 9.0); g_Cvar_IncludeMapsReserved = CreateConVar("mce_include_reserved", "2", "Specifies how many private/random maps to include in the vote.", _, true, 0.0, true, 5.0); g_Cvar_NoVoteMode = CreateConVar("mce_novote", "1", "Specifies whether or not MapChooser should pick a map if no votes are received.", _, true, 0.0, true, 1.0); g_Cvar_Extend = CreateConVar("mce_extend", "0", "Number of extensions allowed each map.", _, true, 0.0); g_Cvar_DontChange = CreateConVar("mce_dontchange", "1", "Specifies if a 'Don't Change option should be added to early votes", _, true, 0.0); g_Cvar_VoteDuration = CreateConVar("mce_voteduration", "20", "Specifies how long the mapvote should be available for.", _, true, 5.0); ConVar cvar2; HookConVarChange((cvar2 = CreateConVar("mce_randomized_maps_from_casual_pool", "2", "f we have space left over for randomized maps at the mapvote. how many should be prioritized from the casual pool?")), Cvar_mapsFromCasualPool); g_iMapsFromCasualPool = cvar2.IntValue; delete cvar2; ConVar cvar1; HookConVarChange((cvar1 = CreateConVar("sm_active_players_required", "15", "Amount of players required to be considered active before any restrictions are enabled at all.")), Cvar_playerExcludeSpec); g_iPlayerCount_excludeSpec = cvar1.IntValue; delete cvar1; ConVar cvar; HookConVarChange((cvar = CreateConVar("sm_mapchooser_afk_time", "120", "Time in seconds until a player is considered as AFK and therefore excluded from player average")), Cvar_playerAFKTime); g_iPlayerAFKTime = cvar.IntValue; delete cvar; // MapChooser Extended cvars CreateConVar("mce_version", MCE_VERSION, "MapChooser Extended Version", FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_DONTRECORD); g_Cvar_RunOff = CreateConVar("mce_runoff", "1", "Hold run off votes if winning choice has less than a certain percentage of votes", _, true, 0.0, true, 1.0); g_Cvar_RunOffPercent = CreateConVar("mce_runoffpercent", "50", "If winning choice has less than this percent of votes, hold a runoff", _, true, 0.0, true, 100.0); g_Cvar_BlockSlots = CreateConVar("mce_blockslots", "0", "Block slots to prevent accidental votes. Only applies when Voice Command style menus are in use.", _, true, 0.0, true, 1.0); //g_Cvar_BlockSlotsCount = CreateConVar("mce_blockslots_count", "2", "Number of slots to block.", _, true, 1.0, true, 3.0); g_Cvar_MaxRunOffs = CreateConVar("mce_maxrunoffs", "1", "Number of run off votes allowed each map.", _, true, 0.0); g_Cvar_StartTimePercent = CreateConVar("mce_start_percent", "35.0", "Specifies when to start the vote based on percents.", _, true, 0.0, true, 100.0); g_Cvar_StartTimePercentEnable = CreateConVar("mce_start_percent_enable", "0", "Enable or Disable percentage calculations when to start vote.", _, true, 0.0, true, 1.0); g_Cvar_WarningTime = CreateConVar("mce_warningtime", "15.0", "Warning time in seconds.", _, true, 0.0, true, 60.0); g_Cvar_RunOffWarningTime = CreateConVar("mce_runoffvotewarningtime", "5.0", "Warning time for runoff vote in seconds.", _, true, 0.0, true, 30.0); g_Cvar_TimerLocation = CreateConVar("mce_warningtimerlocation", "0", "Location for the warning timer text. 0 is HintBox, 1 is Center text, 2 is Chat. Defaults to HintBox.", _, true, 0.0, true, 2.0); g_Cvar_MarkCustomMaps = CreateConVar("mce_markcustommaps", "1", "Mark custom maps in the vote list. 0 = Disabled, 1 = Mark with *, 2 = Mark with phrase.", _, true, 0.0, true, 2.0); g_Cvar_ExtendPosition = CreateConVar("mce_extendposition", "0", "Position of Extend/Dont Change options. 0 = at end, 1 = at start.", _, true, 0.0, true, 1.0); g_Cvar_RandomizeNominations = CreateConVar("mce_randomizeorder", "0", "Randomize map order?", _, true, 0.0, true, 1.0); g_Cvar_HideTimer = CreateConVar("mce_hidetimer", "0", "Hide the MapChooser Extended warning timer", _, true, 0.0, true, 1.0); g_Cvar_NoVoteOption = CreateConVar("mce_addnovote", "1", "Add \"No Vote\" to vote menu?", _, true, 0.0, true, 1.0); g_Cvar_ShufflePerClient = CreateConVar("mce_shuffle_per_client", "1", "Random shuffle map vote menu per client?", _, true, 0.0, true, 1.0); g_Cvar_NoRestrictionTimeframeEnable = CreateConVar("mce_no_restriction_timeframe_enable", "1", "Enable timeframe where all nomination restrictions and cooldowns are disabled?", _, true, 0.0, true, 1.0); g_Cvar_NoRestrictionTimeframeMinTime = CreateConVar("mce_no_restriction_timeframe_mintime", "0100", "Start of the timeframe where all nomination restrictions and cooldowns are disabled (Format: HHMM)", _, true, 0000.0, true, 2359.0); g_Cvar_NoRestrictionTimeframeMaxTime = CreateConVar("mce_no_restriction_timeframe_maxtime", "0700", "End of the timeframe where all nomination restrictions and cooldowns are disabled (Format: HHMM)", _, true, 0000.0, true, 2359.0); RegAdminCmd("sm_mapvote", Command_Mapvote, ADMFLAG_CHANGEMAP, "sm_mapvote - Forces MapChooser to attempt to run a map vote now."); RegAdminCmd("sm_setnextmap", Command_SetNextmap, ADMFLAG_CHANGEMAP, "sm_setnextmap "); // Mapchooser Extended Commands RegAdminCmd("mce_reload_maplist", Command_ReloadMaps, ADMFLAG_CHANGEMAP, "mce_reload_maplist - Reload the Official Maplist file."); RegConsoleCmd("sm_extends", Command_ExtendsLeft, "sm_extends - Shows how many extends are left on the current map."); RegConsoleCmd("sm_extendsleft", Command_ExtendsLeft, "sm_extendsleft - Shows how many extends are left on the current map."); RegConsoleCmd("sm_houravg", Command_hours_average, "Prints in the chat what the current hour average of each player accumulated is."); RegConsoleCmd("sm_avghour", Command_hours_average, "Prints in the chat what the current hour average of each player accumulated is."); g_Cvar_Winlimit = FindConVar("mp_winlimit"); g_Cvar_Maxrounds = FindConVar("mp_maxrounds"); g_Cvar_Fraglimit = FindConVar("mp_fraglimit"); EngineVersion version = GetEngineVersion(); static char mapListPath[PLATFORM_MAX_PATH]; BuildPath(Path_SM, mapListPath, PLATFORM_MAX_PATH, "configs/mapchooser_extended/maps/%s.txt", g_GameModName); SetMapListCompatBind("official", mapListPath); switch(version) { case Engine_TF2: { g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime"); } case Engine_CSGO: { g_Cvar_GameType = FindConVar("game_type"); g_Cvar_GameMode = FindConVar("game_mode"); g_Cvar_Bonusroundtime = FindConVar("mp_round_restart_delay"); } case Engine_DODS: { g_Cvar_Bonusroundtime = FindConVar("dod_bonusroundtime"); } case Engine_CSS: { g_Cvar_Bonusroundtime = FindConVar("mp_round_restart_delay"); } default: { g_Cvar_Bonusroundtime = FindConVar("mp_bonusroundtime"); } } switch(version) { case Engine_TF2: { HookEvent("teamplay_restart_round", Event_TFRestartRound); HookEvent("pve_win_panel", Event_MvMWinPanel); } case Engine_NuclearDawn: { HookEvent("round_win", Event_RoundEnd); } case Engine_CSGO: { HookEvent("round_end", Event_RoundEnd); HookEvent("cs_intermission", Event_Intermission); HookEvent("announce_phase_end", Event_PhaseEnd); } case Engine_DODS: { HookEvent("dod_round_win", Event_RoundEnd); } default: { HookEvent("round_end", Event_RoundEnd); } } HookEvent("player_death", Event_PlayerDeath); AutoExecConfig(true, "mapchooser_extended"); //Change the mp_bonusroundtime max so that we have time to display the vote //If you display a vote during bonus time good defaults are 17 vote duration and 19 mp_bonustime SetConVarBounds(g_Cvar_Bonusroundtime, ConVarBound_Upper, true, 30.0); g_NominationsResetForward = CreateGlobalForward("OnNominationRemoved", ET_Ignore, Param_String, Param_Cell); g_MapVoteStartedForward = CreateGlobalForward("OnMapVoteStarted", ET_Ignore); //MapChooser Extended Forwards g_MapVoteStartForward = CreateGlobalForward("OnMapVoteStart", ET_Ignore); // Deprecated g_MapVoteEndForward = CreateGlobalForward("OnMapVoteEnd", ET_Ignore, Param_String); g_MapVoteWarningStartForward = CreateGlobalForward("OnMapVoteWarningStart", ET_Ignore); g_MapVoteWarningTickForward = CreateGlobalForward("OnMapVoteWarningTick", ET_Ignore, Param_Cell); g_MapVoteRunoffStartForward = CreateGlobalForward("OnMapVoteRunnoffWarningStart", ET_Ignore); InternalRestoreMapCooldowns(); int total_time = (GetConVarInt(g_Cvar_VoteDuration) * 2) + GetConVarInt(g_Cvar_RunOffWarningTime) + 10; ServerCommand("sm_cvar mp_chattime %i", total_time); //prevents map switching supposedly. } public void Cvar_mapsFromCasualPool(ConVar convar, const char[] oldValue, const char[] newValue) { g_iMapsFromCasualPool = convar.IntValue; } public void Cvar_playerExcludeSpec(ConVar convar, const char[] oldValue, const char[] newValue) { g_iPlayerCount_excludeSpec = convar.IntValue; } public void Cvar_playerAFKTime(ConVar convar, const char[] oldValue, const char[] newValue) { g_iPlayerAFKTime = convar.IntValue; } public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { if(LibraryExists("mapchooser")) { strcopy(error, err_max, "MapChooser already loaded, aborting."); return APLRes_Failure; } RegPluginLibrary("mapchooser"); MarkNativeAsOptional("GetEngineVersion"); CreateNative("NominateMap", Native_NominateMap); CreateNative("RemoveNominationByMap", Native_RemoveNominationByMap); CreateNative("RemoveNominationByOwner", Native_RemoveNominationByOwner); CreateNative("InitiateMapChooserVote", Native_InitiateVote); CreateNative("CanMapChooserStartVote", Native_CanVoteStart); CreateNative("HasEndOfMapVoteFinished", Native_CheckVoteDone); CreateNative("GetExcludeMapList", Native_GetExcludeMapList); CreateNative("GetNominatedMapList", Native_GetNominatedMapList); CreateNative("EndOfMapVoteEnabled", Native_EndOfMapVoteEnabled); // MapChooser Extended natives CreateNative("IsMapOfficial", Native_IsMapOfficial); CreateNative("CanNominate", Native_CanNominate); CreateNative("ExcludeMap", Native_ExcludeMap); CreateNative("ExcludeMapTime", Native_ExcludeMapTime); CreateNative("GetMapCooldown", Native_GetMapCooldown); CreateNative("GetMapCooldownTime", Native_GetMapCooldownTime); CreateNative("GetMapMinTime", Native_GetMapMinTime); CreateNative("GetMapMaxTime", Native_GetMapMaxTime); CreateNative("GetMapMinPlayers", Native_GetMapMinPlayers); CreateNative("GetMapMaxPlayers", Native_GetMapMaxPlayers); CreateNative("GetMapTimeRestriction", Native_GetMapTimeRestriction); CreateNative("GetMapPlayerRestriction", Native_GetMapPlayerRestriction); CreateNative("GetMapGroups", Native_GetMapGroups); CreateNative("GetMapGroupRestriction", Native_GetMapGroupRestriction); CreateNative("GetMapVIPRestriction", Native_GetMapVIPRestriction); CreateNative("GetExtendsLeft", Native_GetExtendsLeft); CreateNative("AreRestrictionsActive", Native_AreRestrictionsActive); CreateNative("SimulateMapEnd", Native_SimulateMapEnd); CreateNative("GetAveragePlayerTimeOnServerMapRestriction", Native_GetAveragePlayerTimeOnServerMapRestriction); return APLRes_Success; } public Action GetInternalGetCvars(Handle timer) { InternalGetCvars(); //reading some cvars from the mapchooser_extended.cfg instead of server.cfg so people can view the values on // https://unloze.com/mapchooser_css_ze/mapchooser_extended.cfg return Plugin_Handled; } public void OnMapStart() { CreateTimer(1.0, GetInternalGetCvars); int total_time = (GetConVarInt(g_Cvar_VoteDuration) * 2) + GetConVarInt(g_Cvar_RunOffWarningTime) + 10; ServerCommand("sm_cvar mp_chattime %i", total_time); //prevents map switching supposedly. g_DidRoundTerminate = false; g_IniatedLastVote = false; g_DidNotExtend = false; ServerCommand("sm_cvar sm_vote_progress_hintbox 1"); //yeah its cheesy but does the job. g_iInterval = 0; static char folder[64]; GetGameFolderName(folder, sizeof(folder)); g_RoundCounting = RoundCounting_Standard; g_ObjectiveEnt = -1; if(strcmp(folder, "tf") == 0 && GameRules_GetProp("m_bPlayingMannVsMachine")) { g_RoundCounting = RoundCounting_MvM; g_ObjectiveEnt = EntIndexToEntRef(FindEntityByClassname(-1, "tf_objective_resource")); } else if(strcmp(folder, "csgo") == 0 && GetConVarInt(g_Cvar_GameType) == GameType_GunGame && GetConVarInt(g_Cvar_GameMode) == GunGameMode_ArmsRace) { g_RoundCounting = RoundCounting_ArmsRace; } if(g_Config) delete g_Config; char sConfigFile[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/mapchooser_extended.cfg"); if(!FileExists(sConfigFile)) { LogMessage("Could not find config: \"%s\"", sConfigFile); return; } LogMessage("Found config: \"%s\"", sConfigFile); g_Config = new KeyValues("mapchooser_extended"); if(!g_Config.ImportFromFile(sConfigFile)) { delete g_Config; LogMessage("ImportFromFile() failed!"); return; } g_Config.Rewind(); //this does not detect obvioulsy when there is more or less than 15 players active because its the mapstart // nobody has connected yet. OnMapEnd also does not work for checking this. so no point setting it to false here. if(InternalAreRestrictionsActive(true)) g_SaveCDOnMapEnd = true; else g_SaveCDOnMapEnd = false; } public void OnConfigsExecuted() { if(ReadMapList(g_MapList, g_mapFileSerial, "mapchooser", MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_MAPSFOLDER) != INVALID_HANDLE) { if(g_mapFileSerial == -1) LogError("Unable to create a valid map list."); } SetupTimeleftTimer(); g_TotalRounds = 0; g_Extends = 0; g_MapVoteCompleted = false; g_NominateCount = 0; g_NominateReservedCount = 0; for (int i = 0; i < MaxClients; i++) { if (g_NominateList[i] != INVALID_HANDLE) { ClearArray(g_NominateList[i]); } } ClearArray(g_NominateOwners); for(int i = 0; i < MAXTEAMS; i++) g_winCount[i] = 0; /* Check if mapchooser will attempt to start mapvote during bonus round time */ if (!GetConVarInt(g_Cvar_StartRounds)) { if(!GetConVarInt(g_Cvar_StartTime) && GetConVarFloat(g_Cvar_Bonusroundtime) <= GetConVarFloat(g_Cvar_VoteDuration)) LogError("Warning - Bonus Round Time shorter than Vote Time. Votes during bonus round may not have time to complete"); } InitializeOfficialMapList(); } public void OnMapEnd() { int total_time = (GetConVarInt(g_Cvar_VoteDuration) * 2) + GetConVarInt(g_Cvar_RunOffWarningTime) + 10; ServerCommand("sm_cvar mp_chattime %i", total_time); g_DidNotExtend = false; ServerCommand("sm_cvar sm_vote_progress_hintbox 1"); //yeah its cheesy but does the job. g_iInterval = 0; g_HasVoteStarted = false; g_WaitingForVote = false; g_DidRoundTerminate = false; g_IniatedLastVote = false; g_ChangeMapAtRoundEnd = false; g_ChangeMapInProgress = false; g_HasIntermissionStarted = false; g_VoteTimer = INVALID_HANDLE; g_RetryTimer = INVALID_HANDLE; g_WarningTimer = INVALID_HANDLE; g_RunoffCount = 0; static char map[PLATFORM_MAX_PATH]; int Cooldown; if(g_SaveCDOnMapEnd) { GetCurrentMap(map, PLATFORM_MAX_PATH); Cooldown = InternalGetMapCooldown(map); g_OldMapList.SetValue(map, Cooldown, true); Cooldown = GetTime() + InternalGetMapCooldownTime(map) - RoundToFloor(GetGameTime()); g_TimeMapList.SetValue(map, Cooldown, true); } StringMapSnapshot OldMapListSnapshot = g_OldMapList.Snapshot(); for(int i = 0; i < OldMapListSnapshot.Length; i++) { OldMapListSnapshot.GetKey(i, map, sizeof(map)); g_OldMapList.GetValue(map, Cooldown); Cooldown--; if(Cooldown > 0) g_OldMapList.SetValue(map, Cooldown, true); else g_OldMapList.Remove(map); } delete OldMapListSnapshot; StringMapSnapshot TimeMapListSnapshot = g_TimeMapList.Snapshot(); for(int i = 0; i < TimeMapListSnapshot.Length; i++) { TimeMapListSnapshot.GetKey(i, map, sizeof(map)); g_TimeMapList.GetValue(map, Cooldown); if(Cooldown < GetTime()) g_TimeMapList.Remove(map); } delete OldMapListSnapshot; InternalStoreMapCooldowns(); } public void OnClientPutInServer(int client) { if (g_NominateList[client] != INVALID_HANDLE) { ClearArray(g_NominateList[client]); } } public void OnClientDisconnect(int client) { is_bot_player[client] = false; player_mapvote_worth[client] = 0.0; Format(player_mapvote[client], 128, ""); int index = FindValueInArray(g_NominateOwners, client); if(index == -1) return; //2023 edit for handling multiple nominations -jenz for (int i = 0; i < GetArraySize(g_NominateList[client]); i++) { Call_StartForward(g_NominationsResetForward); char oldmap[PLATFORM_MAX_PATH]; GetArrayString(g_NominateList[client], i, oldmap, PLATFORM_MAX_PATH); Call_PushString(oldmap); Call_PushCell(client); Call_Finish(); } RemoveFromArray(g_NominateOwners, index); for (int i = 0; i < GetArraySize(g_NominateList[client]); i++) { RemoveFromArray(g_NominateList[client], i); } ClearArray(g_NominateList[client]); g_NominateCount--; } public Action Command_SetNextmap(int client, int args) { if(args < 1) { CReplyToCommand(client, "[MCE] Usage: sm_setnextmap "); return Plugin_Handled; } static char map[PLATFORM_MAX_PATH]; GetCmdArg(1, map, PLATFORM_MAX_PATH); if(!IsMapValid(map)) { CReplyToCommand(client, "[MCE] %t", "Map was not found", map); return Plugin_Handled; } ShowActivity(client, "%t", "Changed Next Map", map); LogAction(client, -1, "\"%L\" changed nextmap to \"%s\"", client, map); SetNextMap(map); g_MapVoteCompleted = true; //checking on MapStart and MapEnd is not good enough. Players are not considered alive and on teams at those points in time. //therefore instead applying the bool here after the mapvote completed. if(InternalAreRestrictionsActive(false)) g_SaveCDOnMapEnd = true; else g_SaveCDOnMapEnd = false; return Plugin_Handled; } public Action Command_ReloadMaps(int client, int args) { InitializeOfficialMapList(); return Plugin_Handled; } public Action Command_hours_average(int client, int args) { float total_votes = 0.0; for (int i = 0; i < MaxClients; i++) { if (IsValidClient(i) && !is_bot_player[i] && PM_IsPlayerSteam(i)) { GetPlayerWorthRTV_(i); float nominate_worth = GetPlayerWorthRTV_boost_(i); if (nominate_worth < 1.0) { nominate_worth = 1.0; } total_votes += nominate_worth; } } float nominate_worth = GetPlayerWorthRTV_boost_(client); if (nominate_worth < 1.0) { nominate_worth = 1.0; } CReplyToCommand(client, "Average hour count for nominations is: %i \nAverage hour count for mapvote and rtv boost is: %i \nYour mapvote and rtv boost is %0.1f \n(%i%% mapvote) (%i%% rtv)", GetAveragePlayerTimeOnServer(), GetAveragePlayerTimeOnServerRTV(), nominate_worth, RoundToFloor((nominate_worth/total_votes) * 100), GetRtvPercentage(client)); return Plugin_Handled; } public Action Command_ExtendsLeft(int client, int args) { CReplyToCommand(client, "[MCE] Available Extends: %d", GetConVarInt(g_Cvar_Extend) - g_Extends); return Plugin_Handled; } public void OnMapTimeLeftChanged() { if(GetArraySize(g_MapList)) { //LogMessage("Mapchooser_extended_avg OnMapTimeLeftChanged call."); SetupTimeleftTimer(); } } void SetupTimeleftTimer() { int time; if(GetMapTimeLeft(time) && time > 0) { int startTime; if(GetConVarBool(g_Cvar_StartTimePercentEnable)) { int timeLimit; if(GetMapTimeLimit(timeLimit) && timeLimit > 0) { startTime = GetConVarInt(g_Cvar_StartTimePercent) * (timeLimit * 60) / 100; } } else startTime = GetConVarInt(g_Cvar_StartTime) * 60; if(time - startTime < 0 && GetConVarBool(g_Cvar_EndOfMapVote) && !g_MapVoteCompleted && !g_HasVoteStarted && !g_WaitingForVote) { if (!g_DidNotExtend && !g_IniatedLastVote) { SetupWarningTimer(WarningType_Vote); //PrintToChatAll("SetupWarningTimer 1"); } } else { if(g_WarningTimer == INVALID_HANDLE) { if(g_VoteTimer != INVALID_HANDLE) { KillTimer(g_VoteTimer); g_VoteTimer = INVALID_HANDLE; } //g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartMapVoteTimer_StartMapVote, _, TIMER_FLAG_NO_MAPCHANGE); //LogMessage("Mapchooser_extended_avg g_VoteTimer Timer_StartWarningTimer"); if (GetConVarInt(g_Cvar_Extend) - g_Extends > 0 && !g_DidNotExtend) { g_VoteTimer = CreateTimer(float(time - startTime), Timer_StartWarningTimer, _, TIMER_FLAG_NO_MAPCHANGE); } } } } } public Action Timer_StartWarningTimer(Handle timer) { g_VoteTimer = INVALID_HANDLE; int timeleft; GetMapTimeLeft(timeleft); if (timeleft <= 0 && !g_ChangeMapAtRoundEnd) //somehow a random vote might get triggered. this should prevent it { return Plugin_Stop; } if(!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE) { if (!g_IniatedLastVote) { SetupWarningTimer(WarningType_Vote); //PrintToChatAll("SetupWarningTimer 2"); } } return Plugin_Stop; } public Action Timer_StartMapVote(Handle timer, Handle data) { static int timePassed; // This is still necessary because InitiateVote still calls this directly via the retry timer if(!GetArraySize(g_MapList) || !GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted) { g_WarningTimer = INVALID_HANDLE; //LogMessage("Mapchooser_extended_avg Timer_Start MapVote Plugin_Stop"); return Plugin_Stop; } ResetPack(data); int warningMaxTime = ReadPackCell(data); int warningTimeRemaining = warningMaxTime - timePassed; char warningPhrase[32]; ReadPackString(data, warningPhrase, sizeof(warningPhrase)); // Tick timer for external plugins Call_StartForward(g_MapVoteWarningTickForward); Call_PushCell(warningTimeRemaining); Call_Finish(); if(timePassed == 0 || !GetConVarBool(g_Cvar_HideTimer)) { TimerLocation timerLocation = view_as(GetConVarInt(g_Cvar_TimerLocation)); switch(timerLocation) { case TimerLocation_Center: { PrintCenterTextAll("%t", warningPhrase, warningTimeRemaining); } case TimerLocation_Chat: { PrintToChatAll("%t", warningPhrase, warningTimeRemaining); } default: { PrintHintTextToAll("%t", warningPhrase, warningTimeRemaining); } } } //LogMessage("timePassed: %i \n warningMaxTime: %i", timePassed, warningMaxTime); if(timePassed++ >= warningMaxTime) { if(timer == g_RetryTimer) { g_WaitingForVote = false; g_RetryTimer = INVALID_HANDLE; } else g_WarningTimer = INVALID_HANDLE; timePassed = 0; MapChange mapChange = view_as(ReadPackCell(data)); Handle hndl = view_as(ReadPackCell(data)); //LogMessage("Mapchooser_extended_avg calling InitiateVote()"); if (mapChange == MapChange_MapEnd && GetConVarInt(g_Cvar_Extend) - g_Extends < 1) { return Plugin_Stop; //feature where normal map changes does not happen if we ran out of extends, instead end of map does the actual real vote. } InitiateVote(mapChange, hndl); return Plugin_Stop; } return Plugin_Continue; } public void Event_TFRestartRound(Handle event, const char[] name, bool dontBroadcast) { /* Game got restarted - reset our round count tracking */ g_TotalRounds = 0; } public void Event_MvMWinPanel(Handle event, const char[] name, bool dontBroadcast) { if(GetEventInt(event, "winning_team") == 2) { int objectiveEnt = EntRefToEntIndex(g_ObjectiveEnt); if(objectiveEnt != INVALID_ENT_REFERENCE) { g_TotalRounds = GetEntProp(g_ObjectiveEnt, Prop_Send, "m_nMannVsMachineWaveCount"); CheckMaxRounds(g_TotalRounds); } } } public void Event_Intermission(Handle event, const char[] name, bool dontBroadcast) { g_HasIntermissionStarted = true; } public void Event_PhaseEnd(Handle event, const char[] name, bool dontBroadcast) { /* announce_phase_end fires for both half time and the end of the map, but intermission fires first for end of the map. */ if(g_HasIntermissionStarted) return; /* No intermission yet, so this must be half time. Swap the score counters. */ int t_score = g_winCount[2]; g_winCount[2] = g_winCount[3]; g_winCount[3] = t_score; } public void Event_WeaponRank(Handle event, const char[] name, bool dontBroadcast) { int rank = GetEventInt(event, "weaponrank"); if(rank > g_TotalRounds) { g_TotalRounds = rank; CheckMaxRounds(g_TotalRounds); } } public Action CS_OnTerminateRound(float &delay, CSRoundEndReason &reason) { int timeleft; GetMapTimeLeft(timeleft); if (g_WaitingForVote && timeleft <= 0) { g_DidRoundTerminate = true; return Plugin_Handled; //it keeps retriggering this Action. } if (g_IniatedLastVote) { return Plugin_Handled; //just extra safety check } if (timeleft <= 0 && !g_ChangeMapAtRoundEnd) { int total_time = (GetConVarInt(g_Cvar_VoteDuration) * 2) + GetConVarInt(g_Cvar_RunOffWarningTime) + 10; if (!g_IniatedLastVote) { if (IsVoteInProgress()) //fucking admins being brain dead doing extend votes right when the map ends. { PrintToChatAll("The admin slept on the job again. Should had made that extend vote 2-3 minutes before the timeleft ran out instead of panic extend voting the last second possible."); CancelVote(); } g_IniatedLastVote = true; CreateTimer(float(total_time + 10), Timer_fall_back_map_switch, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); //sometimes it will simply happen that theres a mapvote where nobody votes anything. //have to prevent server from being stuck in those situations with this timer. InitiateVote(MapChange_Instant); //feature so mapvote happens at actual mapend CreateTimer(1.0, unfreeze_players, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); //extending the map allows players to run around instead of being frozen. should just be careful about it running into edicts limits } delay = float(total_time); return Plugin_Changed; } g_DidRoundTerminate = false; return Plugin_Continue; } public Action unfreeze_players(Handle hTimer, Handle dp) { ServerCommand("sm_testround"); CreateTimer(1.0, unfreeze_players2, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); return Plugin_Handled; } public Action unfreeze_players2(Handle hTimer, Handle dp) { ServerCommand("sm_extend 20"); //this is not actually meant for extending the map. Its meant to unfreeze players during the mapvote. return Plugin_Handled; } public Action Timer_fall_back_map_switch(Handle hTimer, Handle dp) { ForceChangeLevel("ze_random_v9", "nobody voted at mapvote"); return Plugin_Handled; } /* You ask, why don't you just use team_score event? And I answer... Because CSS doesn't. */ public void Event_RoundEnd(Handle event, const char[] name, bool dontBroadcast) { g_DidRoundTerminate = false; if(g_ChangeMapAtRoundEnd) { char map[32]; GetNextMap(map, sizeof(map)); PrintToChatAll("[MCE] Next Map: %s", map); PrintToChatAll("[MCE] Next Map: %s", map); PrintToChatAll("[MCE] Next Map: %s", map); } if(g_RoundCounting == RoundCounting_ArmsRace) return; if(g_ChangeMapAtRoundEnd && !g_IniatedLastVote) { g_ChangeMapAtRoundEnd = false; CreateTimer(2.0, Timer_ChangeMap, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE); g_ChangeMapInProgress = true; } int winner; if(strcmp(name, "round_win") == 0 || strcmp(name, "dod_round_win") == 0) winner = GetEventInt(event, "team"); // Nuclear Dawn & DoD:S else winner = GetEventInt(event, "winner"); if(winner == 0 || winner == 1 || !GetConVarBool(g_Cvar_EndOfMapVote)) return; if(winner >= MAXTEAMS) SetFailState("Mod exceed maximum team count - Please file a bug report."); g_TotalRounds++; g_winCount[winner]++; if(!GetArraySize(g_MapList) || g_HasVoteStarted || g_MapVoteCompleted) { return; } CheckWinLimit(g_winCount[winner]); CheckMaxRounds(g_TotalRounds); } public void CheckWinLimit(int winner_score) { int winlimit = GetConVarInt(g_Cvar_Winlimit); if(winlimit) { if(winner_score >= (winlimit - GetConVarInt(g_Cvar_StartRounds))) { if(!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE) { SetupWarningTimer(WarningType_Vote, MapChange_MapEnd); //PrintToChatAll("SetupWarningTimer 3"); } } } } public void CheckMaxRounds(int roundcount) { int maxrounds = 0; if(g_RoundCounting == RoundCounting_ArmsRace) maxrounds = GameRules_GetProp("m_iNumGunGameProgressiveWeaponsCT"); else if(g_RoundCounting == RoundCounting_MvM) maxrounds = GetEntProp(g_ObjectiveEnt, Prop_Send, "m_nMannVsMachineMaxWaveCount"); else return; if(maxrounds) { if(roundcount >= (maxrounds - GetConVarInt(g_Cvar_StartRounds))) { if(!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE) { SetupWarningTimer(WarningType_Vote, MapChange_MapEnd); //PrintToChatAll("SetupWarningTimer 4"); } } } } public Action Event_PlayerDeath(Handle event, const char[] name, bool dontBroadcast) { if(!GetArraySize(g_MapList) || g_HasVoteStarted) return Plugin_Continue; if(!GetConVarInt(g_Cvar_Fraglimit) || !GetConVarBool(g_Cvar_EndOfMapVote)) return Plugin_Continue; if(g_MapVoteCompleted) return Plugin_Continue; int fragger = GetClientOfUserId(GetEventInt(event, "attacker")); if(!fragger) return Plugin_Continue; if(GetClientFrags(fragger) >= (GetConVarInt(g_Cvar_Fraglimit) - GetConVarInt(g_Cvar_StartFrags))) { if(!g_WarningInProgress || g_WarningTimer == INVALID_HANDLE) { SetupWarningTimer(WarningType_Vote, MapChange_MapEnd); //PrintToChatAll("SetupWarningTimer 5"); } } return Plugin_Continue; } public Action Command_Mapvote(int client, int args) { ShowActivity2(client, "[MCE] ", "%t", "Initiated Vote Map"); SetupWarningTimer(WarningType_Vote, MapChange_MapEnd, INVALID_HANDLE, true); //PrintToChatAll("SetupWarningTimer 6"); return Plugin_Handled; } public Handle get_most_nominated_maps(bool create_next_vote) { int arraySize = ByteCountToCells(PLATFORM_MAX_PATH); Handle most_nominated_maps = CreateArray(arraySize); StringMap sm = new StringMap(); for (int i = 0; i < MaxClients; i++) { for (int j = 0; j < GetArraySize(g_NominateList[i]); j++) { char map_iteration[PLATFORM_MAX_PATH]; GetArrayString(g_NominateList[i], j, map_iteration, PLATFORM_MAX_PATH); int nominate_count_for_particular_map = 0; sm.GetValue(map_iteration, nominate_count_for_particular_map); //if i is 0 its admin nominated map that must come into the vote. if (!i) { nominate_count_for_particular_map = 1999; } else { int nominate_worth = RoundToFloor(GetPlayerWorthRTV_boost_(i)); if (nominate_worth < 1) { nominate_worth = 1; } nominate_count_for_particular_map += nominate_worth; } sm.SetValue(map_iteration, nominate_count_for_particular_map, true); if (!create_next_vote) { /* Notify Nominations that this map is now free */ Call_StartForward(g_NominationsResetForward); Call_PushString(map_iteration); Call_PushCell(i + 100); //differentiate between all other calls and the call invoked by get_most_nominated_maps() Call_Finish(); } } } static char map_[PLATFORM_MAX_PATH]; StringMapSnapshot sm_snapshot = sm.Snapshot(); ArrayList SortedList = CreateArray(arraySize); for(int i = 0; i < sm_snapshot.Length; i++) { sm_snapshot.GetKey(i, map_, sizeof(map_)); SortedList.PushString(map_); } SortedList.SortCustom(NominateListSortCmp, sm); for(int i = 0; i < GetArraySize(SortedList); i++) { char picked_map[MAX_NAME_LENGTH]; GetArrayString(SortedList, i, picked_map, sizeof(picked_map)); //2023 edit: respecting that only right amount of maps per group is allowed in vote int groups_[32]; int groups[32]; int groupsfound = InternalGetMapGroups(picked_map, groups, sizeof(groups)); bool skip_nomination = false; for (int group = 0; group < groupsfound; group ++) { int groupcur = 0; int groupmax = InternalGetGroupMax(groups[group]); if (groupmax >= 0) { for (int j = 0; j < GetArraySize(most_nominated_maps); j++) { //Native_GetMapGroupRestriction GetArrayString(most_nominated_maps, j, map_, PLATFORM_MAX_PATH); int tmp = InternalGetMapGroups(map_, groups_, sizeof(groups_)); if (FindIntInArray(groups_, tmp, groups[group]) != -1) { groupcur++; } if (groupcur >= groupmax) { skip_nomination = true; break; } } if (skip_nomination) { break; } } } if (skip_nomination) { continue; } PushArrayString(most_nominated_maps, picked_map); } delete sm; return most_nominated_maps; } int NominateListSortCmp(int index1, int index2, Handle array, Handle hndl) { char map1[PLATFORM_MAX_PATH]; char map2[PLATFORM_MAX_PATH]; GetArrayString(array, index1, map1, sizeof(map1)); GetArrayString(array, index2, map2, sizeof(map2)); int count1, count2; StringMap sm = view_as(hndl); sm.GetValue(map1, count1); sm.GetValue(map2, count2); if (count1 == count2) return 0; return count1 > count2 ? -1 : 1; } /** * Starts a new map vote * * @param when When the resulting map change should occur. * @param inputlist Optional list of maps to use for the vote, otherwise an internal list of nominations + random maps will be used. */ void InitiateVote(MapChange when, Handle inputlist=INVALID_HANDLE) { g_WaitingForVote = true; g_WarningInProgress = false; int MenuRandomShuffleStart = 0; int MenuRandomShuffleStop = 0; // Check if a vote is in progress first if(IsVoteInProgress()) { CPrintToChatAll("[MCE] %t", "Cannot Start Vote", FAILURE_TIMER_LENGTH); Handle data; g_RetryTimer = CreateDataTimer(1.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); //LogMessage("Mapchooser_extended_avg: called Timer_StartMapVote"); /* Mapchooser Extended */ WritePackCell(data, FAILURE_TIMER_LENGTH); if(GetConVarBool(g_Cvar_RunOff) && g_RunoffCount > 0) WritePackString(data, "Revote Warning"); else { if (when == MapChange_MapEnd) { WritePackString(data, "Vote Warning Extend"); } else { WritePackString(data, "Vote Warning"); } } /* End Mapchooser Extended */ WritePackCell(data, view_as(when)); WritePackCell(data, view_as(inputlist)); ResetPack(data); return; } /* If the main map vote has completed (and chosen result) and its currently changing (not a delayed change) we block further attempts */ if(g_MapVoteCompleted && g_ChangeMapInProgress) { //LogMessage("Mapchooser_extended_avg ended here: g_MapVoteCompleted: %i \n g_ChangeMapInProgress: %i", g_MapVoteCompleted, g_ChangeMapInProgress); return; } CreateNextVote(); g_ChangeTime = when; ServerCommand("sm_cvar sm_vote_progress_hintbox 0"); //yeah its cheesy but does the job. g_iInterval = GetConVarInt(g_Cvar_VoteDuration); CreateTimer(1.0, Timer_Countdown, _, TIMER_REPEAT); g_HasVoteStarted = true; g_VoteMenu = CreateMenu(Handler_MapVoteMenu, MenuAction_End | MenuAction_Display | MenuAction_DisplayItem | MenuAction_VoteCancel); g_AddNoVote = GetConVarBool(g_Cvar_NoVoteOption); // Block Vote Slots if(GetConVarBool(g_Cvar_BlockSlots)) { Handle radioStyle = GetMenuStyleHandle(MenuStyle_Radio); if(GetMenuStyle(g_VoteMenu) == radioStyle) { g_BlockedSlots = true; AddMenuItem(g_VoteMenu, LINE_ONE, "Choose something...", ITEMDRAW_DISABLED); AddMenuItem(g_VoteMenu, LINE_TWO, "...will ya?", ITEMDRAW_DISABLED); MenuRandomShuffleStart += 2; if(!g_AddNoVote) { AddMenuItem(g_VoteMenu, LINE_SPACER, "", ITEMDRAW_SPACER); MenuRandomShuffleStart++; } } else g_BlockedSlots = false; } if(g_AddNoVote) SetMenuOptionFlags(g_VoteMenu, MENUFLAG_BUTTON_NOVOTE); SetMenuTitle(g_VoteMenu, "Vote Nextmap"); SetVoteResultCallback(g_VoteMenu, Handler_MapVoteFinished); /* Call OnMapVoteStarted() Forward */ // Call_StartForward(g_MapVoteStartedForward); // Call_Finish(); /** * TODO: Make a proper decision on when to clear the nominations list. * Currently it clears when used, and stays if an external list is provided. * Is this the right thing to do? External lists will probably come from places * like sm_mapvote from the adminmenu in the future. */ static char map[PLATFORM_MAX_PATH]; if (when == MapChange_MapEnd) //18th september feature idea where a normal mapvote at 3 minutes is between extend or not extend. { SetMenuTitle(g_VoteMenu, "Vote Extend"); AddMenuItem(g_VoteMenu, LINE_ONE, "Choose something...", ITEMDRAW_DISABLED); AddMenuItem(g_VoteMenu, LINE_TWO, "...will ya?", ITEMDRAW_DISABLED); AddExtendToMenu(g_VoteMenu, when); //AddMenuItem(g_VoteMenu, LINE_ONE, "Choose something...", ITEMDRAW_DISABLED); AddMapItem("Dont extend"); SetMenuOptionFlags(g_VoteMenu, MENUFLAG_BUTTON_NOVOTE); //should add no vote option for the extend vote? } /* No input given - User our internal nominations and maplist */ else if(inputlist == INVALID_HANDLE) { Handle randomizeList = INVALID_HANDLE; //2023 edit to allow multiple nominations per player Handle most_nominated_maps = get_most_nominated_maps(false); int voteSize = GetVoteSize(0); //voteSize wrong size probably for my for loop if(GetConVarBool(g_Cvar_RandomizeNominations)) randomizeList = CloneArray(most_nominated_maps); int nominateCount = GetArraySize(most_nominated_maps); /* Smaller of the two - It should be impossible for nominations to exceed the size though (cvar changed mid-map?) */ int nominationsToAdd = nominateCount >= voteSize ? voteSize : nominateCount; bool extendFirst = GetConVarBool(g_Cvar_ExtendPosition); if(extendFirst) { AddExtendToMenu(g_VoteMenu, when); MenuRandomShuffleStart++; } for(int i = 0; i < nominationsToAdd; i++) { GetArrayString(most_nominated_maps, i, map, PLATFORM_MAX_PATH); if(randomizeList == INVALID_HANDLE) AddMapItem(map); RemoveStringFromArray(g_NextMapList, map); } /* There should currently be 'nominationsToAdd' unique maps in the vote */ int i = nominationsToAdd; int count = 0; int availableMaps = GetArraySize(g_NextMapList); if(i < voteSize && availableMaps == 0) { if(i == 0) { LogError("No maps available for vote."); return; } else { LogMessage("Not enough maps to fill map list, reducing map count. Adjust mce_include and mce_exclude to avoid this warning."); voteSize = i; } } while(i < voteSize) { GetArrayString(g_NextMapList, count, map, PLATFORM_MAX_PATH); count++; if(randomizeList == INVALID_HANDLE) { /* Insert the map and increment our count */ AddMapItem(map); } else PushArrayString(randomizeList, map); i++; //Run out of maps, this will have to do. if(count >= availableMaps) break; } if(randomizeList != INVALID_HANDLE) { // Fisher-Yates Shuffle for(int j = GetArraySize(randomizeList) - 1; j >= 1; j--) { int k = GetRandomInt(0, j); SwapArrayItems(randomizeList, j, k); } for(int j = 0; j < GetArraySize(randomizeList); j++) { GetArrayString(randomizeList, j, map, PLATFORM_MAX_PATH); AddMapItem(map); } delete randomizeList; randomizeList = INVALID_HANDLE; delete most_nominated_maps; most_nominated_maps = INVALID_HANDLE; } /* Wipe out our nominations list - Nominations have already been informed of this */ g_NominateCount = 0; g_NominateReservedCount = 0; ClearArray(g_NominateOwners); for (int j = 0; j < MaxClients; j++) { ClearArray(g_NominateList[j]); } if(!extendFirst) { AddExtendToMenu(g_VoteMenu, when); MenuRandomShuffleStop++; } } else //We were given a list of maps to start the vote with { int size = GetArraySize(inputlist); for(int i = 0; i < size; i++) { GetArrayString(inputlist, i, map, PLATFORM_MAX_PATH); if(IsMapValid(map)) { AddMapItem(map); } // New in Mapchooser Extended else if(StrEqual(map, VOTE_DONTCHANGE)) { AddMenuItem(g_VoteMenu, VOTE_DONTCHANGE, "Dont Change"); } else if(StrEqual(map, VOTE_EXTEND)) { AddMenuItem(g_VoteMenu, VOTE_EXTEND, "Extend Map"); } } delete inputlist; } int voteDuration = GetConVarInt(g_Cvar_VoteDuration); //SetMenuExitButton(g_VoteMenu, false); if(GetVoteSize(0) <= GetMaxPageItems(GetMenuStyle(g_VoteMenu))) { //This is necessary to get items 9 and 0 as usable voting items SetMenuPagination(g_VoteMenu, MENU_NO_PAGINATION); } if(GetConVarInt(g_Cvar_ShufflePerClient)) MenuShufflePerClient(g_VoteMenu, MenuRandomShuffleStart, GetMenuItemCount(g_VoteMenu) - MenuRandomShuffleStop); //VoteMenuToAll(g_VoteMenu, voteDuration); //2023 excluding nosteamers and autismbots. int clients[MAXPLAYERS + 1]; int count = 0; //here we pick clients to show the vote to, we dont show it to autismbots and nosteamers. for (int i = 0; i < MaxClients; i++) { player_mapvote_worth[i] = 0.0; Format(player_mapvote[i], 128, ""); if (IsValidClient(i) && PM_IsPlayerSteam(i) && !is_bot_player[i]) { GetPlayerWorthRTV_(i); float nominate_worth = GetPlayerWorthRTV_boost_(i); if (nominate_worth < 1.0) { nominate_worth = 1.0; } player_mapvote_worth[i] = nominate_worth; clients[count] = i; count++; } } VoteMenu(g_VoteMenu, clients, sizeof(clients), voteDuration); /* Call OnMapVoteStarted() Forward */ Call_StartForward(g_MapVoteStartForward); // Deprecated Call_Finish(); Call_StartForward(g_MapVoteStartedForward); Call_Finish(); LogAction(-1, -1, "Voting for next map has started."); CPrintToChatAll("[MCE] %t", "Nextmap Voting Started"); } public Action Timer_Countdown(Handle timer) { if (g_iInterval <= 0) return Plugin_Stop; float most_voted[3]; char most_voted_map[3][PLATFORM_MAX_PATH]; for (int j = 0; j < 3; j++) { float max_count = 0.0; StringMap sm = new StringMap(); char picked_map[PLATFORM_MAX_PATH]; for (int i = 0; i < MaxClients; i++) { if (IsValidClient(i) && !StrEqual(player_mapvote[i], "") && !is_bot_player[i] && PM_IsPlayerSteam(i)) { if (StrEqual(player_mapvote[i], most_voted_map[0]) || StrEqual(player_mapvote[i], most_voted_map[1]) || StrEqual(player_mapvote[i], most_voted_map[2])) { continue; } float value = 0.0; sm.GetValue(player_mapvote[i], value); value += player_mapvote_worth[i]; sm.SetValue(player_mapvote[i], value, true); if (value >= max_count) { max_count = value; strcopy(picked_map, sizeof(picked_map), player_mapvote[i]); } } } most_voted[j] = max_count; Format(most_voted_map[j], PLATFORM_MAX_PATH, picked_map); delete sm; } float total_votes = 0.0; float voted_so_far = 0.0; for (int i = 0; i < MaxClients; i++) { if (IsValidClient(i) && !is_bot_player[i] && PM_IsPlayerSteam(i)) { total_votes += player_mapvote_worth[i]; //LogMessage("client: %N player_mapvote_worth i: %f", i, player_mapvote_worth[i]); if (!StrEqual(player_mapvote[i], "")) { voted_so_far += player_mapvote_worth[i]; } } } //why on earth is %%%s needed for formatting it to show a % lmao. just %s, "%" or %% on their own dont work. if (strlen(most_voted_map[2]) > 0) { //displaying 3 most voted maps PrintHintTextToAll("Votes: %i/100%%%s, %ds left\n1. %s: (%i%%%s)\n2. %s: (%i%%%s)\n3. %s: (%i%%%s)", RoundToFloor((voted_so_far/total_votes) * 100), "%", g_iInterval, most_voted_map[0], RoundToFloor((most_voted[0]/total_votes) * 100), "%", most_voted_map[1], RoundToFloor((most_voted[1]/total_votes) * 100), "%", most_voted_map[2], RoundToFloor((most_voted[2]/total_votes) * 100), "%"); } else if (strlen(most_voted_map[1]) > 0) { //displaying 2 most voted maps PrintHintTextToAll("Votes: %i/100%%%s, %ds left\n1. %s: (%i%%%s)\n2. %s: (%i%%%s)", RoundToFloor((voted_so_far/total_votes) * 100), "%", g_iInterval, most_voted_map[0], RoundToFloor((most_voted[0]/total_votes) * 100), "%", most_voted_map[1], RoundToFloor((most_voted[1]/total_votes) * 100), "%"); } else if (strlen(most_voted_map[0]) > 0) { //displaying the most voted map PrintHintTextToAll("Votes: %i/100%%%s, %ds left\n1. %s: (%i%%%s)", RoundToFloor((voted_so_far/total_votes) * 100), "%", g_iInterval, most_voted_map[0], RoundToFloor((most_voted[0]/total_votes) * 100), "%"); } else { //displaying just the votecount and vote duration remaining PrintHintTextToAll("Votes: %i/100%%%s, %ds left", RoundToFloor((voted_so_far/total_votes) * 100), "%", g_iInterval); } g_iInterval--; return Plugin_Continue; } public void Handler_VoteFinishedGeneric(char[] map, float num_votes, float map_votes) { Call_StartForward(g_MapVoteEndForward); Call_PushString(map); Call_Finish(); if(strcmp(map, VOTE_EXTEND, false) == 0) { g_Extends++; int time; if(GetMapTimeLimit(time)) { if(time > 0) ExtendMapTimeLimit(GetConVarInt(g_Cvar_ExtendTimeStep)*60); } int winlimit = GetConVarInt(g_Cvar_Winlimit); if(winlimit) SetConVarInt(g_Cvar_Winlimit, winlimit + GetConVarInt(g_Cvar_ExtendRoundStep)); int maxrounds = GetConVarInt(g_Cvar_Maxrounds); if(maxrounds) SetConVarInt(g_Cvar_Maxrounds, maxrounds + GetConVarInt(g_Cvar_ExtendRoundStep)); int fraglimit = GetConVarInt(g_Cvar_Fraglimit); if(fraglimit) SetConVarInt(g_Cvar_Fraglimit, fraglimit + GetConVarInt(g_Cvar_ExtendFragStep)); PrintToChatAll("The current map has been extended. (Received %i%s of votes)", RoundToFloor((map_votes /num_votes)*100.0), "%"); LogAction(-1, -1, "Voting for next map has finished. The current map has been extended."); CPrintToChatAll("[MCE] Available Extends: %d", GetConVarInt(g_Cvar_Extend) - g_Extends); // We extended, so well have to vote again. g_RunoffCount = 0; g_HasVoteStarted = false; if (GetConVarInt(g_Cvar_Extend) - g_Extends < 1) { g_DidNotExtend = true; //dont come with the map extend vote if all extends were used. } SetupTimeleftTimer(); } else if(strcmp(map, VOTE_DONTCHANGE, false) == 0) { PrintToChatAll("Current map continues! The Vote has spoken! (Received %i%s of votes)", RoundToFloor((map_votes /num_votes)*100.0), "%"); LogAction(-1, -1, "Voting for next map has finished. 'No Change' was the winner"); g_RunoffCount = 0; g_HasVoteStarted = false; SetupTimeleftTimer(); } else { if(g_ChangeTime == MapChange_MapEnd) { //SetNextMap(map); //feature edit where MapChange_MapEnd is only used to decide if extend or not extend g_DidNotExtend = true; //just so warningtimer wont start running again if an admin removes or adds time when players voted to not extend. } else if(g_ChangeTime == MapChange_Instant || g_DidRoundTerminate) //g_DidRoundTerminate implies that rtv vote is running while the last round already ended. //therefore rtv vote has to switch map right now instead of roundend. { PrintToChatAll("[MCE] Next Map: %s", map); PrintToChatAll("[MCE] Next Map: %s", map); PrintToChatAll("[MCE] Next Map: %s", map); Handle data; CreateDataTimer(4.0, Timer_ChangeMap, data); WritePackString(data, map); g_ChangeMapInProgress = false; } else // MapChange_RoundEnd { SetNextMap(map); g_ChangeMapAtRoundEnd = true; } g_HasVoteStarted = false; g_MapVoteCompleted = true; //checking on MapStart and MapEnd is not good enough. Players are not considered alive and on teams at those points in time. //therefore instead applying the bool here after the mapvote completed. if(InternalAreRestrictionsActive(false)) g_SaveCDOnMapEnd = true; else g_SaveCDOnMapEnd = false; //PrintToChatAll("map: %s", map); if (g_ChangeTime == MapChange_MapEnd) { CPrintToChatAll("[MCE] %t", "Extend Voting Failed", RoundToFloor((map_votes /num_votes)*100.0)); g_MapVoteCompleted = false; //this was only the extend or dont extend vote, still need actual mapvote. } else { CPrintToChatAll("[MCE] %t", "Nextmap Voting Finished", map, RoundToFloor((map_votes /num_votes)*100.0)); LogAction(-1, -1, "Voting for next map has finished. Nextmap: %s.", map); } } } public void Handler_MapVoteFinished(Handle menu, int num_votes, int num_clients, const int[][] client_info, int num_items, const int[][] item_info) { g_iInterval = 1; //we display it shortly after finishing ServerCommand("sm_cvar sm_vote_progress_hintbox 1"); //yeah its cheesy but does the job. //reweighting votes StringMap sm = new StringMap(); for (int i = 0; i < num_clients; i++) { int client = client_info[i][0]; if (IsValidClient(client) && !StrEqual(player_mapvote[client], "")) { //default intention is 1-5 votes, just like rtv. int item_index = client_info[i][VOTEINFO_CLIENT_ITEM]; static char map[PLATFORM_MAX_PATH]; for(int j = 0; j < num_items; j++) { if (item_info[j][VOTEINFO_ITEM_INDEX] == item_index) { GetMapItem(menu, item_info[j][VOTEINFO_ITEM_INDEX], map, PLATFORM_MAX_PATH); break; } } float value = 0.0; sm.GetValue(map, value); float nominate_worth = GetPlayerWorthRTV_boost_(client); if (nominate_worth < 1.0) { nominate_worth = 1.0; } value += nominate_worth; sm.SetValue(map, value, true); } } //ordering stringmap by voteweight float[] weighted_votes = new float[num_items]; char[][] weighted_maps = new char[num_items][PLATFORM_MAX_PATH]; for (int i = 0; i < num_items; i++) { StringMapSnapshot keys = sm.Snapshot(); float max_count = 0.0; char picked_map[PLATFORM_MAX_PATH]; for (int j = 0; j < keys.Length; j++) { int size = keys.KeyBufferSize(j); char[] buffer = new char[size]; keys.GetKey(j, buffer, size); float value = 0.0; sm.GetValue(buffer, value); //first selection has most votes, second selection second most etc etc if (value >= max_count) { max_count = value; strcopy(picked_map, sizeof(picked_map), buffer); } } sm.Remove(picked_map); weighted_votes[i] = max_count; Format(weighted_maps[i], 128, picked_map); delete keys; } delete sm; float total_votes = 0.0; for (int i = 0; i < MaxClients; i++) { if (IsValidClient(i) && !is_bot_player[i] && PM_IsPlayerSteam(i)) { total_votes += player_mapvote_worth[i]; } } // Implement revote logic - Only run this` block if revotes are enabled and this isn't the last revote' //LogMessage("Mapchooser_extended_avg Handler_MapVoteFinished."); int required_percent = GetConVarInt(g_Cvar_RunOffPercent); int most_voted_map_percentage = RoundToFloor((weighted_votes[0] / total_votes) * 100); if(GetConVarBool(g_Cvar_RunOff) && g_RunoffCount < GetConVarInt(g_Cvar_MaxRunOffs) && num_items > 1 && g_ChangeTime != MapChange_MapEnd && (weighted_votes[0] == weighted_votes[1] || most_voted_map_percentage < required_percent)) { //LogMessage("Mapchooser_extended_avg Handler_MapVoteFinished passed check1."); g_RunoffCount++; if(weighted_votes[0] == weighted_votes[1]) { g_HasVoteStarted = false; //Revote is needed int arraySize = ByteCountToCells(PLATFORM_MAX_PATH); Handle mapList = CreateArray(arraySize); //adding all maps with same amount of votes for(int i = 0; i < num_items; i++) { if(weighted_votes[i] == weighted_votes[0]) { PushArrayString(mapList, weighted_maps[i]); } else break; } CPrintToChatAll("[MCE] %t", "Tie Vote", GetArraySize(mapList)); //LogMessage("Mapchooser_extended_avg reached WarningType_Revote 1"); SetupWarningTimer(WarningType_Revote, view_as(g_ChangeTime), mapList); //PrintToChatAll("SetupWarningTimer 7"); return; } else if(most_voted_map_percentage < required_percent) { g_HasVoteStarted = false; //Revote is needed int arraySize = ByteCountToCells(PLATFORM_MAX_PATH); Handle mapList = CreateArray(arraySize); PushArrayString(mapList, weighted_maps[0]); // We allow more than two maps for a revote if they are tied for(int i = 1; i < num_items; i++) { if(GetArraySize(mapList) < 2 || weighted_votes[i] == weighted_votes[i - 1]) { PushArrayString(mapList, weighted_maps[i]); } else break; } CPrintToChatAll("[MCE] %t", "Revote Is Needed", required_percent); //LogMessage("Mapchooser_extended_avg reached WarningType_Revote 2"); SetupWarningTimer(WarningType_Revote, view_as(g_ChangeTime), mapList); //PrintToChatAll("SetupWarningTimer 8"); return; } } g_WaitingForVote = false; //LogMessage("Mapchooser_extended_avg no revote needed, continue as normal."); // No revote needed, continue as normal. Handler_VoteFinishedGeneric(weighted_maps[0], total_votes, weighted_votes[0]); } public int Handler_MapVoteMenu(Handle menu, MenuAction action, int param1, int param2) { switch(action) { case MenuAction_End: { g_VoteMenu = INVALID_HANDLE; delete menu; } case MenuAction_Select: { char map[PLATFORM_MAX_PATH]; GetMenuItem(menu, param2, map, PLATFORM_MAX_PATH, _, _, _, param1); if (StrEqual(map, "##dontchange##")) { Format(map, sizeof(map), "Dont change"); } else if (StrEqual(map, "##extend##")) { Format(map, sizeof(map), "Extend"); } Format(player_mapvote[param1], 128, map); } case MenuAction_Display: { Format(player_mapvote[param1], 128, ""); static char buffer[255]; //displaying to the client how much percent his vote is worth. float total_votes = 0.0; for (int i = 0; i < MaxClients; i++) { if (IsValidClient(i) && !is_bot_player[i] && PM_IsPlayerSteam(i)) { total_votes += player_mapvote_worth[i]; } } //second parameter shows for example 5.0, last parameter is how much percentage of the entire vote the client decides. if (g_ChangeTime == MapChange_MapEnd) { Format(buffer, sizeof(buffer), "%T", "Vote Extend", param1, player_mapvote_worth[param1], RoundToFloor((player_mapvote_worth[param1]/total_votes) * 100)); } else { Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1, player_mapvote_worth[param1], RoundToFloor((player_mapvote_worth[param1]/total_votes) * 100)); } Handle panel = view_as(param2); SetPanelTitle(panel, buffer); //char PannelText[256] = "Warning: The Position of the Maps are different for each Player."; //DrawPanelText(panel, PannelText); } case MenuAction_DisplayItem: { char map[PLATFORM_MAX_PATH]; char buffer[255]; int mark = GetConVarInt(g_Cvar_MarkCustomMaps); GetMenuItem(menu, param2, map, PLATFORM_MAX_PATH, _, _, _, param1); if(StrEqual(map, VOTE_EXTEND, false)) { Format(buffer, sizeof(buffer), "%T", "Extend Map", param1); } else if(StrEqual(map, VOTE_DONTCHANGE, false)) { Format(buffer, sizeof(buffer), "%T", "Dont Change", param1); } // Mapchooser Extended else if(StrEqual(map, LINE_ONE, false)) { Format(buffer, sizeof(buffer),"%T", "Line One", param1); } else if(StrEqual(map, LINE_TWO, false)) { Format(buffer, sizeof(buffer),"%T", "Line Two", param1); } // Note that the first part is to discard the spacer line else if(!StrEqual(map, LINE_SPACER, false)) { if(mark == 1 && !InternalIsMapOfficial(map)) { Format(buffer, sizeof(buffer), "%T", "Custom Marked", param1, map); } else if(mark == 2 && !InternalIsMapOfficial(map)) { Format(buffer, sizeof(buffer), "%T", "Custom", param1, map); } else if(InternalGetMapVIPRestriction(map)) { Format(buffer, sizeof(buffer), "%s (%T)", map, "VIP Nomination", param1); } } if(buffer[0] != '\0') { return RedrawMenuItem(buffer); } // End Mapchooser Extended } case MenuAction_VoteCancel: { // If we receive 0 votes, pick at random. if(param1 == VoteCancel_NoVotes && GetConVarBool(g_Cvar_NoVoteMode)) { int count = GetMenuItemCount(menu); int item; static char map[PLATFORM_MAX_PATH]; do { int startInt = 0; if(g_BlockedSlots) { if(g_AddNoVote) { startInt = 2; } else { startInt = 3; } } item = GetRandomInt(startInt, count - 1); GetMenuItem(menu, item, map, PLATFORM_MAX_PATH, _, _, _, param1); } while(strcmp(map, VOTE_EXTEND, false) == 0); SetNextMap(map); g_MapVoteCompleted = true; } g_WaitingForVote = false; g_HasVoteStarted = false; g_RunoffCount = 0; g_iInterval = 1; //we display it shortly after finishing ServerCommand("sm_cvar sm_vote_progress_hintbox 1"); //yeah its cheesy but does the job. } } return 0; } public Action Timer_ChangeMap(Handle hTimer, Handle dp) { g_ChangeMapInProgress = false; char map[PLATFORM_MAX_PATH]; if(dp == INVALID_HANDLE) { if(!GetNextMap(map, PLATFORM_MAX_PATH)) { //No passed map and no set nextmap. fail! return Plugin_Stop; } } else { ResetPack(dp); ReadPackString(dp, map, PLATFORM_MAX_PATH); } PrintToChatAll("[MCE] Next Map: %s", map); PrintToChatAll("[MCE] Next Map: %s", map); PrintToChatAll("[MCE] Next Map: %s", map); ForceChangeLevel(map, "Map Vote"); return Plugin_Stop; } bool RemoveStringFromArray(Handle array, char[] str) { int index = FindStringInArray(array, str); if(index != -1) { RemoveFromArray(array, index); return true; } return false; } void CreateNextVote() { assert(g_NextMapList) ClearArray(g_NextMapList); static char map[PLATFORM_MAX_PATH]; Handle tempMaps = CloneArray(g_MapList); GetCurrentMap(map, PLATFORM_MAX_PATH); RemoveStringFromArray(tempMaps, map); if(GetArraySize(tempMaps) > GetConVarInt(g_Cvar_ExcludeMaps) && InternalAreRestrictionsActive(false)) { StringMapSnapshot OldMapListSnapshot = g_OldMapList.Snapshot(); for(int i = 0; i < OldMapListSnapshot.Length; i++) { OldMapListSnapshot.GetKey(i, map, sizeof(map)); RemoveStringFromArray(tempMaps, map); } delete OldMapListSnapshot; } if(InternalAreRestrictionsActive(false)) { StringMapSnapshot TimeMapListSnapshot = g_TimeMapList.Snapshot(); for(int i = 0; i < TimeMapListSnapshot.Length; i++) { TimeMapListSnapshot.GetKey(i, map, sizeof(map)); int Cooldown; g_TimeMapList.GetValue(map, Cooldown); if(Cooldown > GetTime()) RemoveStringFromArray(tempMaps, map); } delete TimeMapListSnapshot; } int voteSize = GetVoteSize(0); int limit = (voteSize < GetArraySize(tempMaps) ? voteSize : GetArraySize(tempMaps)); // group -> number of maps nominated from group StringMap groupmap = new StringMap(); char groupstr[8]; // populate groupmap with maps from nomination list static char map_[PLATFORM_MAX_PATH]; int groups_[32]; //2023 edit Handle most_nominated_maps = get_most_nominated_maps(true); for(int i = 0; i < GetArraySize(most_nominated_maps); i++) { GetArrayString(most_nominated_maps, i, map_, PLATFORM_MAX_PATH); int groupsfound = InternalGetMapGroups(map_, groups_, sizeof(groups_)); for(int group = 0; group < groupsfound; group++) { IntToString(group, groupstr, sizeof(groupstr)); int groupcur = 0; groupmap.GetValue(groupstr, groupcur); groupcur++; groupmap.SetValue(groupstr, groupcur, true); } } // find random maps which honor all restrictions int pickedFromCasualPool = 0; for(int i = 0; i < limit; i++) { int b; for(int j = 0; j < 1000; j++) { b = GetRandomInt(0, GetArraySize(tempMaps) - 1); GetArrayString(tempMaps, b, map, PLATFORM_MAX_PATH); //if we abandon the feature again its important to just set mce_randomized_maps_from_casual_pool to 0. if (pickedFromCasualPool < g_iMapsFromCasualPool) { if (!InternalGetCasualPool(map)) { continue; } //requested by keen in september 2024 that we have a pool that is prioritized when taking random maps. //it might be considered micro management which would indeed be bad. i guess this is mostly about pleasing a regular players request //which does not seem too outlandish to make possible. pickedFromCasualPool++; break; } if(!InternalAreRestrictionsActive(false)) break; if(InternalGetMapVIPRestriction(map)) continue; if(InternalGetMapCooldownTime(map) > GetTime()) continue; if(InternalGetMapTimeRestriction(map) != 0) continue; if(InternalGetMapPlayerRestriction(map) != 0) continue; if (InternalGetAveragePlayerHourRestriction(map) != 0) continue; bool okay = true; int groups[32]; int groupsfound = InternalGetMapGroups(map, groups, sizeof(groups)); for(int group = 0; group < groupsfound; group++) { IntToString(group, groupstr, sizeof(groupstr)); int groupmax = InternalGetGroupMax(groups[group]); if(groupmax >= 0) { int groupcur = 0; groupmap.GetValue(groupstr, groupcur); if(groupcur >= groupmax) { okay = false; break; } groupcur++; groupmap.SetValue(groupstr, groupcur, true); } } if(okay) break; } PushArrayString(g_NextMapList, map); RemoveFromArray(tempMaps, b); } delete groupmap; delete tempMaps; } bool CanVoteStart() { if(g_WaitingForVote || g_HasVoteStarted) return false; return true; } NominateResult InternalNominateMap(char[] map, int owner) { if(!IsMapValid(map)) { return Nominate_InvalidMap; } /* Look to replace an existing nomination by this client - Nominations made with owner = 0 arent replaced */ //2023 edit: change clients first nomination out of the clients multiple nominations, make a check if client filled all his nomination slots //currently hard coded to 3 maps, just add a cvar to replace it with in the future if(owner && ((FindValueInArray(g_NominateOwners, owner)) != -1) && GetArraySize(g_NominateList[owner]) > 2) { char oldmap[PLATFORM_MAX_PATH]; GetArrayString(g_NominateList[owner], 0, oldmap, PLATFORM_MAX_PATH); Call_StartForward(g_NominationsResetForward); Call_PushString(oldmap); Call_PushCell(owner); Call_Finish(); RemoveFromArray(g_NominateList[owner], 0); PushArrayString(g_NominateList[owner], map); return Nominate_Replaced; } /* Too many nominated maps. */ //2023 edit: we dont want this check /* if(g_NominateCount >= GetVoteSize(0) && !force) { return Nominate_VoteFull; } */ if (owner != 0 && g_NominateList[owner] != INVALID_HANDLE) { for (int j = 0; j < GetArraySize(g_NominateList[owner]); j++) { char map_iteration[PLATFORM_MAX_PATH]; GetArrayString(g_NominateList[owner], j, map_iteration, PLATFORM_MAX_PATH); if (StrEqual(map, map_iteration, false)) { return Nominate_InvalidMap; } } } if (g_NominateList[owner] == INVALID_HANDLE) { if (IsClientConnected(owner) && IsClientInGame(owner)) { ReplyToCommand(owner, "This should be invalid."); } return Nominate_InvalidMap; } PushArrayString(g_NominateList[owner], map); PushArrayCell(g_NominateOwners, owner); //maybe i only want to do this for the first nomination of each client if(owner == 0 && g_NominateReservedCount < GetVoteSize(0)) g_NominateReservedCount++; else g_NominateCount++; while(GetArraySize(g_NominateList[owner]) > GetVoteSize(0)) { char oldmap[PLATFORM_MAX_PATH]; GetArrayString(g_NominateList[owner], 0, oldmap, PLATFORM_MAX_PATH); Call_StartForward(g_NominationsResetForward); Call_PushString(oldmap); Call_PushCell(owner); int owner_ = GetArrayCell(g_NominateOwners, 0); Call_Finish(); RemoveFromArray(g_NominateList[owner], 0); RemoveFromArray(g_NominateOwners, 0); if(owner_ == 0) g_NominateReservedCount--; else g_NominateCount--; } return Nominate_Added; } /* Add natives to allow nominate and initiate vote to be call */ /* native bool NominateMap(const char[] map, bool force, &NominateError:error); */ public int Native_NominateMap(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); return view_as(InternalNominateMap(map, GetNativeCell(3))); } bool InternalRemoveNominationByMap(char[] map) { for (int client = 0; client < MaxClients; client++) { for(int i = 0; i < GetArraySize(g_NominateList[client]); i++) { char oldmap[PLATFORM_MAX_PATH]; GetArrayString(g_NominateList[client], i, oldmap, PLATFORM_MAX_PATH); if(strcmp(map, oldmap, false) == 0) { Call_StartForward(g_NominationsResetForward); Call_PushString(oldmap); Call_PushCell(client); Call_Finish(); int owner = GetArrayCell(g_NominateOwners, i); if(owner) g_NominateCount--; else g_NominateReservedCount--; RemoveFromArray(g_NominateList[client], i); RemoveFromArray(g_NominateOwners, i); return true; } } } return false; } /* native bool RemoveNominationByMap(const char[] map); */ public int Native_RemoveNominationByMap(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); return view_as(InternalRemoveNominationByMap(map)); } bool InternalRemoveNominationByOwner(int owner) { int index; if(owner && ((index = FindValueInArray(g_NominateOwners, owner)) != -1)) { char oldmap[PLATFORM_MAX_PATH]; GetArrayString(g_NominateList[owner], index, oldmap, PLATFORM_MAX_PATH); Call_StartForward(g_NominationsResetForward); Call_PushString(oldmap); Call_PushCell(owner); Call_Finish(); RemoveFromArray(g_NominateList[owner], index); //maybe only do once or change g_NominateOwners RemoveFromArray(g_NominateOwners, index); g_NominateCount--; return true; } return false; } /* native bool RemoveNominationByOwner(owner); */ public int Native_RemoveNominationByOwner(Handle plugin, int numParams) { return view_as(InternalRemoveNominationByOwner(GetNativeCell(1))); } /* native InitiateMapChooserVote(); */ public int Native_InitiateVote(Handle plugin, int numParams) { MapChange when = view_as(GetNativeCell(1)); Handle inputarray = view_as(GetNativeCell(2)); LogAction(-1, -1, "Starting map vote because outside request"); int timeleft; GetMapTimeLeft(timeleft); if (timeleft <= 0 && !g_ChangeMapAtRoundEnd) //somehow a random vote might get triggered. this should prevent it { return 0; } if (!g_IniatedLastVote) { SetupWarningTimer(WarningType_Vote, when, inputarray); //PrintToChatAll("SetupWarningTimer 9"); } return 0; } public int Native_CanVoteStart(Handle plugin, int numParams) { return CanVoteStart(); } public int Native_CheckVoteDone(Handle plugin, int numParams) { return g_MapVoteCompleted; } public int Native_EndOfMapVoteEnabled(Handle plugin, int numParams) { return GetConVarBool(g_Cvar_EndOfMapVote); } public int Native_GetExcludeMapList(Handle plugin, int numParams) { Handle array = view_as(GetNativeCell(1)); if(array == INVALID_HANDLE) return 0; static char map[PLATFORM_MAX_PATH]; StringMapSnapshot OldMapListSnapshot = g_OldMapList.Snapshot(); for(int i = 0; i < OldMapListSnapshot.Length; i++) { OldMapListSnapshot.GetKey(i, map, sizeof(map)); PushArrayString(array, map); } delete OldMapListSnapshot; return 0; } //GetNominatedMapList public int Native_GetNominatedMapList(Handle plugin, int numParams) { Handle maparray = view_as(GetNativeCell(1)); Handle ownerarray = view_as(GetNativeCell(2)); if(maparray == INVALID_HANDLE) return 0; static char map[PLATFORM_MAX_PATH]; for (int client = 0; client < MaxClients; client++) { for(int i = 0; i < GetArraySize(g_NominateList[client]); i++) { GetArrayString(g_NominateList[client], i, map, PLATFORM_MAX_PATH); PushArrayString(maparray, map); // If the optional parameter for an owner list was passed, then we need to fill that out as well if(ownerarray != INVALID_HANDLE) { //int index = GetArrayCell(g_NominateOwners, i); PushArrayCell(ownerarray, client); } } } return 0; } // Functions new to Mapchooser Extended stock void SetupWarningTimer(WarningType type, MapChange when=MapChange_MapEnd, Handle mapList=INVALID_HANDLE, bool force=false) { if(!GetArraySize(g_MapList) || g_ChangeMapInProgress || g_HasVoteStarted || (!force && ((when == MapChange_MapEnd && !GetConVarBool(g_Cvar_EndOfMapVote)) || g_MapVoteCompleted))) return; bool interrupted = false; if(g_WarningInProgress && g_WarningTimer != INVALID_HANDLE) { interrupted = true; KillTimer(g_WarningTimer); } g_WarningInProgress = true; Handle forwardVote; Handle cvarTime; static char translationKey[64]; switch(type) { case WarningType_Vote: { forwardVote = g_MapVoteWarningStartForward; cvarTime = g_Cvar_WarningTime; if (when == MapChange_MapEnd) { strcopy(translationKey, sizeof(translationKey), "Vote Warning Extend"); } else { strcopy(translationKey, sizeof(translationKey), "Vote Warning"); } //LogMessage("Mapchooser_extended_avg WarningType_Vote"); } case WarningType_Revote: { forwardVote = g_MapVoteRunoffStartForward; cvarTime = g_Cvar_RunOffWarningTime; strcopy(translationKey, sizeof(translationKey), "Revote Warning"); //LogMessage("Mapchooser_extended_avg WarningType_Revote. cvarTime: %i", GetConVarInt(cvarTime)); } } if(!interrupted) { Call_StartForward(forwardVote); Call_Finish(); } Handle data; g_WarningTimer = CreateDataTimer(1.0, Timer_StartMapVote, data, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT); WritePackCell(data, GetConVarInt(cvarTime)); WritePackString(data, translationKey); WritePackCell(data, view_as(when)); WritePackCell(data, view_as(mapList)); ResetPack(data); } stock void InitializeOfficialMapList() { // If this fails, we want it to have an empty adt_array if(ReadMapList(g_OfficialList, g_mapOfficialFileSerial, "official", MAPLIST_FLAG_CLEARARRAY|MAPLIST_FLAG_NO_DEFAULT) != INVALID_HANDLE) { LogMessage("Loaded map list for %s.", g_GameModName); } // Check if the map list was ever loaded else if(g_mapOfficialFileSerial == -1) { LogMessage("No official map list found for %s. Consider submitting one!", g_GameModName); } } stock bool IsMapEndVoteAllowed() { if(!GetConVarBool(g_Cvar_EndOfMapVote) || g_MapVoteCompleted || g_HasVoteStarted) return false; return true; } public int Native_IsMapOfficial(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); return InternalIsMapOfficial(map); } bool InternalIsMapOfficial(const char[] mapname) { int officialMapIndex = FindStringInArray(g_OfficialList, mapname); return (officialMapIndex > -1); } public int Native_IsWarningTimer(Handle plugin, int numParams) { return g_WarningInProgress; } public int Native_CanNominate(Handle plugin, int numParams) { if(g_HasVoteStarted) { return view_as(CanNominate_No_VoteInProgress); } if(g_MapVoteCompleted) { return view_as(CanNominate_No_VoteComplete); } if(g_NominateCount >= GetVoteSize()) { return view_as(CanNominate_No_VoteFull); } return view_as(CanNominate_Yes); } public int Native_ExcludeMap(Handle plugin, int numParams) { if(!InternalAreRestrictionsActive(false)) return true; int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); int Cooldown; int Mode = GetNativeCell(3); if(Mode == 0) { Cooldown = InternalGetMapCooldown(map); } else if(Mode == 1) { Cooldown = GetNativeCell(2); } else if(Mode == 2) { g_OldMapList.GetValue(map, Cooldown); int NewCooldown = GetNativeCell(2); if(NewCooldown > Cooldown) Cooldown = NewCooldown; } g_OldMapList.SetValue(map, Cooldown, true); InternalStoreMapCooldowns(); return true; } public int Native_ExcludeMapTime(Handle plugin, int numParams) { if(!InternalAreRestrictionsActive(false)) return true; int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); int Cooldown; int Mode = GetNativeCell(3); if(Mode == 0) { Cooldown = InternalGetMapCooldownTime(map); } else if(Mode == 1) { Cooldown = GetNativeCell(2); } else if(Mode == 2) { g_TimeMapList.GetValue(map, Cooldown); int NewCooldown = GetTime() + GetNativeCell(2); if(NewCooldown > Cooldown) Cooldown = GetNativeCell(2); else return true; } Cooldown += GetTime(); g_TimeMapList.SetValue(map, Cooldown, true); InternalStoreMapCooldowns(); return true; } public int Native_GetMapCooldown(Handle plugin, int numParams) { if(!InternalAreRestrictionsActive(false)) return 0; int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); int Cooldown = 0; g_OldMapList.GetValue(map, Cooldown); return Cooldown; } public int Native_GetMapCooldownTime(Handle plugin, int numParams) { if(!InternalAreRestrictionsActive(false)) return 0; int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); int Cooldown = 0; g_TimeMapList.GetValue(map, Cooldown); return Cooldown; } public int Native_GetMapMinTime(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); return InternalGetMapMinTime(map); } public int Native_GetMapMaxTime(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); return InternalGetMapMaxTime(map); } public int Native_GetMapMinPlayers(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); return InternalGetMapMinPlayers(map); } public int Native_GetMapMaxPlayers(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); return InternalGetMapMaxPlayers(map); } public int Native_GetMapTimeRestriction(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); return InternalGetMapTimeRestriction(map); } //GetAveragePlayerTimeOnServerMapRestriction public int Native_GetAveragePlayerTimeOnServerMapRestriction(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); return InternalGetAveragePlayerHourRestriction(map); } stock int InternalGetAveragePlayerHourRestriction(const char[] map) { int players_average_hours = GetAveragePlayerTimeOnServer(); int MinAverageHours = 0; if(g_Config && g_Config.JumpToKey(map)) { MinAverageHours = g_Config.GetNum("MinHoursAvg", MinAverageHours); g_Config.Rewind(); } //0 means map can be nominated, anything above 0 means more hours are required if (players_average_hours >= MinAverageHours) { return 0; } return MinAverageHours - players_average_hours; } //GetMapPlayerRestriction public int Native_GetMapPlayerRestriction(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); return InternalGetMapPlayerRestriction(map); } public int Native_GetMapGroups(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); int size = GetNativeCell(3); if(len <= 0) return -999; char[] map = new char[len+1]; GetNativeString(1, map, len+1); int[] groups = new int[size]; int found = InternalGetMapGroups(map, groups, size); if(found >= 0) SetNativeArray(2, groups, size); return found; } //GetMapGroupRestriction public int Native_GetMapGroupRestriction(Handle plugin, int numParams) { GetNativeCell(2); int len; GetNativeStringLength(1, len); if(len <= 0) return -999; char[] map = new char[len+1]; GetNativeString(1, map, len+1); int groups[32]; int groupsfound = InternalGetMapGroups(map, groups, sizeof(groups)); for(int group = 0; group < groupsfound; group ++) { int groupcur = 0; int groupmax = InternalGetGroupMax(groups[group]); if(groupmax >= 0) { static char map_[PLATFORM_MAX_PATH]; char kv_map[PLATFORM_MAX_PATH]; int groups_[32]; KeyValues kv = CreateKeyValues("cur_groups"); for (int clienti = 0; clienti < MaxClients; clienti++) { for(int i = 0; i < GetArraySize(g_NominateList[clienti]); i++) { GetArrayString(g_NominateList[clienti], i, map_, PLATFORM_MAX_PATH); int tmp = InternalGetMapGroups(map_, groups_, sizeof(groups_)); kv.GetString(map_, kv_map, sizeof(kv_map), "not_found"); if(FindIntInArray(groups_, tmp, groups[group]) != -1 && StrEqual(kv_map, "not_found")) { groupcur++; kv.SetString(map_, map_); } } } delete kv; } } return -1; } public int Native_GetMapVIPRestriction(Handle plugin, int numParams) { int client = GetNativeCell(2); int len; GetNativeStringLength(1, len); if(len <= 0) return false; char[] map = new char[len+1]; GetNativeString(1, map, len+1); // Check if client should bypass vip restrictions if(client >= 1 && client <= MaxClients) { // Client has bypass flag, dont return vip restrictions if(CheckCommandAccess(client, "sm_nominate_ignore", ADMFLAG_CHEATS)) return false; // Client has vip flag, dont return vip restrictions if(CheckCommandAccess(client, "sm_nominate_vip", ADMFLAG_CUSTOM1)) return false; } return InternalGetMapVIPRestriction(map); } public int Native_GetExtendsLeft(Handle plugin, int numParams) { return GetConVarInt(g_Cvar_Extend) - g_Extends; } public int Native_AreRestrictionsActive(Handle plugin, int numParams) { return InternalAreRestrictionsActive(false); } public int Native_SimulateMapEnd(Handle plugin, int numParams) { OnMapEnd(); return 0; } stock void AddMapItem(const char[] map) { AddMenuItem(g_VoteMenu, map, map); } stock void GetMapItem(Handle menu, int position, char[] map, int mapLen) { GetMenuItem(menu, position, map, mapLen, _, _, _, -1); } stock void AddExtendToMenu(Handle menu, MapChange when) { /* Do we add any special items? */ // Moved for Mapchooser Extended int timeleft; GetMapTimeLeft(timeleft); if (timeleft <= 0 && !g_ChangeMapAtRoundEnd) { return; //dont add dont change or extend because the map is over so now we must go for other maps. } if((when == MapChange_Instant || when == MapChange_RoundEnd) && GetConVarBool(g_Cvar_DontChange)) { // Built-in votes doesnt have "Dont Change", send Extend instead AddMenuItem(menu, VOTE_DONTCHANGE, "Dont Change"); } else if(GetConVarBool(g_Cvar_Extend) && g_Extends < GetConVarInt(g_Cvar_Extend)) { AddMenuItem(menu, VOTE_EXTEND, "Extend Map"); } } // 0 = IncludeMaps, 1 = Reserved, 2 = IncludeMaps+Reserved stock int GetVoteSize(int what=0) { int includeMaps = GetConVarInt(g_Cvar_IncludeMaps); int includeMapsReserved = GetConVarInt(g_Cvar_IncludeMapsReserved); if(what == 0) return includeMaps; else if(what == 1) return includeMapsReserved; else if(what == 2) return includeMaps + includeMapsReserved; return 0; } stock int InternalGetMapCooldown(const char[] map) { int Cooldown = g_Cvar_ExcludeMaps.IntValue; if(g_Config && g_Config.JumpToKey(map)) { Cooldown = g_Config.GetNum("Cooldown", Cooldown); g_Config.Rewind(); } return Cooldown; } stock int InternalGetMapCooldownTime(const char[] map) { char time[16]; g_Cvar_ExcludeMapsTime.GetString(time, sizeof(time)); int Cooldown = TimeStrToSeconds(time); if(g_Config && g_Config.JumpToKey(map)) { g_Config.GetString("CooldownTime", time, sizeof(time), ""); if(time[0]) Cooldown = TimeStrToSeconds(time); g_Config.Rewind(); } return Cooldown; } stock int InternalGetMapMinTime(const char[] map) { int MinTime = 0; if(g_Config && g_Config.JumpToKey(map)) { MinTime = g_Config.GetNum("MinTime", MinTime); g_Config.Rewind(); } return MinTime; } stock int InternalGetMapMaxTime(const char[] map) { int MaxTime = 0; if(g_Config && g_Config.JumpToKey(map)) { MaxTime = g_Config.GetNum("MaxTime", MaxTime); g_Config.Rewind(); } return MaxTime; } stock int InternalGetMapMinPlayers(const char[] map) { int MinPlayers = 0; if(g_Config && g_Config.JumpToKey(map)) { MinPlayers = g_Config.GetNum("MinPlayers", MinPlayers); g_Config.Rewind(); } return MinPlayers; } stock int InternalGetMapMaxPlayers(const char[] map) { int MaxPlayers = 0; if(g_Config && g_Config.JumpToKey(map)) { MaxPlayers = g_Config.GetNum("MaxPlayers", MaxPlayers); g_Config.Rewind(); } return MaxPlayers; } stock int InternalGetMapGroups(const char[] map, int[] groups, int size) { int found = 0; if(g_Config && g_Config.JumpToKey("_groups")) { if(!g_Config.GotoFirstSubKey(false)) { g_Config.Rewind(); return -999; } do { char groupstr[8]; g_Config.GetSectionName(groupstr, sizeof(groupstr)); int group = StringToInt(groupstr); if(g_Config.JumpToKey(map, false)) { groups[found++] = group; if(found >= size) { g_Config.Rewind(); return found; } g_Config.GoBack(); } } while(g_Config.GotoNextKey()); g_Config.Rewind(); } return found; } stock void InternalGetCvars() { if (g_Config && g_Config.JumpToKey("_cvars")) { g_Config.GotoFirstSubKey(false); char cvar_name[256]; char cvar_value[256]; do { g_Config.GetSectionName(cvar_name, sizeof(cvar_name)); g_Config.GetString(NULL_STRING, cvar_value, sizeof(cvar_value)); ServerCommand("sm_cvar %s %s", cvar_name, cvar_value); } while (g_Config.GotoNextKey(false)); g_Config.Rewind(); } } stock int InternalGetGroupMax(int group) { char groupstr[8]; IntToString(group, groupstr, sizeof(groupstr)); if(g_Config && g_Config.JumpToKey("_groups")) { if(g_Config.JumpToKey(groupstr, false)) { int max = g_Config.GetNum("_max", -1); g_Config.Rewind(); return max; } g_Config.Rewind(); } return -1; } // 0 = Okay // >0 = Minutes till Okay stock int InternalGetMapTimeRestriction(const char[] map) { char sTime[8]; FormatTime(sTime, sizeof(sTime), "%H%M"); int CurTime = StringToInt(sTime); int MinTime = InternalGetMapMinTime(map); int MaxTime = InternalGetMapMaxTime(map); //Wrap around. CurTime = (CurTime <= MinTime) ? CurTime + 2400 : CurTime; MaxTime = (MaxTime <= MinTime) ? MaxTime + 2400 : MaxTime; if (!(MinTime <= CurTime <= MaxTime)) { //Wrap around. MinTime = (MinTime <= CurTime) ? MinTime + 2400 : MinTime; MinTime = (MinTime <= MaxTime) ? MinTime + 2400 : MinTime; // Convert our 'time' to minutes. CurTime = (RoundToFloor(float(CurTime / 100)) * 60) + (CurTime % 100); MinTime = (RoundToFloor(float(MinTime / 100)) * 60) + (MinTime % 100); MaxTime = (RoundToFloor(float(MaxTime / 100)) * 60) + (MaxTime % 100); return MinTime - CurTime; } return 0; } public void OnClientPostAdminCheck(int client) { is_bot_player[client] = false; player_mapvote_worth[client] = 0.0; Format(player_mapvote[client], 128, ""); char auth[50]; GetClientAuthId(client, AuthId_Engine, auth, sizeof(auth)); if (StrEqual("[U:1:1221121532]", auth, false) || StrEqual("STEAM_0:0:610560766", auth, false)) { is_bot_player[client] = true; } if (StrEqual("[U:1:408797742]", auth, false) || StrEqual("STEAM_0:0:204398871", auth, false)) { is_bot_player[client] = true; } if (StrEqual("[U:1:1036189204]", auth, false) || StrEqual("STEAM_0:0:518094602", auth, false)) { is_bot_player[client] = true; } if (StrEqual("[U:1:120378081]", auth, false) || StrEqual("STEAM_0:1:60189040", auth, false)) { is_bot_player[client] = true; } } // <0 = Less than MinPlayers // 0 = Okay // >0 = More than MaxPlayers stock int InternalGetMapPlayerRestriction(const char[] map) { //int NumPlayers = GetClientCount(false); int NumPlayers = 0; //excluding non connected players, fakeclients, sourceTV, autism bots and nosteamers from player count restriction for (int client = 1; client <= MaxClients; client++) { if (IsClientConnected(client) && IsClientInGame(client) && !IsFakeClient(client) && !IsClientSourceTV(client) && !is_bot_player[client] && PM_IsPlayerSteam(client)) { NumPlayers++; } } int MinPlayers = InternalGetMapMinPlayers(map); int MaxPlayers = InternalGetMapMaxPlayers(map); if(MinPlayers && NumPlayers < MinPlayers) return NumPlayers - MinPlayers; if(MaxPlayers && NumPlayers > MaxPlayers) return NumPlayers - MaxPlayers; return 0; } /* false means we never put the map on cooldown if time range is inside the no map restriction time or if less than the minimum active players for enabling map restrictions are on the server. true means we put maps on Cooldown even if there was no map restrictions because of less than 15 active players (the number is a cvar, its not static) true still respects the time range with no map restrictions (23:30 to 10:30 right now) and wont apply cooldown there. */ stock bool InternalAreRestrictionsActive(bool skip_player_check) { if (!GetConVarBool(g_Cvar_NoRestrictionTimeframeEnable)) return true; char sTime[8]; FormatTime(sTime, sizeof(sTime), "%H%M"); int CurTime = StringToInt(sTime); int MinTime = GetConVarInt(g_Cvar_NoRestrictionTimeframeMinTime); int MaxTime = GetConVarInt(g_Cvar_NoRestrictionTimeframeMaxTime); //Wrap around. CurTime = (CurTime <= MinTime) ? CurTime + 2400 : CurTime; MaxTime = (MaxTime <= MinTime) ? MaxTime + 2400 : MaxTime; if ((MinTime <= CurTime <= MaxTime)) { return false; } int ActivePlayerCount = 0; for (int i = 0; i < MaxClients; i++) { if (IsValidClient(i) && !IsFakeClient(i) && !IsClientSourceTV(i) && !is_bot_player[i] && GetClientIdleTime(i) < g_iPlayerAFKTime && (GetClientTeam(i) == CS_TEAM_T || GetClientTeam(i) == CS_TEAM_CT)) { ActivePlayerCount++; } } if (ActivePlayerCount <= g_iPlayerCount_excludeSpec && !skip_player_check) { return false; } return true; } stock int FindIntInArray(int[] array, int size, int value) { for(int i = 0; i < size; i++) { if(array[i] == value) return i; } return -1; } stock bool InternalGetCasualPool(const char[] map) { int CasualPool = 0; if(g_Config && g_Config.JumpToKey(map)) { CasualPool = g_Config.GetNum("CasualPool", CasualPool); g_Config.Rewind(); } return view_as(CasualPool); } stock bool InternalGetMapVIPRestriction(const char[] map) { int VIP = 0; if(g_Config && g_Config.JumpToKey(map)) { VIP = g_Config.GetNum("VIP", VIP); g_Config.Rewind(); } return view_as(VIP); } stock void InternalRestoreMapCooldowns() { char sCooldownFile[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sCooldownFile, sizeof(sCooldownFile), "configs/mapchooser_extended/cooldowns.cfg"); if(!FileExists(sCooldownFile)) { LogMessage("Could not find cooldown file: \"%s\"", sCooldownFile); return; } KeyValues Cooldowns = new KeyValues("mapchooser_extended"); if(!Cooldowns.ImportFromFile(sCooldownFile)) { LogMessage("Unable to load cooldown file: \"%s\"", sCooldownFile); delete Cooldowns; return; } if(!Cooldowns.GotoFirstSubKey(true)) { LogMessage("Unable to goto first sub key: \"%s\"", sCooldownFile); delete Cooldowns; return; } int Cooldown; char map[PLATFORM_MAX_PATH]; do { if(!Cooldowns.GetSectionName(map, sizeof(map))) { LogMessage("Unable to get section name: \"%s\"", sCooldownFile); delete Cooldowns; return; } if((Cooldown = Cooldowns.GetNum("Cooldown", -1)) > 0) { LogMessage("Restored cooldown: %s -> %d", map, Cooldown); g_OldMapList.SetValue(map, Cooldown, true); } if((Cooldown = Cooldowns.GetNum("CooldownTime", -1)) > 0) { LogMessage("Restored time cooldown: %s -> %d", map, Cooldown); g_TimeMapList.SetValue(map, Cooldown, true); } } while(Cooldowns.GotoNextKey(true)); delete Cooldowns; } stock void InternalStoreMapCooldowns() { char sCooldownFile[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sCooldownFile, sizeof(sCooldownFile), "configs/mapchooser_extended/cooldowns.cfg"); if(!FileExists(sCooldownFile)) { LogMessage("Could not find cooldown file: \"%s\"", sCooldownFile); return; } KeyValues Cooldowns = new KeyValues("mapchooser_extended"); int Cooldown; char map[PLATFORM_MAX_PATH]; StringMapSnapshot OldMapListSnapshot = g_OldMapList.Snapshot(); for(int i = 0; i < OldMapListSnapshot.Length; i++) { OldMapListSnapshot.GetKey(i, map, sizeof(map)); g_OldMapList.GetValue(map, Cooldown); if (!Cooldowns.JumpToKey(map, true)) { LogMessage("Unable to create/find key: %s", map); delete OldMapListSnapshot; delete Cooldowns; return; } Cooldowns.SetNum("Cooldown", Cooldown); Cooldowns.Rewind(); } delete OldMapListSnapshot; StringMapSnapshot TimeMapListSnapshot = g_TimeMapList.Snapshot(); for(int i = 0; i < TimeMapListSnapshot.Length; i++) { TimeMapListSnapshot.GetKey(i, map, sizeof(map)); g_TimeMapList.GetValue(map, Cooldown); if (!Cooldowns.JumpToKey(map, true)) { LogMessage("Unable to create/find key: %s", map); delete TimeMapListSnapshot; delete Cooldowns; return; } Cooldowns.SetNum("CooldownTime", Cooldown); Cooldowns.Rewind(); } delete TimeMapListSnapshot; if(!Cooldowns.ExportToFile(sCooldownFile)) { LogMessage("Unable to export cooldown file: \"%s\"", sCooldownFile); delete Cooldowns; return; } delete Cooldowns; } stock int TimeStrToSeconds(const char[] str) { int seconds = 0; int maxlen = strlen(str); for(int i = 0; i < maxlen;) { int val = 0; i += StringToIntEx(str[i], val); if(str[i] == 'h') { val *= 60; i++; } seconds += val * 60; } return seconds; } stock bool IsValidClient(int client) { if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client) && !IsFakeClient(client) && !IsClientSourceTV(client)) return true; return false; }