cleanup fixes of solo dungeon queue

This commit is contained in:
Nico committed 2026-09-20 14:25:44 +02:00
1 parent f35c3c4ae9
commit 1a9cd340b7
9 files changed
+188 -8

No files matched your search

@@ -22,7 +22,8 @@ namespace lfg
{
LfgGroupData::LfgGroupData(): m_State(LFG_STATE_NONE), m_OldState(LFG_STATE_NONE),
m_Leader(), m_Dungeon(0), m_KicksLeft(LFG_GROUP_MAX_KICKS), m_VoteKickActive(false)
m_Leader(), m_Dungeon(0), m_LowPopulation(false), m_LowPopulationOutsideSince(0),
m_KicksLeft(LFG_GROUP_MAX_KICKS), m_VoteKickActive(false)
{ }
LfgGroupData::~LfgGroupData()
@@ -83,6 +84,34 @@ void LfgGroupData::SetDungeon(uint32 dungeon)
m_Dungeon = dungeon;
}
// LOWPOP_GROUP_LIFECYCLE
void LfgGroupData::SetLowPopulation(bool enabled)
{
m_LowPopulation = enabled;
if (!enabled)
m_LowPopulationOutsideSince = 0;
}
bool LfgGroupData::IsLowPopulation() const
{
return m_LowPopulation;
}
void LfgGroupData::SetLowPopulationOutsideSince(time_t timestamp)
{
m_LowPopulationOutsideSince = timestamp;
}
time_t LfgGroupData::GetLowPopulationOutsideSince() const
{
return m_LowPopulationOutsideSince;
}
void LfgGroupData::ClearLowPopulationOutsideSince()
{
m_LowPopulationOutsideSince = 0;
}
void LfgGroupData::DecreaseKicksLeft()
{
if (m_KicksLeft)
@@ -50,6 +50,13 @@ class TC_GAME_API LfgGroupData
// Dungeon
void SetDungeon(uint32 dungeon);
// Low-population dungeon groups
void SetLowPopulation(bool enabled);
bool IsLowPopulation() const;
void SetLowPopulationOutsideSince(time_t timestamp);
time_t GetLowPopulationOutsideSince() const;
void ClearLowPopulationOutsideSince();
// VoteKick
void DecreaseKicksLeft();
@@ -77,6 +84,9 @@ class TC_GAME_API LfgGroupData
GuidSet m_Players; ///< Players in group
// Dungeon
uint32 m_Dungeon; ///< Dungeon entry
// Low-population dungeon group
bool m_LowPopulation; ///< Group was started below normal dungeon group size
time_t m_LowPopulationOutsideSince; ///< Time the last player left the dungeon map, 0 if inside
// Vote Kick
uint8 m_KicksLeft; ///< Number of kicks left
bool m_VoteKickActive;
+93 -1
View File
@@ -300,6 +300,55 @@ void LFGMgr::Update(uint32 diff)
time_t currTime = GameTime::GetGameTime();
// LOWPOP_GROUP_LIFECYCLE
// A solo low-population LFG group may remain alive outside the dungeon for a
// configurable grace period. A timeout of 0 disables automatic cleanup.
int32 const lowPopulationOutsideTimeout =
sWorld->getIntConfig(CONFIG_DUNGEON_FINDER_LOW_POPULATION_SOLO_GROUP_OUTSIDE_TIMEOUT);
if (lowPopulationOutsideTimeout > 0)
{
GuidList expiredLowPopulationGroups;
for (LfgGroupDataContainer::const_iterator it = GroupsStore.begin(); it != GroupsStore.end(); ++it)
{
LfgGroupData const& groupData = it->second;
time_t const outsideSince = groupData.GetLowPopulationOutsideSince();
if (!groupData.IsLowPopulation() || groupData.GetPlayerCount() != 1 || !outsideSince)
continue;
if (currTime >= outsideSince + lowPopulationOutsideTimeout)
expiredLowPopulationGroups.push_back(it->first);
}
// Disband only after iterating GroupsStore because OnDisband removes
// the group's LFG data from this same container.
for (GuidList::const_iterator it = expiredLowPopulationGroups.begin(); it != expiredLowPopulationGroups.end(); ++it)
{
ObjectGuid gguid = *it;
Group* group = sGroupMgr->GetGroupByGUID(gguid.GetCounter());
if (!group)
{
RemoveGroupData(gguid);
continue;
}
if (group->GetMembersCount() != 1)
{
ClearLowPopulationOutsideTimer(gguid);
continue;
}
TC_LOG_DEBUG("lfg", "Low-population solo LFG group %s expired after %u seconds outside the dungeon.",
gguid.ToString().c_str(), uint32(lowPopulationOutsideTimeout));
LeaveLfg(gguid);
group->Disband();
}
}
// Remove obsolete role checks
for (LfgRoleCheckContainer::iterator it = RoleChecksStore.begin(); it != RoleChecksStore.end();)
{
@@ -1013,6 +1062,10 @@ void LFGMgr::MakeNewGroup(LfgProposal const& proposal)
ASSERT(dungeon);
Group* grp = proposal.group ? sGroupMgr->GetGroupByGUID(proposal.group.GetCounter()) : nullptr;
bool const lowPopulation = proposal.players.size() < dungeon->GetMaxGroupSize();
if (grp)
SetLowPopulationGroup(proposal.group, lowPopulation);
for (GuidList::const_iterator it = players.begin(); it != players.end(); ++it)
{
ObjectGuid pguid = (*it);
@@ -1035,6 +1088,7 @@ void LFGMgr::MakeNewGroup(LfgProposal const& proposal)
ObjectGuid gguid = grp->GetGUID();
SetState(gguid, LFG_STATE_PROPOSAL);
sGroupMgr->AddGroup(grp);
SetLowPopulationGroup(gguid, lowPopulation);
}
else if (group != grp)
grp->AddMember(player);
@@ -1053,7 +1107,10 @@ void LFGMgr::MakeNewGroup(LfgProposal const& proposal)
{
uint32 rDungeonId = (*dungeons.begin());
LFGDungeonEntry const* dungeon = sLFGDungeonStore.LookupEntry(rDungeonId);
if (dungeon && dungeon->TypeID == LFG_TYPE_RANDOM)
// LOWPOP_CLIENT_STATUS
// Low-population fallback groups deliberately skip the normal
// random-dungeon requeue cooldown.
if (dungeon && dungeon->TypeID == LFG_TYPE_RANDOM && !lowPopulation)
player->CastSpell(player, LFG_SPELL_DUNGEON_COOLDOWN, false);
}
}
@@ -1941,6 +1998,12 @@ void LFGMgr::SetTicket(ObjectGuid guid, WorldPackets::LFG::RideTicket const& tic
PlayersStore[guid].SetTicket(ticket);
}
// LOWPOP_LEAVE_CLEANUP
void LFGMgr::ClearTicket(ObjectGuid guid)
{
PlayersStore[guid].SetTicket(WorldPackets::LFG::RideTicket());
}
void LFGMgr::RemovePlayerData(ObjectGuid guid)
{
TC_LOG_TRACE("lfg.data.player.remove", "Player: %s", guid.ToString().c_str());
@@ -1949,6 +2012,35 @@ void LFGMgr::RemovePlayerData(ObjectGuid guid)
PlayersStore.erase(it);
}
// LOWPOP_GROUP_LIFECYCLE
void LFGMgr::SetLowPopulationGroup(ObjectGuid gguid, bool enabled)
{
GroupsStore[gguid].SetLowPopulation(enabled);
}
bool LFGMgr::IsLowPopulationGroup(ObjectGuid gguid)
{
LfgGroupDataContainer::const_iterator it = GroupsStore.find(gguid);
return it != GroupsStore.end() && it->second.IsLowPopulation();
}
void LFGMgr::StartLowPopulationOutsideTimer(ObjectGuid gguid)
{
LfgGroupDataContainer::iterator it = GroupsStore.find(gguid);
if (it == GroupsStore.end() || !it->second.IsLowPopulation())
return;
if (!it->second.GetLowPopulationOutsideSince())
it->second.SetLowPopulationOutsideSince(GameTime::GetGameTime());
}
void LFGMgr::ClearLowPopulationOutsideTimer(ObjectGuid gguid)
{
LfgGroupDataContainer::iterator it = GroupsStore.find(gguid);
if (it != GroupsStore.end())
it->second.ClearLowPopulationOutsideSince();
}
void LFGMgr::RemoveGroupData(ObjectGuid guid)
{
TC_LOG_TRACE("lfg.data.group.remove", "Group: %s", guid.ToString().c_str());
+10
View File
@@ -395,6 +395,14 @@ class TC_GAME_API LFGMgr
uint8 RemovePlayerFromGroup(ObjectGuid gguid, ObjectGuid guid);
/// Adds player to group
void AddPlayerToGroup(ObjectGuid gguid, ObjectGuid guid);
/// Marks or clears a group as a low-population dungeon group
void SetLowPopulationGroup(ObjectGuid gguid, bool enabled);
/// Returns true if the group was started through the low-population fallback
bool IsLowPopulationGroup(ObjectGuid gguid);
/// Starts the outside-dungeon cleanup timer for a solo low-population group
void StartLowPopulationOutsideTimer(ObjectGuid gguid);
/// Clears the outside-dungeon cleanup timer
void ClearLowPopulationOutsideTimer(ObjectGuid gguid);
// LFGHandler
/// Get locked dungeons
@@ -427,6 +435,8 @@ class TC_GAME_API LFGMgr
void LeaveLfg(ObjectGuid guid, bool disconnected = false);
/// Gets unique join queue data
WorldPackets::LFG::RideTicket const* GetTicket(ObjectGuid guid) const;
/// Clears the player's LFG ride ticket
void ClearTicket(ObjectGuid guid);
// LfgQueue
/// Get last lfg state (NONE, DUNGEON or FINISHED_DUNGEON)
+27 -4
View File
@@ -92,6 +92,9 @@ void LFGPlayerScript::OnMapChanged(Player* player)
return;
}
if (sLFGMgr->IsLowPopulationGroup(group->GetGUID()))
sLFGMgr->ClearLowPopulationOutsideTimer(group->GetGUID());
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
if (Player* member = itr->GetSource())
player->GetSession()->SendNameQueryOpcode(member->GetGUID());
@@ -104,10 +107,19 @@ void LFGPlayerScript::OnMapChanged(Player* player)
Group* group = player->GetGroup();
if (group && group->GetMembersCount() == 1)
{
sLFGMgr->LeaveLfg(group->GetGUID());
group->Disband();
TC_LOG_DEBUG("lfg", "LFGPlayerScript::OnMapChanged, Player %s(%s) is last in the lfggroup so we disband the group.",
player->GetName().c_str(), player->GetGUID().ToString().c_str());
if (sLFGMgr->IsLowPopulationGroup(group->GetGUID()))
{
sLFGMgr->StartLowPopulationOutsideTimer(group->GetGUID());
TC_LOG_DEBUG("lfg", "LFGPlayerScript::OnMapChanged, Player %s(%s) left the dungeon as the last member of a low-population LFG group. Starting outside timer.",
player->GetName().c_str(), player->GetGUID().ToString().c_str());
}
else
{
sLFGMgr->LeaveLfg(group->GetGUID());
group->Disband();
TC_LOG_DEBUG("lfg", "LFGPlayerScript::OnMapChanged, Player %s(%s) is last in the lfggroup so we disband the group.",
player->GetName().c_str(), player->GetGUID().ToString().c_str());
}
}
player->RemoveAurasDueToSpell(LFG_SPELL_LUCK_OF_THE_DRAW);
}
@@ -155,6 +167,7 @@ void LFGGroupScript::OnRemoveMember(Group* group, ObjectGuid guid, RemoveMethod
gguid.ToString().c_str(), guid.ToString().c_str(), method, kicker.ToString().c_str(), (reason ? reason : ""));
bool isLFG = group->isLFGGroup();
bool lowPopulation = isLFG && sLFGMgr->IsLowPopulationGroup(gguid);
if (isLFG && method == GROUP_REMOVEMETHOD_KICK) // Player have been kicked
{
@@ -183,6 +196,16 @@ void LFGGroupScript::OnRemoveMember(Group* group, ObjectGuid guid, RemoveMethod
if (Player* player = ObjectAccessor::FindPlayer(guid))
{
// LOWPOP_LEAVE_CLEANUP
// Every voluntary leaver from a low-population LFG group gets a fully
// cleared personal LFG ticket/status. Remaining group members stay.
if (lowPopulation && method == GROUP_REMOVEMETHOD_LEAVE)
{
sLFGMgr->ClearTicket(guid);
player->GetSession()->SendLfgUpdateStatus(LfgUpdateData(LFG_UPDATETYPE_REMOVED_FROM_QUEUE), true);
player->GetSession()->SendLfgUpdateStatus(LfgUpdateData(LFG_UPDATETYPE_REMOVED_FROM_QUEUE), false);
}
if (method == GROUP_REMOVEMETHOD_LEAVE && state == LFG_STATE_DUNGEON &&
players >= LFG_GROUP_KICK_VOTES_NEEDED)
player->CastSpell(player, LFG_SPELL_DUNGEON_DESERTER, true);
+1 -1
View File
@@ -671,7 +671,7 @@ bool Group::RemoveMember(ObjectGuid guid, const RemoveMethod& method /*= GROUP_R
SendRaidMarkerUpdateToPlayer(guid, true);
if (isLFGGroup() && GetMembersCount() == 1)
if (isLFGGroup() && GetMembersCount() == 1 && !sLFGMgr->IsLowPopulationGroup(GetGUID()))
{
Player* leader = ObjectAccessor::FindConnectedPlayer(GetLeaderGUID());
uint32 mapId = sLFGMgr->GetDungeonMapId(GetGUID());
+14 -1
View File
@@ -354,10 +354,23 @@ void WorldSession::SendLfgUpdateStatus(lfg::LfgUpdateData const& updateData, boo
join = true;
break;
case lfg::LFG_UPDATETYPE_UPDATE_STATUS:
{
// LOWPOP_CLIENT_STATUS
// A low-population LFG group can legitimately remain active with
// one player outside the dungeon. Keep it client-side joined so
// the normal LFG dungeon controls remain available.
bool lowPopulationDungeon = false;
if (Group* group = GetPlayer()->GetGroup())
lowPopulationDungeon = group->isLFGGroup() && sLFGMgr->IsLowPopulationGroup(group->GetGUID());
join = updateData.state != lfg::LFG_STATE_ROLECHECK && updateData.state != lfg::LFG_STATE_NONE &&
updateData.state != lfg::LFG_STATE_DUNGEON && updateData.state != lfg::LFG_STATE_FINISHED_DUNGEON;
(lowPopulationDungeon ||
(updateData.state != lfg::LFG_STATE_DUNGEON &&
updateData.state != lfg::LFG_STATE_FINISHED_DUNGEON));
queued = updateData.state == lfg::LFG_STATE_QUEUED || updateData.state == lfg::LFG_STATE_RAIDBROWSER;
break;
}
default:
break;
}
+2
View File
@@ -1527,6 +1527,8 @@ void World::LoadConfigSettings(bool reload)
sConfigMgr->GetIntDefault("DungeonFinder.LowPopulation.FallbackDelay", 60);
m_int_configs[CONFIG_DUNGEON_FINDER_LOW_POPULATION_MIN_PLAYERS] =
sConfigMgr->GetIntDefault("DungeonFinder.LowPopulation.MinPlayers", 1);
m_int_configs[CONFIG_DUNGEON_FINDER_LOW_POPULATION_SOLO_GROUP_OUTSIDE_TIMEOUT] =
sConfigMgr->GetIntDefault("DungeonFinder.LowPopulation.SoloGroupOutsideTimeout", 300);
if (m_int_configs[CONFIG_DUNGEON_FINDER_LOW_POPULATION_MIN_PLAYERS] < 1)
{
+1
View File
@@ -367,6 +367,7 @@ enum WorldIntConfigs : uint8
CONFIG_LFG_OPTIONSMASK,
CONFIG_DUNGEON_FINDER_LOW_POPULATION_FALLBACK_DELAY,
CONFIG_DUNGEON_FINDER_LOW_POPULATION_MIN_PLAYERS,
CONFIG_DUNGEON_FINDER_LOW_POPULATION_SOLO_GROUP_OUTSIDE_TIMEOUT,
CONFIG_MAX_INSTANCES_PER_HOUR,
CONFIG_WARDEN_CLIENT_RESPONSE_DELAY,
CONFIG_WARDEN_CLIENT_CHECK_HOLDOFF,