Files
ThordekkCore/GROUP_OPERATIONS_IMPLEMENTATION_PLAN.md
T
2026-01-20 21:33:16 -03:00

17 KiB

Group-Level Operations - Implementation Plan

Overview

This document details the complete implementation strategy for the remaining 11 TODO markers from the Phase 7 legacy singleton call migration that require group-level coordination.


✅ COMPLETED: LFG Bot Selection (3/14 TODOs)

Status: ✅ Implemented and Committed (ddff3a92)
Solution: Static utility methods with human player filtering
Impact: Human players get instant LFG queue fills with proper role bots

Files Modified:

  • LFGBotSelector.h - Added static FindAvailableTanks/Healers/DPS methods
  • LFGBotSelector.cpp - Implemented with group filtering logic
  • LFGBotManager.cpp - Updated to use static methods

📋 REMAINING: 11 TODOs in 2 Categories

Category Count Complexity Priority
Loot Distribution 4 Medium HIGH
Instance Coordination 7 High CRITICAL

1. Loot Distribution Group Methods (4 TODOs)

Problem Statement

Four methods in UnifiedLootManager::DistributionModule coordinate group-wide loot but LootDistribution is now per-bot:

void DistributeLoot(Group* group, LootItem const& item);
void ExecuteLootDistribution(Group* group, uint32 rollId);
void ResolveRollTies(Group* group, uint32 rollId);
void HandleLootNinja(Group* group, uint32 suspectedPlayer);

WoW Loot Methods That Must Work

Loot Method Description Bot Coordination Needed
Personal Loot Each player gets own loot ✅ None (per-bot only)
Group Loot Need/Greed/Pass rolling ✅ Roll aggregation
Master Loot Leader assigns loot ✅ Leader decision with bot input
Need Before Greed Need > Greed > Pass priority ✅ Roll aggregation
Round Robin Sequential assignment ✅ Game handles (minimal bot logic)
Free-for-All First to loot wins ✅ Game handles (minimal bot logic)

Implementation Strategy: Hybrid Evaluation

Principle: Each bot evaluates items independently, group coordinator aggregates decisions

File: UnifiedLootManager.cpp

Method 1: DistributeLoot (Group Loot / Need Before Greed)

void UnifiedLootManager::DistributionModule::DistributeLoot(Group* group, LootItem const& item)
{
    if (!group)
        return;

    LootMethod method = group->GetLootMethod();
    
    switch (method)
    {
        case LOOT_METHOD_MASTER_LOOT:
            HandleMasterLoot(group, item);
            break;
        case LOOT_METHOD_GROUP_LOOT:
        case LOOT_METHOD_NEED_BEFORE_GREED:
            HandleGroupLoot(group, item);
            break;
        default:
            // Personal/Free-for-all/Round-robin handled by game
            break;
    }
    
    _itemsDistributed++;
}

private:
void HandleMasterLoot(Group* group, LootItem const& item)
{
    Player* leader = ObjectAccessor::FindPlayer(group->GetLeaderGUID());
    if (!leader)
        return;
    
    // Collect upgrade evaluations from all bot members
    struct BotEvaluation {
        Player* bot;
        float upgradeValue;
        LootPriority priority;
    };
    
    std::vector<BotEvaluation> evaluations;
    
    for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
    {
        Player* member = ref->GetSource();
        if (!member || member->IsRealPlayer())  // Skip humans
            continue;
        
        if (IGameSystemsManager* sys = GetGameSystems(member))
        {
            auto* lootDist = sys->GetLootDistribution();
            BotEvaluation eval;
            eval.bot = member;
            eval.upgradeValue = lootDist->CalculateUpgradeValue(item);
            eval.priority = lootDist->CalculateLootPriority(item);
            evaluations.push_back(eval);
        }
    }
    
    // Sort by priority then upgrade value
    std::sort(evaluations.begin(), evaluations.end(),
        [](const BotEvaluation& a, const BotEvaluation& b) {
            if (a.priority != b.priority)
                return a.priority > b.priority;
            return a.upgradeValue > b.upgradeValue;
        });
    
    // Award to highest priority bot
    if (!evaluations.empty())
    {
        Player* winner = evaluations[0].bot;
        TC_LOG_INFO("playerbot.loot", "Master Loot: {} awarded to {} (upgrade: {:.1f}%)",
            item.itemId, winner->GetName(), evaluations[0].upgradeValue);
        // Game will handle actual loot award
    }
}

