From 89fd5268c9d9ce48ddc0b020910139ac8015644c Mon Sep 17 00:00:00 2001 From: Dustin Hendrickson Date: Thu, 31 Jul 2025 16:21:20 -0700 Subject: [PATCH] Addiing support for offline real palyer guild exclusion --- conf/mod_player_bot_level_brackets.conf.dist | 7 + ...07_31_bot_level_brackets_guild_tracker.sql | 12 ++ src/mod-player-bot-level-brackets.cpp | 139 +++++++++++++++++- 3 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 data/sql/characters/base/2025_07_31_bot_level_brackets_guild_tracker.sql diff --git a/conf/mod_player_bot_level_brackets.conf.dist b/conf/mod_player_bot_level_brackets.conf.dist index 041dfda..2bf0cfe 100644 --- a/conf/mod_player_bot_level_brackets.conf.dist +++ b/conf/mod_player_bot_level_brackets.conf.dist @@ -50,6 +50,13 @@ BotLevelBrackets.FlaggedProcessLimit = 5 # Valid values: 0 (disabled) / 1 (enabled) BotLevelBrackets.IgnoreGuildBotsWithRealPlayers = 1 +# +# BotLevelBrackets.GuildTrackerUpdateFrequency +# Description: The frequency (in seconds) at which the persistent guild tracker database is updated. +# This tracks which guilds have real players even when they are offline. +# Default: 600 (10 minutes) +BotLevelBrackets.GuildTrackerUpdateFrequency = 600 + # # BotLevelBrackets.IgnoreFriendListed # Description: Ignore bots that are on real players friend's lists from any brackets. diff --git a/data/sql/characters/base/2025_07_31_bot_level_brackets_guild_tracker.sql b/data/sql/characters/base/2025_07_31_bot_level_brackets_guild_tracker.sql new file mode 100644 index 0000000..e39126e --- /dev/null +++ b/data/sql/characters/base/2025_07_31_bot_level_brackets_guild_tracker.sql @@ -0,0 +1,12 @@ +-- Bot Level Brackets Guild Tracker Table +-- This table tracks guilds that have real (non-bot) players to prevent bot level changes +-- when real players are in the guild, even when they are offline. + +DROP TABLE IF EXISTS `bot_level_brackets_guild_tracker`; + +CREATE TABLE `bot_level_brackets_guild_tracker` ( + `guild_id` int(10) unsigned NOT NULL COMMENT 'Guild ID from guild table', + `has_real_players` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Whether this guild has real (non-bot) players', + `last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last time this record was updated', + PRIMARY KEY (`guild_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Tracks guilds with real players for bot level bracket restrictions'; diff --git a/src/mod-player-bot-level-brackets.cpp b/src/mod-player-bot-level-brackets.cpp index 47ba74a..2927e1c 100644 --- a/src/mod-player-bot-level-brackets.cpp +++ b/src/mod-player-bot-level-brackets.cpp @@ -54,6 +54,7 @@ static std::vector g_HordeLevelRanges; static uint32 g_BotDistCheckFrequency = 300; // in seconds static uint32 g_BotDistFlaggedCheckFrequency = 15; // in seconds +static uint32 g_GuildTrackerUpdateFrequency = 600; // in seconds (10 minutes) static bool g_BotDistFullDebugMode = false; static bool g_BotDistLiteDebugMode = false; static bool g_UseDynamicDistribution = false; @@ -77,6 +78,9 @@ static std::vector g_ExcludeBotNames; // Array for real player guild IDs. std::unordered_set g_RealPlayerGuildIds; +// Persistent guild tracker - stores guild IDs that have real players (from database) +std::unordered_set g_PersistentRealPlayerGuildIds; + struct PendingResetEntry { ObjectGuid botGuid; @@ -108,6 +112,7 @@ static void LoadBotLevelBracketsConfig() g_BotDistLiteDebugMode = sConfigMgr->GetOption("BotLevelBrackets.LiteDebugMode", false); g_BotDistCheckFrequency = sConfigMgr->GetOption("BotLevelBrackets.CheckFrequency", 300); g_BotDistFlaggedCheckFrequency = sConfigMgr->GetOption("BotLevelBrackets.CheckFlaggedFrequency", 15); + g_GuildTrackerUpdateFrequency = sConfigMgr->GetOption("BotLevelBrackets.GuildTrackerUpdateFrequency", 600); g_UseDynamicDistribution = sConfigMgr->GetOption("BotLevelBrackets.Dynamic.UseDynamicDistribution", false); g_RealPlayerWeight = sConfigMgr->GetOption("BotLevelBrackets.Dynamic.RealPlayerWeight", 1.0f); g_SyncFactions = sConfigMgr->GetOption("BotLevelBrackets.Dynamic.SyncFactions", false); @@ -341,6 +346,122 @@ static void LoadSocialFriendList() } +/** + * @brief Loads the persistent guild tracker data from the database. + * + * This function queries the bot_level_brackets_guild_tracker table to load all guild IDs + * that have real players. This provides persistent storage of guild status even when + * real players are offline. The data is loaded into g_PersistentRealPlayerGuildIds. + */ +static void LoadPersistentGuildTracker() +{ + g_PersistentRealPlayerGuildIds.clear(); + QueryResult result = CharacterDatabase.Query("SELECT guild_id FROM bot_level_brackets_guild_tracker WHERE has_real_players = 1"); + + if (!result) + { + if (g_BotDistFullDebugMode) + { + LOG_INFO("server.loading", "[BotLevelBrackets] No guilds with real players found in persistent storage."); + } + return; + } + + if (g_BotDistFullDebugMode) + { + LOG_INFO("server.loading", "[BotLevelBrackets] Loading persistent guild tracker data from database..."); + } + + do + { + uint32 guildId = result->Fetch()->Get(); + g_PersistentRealPlayerGuildIds.insert(guildId); + if (g_BotDistFullDebugMode) + { + LOG_INFO("server.loading", "[BotLevelBrackets] Loaded guild {} as having real players.", guildId); + } + } while (result->NextRow()); + + if (g_BotDistFullDebugMode || g_BotDistLiteDebugMode) + { + LOG_INFO("server.loading", "[BotLevelBrackets] Loaded {} guilds with real players from persistent storage.", g_PersistentRealPlayerGuildIds.size()); + } +} + + +/** + * @brief Updates the persistent guild tracker database with current guild status. + * + * This function scans all characters in the database to determine which guilds have real players + * (non-bot players) and updates the bot_level_brackets_guild_tracker table accordingly. + * It runs periodically to ensure the database reflects the current state of guild memberships. + */ +static void UpdatePersistentGuildTracker() +{ + if (g_BotDistFullDebugMode) + { + LOG_INFO("server.loading", "[BotLevelBrackets] Starting persistent guild tracker update..."); + } + + // Query to get all guilds and check if they have real players + QueryResult result = CharacterDatabase.Query( + "SELECT DISTINCT g.guildid, " + "CASE WHEN EXISTS(" + " SELECT 1 FROM characters c " + " WHERE c.guid = gm.guid " + " AND c.guid NOT IN (" + " SELECT owner FROM ai_playerbot_random_bots" + " )" + ") THEN 1 ELSE 0 END as has_real_players " + "FROM guild g " + "LEFT JOIN guild_member gm ON g.guildid = gm.guildid" + ); + + if (!result) + { + if (g_BotDistFullDebugMode) + { + LOG_INFO("server.loading", "[BotLevelBrackets] No guild data found for persistent tracker update."); + } + return; + } + + std::unordered_set currentRealPlayerGuilds; + uint32 updatedCount = 0; + + do + { + uint32 guildId = result->Fetch()->Get(); + bool hasRealPlayers = result->Fetch()->Get(); + + if (hasRealPlayers) + { + currentRealPlayerGuilds.insert(guildId); + } + + // Update or insert the record + CharacterDatabase.Execute( + "INSERT INTO bot_level_brackets_guild_tracker (guild_id, has_real_players) " + "VALUES ({}, {}) " + "ON DUPLICATE KEY UPDATE has_real_players = VALUES(has_real_players)", + guildId, hasRealPlayers ? 1 : 0 + ); + + updatedCount++; + + } while (result->NextRow()); + + // Update our in-memory cache + g_PersistentRealPlayerGuildIds = currentRealPlayerGuilds; + + if (g_BotDistFullDebugMode || g_BotDistLiteDebugMode) + { + LOG_INFO("server.loading", "[BotLevelBrackets] Updated {} guild records in persistent tracker. {} guilds have real players.", + updatedCount, g_PersistentRealPlayerGuildIds.size()); + } +} + + /** * @brief Populates the global set of real player guild IDs from the provided player map. * @@ -530,7 +651,8 @@ static bool BotInGuildWithRealPlayer(Player* bot) { return false; } - return g_RealPlayerGuildIds.count(guildId) > 0; + // Check both online real players and persistent database storage + return g_RealPlayerGuildIds.count(guildId) > 0 || g_PersistentRealPlayerGuildIds.count(guildId) > 0; } @@ -979,7 +1101,7 @@ static int GetOrFlagPlayerBracket(Player* player) class BotLevelBracketsWorldScript : public WorldScript { public: - BotLevelBracketsWorldScript() : WorldScript("BotLevelBracketsWorldScript"), m_timer(0), m_flaggedTimer(0) { } + BotLevelBracketsWorldScript() : WorldScript("BotLevelBracketsWorldScript"), m_timer(0), m_flaggedTimer(0), m_guildTrackerTimer(0) { } /** * @brief Called when the module is started up. @@ -994,6 +1116,7 @@ public: { LoadBotLevelBracketsConfig(); LoadSocialFriendList(); + LoadPersistentGuildTracker(); if (!g_BotLevelBracketsEnabled) { LOG_INFO("server.loading", "[BotLevelBrackets] Module disabled via configuration."); @@ -1048,6 +1171,7 @@ public: m_timer += diff; m_flaggedTimer += diff; + m_guildTrackerTimer += diff; if (m_flaggedTimer >= g_BotDistFlaggedCheckFrequency * 1000) { @@ -1059,6 +1183,16 @@ public: m_flaggedTimer = 0; } + if (m_guildTrackerTimer >= g_GuildTrackerUpdateFrequency * 1000) + { + if (g_BotDistFullDebugMode) + { + LOG_INFO("server.loading", "[BotLevelBrackets] Guild Tracker Update Triggering."); + } + UpdatePersistentGuildTracker(); + m_guildTrackerTimer = 0; + } + if (m_timer < g_BotDistCheckFrequency * 1000) { return; @@ -1562,6 +1696,7 @@ public: private: uint32 m_timer; // For distribution adjustments uint32 m_flaggedTimer; // For pending reset checks + uint32 m_guildTrackerTimer; // For guild tracker updates };