From f590dcc7d1fb0591d877938ba75568d2dd50bb73 Mon Sep 17 00:00:00 2001 From: jenz Date: Sun, 19 Apr 2026 15:26:58 +0200 Subject: [PATCH] added new cooldown system that tracks minutes played into database rows and sets maps on cooldown until they fallen down enough of the listing --- .../scripting/include/mapchooser_extended.inc | 3 + .../scripting/map_cooldown_tracker.sp | 195 ++++++++++++++++++ .../mapchooser_extended_avg_mapend.sp | 124 +++++++---- .../scripting/nominations_extended_avg.sp | 27 +++ 4 files changed, 312 insertions(+), 37 deletions(-) create mode 100644 mapchooser_extended/scripting/map_cooldown_tracker.sp diff --git a/mapchooser_extended/scripting/include/mapchooser_extended.inc b/mapchooser_extended/scripting/include/mapchooser_extended.inc index 691b9f2..ade1105 100755 --- a/mapchooser_extended/scripting/include/mapchooser_extended.inc +++ b/mapchooser_extended/scripting/include/mapchooser_extended.inc @@ -108,6 +108,8 @@ native CanNominateResult CanNominate(); native bool ExcludeMap(const char[] map, int cooldown = 0, int mode = 0); // Cooldown in seconds native bool ExcludeMapTime(const char[] map, int cooldown = 0, int mode = 0); +// cooldown based on mysql table rows listing +native bool ExcludeMapListingPriority(const char[] map, int index); native int GetMapCooldown(const char[] map); native int GetMapCooldownTime(const char[] map); // in unix time @@ -117,6 +119,7 @@ native int GetMapMaxTime(const char[] map); native int GetMapMinPlayers(const char[] map); native int GetMapMaxPlayers(const char[] map); native int GetMapMinHoursAvg(const char[] map); +native int GetExcludedMapListingPriority(const char[] map); // 0 = Okay // >0 = Minutes till Okay diff --git a/mapchooser_extended/scripting/map_cooldown_tracker.sp b/mapchooser_extended/scripting/map_cooldown_tracker.sp new file mode 100644 index 0000000..d3fea57 --- /dev/null +++ b/mapchooser_extended/scripting/map_cooldown_tracker.sp @@ -0,0 +1,195 @@ +#pragma semicolon 1 +#define PLUGIN_AUTHOR "jenz" +#define PLUGIN_VERSION "1.0" +#include +#include +#include + +int g_iPlayerCount_time_track; +int g_iTopMapsToMarkWithCooldown; +int g_iUnmarkCooldowns; +int g_iCurrentPlayerCount; +int g_iMapStartTime; +Database g_hDatabase; + +public Plugin myinfo = +{ + name = "map_tracker_cooldown", + author = PLUGIN_AUTHOR, + description = "assigns cooldowns on maps and tracks their played time on the server", + version = PLUGIN_VERSION, + url = "www.unloze.com" +}; + +public void OnPluginStart() +{ + if (!g_hDatabase) + { + Database.Connect(SQL_OnDatabaseConnect, "unloze_playtimestats"); + } + + ConVar cvar; + HookConVarChange((cvar = CreateConVar("mce_min_players_track_time", "20", "Playercount in full number required for tracking minutes spend on a map.")), Cvar_playersCount); + g_iPlayerCount_time_track = cvar.IntValue; + delete cvar; + + ConVar cvar1; + HookConVarChange((cvar1 = CreateConVar("mce_top_ordered_maps_on_cooldown", "30", "These highest listed maps will receive the cooldown boolean")), Cvar_TopMarkedWithCooldown); + g_iTopMapsToMarkWithCooldown = cvar1.IntValue; + delete cvar1; + + ConVar cvar2; + HookConVarChange((cvar2 = CreateConVar("mce_UnmarkCooldowns", "120", "How far down the list does a map need to be for getting unmarked again")), Cvar_UnmarkedCooldown); + g_iUnmarkCooldowns = cvar2.IntValue; + delete cvar2; + + g_iCurrentPlayerCount = GetClientCount(false); + CreateTimer(30.0, CheckPlayerCount, _, TIMER_REPEAT); +} + +public Action CheckPlayerCount(Handle timer) +{ + g_iCurrentPlayerCount = GetClientCount(false); + return Plugin_Continue; +} + +public void Cvar_playersCount(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_iPlayerCount_time_track = convar.IntValue; +} + +public void Cvar_TopMarkedWithCooldown(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_iTopMapsToMarkWithCooldown = convar.IntValue; +} + +public void Cvar_UnmarkedCooldown(ConVar convar, const char[] oldValue, const char[] newValue) +{ + g_iUnmarkCooldowns = convar.IntValue; +} + +public void SQL_OnDatabaseConnect(Database db, const char[] error, any data) +{ + if(!db || strlen(error)) + { + LogError("Database error: %s", error); + return; + } + g_hDatabase = db; + UpdateTopListedMapsWithCooldown(); +} + +public void OnMapStart() +{ + int i_port = GetConVarInt(FindConVar("hostport")); + if (i_port != 27019) + { + return; + } + + g_iMapStartTime = GetTime(); + if (!g_hDatabase) + { + Database.Connect(SQL_OnDatabaseConnect, "unloze_playtimestats"); + } + else + { + UpdateTopListedMapsWithCooldown(); + } +} + +public void UpdateTopListedMapsWithCooldown() +{ + //just set every map in top g_iTopMapsToMarkWithCooldown on cooldown + char sQuery[512]; + Format(sQuery, sizeof(sQuery), "update unloze_playtimestats.map_minutes_played set on_cooldown = true where mapname in ( SELECT mapname FROM (select mmp.mapname from unloze_playtimestats.map_minutes_played mmp order by mmp.minutes desc limit %i) as top) and on_cooldown is false", g_iTopMapsToMarkWithCooldown); + g_hDatabase.Query(SQL_FinishedQuery1, sQuery, _, DBPrio_Low); +} + +public void SQL_FinishedQuery1(Database db, DBResultSet results, const char[] error, DataPack data) +{ + delete results; + if (!db || strlen(error)) + { + LogError("Query error: %s", error); + } + delete data; + UpdateUnmarkCooldown(); +} + +public void UpdateUnmarkCooldown() +{ + //if map in listing fell below g_iUnmarkCooldowns then set the bool to false again. + char sQuery[512]; + Format(sQuery, sizeof(sQuery), "update unloze_playtimestats.map_minutes_played set on_cooldown = false where mapname not in ( SELECT mapname FROM (select mmp.mapname from unloze_playtimestats.map_minutes_played mmp order by mmp.minutes desc limit %i) as top) and on_cooldown is true", g_iUnmarkCooldowns); + g_hDatabase.Query(SQL_FinishedQuery2, sQuery, _, DBPrio_Low); +} + +public void SQL_FinishedQuery2(Database db, DBResultSet results, const char[] error, DataPack data) +{ + delete results; + if (!db || strlen(error)) + { + LogError("Query error: %s", error); + } + delete data; + SelectMapsToCooldownForMapChooser(); +} + +public void SelectMapsToCooldownForMapChooser() +{ + char sQuery[512]; + Format(sQuery, sizeof(sQuery), "select mapname, map_list_index from (select mapname, ROW_NUMBER() OVER(ORDER BY minutes DESC) as map_list_index, mmp.on_cooldown from unloze_playtimestats.map_minutes_played mmp) as targets where on_cooldown"); + g_hDatabase.Query(SQL_FinishedQuerySelectMaps, sQuery, _, DBPrio_Low); +} + +public void SQL_FinishedQuerySelectMaps(Database db, DBResultSet results, const char[] error, DataPack data) +{ + if (!db || strlen(error)) + { + LogError("Query error in SQL_FinishedQuerySelectMaps: %s", error); + delete results; + return; + } + + while (results.RowCount && results.FetchRow()) + { + char mapname[256]; + results.FetchString(0, mapname, sizeof(mapname)); + int map_list_index = results.FetchInt(1); + ExcludeMapListingPriority(mapname, g_iUnmarkCooldowns - map_list_index); + } + delete results; +} + +public void OnMapEnd() +{ + int i_port = GetConVarInt(FindConVar("hostport")); + if (i_port != 27019) + { + return; + } + if (g_iCurrentPlayerCount > g_iPlayerCount_time_track) + { + int elapsed = GetTime() - g_iMapStartTime; + int minutes = RoundToFloor(elapsed / 60.0); + LogMessage("minutes: %i", minutes); + + char Mapname[128]; + GetCurrentMap(Mapname, sizeof(Mapname)); + + char sQuery[512]; + Format(sQuery, sizeof(sQuery), "INSERT INTO `map_minutes_played` (`mapname`, `minutes`, `updated_on`) VALUES ('%s', '%i', NOW()) ON DUPLICATE KEY UPDATE `minutes` = `minutes` + '%i', `updated_on` = NOW()", Mapname, minutes, minutes); + g_hDatabase.Query(SQL_FinishedQuery, sQuery, _, DBPrio_Low); + } +} + +public void SQL_FinishedQuery(Database db, DBResultSet results, const char[] error, DataPack data) +{ + delete results; + if (!db || strlen(error)) + { + LogError("Query error: %s", error); + } + delete data; +} diff --git a/mapchooser_extended/scripting/mapchooser_extended_avg_mapend.sp b/mapchooser_extended/scripting/mapchooser_extended_avg_mapend.sp index 2a74bfe..7c271f6 100755 --- a/mapchooser_extended/scripting/mapchooser_extended_avg_mapend.sp +++ b/mapchooser_extended/scripting/mapchooser_extended_avg_mapend.sp @@ -125,6 +125,7 @@ Handle g_MapList = INVALID_HANDLE; Handle g_NominateList[MAXPLAYERS + 1]; StringMap g_OldMapList; StringMap g_TimeMapList; +StringMap g_CooldownList; Handle g_NextMapList = INVALID_HANDLE; Handle g_VoteMenu = INVALID_HANDLE; KeyValues g_Config; @@ -249,6 +250,7 @@ public void OnPluginStart() g_MapList = CreateArray(arraySize); g_OldMapList = new StringMap(); g_TimeMapList = new StringMap(); + g_CooldownList = new StringMap(); g_NextMapList = CreateArray(arraySize); g_OfficialList = CreateArray(arraySize); @@ -485,6 +487,8 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("AreRestrictionsActive", Native_AreRestrictionsActive); CreateNative("SimulateMapEnd", Native_SimulateMapEnd); CreateNative("GetAveragePlayerTimeOnServerMapRestriction", Native_GetAveragePlayerTimeOnServerMapRestriction); + CreateNative("ExcludeMapListingPriority", Native_ExcludeMapListingPriority); + CreateNative("GetExcludedMapListingPriority", Native_GetExcludedMapListingPriority); return APLRes_Success; } @@ -648,6 +652,15 @@ public void OnMapEnd() GetCurrentMap(map, PLATFORM_MAX_PATH); int Cooldown = InternalGetMapCooldown(map); + //resetting all again so they are added again onmapstart + StringMapSnapshot CooldownList = g_CooldownList.Snapshot(); + for(int i = 0; i < CooldownList.Length; i++) + { + CooldownList.GetKey(i, map, sizeof(map)); + g_CooldownList.Remove(map); + } + + StringMapSnapshot OldMapListSnapshot = g_OldMapList.Snapshot(); for(int i = 0; i < OldMapListSnapshot.Length; i++) { @@ -1038,42 +1051,43 @@ public Action Timer_fall_back_map_switch(Handle hTimer, Handle dp) static char map[PLATFORM_MAX_PATH]; Handle tempMaps = CloneArray(g_MapList); - //if we stop using the casual pool feature we instead just rely on the default map to be used. - if (g_iMapsFromCasualPool > 0) + int b; + for(int j = 0; j < 1000; j++) { - int b; - for(int j = 0; j < 1000; j++) - { - b = GetRandomInt(0, GetArraySize(tempMaps) - 1); - GetArrayString(tempMaps, b, map, PLATFORM_MAX_PATH); - - if (!InternalGetCasualPool(map)) - { - continue; - } - if (InternalGetMapCooldownTime(map) > GetTime()) - { - continue; - } - //found a map from casual pool to force to. + b = GetRandomInt(0, GetArraySize(tempMaps) - 1); + GetArrayString(tempMaps, b, map, PLATFORM_MAX_PATH); + if(!InternalAreRestrictionsActive()) break; - } - ForceChangeLevel(map, "nobody voted at mapvote"); - } - else - { - char map_line[256]; - File f = OpenFile("cfg/defaultmap.cfg", "r"); - if (f != null) + + //if we stop using the casual pool feature we instead just rely on the default map to be used. + if (g_iMapsFromCasualPool > 0 && !InternalGetCasualPool(map)) { - f.ReadLine(map_line, sizeof(map_line)); - CloseHandle(f); + continue; } - ReplaceString(map_line, sizeof(map_line), "map ", ""); - TrimString(map_line); - //LogMessage("map_line: %s", map_line); - ForceChangeLevel(map_line, "nobody voted at mapvote"); + + if (InternalGetExcludedMapListingPriority(map) != 0) + { + continue; + } + + if(InternalGetMapCooldownTime(map) > GetTime()) + continue; + + if(InternalGetMapVIPRestriction(map)) + continue; + + if(InternalGetMapTimeRestriction(map) != 0) + continue; + + if(InternalGetMapPlayerRestriction(map) != 0) + continue; + + if (InternalGetAveragePlayerHourRestriction(map) != 0) + continue; + break; } + ForceChangeLevel(map, "nobody voted at mapvote"); + delete tempMaps; return Plugin_Handled; } @@ -2193,9 +2207,6 @@ void CreateNextVote() } if (InternalGetMapCooldownTime(map) > GetTime()) 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; } @@ -2203,6 +2214,11 @@ void CreateNextVote() if(!InternalAreRestrictionsActive()) break; + if (InternalGetExcludedMapListingPriority(map) != 0) + { + continue; + } + if(InternalGetMapCooldownTime(map) > GetTime()) continue; @@ -2639,6 +2655,22 @@ public int Native_ExcludeMap(Handle plugin, int numParams) return true; } +//ExcludeMapListingPriority +public int Native_ExcludeMapListingPriority(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); + int index = GetNativeCell(2); + g_CooldownList.SetValue(map, index, true); + return 0; +} + public int Native_ExcludeMapTime(Handle plugin, int numParams) { int len; @@ -2730,6 +2762,27 @@ public int Native_GetMapCooldownTime(Handle plugin, int numParams) return Cooldown; } +//GetExcludedMapListingPriority +public int Native_GetExcludedMapListingPriority(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 InternalGetExcludedMapListingPriority(map); +} + +stock int InternalGetExcludedMapListingPriority(const char[] map) +{ + int Index_Cooldown = 0; + g_CooldownList.GetValue(map, Index_Cooldown); + return Index_Cooldown; +} + public int Native_GetMapMinTime(Handle plugin, int numParams) { int len; @@ -3282,9 +3335,6 @@ stock bool InternalAreRestrictionsActive() return false; } - //if (IsValidClient(i) && !IsFakeClient(i) && !IsClientSourceTV(i) && !is_bot_player[i] - // && (GetClientTeam(i) == CS_TEAM_T || GetClientTeam(i) == CS_TEAM_CT)) - // a lot more simplified again so its easier to understand. int NumPlayers = GetClientCount(false); if (NumPlayers <= g_iSkipAllRestrictions) diff --git a/mapchooser_extended/scripting/nominations_extended_avg.sp b/mapchooser_extended/scripting/nominations_extended_avg.sp index af6e5c9..97a987c 100755 --- a/mapchooser_extended/scripting/nominations_extended_avg.sp +++ b/mapchooser_extended/scripting/nominations_extended_avg.sp @@ -529,6 +529,13 @@ public Action Command_Nominate(int client, int args) return Plugin_Handled; } + //yeah admins and leaders cant bypass these restrictions, maybe the admins really are the problem sometimes for skipping restrictions. + int Index_Cooldown = GetExcludedMapListingPriority(mapname); + if (Index_Cooldown > 0) + { + CPrintToChat(client, "[NE] Map is on Cooldown for being overplayed. List Posistion: %i", Index_Cooldown); + return Plugin_Handled; + } //July 2024 edit: any person who is potential leader can just skip all cooldowns and map restrictions. same for admins. if(!CheckCommandAccess(client, "sm_nominate_ignore", ADMFLAG_KICK, true) && !Leader_Is(client)) @@ -942,6 +949,7 @@ Menu BuildMapMenu(const char[] filter, int client) { if(AreRestrictionsActive() && ( GetMapCooldownTime(map) > GetTime() || + GetExcludedMapListingPriority(map) > 0 || GetMapTimeRestriction(map) || GetMapPlayerRestriction(map) || GetAveragePlayerTimeOnServerMapRestriction(map) > 0 || @@ -1034,6 +1042,12 @@ public int Handler_MapSelectMenu(Menu menu, MenuAction action, int param1, int p } GetClientName(param1, name, MAX_NAME_LENGTH); + int Index_Cooldown = GetExcludedMapListingPriority(map); + if (Index_Cooldown > 0) + { + PrintToChat(param1, "[NE] You cant nominate this map right now. Overplayed Position: %i", Index_Cooldown); + return 0; + } if (!CheckCommandAccess(param1, "sm_nominate_ignore", ADMFLAG_KICK, true) && !Leader_Is(param1)) { @@ -1132,6 +1146,11 @@ public int Handler_MapSelectMenu(Menu menu, MenuAction action, int param1, int p delete MapList; delete OwnerList; + int Index_Cooldown = GetExcludedMapListingPriority(map); + if (Index_Cooldown > 0) + { + return ITEMDRAW_DISABLED; + } if(!CheckCommandAccess(param1, "sm_nominate_ignore", ADMFLAG_KICK, true) && !Leader_Is(param1)) { if(AreRestrictionsActive() && ( @@ -1187,6 +1206,13 @@ public int Handler_MapSelectMenu(Menu menu, MenuAction action, int param1, int p Format(buffer, sizeof(buffer), "%s (%T)", buffer, "VIP Restriction", param1); } + int Index_Cooldown = GetExcludedMapListingPriority(map); + if (Index_Cooldown > 0) + { + Format(display, sizeof(display), "%s (Overplayed pos: %i)", map, Index_Cooldown); + return RedrawMenuItem(display); + } + int Cooldown = GetMapCooldownTime(map); if(RestrictionsActive && Cooldown > GetTime()) { @@ -1296,6 +1322,7 @@ public int Handler_AdminMapSelectMenu(Menu menu, MenuAction action, int param1, { if(AreRestrictionsActive() && ( GetMapCooldownTime(map) > GetTime() || //this one is fine cause its admin nomination. + GetExcludedMapListingPriority(map) > 0 || GetMapTimeRestriction(map) || GetMapPlayerRestriction(map) || GetAveragePlayerTimeOnServerMapRestriction(map) > 0 ||