void HandleGroupLoot(Group* group, LootItem const& item)
{
    // Each bot decides Need/Greed/Pass independently
    struct BotRoll {
        Player* bot;
        LootRollType rollType;
        uint32 rollValue;  // 1-100
    };
    
    std::vector<BotRoll> rolls;
    
    for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
    {
        Player* member = ref->GetSource();
        if (!member || member->IsRealPlayer())
            continue;
        
        if (IGameSystemsManager* sys = GetGameSystems(member))
        {
            BotRoll roll;
            roll.bot = member;
            roll.rollType = sys->GetLootDistribution()->DetermineLootDecision(
                item, LootDecisionStrategy::NEED_BEFORE_GREED);
            roll.rollValue = (roll.rollType != LootRollType::PASS) ? urand(1, 100) : 0;
            rolls.push_back(roll);
            
            TC_LOG_DEBUG("playerbot.loot", "Bot {} rolled {} ({})",
                member->GetName(), roll.rollValue,
                roll.rollType == LootRollType::NEED ? "NEED" : 
                roll.rollType == LootRollType::GREED ? "GREED" : "PASS");
        }
    }
    
    // Determine winner: Need > Greed, highest roll wins
    DetermineGroupLootWinner(rolls, item);
}

void DetermineGroupLootWinner(std::vector<BotRoll>& rolls, LootItem const& item)
{
    // Separate by roll type
    std::vector<BotRoll> needRolls, greedRolls;
    for (const auto& roll : rolls)
    {
        if (roll.rollType == LootRollType::NEED)
            needRolls.push_back(roll);
        else if (roll.rollType == LootRollType::GREED)
            greedRolls.push_back(roll);
    }
    
    // Need rolls win
    auto* category = !needRolls.empty() ? &needRolls : 
                     !greedRolls.empty() ? &greedRolls : nullptr;
    
    if (!category)
        return;  // Everyone passed
    
    // Highest roll wins
    auto winner = std::max_element(category->begin(), category->end(),
        [](const BotRoll& a, const BotRoll& b) {
            return a.rollValue < b.rollValue;
        });
    
    if (winner != category->end())
    {
        TC_LOG_INFO("playerbot.loot", "Loot Winner: {} rolled {} ({})",
            winner->bot->GetName(), winner->rollValue,
            winner->rollType == LootRollType::NEED ? "NEED" : "GREED");
    }
}

Method 2-4: ExecuteLootDistribution, ResolveRollTies, HandleLootNinja
These follow similar patterns using group iteration + per-bot evaluation.

Testing Requirements

  • ✅ Master Loot: Verify highest upgrade bot receives item
  • ✅ Group Loot: Verify Need > Greed > Pass priority
  • ✅ Roll Ties: Verify proper tiebreaker logic
  • ✅ Human Players: Verify bots don't interfere with human loot decisions

2. Instance Coordination (7 TODOs) - CRITICAL

Problem Statement

Seven methods in DungeonBehavior.cpp coordinate dungeon/raid group mechanics:

void InitializeInstanceCoordination(Group* group, InstanceScript* instance);
void UpdateInstanceCoordination(Group* group, uint32 diff);
void HandleInstanceCompletion(Group* group);
void HandleInstanceFailure(Group* group);
void PrepareForEncounter(Group* group, uint32 encounterId);
void MonitorEncounterProgress(Group* group, uint32 encounterId);
void HandleEncounterRecovery(Group* group, uint32 encounterId);

Critical Raid/Mythic+ Mechanics That Must Work:

  • Interrupt rotation (who interrupts next cast?)
  • Tank swap timing (when to taunt?)
  • Soak assignments (who soaks which mechanic?)
  • Phase transition coordination
  • Role assignments (tank/healer/DPS positioning)

Implementation Strategy: Dual-Layer Architecture

Two Separate Systems:

  1. Per-Bot InstanceCoordination (existing, keep as-is)

    • Individual bot positioning
    • Personal cooldown tracking
    • Bot's assigned role/job execution
  2. NEW: GroupInstanceCoordinator (group-level singleton or group-attached)

    • Interrupt rotation management
    • Tank swap coordination
    • Soak assignments
    • Phase transitions

