mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-03-09 10:40:28 +00:00
feat(Core/LFG): Implement dungeon selection cooldown to prevent repeat assignme… (#24916)
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -3404,6 +3404,16 @@ DungeonFinder.CastDeserter = 1
|
||||
|
||||
DungeonFinder.AllowCompleted = 1
|
||||
|
||||
#
|
||||
# DungeonFinder.DungeonSelectionCooldown
|
||||
#
|
||||
# Description: Duration in minutes for the dungeon selection cooldown. Players who complete a
|
||||
# random dungeon via LFG will not be assigned the same dungeon again for this
|
||||
# duration. This reduces the chance of getting the same dungeon in a row.
|
||||
# Default: 0 - (Disabled)
|
||||
|
||||
DungeonFinder.DungeonSelectionCooldown = 0
|
||||
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
|
||||
@@ -165,6 +165,87 @@ namespace lfg
|
||||
LOG_INFO("server.loading", " ");
|
||||
}
|
||||
|
||||
void LFGMgr::AddDungeonCooldown(ObjectGuid guid, uint32 dungeonId)
|
||||
{
|
||||
if (!sWorld->getIntConfig(CONFIG_LFG_DUNGEON_SELECTION_COOLDOWN))
|
||||
return;
|
||||
|
||||
DungeonCooldownStore[guid][dungeonId] = GameTime::Now();
|
||||
}
|
||||
|
||||
void LFGMgr::CleanupDungeonCooldowns()
|
||||
{
|
||||
if (!sWorld->getIntConfig(CONFIG_LFG_DUNGEON_SELECTION_COOLDOWN))
|
||||
return;
|
||||
|
||||
Seconds cooldownDuration = GetDungeonCooldownDuration();
|
||||
|
||||
for (auto itPlayer = DungeonCooldownStore.begin(); itPlayer != DungeonCooldownStore.end(); )
|
||||
{
|
||||
for (auto itDungeon = itPlayer->second.begin(); itDungeon != itPlayer->second.end(); )
|
||||
{
|
||||
if (GameTime::HasElapsed(itDungeon->second, cooldownDuration))
|
||||
itDungeon = itPlayer->second.erase(itDungeon);
|
||||
else
|
||||
++itDungeon;
|
||||
}
|
||||
|
||||
if (itPlayer->second.empty())
|
||||
itPlayer = DungeonCooldownStore.erase(itPlayer);
|
||||
else
|
||||
++itPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
void LFGMgr::ClearDungeonCooldowns()
|
||||
{
|
||||
DungeonCooldownStore.clear();
|
||||
}
|
||||
|
||||
Seconds LFGMgr::GetDungeonCooldownDuration() const
|
||||
{
|
||||
return Seconds(sWorld->getIntConfig(CONFIG_LFG_DUNGEON_SELECTION_COOLDOWN) * MINUTE);
|
||||
}
|
||||
|
||||
LfgDungeonSet LFGMgr::FilterCooldownDungeons(LfgDungeonSet const& dungeons, LfgRolesMap const& players)
|
||||
{
|
||||
if (!sWorld->getIntConfig(CONFIG_LFG_DUNGEON_SELECTION_COOLDOWN))
|
||||
return dungeons;
|
||||
|
||||
Seconds cooldownDuration = GetDungeonCooldownDuration();
|
||||
|
||||
LfgDungeonSet filtered;
|
||||
for (uint32 dungeonId : dungeons)
|
||||
{
|
||||
bool onCooldown = false;
|
||||
for (auto const& playerPair : players)
|
||||
{
|
||||
auto itPlayer = DungeonCooldownStore.find(playerPair.first);
|
||||
if (itPlayer != DungeonCooldownStore.end())
|
||||
{
|
||||
auto itDungeon = itPlayer->second.find(dungeonId);
|
||||
if (itDungeon != itPlayer->second.end() && !GameTime::HasElapsed(itDungeon->second, cooldownDuration))
|
||||
{
|
||||
onCooldown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!onCooldown)
|
||||
filtered.insert(dungeonId);
|
||||
}
|
||||
|
||||
// If all dungeons are on cooldown, return original set to avoid blocking the queue
|
||||
if (filtered.empty())
|
||||
{
|
||||
LOG_DEBUG("lfg", "LFGMgr::FilterCooldownDungeons: All {} dungeons on cooldown for group, bypassing cooldown filter", dungeons.size());
|
||||
return dungeons;
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
LFGDungeonData const* LFGMgr::GetLFGDungeon(uint32 id)
|
||||
{
|
||||
LFGDungeonContainer::const_iterator itr = LfgDungeonStore.find(id);
|
||||
@@ -329,6 +410,9 @@ namespace lfg
|
||||
BootsStore.erase(itBoot);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup expired dungeon cooldowns
|
||||
CleanupDungeonCooldowns();
|
||||
}
|
||||
else if (task == 1)
|
||||
{
|
||||
@@ -2263,6 +2347,9 @@ namespace lfg
|
||||
continue;
|
||||
}
|
||||
|
||||
// Record dungeon cooldown for this player (the actual dungeon completed, not the random entry)
|
||||
AddDungeonCooldown(guid, dungeonId);
|
||||
|
||||
Player* player = ObjectAccessor::FindPlayer(guid);
|
||||
if (!player || player->FindMap() != currMap) // pussywizard: currMap - multithreading crash if on other map (map id check is not enough, binding system is not reliable)
|
||||
{
|
||||
|
||||
@@ -450,6 +450,10 @@ namespace lfg
|
||||
void LoadRewards();
|
||||
/// Loads dungeons from dbc and adds teleport coords
|
||||
void LoadLFGDungeons(bool reload = false);
|
||||
/// Filters out recently completed dungeons from the proposal set for the given players
|
||||
LfgDungeonSet FilterCooldownDungeons(LfgDungeonSet const& dungeons, LfgRolesMap const& players);
|
||||
/// Clears all dungeon cooldowns for all players
|
||||
void ClearDungeonCooldowns();
|
||||
|
||||
// Multiple files
|
||||
/// Check if given guid applied for random dungeon
|
||||
@@ -636,6 +640,14 @@ namespace lfg
|
||||
LfgPlayerDataContainer PlayersStore; ///< Player data
|
||||
LfgGroupDataContainer GroupsStore; ///< Group data
|
||||
bool m_Testing;
|
||||
|
||||
// Dungeon cooldown system - prevents same dungeon being assigned in a row
|
||||
typedef std::unordered_map<uint32 /*dungeonId*/, TimePoint /*completionTime*/> LfgDungeonCooldownMap;
|
||||
typedef std::unordered_map<ObjectGuid /*playerGuid*/, LfgDungeonCooldownMap> LfgDungeonCooldownContainer;
|
||||
LfgDungeonCooldownContainer DungeonCooldownStore; ///< Stores dungeon cooldowns per player
|
||||
void AddDungeonCooldown(ObjectGuid guid, uint32 dungeonId);
|
||||
void CleanupDungeonCooldowns();
|
||||
[[nodiscard]] Seconds GetDungeonCooldownDuration() const;
|
||||
};
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_enum_v<T>)>
|
||||
|
||||
@@ -415,7 +415,10 @@ namespace lfg
|
||||
proposal.cancelTime = GameTime::GetGameTime().count() + LFG_TIME_PROPOSAL;
|
||||
proposal.state = LFG_PROPOSAL_INITIATING;
|
||||
proposal.leader.Clear();
|
||||
proposal.dungeonId = Acore::Containers::SelectRandomContainerElement(proposalDungeons);
|
||||
|
||||
// Filter out recently completed dungeons to prevent same dungeon in a row
|
||||
LfgDungeonSet filteredDungeons = sLFGMgr->FilterCooldownDungeons(proposalDungeons, proposalRoles);
|
||||
proposal.dungeonId = Acore::Containers::SelectRandomContainerElement(filteredDungeons);
|
||||
|
||||
uint32 completedEncounters = 0;
|
||||
bool leader = false;
|
||||
|
||||
@@ -1347,6 +1347,8 @@ enum AcoreStrings
|
||||
LANG_BAN_IP_YOUBANNEDMESSAGE_WORLD = 11017,
|
||||
LANG_BAN_IP_YOUPERMBANNEDMESSAGE_WORLD = 11018,
|
||||
|
||||
LANG_LFG_COOLDOWN_CLEARED = 11019,
|
||||
|
||||
LANG_MUTED_PLAYER = 30000, // Mute for player 2 hour
|
||||
|
||||
// Instant Flight
|
||||
|
||||
@@ -41,6 +41,19 @@ namespace GameTime
|
||||
/// Uptime
|
||||
AC_GAME_API Seconds GetUptime();
|
||||
|
||||
/// Uptime since a given time point
|
||||
inline Microseconds Elapsed(TimePoint start)
|
||||
{
|
||||
return std::chrono::duration_cast<Microseconds>(Now() - start);
|
||||
}
|
||||
|
||||
/// Check if a duration has elapsed since a given time point
|
||||
template<class T>
|
||||
inline bool HasElapsed(TimePoint start, T duration)
|
||||
{
|
||||
return (Now() - start) >= duration;
|
||||
}
|
||||
|
||||
/// Update all timers
|
||||
void UpdateGameTimers();
|
||||
}
|
||||
|
||||
@@ -562,6 +562,7 @@ void WorldConfig::BuildConfigCache()
|
||||
SetConfigValue<uint32>(CONFIG_LFG_OPTIONSMASK, "DungeonFinder.OptionsMask", 5);
|
||||
SetConfigValue<bool>(CONFIG_LFG_CAST_DESERTER, "DungeonFinder.CastDeserter", true);
|
||||
SetConfigValue<bool>(CONFIG_LFG_ALLOW_COMPLETED, "DungeonFinder.AllowCompleted", true);
|
||||
SetConfigValue<uint32>(CONFIG_LFG_DUNGEON_SELECTION_COOLDOWN, "DungeonFinder.DungeonSelectionCooldown", 0);
|
||||
|
||||
// DBC_ItemAttributes
|
||||
SetConfigValue<bool>(CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES, "DBC.EnforceItemAttributes", true);
|
||||
|
||||
@@ -321,6 +321,7 @@ enum ServerConfigs
|
||||
CONFIG_PRESERVE_CUSTOM_CHANNEL_DURATION,
|
||||
CONFIG_PERSISTENT_CHARACTER_CLEAN_FLAGS,
|
||||
CONFIG_LFG_OPTIONSMASK,
|
||||
CONFIG_LFG_DUNGEON_SELECTION_COOLDOWN,
|
||||
CONFIG_MAX_INSTANCES_PER_HOUR,
|
||||
CONFIG_WINTERGRASP_PLR_MAX,
|
||||
CONFIG_WINTERGRASP_PLR_MIN,
|
||||
|
||||
@@ -47,11 +47,12 @@ public:
|
||||
{
|
||||
static ChatCommandTable lfgCommandTable =
|
||||
{
|
||||
{ "player", HandleLfgPlayerInfoCommand, SEC_MODERATOR, Console::No },
|
||||
{ "group", HandleLfgGroupInfoCommand, SEC_MODERATOR, Console::No },
|
||||
{ "queue", HandleLfgQueueInfoCommand, SEC_MODERATOR, Console::Yes },
|
||||
{ "clean", HandleLfgCleanCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "options", HandleLfgOptionsCommand, SEC_GAMEMASTER, Console::Yes },
|
||||
{ "player", HandleLfgPlayerInfoCommand, SEC_MODERATOR, Console::No },
|
||||
{ "group", HandleLfgGroupInfoCommand, SEC_MODERATOR, Console::No },
|
||||
{ "queue", HandleLfgQueueInfoCommand, SEC_MODERATOR, Console::Yes },
|
||||
{ "clean", HandleLfgCleanCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "options", HandleLfgOptionsCommand, SEC_GAMEMASTER, Console::Yes },
|
||||
{ "cooldown", HandleLfgCooldownClearCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
};
|
||||
|
||||
static ChatCommandTable commandTable =
|
||||
@@ -126,6 +127,13 @@ public:
|
||||
sLFGMgr->Clean();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleLfgCooldownClearCommand(ChatHandler* handler)
|
||||
{
|
||||
sLFGMgr->ClearDungeonCooldowns();
|
||||
handler->SendSysMessage(LANG_LFG_COOLDOWN_CLEARED);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_lfg_commandscript()
|
||||
|
||||
Reference in New Issue
Block a user