added new cooldown system that tracks minutes played into database rows and sets maps on cooldown until they fallen down enough of the listing

This commit is contained in:
jenz 2026-04-19 15:26:58 +02:00
parent 01816792ae
commit f590dcc7d1
4 changed files with 312 additions and 37 deletions

View File

@ -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

View File

@ -0,0 +1,195 @@
#pragma semicolon 1
#define PLUGIN_AUTHOR "jenz"
#define PLUGIN_VERSION "1.0"
#include <sourcemod>
#include <cstrike>
#include <mapchooser_extended>
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;
}

View File

@ -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)

View File

@ -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 ||