File: src/modules/Playerbot/Dungeon/GroupInstanceCoordinator.h (NEW)

#pragma once

#include "Common.h"
#include "ObjectGuid.h"
#include <memory>
#include <unordered_map>
#include <queue>
#include <mutex>

class Group;
class Player;
class InstanceScript;

namespace Playerbot
{

/**
 * @brief Group-level instance coordination for raids/dungeons
 *
 * Manages group-wide mechanics that cannot be handled by individual
 * per-bot InstanceCoordination instances:
 * - Interrupt rotation (Mythic+ critical)
 * - Tank swap timing
 * - Soak assignments (boss mechanics)
 * - Phase transition coordination
 *
 * One coordinator per active group in instance.
 * Created when group enters, destroyed when group exits.
 */
class GroupInstanceCoordinator
{
public:
    // Factory methods (one coordinator per group)
    static GroupInstanceCoordinator* GetForGroup(ObjectGuid groupGuid);
    static void CreateForGroup(Group* group, InstanceScript* instance);
    static void RemoveForGroup(ObjectGuid groupGuid);

    // ========================================================================
    // INSTANCE LIFECYCLE
    // ========================================================================
    
    void InitializeInstance(InstanceScript* instance);
    void UpdateCoordination(uint32 diff);
    void HandleInstanceCompletion();
    void HandleInstanceFailure();

    // ========================================================================
    // ENCOUNTER COORDINATION
    // ========================================================================
    
    void PrepareForEncounter(uint32 encounterId);
    void MonitorEncounterProgress(uint32 encounterId);
    void HandleEncounterRecovery(uint32 encounterId);

    // ========================================================================
    // MECHANIC COORDINATION (Critical for Mythic+/Raids)
    // ========================================================================
    
    /**
     * @brief Get next bot in interrupt rotation
     * CRITICAL for Mythic+ where missed interrupts = wipe
     */
    Player* GetNextInterrupter(uint32 spellId);
    void RegisterInterruptUsed(Player* bot, uint32 spellId, uint32 cooldown);

    /**
     * @brief Coordinate tank swap
     * Determines when off-tank should taunt based on stack count
     */
    void CoordinateTankSwap(uint32 stackCount);
    Player* GetCurrentMainTank();
    Player* GetCurrentOffTank();
    void SwapTanks();

    /**
     * @brief Assign soakers for boss mechanics
     * Selects bots to soak damage/debuffs based on health/role
     */
    std::vector<Player*> AssignSoakers(Position soakPosition, uint32 count);

    /**
     * @brief Coordinate phase transition movement
     * Moves entire group to new position for boss phases
     */
    void TransitionToPhase(uint32 phaseId);
    void CoordinateMovement(Position targetPosition);

private:
    explicit GroupInstanceCoordinator(Group* group, InstanceScript* instance);
    ~GroupInstanceCoordinator();

    Group* _group;
    InstanceScript* _instance;

    // Current encounter state
    uint32 _currentEncounterId{0};
    uint32 _currentPhase{0};

    // Interrupt rotation
    std::queue<Player*> _interruptRotation;
    std::map<Player*, uint32> _interruptCooldowns;  // When ready (timestamp)

    // Tank assignments
    Player* _mainTank{nullptr};
    Player* _offTank{nullptr};
    uint32 _tankSwapStackThreshold{5};

    // Singleton storage (one per group)
    static std::unordered_map<ObjectGuid, std::unique_ptr<GroupInstanceCoordinator>> _coordinators;
    static std::mutex _mutex;
};

} // namespace Playerbot

File: GroupInstanceCoordinator.cpp (NEW)

Key Methods:

Player* GroupInstanceCoordinator::GetNextInterrupter(uint32 spellId)
{
    uint32 now = GameTime::GetGameTimeMS();

    // Rebuild rotation if empty
    if (_interruptRotation.empty())
    {
        for (GroupReference* ref = _group->GetFirstMember(); ref; ref = ref->next())
        {
            Player* member = ref->GetSource();
            if (!member || member->IsRealPlayer())
                continue;

            // Check if bot has interrupt available
            if (IGameSystemsManager* sys = GetGameSystems(member))
            {
                if (sys->GetInstanceCoordination()->HasInterruptAvailable())
                    _interruptRotation.push(member);
            }
        }
    }

    // Get next in rotation with available interrupt
    while (!_interruptRotation.empty())
    {
        Player* bot = _interruptRotation.front();
        _interruptRotation.pop();

        // Check cooldown
        auto it = _interruptCooldowns.find(bot);
        if (it == _interruptCooldowns.end() || it->second <= now)
        {
            // This bot can interrupt
            _interruptRotation.push(bot);  // Re-add to back of queue
            return bot;
        }

        // Cooldown not ready, try next
        _interruptRotation.push(bot);
    }

    return nullptr;  // No interrupts available
}

void GroupInstanceCoordinator::CoordinateTankSwap(uint32 stackCount)
{
    if (!_mainTank || !_offTank)
        return;

    if (stackCount >= _tankSwapStackThreshold)
    {
        TC_LOG_INFO("playerbot.dungeon", "Coordinating tank swap at {} stacks", stackCount);
        SwapTanks();
    }
}

void GroupInstanceCoordinator::SwapTanks()
{
    std::swap(_mainTank, _offTank);

    // Command off-tank (now main) to taunt
    if (IGameSystemsManager* sys = GetGameSystems(_mainTank))
    {
        sys->GetInstanceCoordination()->ExecuteTaunt();
    }

    TC_LOG_INFO("playerbot.dungeon", "Tank swap: {} is now main tank", 
        _mainTank->GetName());
}

File: DungeonBehavior.cpp (UPDATE)

Update to use GroupInstanceCoordinator:

bool DungeonBehavior::EnterDungeon(Group* group, uint32 dungeonId)
{
    // ... existing code ...

    // Initialize GROUP coordinator (new)
    if (group->GetInstanceScript())
    {
        GroupInstanceCoordinator::CreateForGroup(group, group->GetInstanceScript());
    }

    // Each bot still has their OWN InstanceCoordination (existing, unchanged)
    // This is accessed via: botAI->GetGameSystems()->GetInstanceCoordination()

    return true;
}

void DungeonBehavior::UpdateDungeonProgress(Group* group)
{
    // Update GROUP coordination (new)
    if (GroupInstanceCoordinator* coordinator = GroupInstanceCoordinator::GetForGroup(group->GetGUID()))
    {
        coordinator->UpdateCoordination(1000);
    }

    // Each bot updates individually (existing, unchanged via BotAI::UpdateAI)
}

void DungeonBehavior::PrepareForEncounter(Group* group, uint32 encounterId)
{
    // Use GROUP coordinator (new)
    if (GroupInstanceCoordinator* coordinator = GroupInstanceCoordinator::GetForGroup(group->GetGUID()))
    {
        coordinator->PrepareForEncounter(encounterId);
    }
}

Testing Requirements

  • ✅ Interrupt Rotation: Verify no missed interrupts in Mythic+ scenario
  • ✅ Tank Swaps: Verify proper taunt timing on stack mechanics
  • ✅ Soak Assignments: Verify correct bots soak damage zones
  • ✅ Phase Transitions: Verify coordinated group movement
  • ✅ Human Players: Verify bots coordinate with human tanks/healers

Implementation Timeline

Phase Task Estimated Effort Priority
1 ✅ LFG Bot Selection 2 hours HIGH
2 Loot Distribution 4 hours HIGH
3 GroupInstanceCoordinator 8 hours CRITICAL
4 Testing & Integration 4 hours CRITICAL

Total: ~18 hours for complete implementation


Success Criteria

For Human Players

  1. LFG: ✅ Human queues for dungeon → instant fill with proper role bots
  2. Loot: ✅ All WoW loot methods work correctly with bots
  3. Raids/Mythic+: ✅ Bots coordinate mechanics (interrupts, tank swaps, soaks)
  4. Performance: ✅ No performance degradation from group coordination

Technical

  1. 0 Active Singleton Calls: All Phase 7 managers use per-bot pattern
  2. Architectural Clarity: Clear separation between per-bot and group-level concerns
  3. Maintainability: Well-documented coordination patterns
  4. Scalability: Supports 5-40 player groups without issues

Approval Required

Please confirm:

  1. ✅ LFG implementation approach is acceptable
  2. ⏳ Loot distribution hybrid evaluation strategy approved?
  3. ⏳ Dual-layer instance coordination architecture approved?

Once approved, I will implement phases 2-4 systematically.