# TrinityCore API Deep Integration Analysis for Playerbot Refactoring ## Enterprise-Grade Integration Documentation **Document Version:** 1.0 **Date:** 2025-10-08 **Target:** Playerbot Module Refactoring **TrinityCore Version:** 3.3.5a (WotLK) --- ## Table of Contents 1. [Executive Summary](#executive-summary) 2. [Motion Master API](#1-motionmaster-api) 3. [Group Management API](#2-group-management-api) 4. [Event Hook System](#3-event-hook-system) 5. [Database System](#4-database-system) 6. [DBC/DB2 Data Stores](#5-dbcdb2-data-stores) 7. [Combat & Spell System](#6-combat--spell-system) 8. [Integration Recommendations](#7-integration-recommendations) --- ## Executive Summary This document provides complete API surface documentation for TrinityCore systems critical to Playerbot refactoring. All APIs are thread-safe unless explicitly noted. Performance characteristics are documented per method. **Key Integration Points:** - **MotionMaster**: Complete movement control system with deduplication built-in - **Group API**: Full group lifecycle management with event hooks - **ScriptMgr**: Comprehensive hook system for all player/group events - **ThreatManager**: Complete threat/tanking mechanics - **DatabaseWorkerPool**: Thread-safe async/sync database operations - **DB2/DBC Stores**: In-memory game data with fast lookups --- ## 1. MotionMaster API ### 1.1 Overview **File:** `C:\TrinityBots\TrinityCore\src\server\game\Movement\MotionMaster.h` MotionMaster is TrinityCore's central movement coordination system. It manages a **priority-based stack of movement generators** with automatic conflict resolution. ### 1.2 Core Architecture ```cpp class MotionMaster { Unit* _owner; // The unit being controlled MovementGeneratorPointer _defaultGenerator; // MOTION_SLOT_DEFAULT (idle/home) MotionMasterContainer _generators; // Priority queue of active movements std::deque _delayedActions; // Actions queued during Update() uint8 _flags; // State flags }; ``` **Key Concepts:** 1. **Movement Slots** (enum MovementSlot): - `MOTION_SLOT_DEFAULT` (0): Base movement (idle/home) - `MOTION_SLOT_ACTIVE` (1): Active movement commands 2. **Movement Types** (enum MovementGeneratorType): - `IDLE_MOTION_TYPE` (0): Standing still - `RANDOM_MOTION_TYPE` (1): Random wandering - `WAYPOINT_MOTION_TYPE` (2): Path following - `CHASE_MOTION_TYPE` (5): Chase target - `FOLLOW_MOTION_TYPE` (14): Follow target - `POINT_MOTION_TYPE` (8): Move to point 3. **Priority System** (enum MovementGeneratorPriority): - `MOTION_PRIORITY_NONE` (0) - `MOTION_PRIORITY_NORMAL` (1) - `MOTION_PRIORITY_HIGHEST` (2) ### 1.3 Query Methods (State Inspection) #### 1.3.1 Get Current Movement Type ```cpp MovementGeneratorType GetCurrentMovementGeneratorType() const; MovementGeneratorType GetCurrentMovementGeneratorType(MovementSlot slot) const; ``` **Purpose:** Determine what movement is currently active **Returns:** One of MovementGeneratorType enum values **Thread Safety:** Safe to call from any thread **Performance:** O(1) - direct pointer dereference **Usage Example:** ```cpp // Check if bot is currently following if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) { // Bot is following someone } // Check specific slot if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType(MOTION_SLOT_ACTIVE) == CHASE_MOTION_TYPE) { // Bot is actively chasing } ``` **Anti-Pattern Detection:** ```cpp // WRONG - Don't duplicate movement if (GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) { return; // Already following } MoveFollow(leader, dist, angle); // Only call if NOT already following // CORRECT - MotionMaster handles this internally MoveFollow(leader, dist, angle); // Will replace existing follow if params differ ``` #### 1.3.2 Check for Specific Movement Generator ```cpp bool HasMovementGenerator(std::function const& filter, MovementSlot slot = MOTION_SLOT_ACTIVE) const; ``` **Purpose:** Check if a movement matching criteria exists **Thread Safety:** Safe **Performance:** O(n) where n = active generators (typically 1-3) **Usage Example:** ```cpp // Check if following specific target bool isFollowingLeader = bot->GetMotionMaster()->HasMovementGenerator( [leaderGuid](MovementGenerator const* gen) { if (gen->GetMovementGeneratorType() != FOLLOW_MOTION_TYPE) return false; auto followGen = static_cast(gen); return followGen->GetTarget() == leaderGuid; } ); ``` #### 1.3.3 Get Movement Information ```cpp std::vector GetMovementGeneratorsInformation() const; struct MovementGeneratorInformation { MovementGeneratorType Type; ObjectGuid TargetGUID; // If following/chasing a target std::string TargetName; }; ``` **Purpose:** Get detailed info about all active movements **Thread Safety:** Safe (returns copy) **Performance:** O(n) with allocation ### 1.4 Movement Commands #### 1.4.1 MoveFollow - Group Following ```cpp void MoveFollow(Unit* target, float dist, Optional angle = {}, Optional duration = {}, bool ignoreTargetWalk = false, MovementSlot slot = MOTION_SLOT_ACTIVE, Optional>&& scriptResult = {}); ``` **Purpose:** Follow a target at specified distance and angle **Thread Safety:** Must call from world thread **Performance:** O(1) if replacing same movement, O(log n) if adding new **Parameters:** - `target`: Unit to follow (must be valid) - `dist`: Follow distance in yards - `angle`: Optional angle relative to target (ChaseAngle) - `duration`: Optional time limit for following - `ignoreTargetWalk`: If true, bot runs even if target walks - `slot`: Movement slot (default ACTIVE) - `scriptResult`: Internal use for script callbacks **ChaseAngle Structure:** ```cpp struct ChaseAngle { ChaseAngle(float angle, float tolerance = M_PI_4); float RelativeAngle; // 0 = front, M_PI = back float Tolerance; // +/- tolerance in radians }; ``` **Usage Example:** ```cpp // Follow leader 5 yards behind Player* leader = GetLeader(); if (leader) { ChaseAngle behindLeader(M_PI, M_PI_4); // PI = behind, PI/4 tolerance bot->GetMotionMaster()->MoveFollow(leader, 5.0f, behindLeader); } // Follow at default distance (in front-right arc) bot->GetMotionMaster()->MoveFollow(leader, 3.0f); ``` **Deduplication Behavior:** - If already following same target with same params → NO-OP - If already following same target with different params → Updates params - If following different target → Stops old follow, starts new **Integration Notes:** 1. **No manual deduplication needed** - MotionMaster handles it 2. **Safe to call every update** - Only triggers network if params change 3. **Automatically stops on target death/teleport** 4. **Respects pathfinding** - Will path around obstacles #### 1.4.2 MoveChase - Combat Following ```cpp void MoveChase(Unit* target, Optional dist = {}, Optional angle = {}); ``` **Purpose:** Chase target for combat (auto-attacks when in range) **Thread Safety:** World thread only **Performance:** O(1) replacement **ChaseRange Structure:** ```cpp struct ChaseRange { ChaseRange(float range); // Simple max range ChaseRange(float minRange, float maxRange); // Min/max range ChaseRange(float minRange, float minTolerance, float maxTolerance, float maxRange); // Full control float MinRange; // Move if closer than this float MinTolerance; // Move this far away if too close float MaxRange; // Move if further than this float MaxTolerance; // Move this close if too far }; ``` **Usage Example:** ```cpp // Melee chase (0-5 yards) bot->GetMotionMaster()->MoveChase(target, ChaseRange(0.0f, 5.0f)); // Ranged chase (5-30 yards, prefer 20-25) bot->GetMotionMaster()->MoveChase(target, ChaseRange(5.0f, 20.0f, 25.0f, 30.0f)); // Tank positioning (behind boss) ChaseAngle behindTarget(M_PI, M_PI_4); bot->GetMotionMaster()->MoveChase(boss, ChaseRange(0.0f, 5.0f), behindTarget); ``` **Difference from MoveFollow:** - **MoveChase**: Combat movement, enables auto-attack, respects combat range - **MoveFollow**: Non-combat following, no auto-attack, tighter following #### 1.4.3 MovePoint - Move to Location ```cpp void MovePoint(uint32 id, Position const& pos, bool generatePath = true, Optional finalOrient = {}, Optional speed = {}, MovementWalkRunSpeedSelectionMode speedSelectionMode = MovementWalkRunSpeedSelectionMode::Default, Optional closeEnoughDistance = {}, Optional>&& scriptResult = {}); void MovePoint(uint32 id, float x, float y, float z, bool generatePath = true, ...); ``` **Purpose:** Move to specific coordinates **Thread Safety:** World thread **Performance:** O(1) + pathfinding cost if generatePath=true **Parameters:** - `id`: Movement identifier (for MovementInform hook) - `pos`/`x,y,z`: Destination coordinates - `generatePath`: If true, use pathfinding (avoids obstacles) - `finalOrient`: Face direction after arrival - `speed`: Custom speed (yards/sec) - `speedSelectionMode`: Force run/walk - `closeEnoughDistance`: Arrival tolerance (default 0.5 yards) **Usage Example:** ```cpp // Move to quest objective Position questPos = GetQuestObjectivePosition(questId); bot->GetMotionMaster()->MovePoint(POINT_QUEST_OBJECTIVE, questPos); // Move to position and face north bot->GetMotionMaster()->MovePoint( POINT_FORMATION, formationPos, true, // Use pathfinding 0.0f // Face north (0 radians) ); // Move directly (no pathfinding) at walk speed bot->GetMotionMaster()->MovePoint( POINT_PATROL, patrolPos, false, // Direct movement {}, // No specific orientation {}, // Default speed MovementWalkRunSpeedSelectionMode::ForceWalk ); ``` **MovementInform Hook:** ```cpp // In bot AI class void MovementInform(uint32 type, uint32 id) override { if (type == POINT_MOTION_TYPE) { switch (id) { case POINT_QUEST_OBJECTIVE: OnQuestObjectiveReached(); break; case POINT_FORMATION: OnFormationPositionReached(); break; } } } ``` #### 1.4.4 MoveIdle - Stop Movement ```cpp void MoveIdle(); ``` **Purpose:** Stop all movement, stand idle **Thread Safety:** World thread **Performance:** O(1) **Usage Example:** ```cpp // Stop movement completely bot->GetMotionMaster()->MoveIdle(); // Common use: Stop before casting stationary spell if (spell->RequiresStanding()) { bot->GetMotionMaster()->MoveIdle(); bot->CastSpell(target, spellId); } ``` #### 1.4.5 Clear - Remove All Movement ```cpp void Clear(); // Clear all active movements void Clear(MovementSlot slot); // Clear specific slot void Clear(MovementGeneratorMode mode); // Clear by mode void Clear(MovementGeneratorPriority priority); // Clear by priority ``` **Purpose:** Remove movement generators **Thread Safety:** World thread **Performance:** O(n) where n = active generators **Usage Example:** ```cpp // Clear all active movement before teleport bot->GetMotionMaster()->Clear(); // Clear only active slot (keeps default/idle) bot->GetMotionMaster()->Clear(MOTION_SLOT_ACTIVE); ``` **Warning:** `Clear()` does NOT make the unit idle - it removes movement entirely. Use `MoveIdle()` for stopping. ### 1.5 Thread Safety Notes **World Thread Only Operations:** - All `Move*()` commands - `Clear()` operations - `Update()` calls **Safe from Any Thread:** - `GetCurrentMovementGeneratorType()` - `GetMovementGeneratorsInformation()` (returns copy) - `HasMovementGenerator()` with read-only lambda **Delayed Actions:** MotionMaster uses a delayed action queue during `Update()`. If you call a movement command during another movement's update, it's queued and executed after current update finishes. ### 1.6 Performance Characteristics | Operation | Complexity | Notes | |-----------|-----------|-------| | `GetCurrentMovementGeneratorType()` | O(1) | Direct pointer access | | `MoveFollow()` (same target/params) | O(1) | NO-OP, no allocation | | `MoveFollow()` (new target) | O(log n) | Priority queue insertion | | `MoveChase()` | O(log n) | Priority queue insertion | | `MovePoint()` | O(1) + pathfinding | Pathfinding can be O(n²) | | `Clear()` | O(n) | n = active generators (typically 1-3) | | `HasMovementGenerator()` | O(n) | Linear search with predicate | **Memory:** - MotionMaster per Unit: ~200 bytes base - MovementGenerator: ~100-300 bytes each - Typical bot: 1-2 active generators = ~500 bytes total ### 1.7 Integration Pattern for Playerbot ```cpp // In LeaderFollowBehavior.cpp - CORRECT PATTERN void LeaderFollowBehavior::Update(uint32 diff) { Player* leader = GetLeader(); if (!leader) return; // Calculate desired follow distance based on role float followDist = CalculateFollowDistance(); // NO deduplication check needed - MotionMaster handles it ChaseAngle followAngle = CalculateFollowAngle(); _bot->GetMotionMaster()->MoveFollow(leader, followDist, followAngle); // MotionMaster will: // 1. Check if already following this leader // 2. Check if distance/angle changed // 3. Only update if params different // 4. Handle network packets automatically } // WRONG PATTERN - Manual deduplication (remove this) void LeaderFollowBehavior::Update(uint32 diff) { // DON'T DO THIS - MotionMaster already does it if (_bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) { return; // Already following } // ... } ``` ### 1.8 Common Pitfalls **Pitfall 1: Manual Deduplication** ```cpp // WRONG - Unnecessary check if (GetCurrentMovementGeneratorType() != FOLLOW_MOTION_TYPE) { MoveFollow(leader, dist); } // CORRECT - Just call it MoveFollow(leader, dist); ``` **Pitfall 2: Clearing Before Every Move** ```cpp // WRONG - Clear is expensive Clear(); MoveFollow(leader, dist); // CORRECT - MoveFollow replaces automatically MoveFollow(leader, dist); ``` **Pitfall 3: Not Using GetCurrentMovementGeneratorType** ```cpp // WRONG - Custom tracking bool _isFollowing = false; // CORRECT - Query MotionMaster bool isFollowing = GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE; ``` --- ## 2. Group Management API ### 2.1 Overview **File:** `C:\TrinityBots\TrinityCore\src\server\game\Groups\Group.h` The Group system manages party/raid composition, loot distribution, and member coordination. ### 2.2 Core Group Structure ```cpp class Group { struct MemberSlot { ObjectGuid guid; // Player GUID std::string name; // Player name Races race; // Race uint8 _class; // Class uint8 group; // Subgroup (0-7 for raids) uint8 flags; // MEMBER_FLAG_ASSISTANT etc. uint8 roles; // Tank/Healer/DPS flags bool readyChecked; // Ready check state }; MemberSlotList m_memberSlots; // All members GroupRefManager m_memberMgr; // Online members InvitesList m_invitees; // Pending invites ObjectGuid m_leaderGuid; // Leader GUID GroupFlags m_groupFlags; // RAID, LFG, etc. LootMethod m_lootMethod; // Loot distribution ObjectGuid m_looterGuid; // Master looter (if applicable) Difficulty m_dungeonDifficulty; // Instance difficulty }; ``` ### 2.3 Group Query Methods #### 2.3.1 Member Queries ```cpp bool IsMember(ObjectGuid guid) const; bool IsLeader(ObjectGuid guid) const; uint32 GetMembersCount() const; ObjectGuid GetLeaderGUID() const; const char* GetLeaderName() const; ``` **Usage Example:** ```cpp Group* group = bot->GetGroup(); if (!group) return; // Check if bot is leader if (group->IsLeader(bot->GetGUID())) { // Bot leads the group } // Get member count uint32 memberCount = group->GetMembersCount(); if (memberCount >= MAX_GROUP_SIZE) { // Group is full } // Get leader ObjectGuid leaderGuid = group->GetLeaderGUID(); Player* leader = ObjectAccessor::FindPlayer(leaderGuid); ``` #### 2.3.2 Enumerate Members ```cpp GroupRefManager& GetMembers(); MemberSlotList const& GetMemberSlots() const; // Iterate online members for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); // Process member } // Modern C++ iteration for (GroupReference const& ref : group->GetMembers()) { Player* member = ref.GetSource(); // Process member } ``` **Usage Example:** ```cpp // Find all online members in range std::vector nearbyMembers; for (GroupReference const& ref : group->GetMembers()) { Player* member = ref.GetSource(); if (bot->IsWithinDistInMap(member, 40.0f)) { nearbyMembers.push_back(member); } } // Count members by role uint32 tankCount = 0, healerCount = 0; for (GroupReference const& ref : group->GetMembers()) { Player* member = ref.GetSource(); uint8 roles = group->GetLfgRoles(member->GetGUID()); if (roles & PLAYER_ROLE_TANK) ++tankCount; if (roles & PLAYER_ROLE_HEALER) ++healerCount; } ``` **Thread Safety:** - `GetMembers()` returns references that are ONLY valid in world thread - Use `ObjectAccessor::FindPlayer()` for safe cross-thread access - Never cache Player* pointers #### 2.3.3 Subgroup Queries (Raids) ```cpp bool SameSubGroup(ObjectGuid guid1, ObjectGuid guid2) const; bool SameSubGroup(Player const* member1, Player const* member2) const; uint8 GetMemberGroup(ObjectGuid guid) const; bool HasFreeSlotSubGroup(uint8 subgroup) const; ``` **Usage Example:** ```cpp // Check if two players are in same raid subgroup if (group->SameSubGroup(bot->GetGUID(), healer->GetGUID())) { // Same subgroup - prioritize healing } // Get bot's subgroup uint8 botSubgroup = group->GetMemberGroup(bot->GetGUID()); // Find first subgroup with space for (uint8 i = 0; i < MAX_RAID_SUBGROUPS; ++i) { if (group->HasFreeSlotSubGroup(i)) { // Subgroup i has space break; } } ``` ### 2.4 Group Modification Methods #### 2.4.1 Adding/Removing Members ```cpp bool AddMember(Player* player); bool RemoveMember(ObjectGuid guid, RemoveMethod method = GROUP_REMOVEMETHOD_DEFAULT, ObjectGuid kicker = ObjectGuid::Empty, const char* reason = nullptr); void ChangeLeader(ObjectGuid guid); void Disband(bool hideDestroy = false); ``` **RemoveMethod enum:** ```cpp enum RemoveMethod { GROUP_REMOVEMETHOD_DEFAULT = 0, GROUP_REMOVEMETHOD_KICK = 1, GROUP_REMOVEMETHOD_LEAVE = 2, GROUP_REMOVEMETHOD_KICK_LFG = 3 }; ``` **Usage Example:** ```cpp // Add bot to group Group* group = leader->GetGroup(); if (!group) { group = new Group; group->Create(leader); } if (group->AddMember(bot)) { // Bot successfully added } // Remove member (kicked) group->RemoveMember( memberGuid, GROUP_REMOVEMETHOD_KICK, leader->GetGUID(), "Inactivity" ); // Promote new leader group->ChangeLeader(newLeaderGuid); // Disband group group->Disband(); ``` **Thread Safety:** MUST be called from world thread #### 2.4.2 Invite Management ```cpp bool AddInvite(Player* player); void RemoveInvite(Player* player); void RemoveAllInvites(); Player* GetInvited(ObjectGuid guid) const; uint32 GetInviteeCount() const; ``` **Usage Example:** ```cpp // Invite player to group if (group->AddInvite(invitee)) { // Send invite packet to player WorldPacket data(SMSG_GROUP_INVITE, ...); invitee->SendDirectMessage(&data); } // Check pending invites if (group->GetInviteeCount() >= 4) { // Too many pending invites } // Clear all invites on group start group->RemoveAllInvites(); ``` ### 2.5 Group Properties #### 2.5.1 Group Type & Flags ```cpp bool isLFGGroup() const; bool isRaidGroup() const; bool isBGGroup() const; bool isBFGroup() const; GroupFlags GetGroupFlags() const; bool IsFull() const; ``` **GroupFlags enum:** ```cpp enum GroupFlags : uint16 { GROUP_FLAG_NONE = 0x000, GROUP_FLAG_FAKE_RAID = 0x001, GROUP_FLAG_RAID = 0x002, GROUP_FLAG_LFG_RESTRICTED = 0x004, GROUP_FLAG_LFG = 0x008, GROUP_FLAG_DESTROYED = 0x010, GROUP_FLAG_ONE_PERSON_PARTY = 0x020, GROUP_FLAG_EVERYONE_ASSISTANT = 0x040, GROUP_FLAG_GUILD_GROUP = 0x100, }; ``` **Usage Example:** ```cpp if (group->isRaidGroup()) { // Raid-specific behavior if (group->IsFull()) { // 40 players in raid } } else { // 5-man dungeon group if (group->IsFull()) { // 5 players in party } } if (group->isLFGGroup()) { // LFG restrictions apply } ``` #### 2.5.2 Loot Settings ```cpp LootMethod GetLootMethod() const; ObjectGuid GetLooterGuid() const; ObjectGuid GetMasterLooterGuid() const; ItemQualities GetLootThreshold() const; void SetLootMethod(LootMethod method); void SetLooterGuid(ObjectGuid guid); void SetMasterLooterGuid(ObjectGuid guid); void SetLootThreshold(ItemQualities threshold); ``` **LootMethod enum:** ```cpp enum LootMethod : uint8 { FREE_FOR_ALL = 0, ROUND_ROBIN = 1, MASTER_LOOT = 2, GROUP_LOOT = 3, NEED_BEFORE_GREED = 4 }; ``` **Usage Example:** ```cpp // Set loot to group loot with epic threshold group->SetLootMethod(GROUP_LOOT); group->SetLootThreshold(ITEM_QUALITY_EPIC); // Set master looter group->SetLootMethod(MASTER_LOOT); group->SetMasterLooterGuid(leader->GetGUID()); // Check current loot settings if (group->GetLootMethod() == MASTER_LOOT) { ObjectGuid masterLooter = group->GetMasterLooterGuid(); // Show master looter UI } ``` ### 2.6 Broadcasting to Group ```cpp void BroadcastPacket(WorldPacket const* packet, bool ignorePlayersInBGRaid, int group = -1, ObjectGuid ignoredPlayer = ObjectGuid::Empty) const; template void BroadcastWorker(Worker const& worker) const; ``` **Usage Example:** ```cpp // Send message to all group members WorldPacket data(SMSG_MESSAGECHAT, ...); group->BroadcastPacket(&data, false); // Send to specific subgroup (raid) group->BroadcastPacket(&data, false, 2); // Subgroup 2 only // Custom worker for each member group->BroadcastWorker([](Player* member) { member->ModifyHealth(1000); // Heal all members }); ``` ### 2.7 Performance Characteristics | Operation | Complexity | Notes | |-----------|-----------|-------| | `IsMember()` | O(n) | Linear search through members | | `IsLeader()` | O(1) | Direct GUID comparison | | `GetMembersCount()` | O(1) | Cached count | | `AddMember()` | O(n) | Database write + network | | `RemoveMember()` | O(n) | Database write + network | | `BroadcastPacket()` | O(n) | n = member count | | Group iteration | O(n) | n = member count | **Memory:** - Group base: ~500 bytes - Per member: ~100 bytes - 5-man group: ~1KB - 40-man raid: ~4.5KB ### 2.8 Thread Safety **World Thread Only:** - All modification methods (`AddMember`, `RemoveMember`, etc.) - Member iteration - Broadcasting packets **Safe from Any Thread:** - `IsMember()` (read-only GUID check) - `GetMembersCount()` (atomic) - `IsLeader()` (read-only) **ObjectAccessor Pattern:** ```cpp // WRONG - Player* may be deleted Player* GetMemberUnsafe(ObjectGuid guid) { for (GroupReference& ref : group->GetMembers()) { if (ref.GetSource()->GetGUID() == guid) return ref.GetSource(); } return nullptr; } // CORRECT - ObjectAccessor validates existence Player* GetMemberSafe(ObjectGuid guid) { return ObjectAccessor::FindPlayer(guid); } ``` --- ## 3. Event Hook System ### 3.1 Overview **File:** `C:\TrinityBots\TrinityCore\src\server\game\Scripting\ScriptMgr.h` TrinityCore's ScriptMgr provides a comprehensive hook system for observing game events without modifying core code. ### 3.2 Available Hook Scripts #### 3.2.1 PlayerScript Hooks **Base Class:** ```cpp class PlayerScript : public ScriptObject { protected: explicit PlayerScript(char const* name) noexcept; public: virtual void OnLogin(Player* player, bool firstLogin); virtual void OnLogout(Player* player); virtual void OnCreate(Player* player); virtual void OnDelete(ObjectGuid guid, uint32 accountId); // Combat events virtual void OnPVPKill(Player* killer, Player* killed); virtual void OnCreatureKill(Player* killer, Creature* killed); virtual void OnPlayerKilledByCreature(Creature* killer, Player* killed); // Character progression virtual void OnLevelChanged(Player* player, uint8 oldLevel); virtual void OnFreeTalentPointsChanged(Player* player, uint32 points); virtual void OnTalentsReset(Player* player, bool noCost); virtual void OnGiveXP(Player* player, uint32& amount, Unit* victim); // Social virtual void OnDuelRequest(Player* target, Player* challenger); virtual void OnDuelStart(Player* player1, Player* player2); virtual void OnDuelEnd(Player* winner, Player* loser, DuelCompleteType type); // Chat virtual void OnChat(Player* player, uint32 type, uint32 lang, std::string& msg); virtual void OnChat(Player* player, uint32 type, uint32 lang, std::string& msg, Group* group); // Spells virtual void OnSpellCast(Player* player, Spell* spell, bool skipCheck); // World interaction virtual void OnMapChanged(Player* player); virtual void OnUpdateZone(Player* player, uint32 newZone, uint32 newArea); virtual void OnQuestStatusChange(Player* player, uint32 questId); }; ``` **Usage Example:** ```cpp // In src/modules/Playerbot/Hooks/PlayerEventHooks.h class PlayerBotEventHook : public PlayerScript { public: PlayerBotEventHook() : PlayerScript("PlayerBotEventHook") { } void OnLogin(Player* player, bool firstLogin) override { // Check if this is a bot if (IsBotAccount(player->GetSession()->GetAccountId())) { BotMgr::HandleBotLogin(player); } } void OnLogout(Player* player) override { if (IsBotAccount(player->GetSession()->GetAccountId())) { BotMgr::HandleBotLogout(player); } } void OnCreatureKill(Player* killer, Creature* killed) override { if (IsBotPlayer(killer)) { BotCombatMgr::OnKillCreature(killer, killed); } } void OnLevelChanged(Player* player, uint8 oldLevel) override { if (IsBotPlayer(player)) { BotProgressionMgr::OnLevelUp(player, oldLevel); } } }; // In src/modules/Playerbot/Hooks/PlayerEventHooks.cpp void AddSC_PlayerBotEventHook() { new PlayerBotEventHook(); } ``` #### 3.2.2 GroupScript Hooks **Base Class:** ```cpp class GroupScript : public ScriptObject { protected: explicit GroupScript(char const* name) noexcept; public: virtual void OnAddMember(Group* group, ObjectGuid guid); virtual void OnInviteMember(Group* group, ObjectGuid guid); virtual void OnRemoveMember(Group* group, ObjectGuid guid, RemoveMethod method, ObjectGuid kicker, char const* reason); virtual void OnChangeLeader(Group* group, ObjectGuid newLeaderGuid, ObjectGuid oldLeaderGuid); virtual void OnDisband(Group* group); }; ``` **Usage Example:** ```cpp // In src/modules/Playerbot/Hooks/GroupEventHooks.h class BotGroupEventHook : public GroupScript { public: BotGroupEventHook() : GroupScript("BotGroupEventHook") { } void OnAddMember(Group* group, ObjectGuid guid) override { Player* player = ObjectAccessor::FindPlayer(guid); if (!player) return; // If bot joins group, start following leader if (IsBotPlayer(player)) { BotGroupMgr::OnJoinGroup(player, group); } // If real player joins bot's group, bots adjust behavior if (!IsBotPlayer(player) && GroupHasBots(group)) { BotGroupMgr::OnRealPlayerJoins(group, player); } } void OnRemoveMember(Group* group, ObjectGuid guid, RemoveMethod method, ObjectGuid kicker, char const* reason) override { Player* player = ObjectAccessor::FindPlayer(guid); if (player && IsBotPlayer(player)) { BotGroupMgr::OnLeaveGroup(player, group, method); } } void OnChangeLeader(Group* group, ObjectGuid newLeaderGuid, ObjectGuid oldLeaderGuid) override { Player* newLeader = ObjectAccessor::FindPlayer(newLeaderGuid); Player* oldLeader = ObjectAccessor::FindPlayer(oldLeaderGuid); // Bots switch following targets if (GroupHasBots(group)) { BotGroupMgr::OnLeaderChange(group, newLeader, oldLeader); } } void OnDisband(Group* group) override { // Clean up bot group data BotGroupMgr::OnGroupDisband(group); } }; void AddSC_BotGroupEventHook() { new BotGroupEventHook(); } ``` #### 3.2.3 WorldScript Hooks **Base Class:** ```cpp class WorldScript : public ScriptObject { public: virtual void OnOpenStateChange(bool open); virtual void OnConfigLoad(bool reload); virtual void OnMotdChange(std::string& newMotd); virtual void OnShutdownInitiate(ShutdownExitCode code, ShutdownMask mask); virtual void OnShutdownCancel(); virtual void OnUpdate(uint32 diff); virtual void OnStartup(); virtual void OnShutdown(); }; ``` **Usage Example:** ```cpp class BotWorldHook : public WorldScript { public: BotWorldHook() : WorldScript("BotWorldHook") { } void OnStartup() override { // Initialize bot system BotMgr::Initialize(); } void OnShutdown() override { // Save all bot data BotMgr::SaveAllBots(); } void OnUpdate(uint32 diff) override { // Update bot manager (runs every world tick) BotMgr::Update(diff); } void OnConfigLoad(bool reload) override { // Reload bot configuration BotConfig::Load(reload); } }; ``` ### 3.3 Hook Registration **In module initialization:** ```cpp // In src/modules/Playerbot/PlayerbotLoader.cpp void AddPlayerbotScripts() { // Register all hook scripts AddSC_PlayerBotEventHook(); AddSC_BotGroupEventHook(); AddSC_BotWorldHook(); } ``` **Script loader setup:** ```cpp // In src/server/scripts/Commands/cs_reload.cpp (already exists in TrinityCore) // Playerbot hooks are automatically loaded with other scripts ``` ### 3.4 Hook Execution Order Hooks are executed in **registration order**. Multiple scripts can register the same hook: ```cpp // Script 1 void OnLogin(Player* player, bool firstLogin) override { // Executes first } // Script 2 void OnLogin(Player* player, bool firstLogin) override { // Executes second } ``` **All hooks execute synchronously** - each completes before the next starts. ### 3.5 Hook Performance | Hook | Frequency | Performance Impact | |------|-----------|-------------------| | `OnLogin` | Per login | Low (one-time) | | `OnLogout` | Per logout | Low (one-time) | | `OnUpdate` (WorldScript) | Every world tick | **HIGH** - Keep logic minimal | | `OnCreatureKill` | Per kill | Medium | | `OnSpellCast` | Per spell | **HIGH** - Very frequent | | `OnGroupAddMember` | Per group join | Low | **Optimization Guidelines:** 1. **OnUpdate**: Do NOT iterate all bots every tick 2. **OnSpellCast**: Only process if spell is relevant to bots 3. **Use early returns**: Check `IsBotPlayer()` FIRST 4. **Avoid database queries**: In frequently-called hooks 5. **Batch operations**: Process multiple events together ### 3.6 Thread Safety **ALL hooks execute on the world thread** - no synchronization needed within hook code. **Safe Operations:** - Direct Player/Group/Unit manipulation - Calling TrinityCore APIs - Accessing module data structures **Unsafe Operations:** - Spawning threads from hooks (use async tasks) - Blocking operations (sleep, long calculations) --- ## 4. Database System ### 4.1 Overview **File:** `C:\TrinityBots\TrinityCore\src\server\database\Database\DatabaseWorkerPool.h` TrinityCore uses a connection pool pattern with async and synchronous query support. ### 4.2 Database Connection Pools **Three databases:** ```cpp extern TC_GAME_API DatabaseWorkerPool LoginDatabase; extern TC_GAME_API DatabaseWorkerPool CharacterDatabase; extern TC_GAME_API DatabaseWorkerPool WorldDatabase; ``` **Connection pool architecture:** ```cpp template class DatabaseWorkerPool { enum InternalIndex { IDX_ASYNC, // Asynchronous queries (background threads) IDX_SYNCH, // Synchronous queries (calling thread) IDX_SIZE }; }; ``` ### 4.3 Query Methods #### 4.3.1 Synchronous Queries (Blocking) ```cpp // String query QueryResult Query(char const* sql, T* connection = nullptr); // Formatted query template QueryResult PQuery(Trinity::FormatString sql, Args&&... args); // Prepared statement query PreparedQueryResult Query(PreparedStatement* stmt); ``` **Usage Example:** ```cpp // String query (use sparingly - SQL injection risk) QueryResult result = CharacterDatabase.Query( "SELECT guid, name, level FROM characters WHERE account = 123" ); if (result) { do { Field* fields = result->Fetch(); uint32 guid = fields[0].Get(); std::string name = fields[1].Get(); uint8 level = fields[2].Get(); // Process result } while (result->NextRow()); } // Formatted query (safer) QueryResult result = CharacterDatabase.PQuery( "SELECT guid, name, level FROM characters WHERE account = {}", accountId ); // Best practice: Prepared statement (see below) ``` **Performance:** Blocks calling thread until query completes. Use for: - Initialization queries - Queries that must complete before continuing - Rare operations #### 4.3.2 Asynchronous Queries (Non-blocking) ```cpp // Async string query QueryCallback AsyncQuery(char const* sql); // Async prepared statement QueryCallback AsyncQuery(PreparedStatement* stmt); ``` **QueryCallback usage:** ```cpp // Issue async query CharacterDatabase.AsyncQuery(stmt) .WithCallback([](QueryResult result) { if (!result) return; do { Field* fields = result->Fetch(); // Process result } while (result->NextRow()); }); ``` **Chaining callbacks:** ```cpp CharacterDatabase.AsyncQuery(loadBotsStmt) .WithCallback([](QueryResult result) { if (!result) return; // Load bot data std::vector botGuids; do { botGuids.push_back(result->Fetch()[0].Get()); } while (result->NextRow()); // Issue second query for each bot for (uint32 guid : botGuids) { PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_BOT_EQUIPMENT); stmt->SetData(0, guid); CharacterDatabase.AsyncQuery(stmt) .WithCallback([guid](QueryResult equipResult) { // Process equipment for this bot }); } }); ``` **Performance:** Non-blocking, executes on background thread. Use for: - Loading data during runtime - Non-critical queries - Large result sets #### 4.3.3 Prepared Statements **Definition (in module):** ```cpp // In PlayerbotDatabase.h enum PlayerbotDatabaseStatements { // Bot queries PLAYERBOT_SEL_BOT_DATA, PLAYERBOT_INS_BOT, PLAYERBOT_UPD_BOT_POSITION, PLAYERBOT_DEL_BOT, // Bot equipment PLAYERBOT_SEL_BOT_EQUIPMENT, PLAYERBOT_REP_BOT_EQUIPMENT, MAX_PLAYERBOT_STATEMENTS }; class PlayerbotDatabaseConnection : public CharacterDatabaseConnection { public: void DoPrepareStatements() override; }; ``` **Preparation:** ```cpp // In PlayerbotDatabase.cpp void PlayerbotDatabaseConnection::DoPrepareStatements() { PrepareStatement(PLAYERBOT_SEL_BOT_DATA, "SELECT guid, owner, ai_state FROM playerbot_data WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(PLAYERBOT_INS_BOT, "INSERT INTO playerbot_data (guid, owner, ai_state) VALUES (?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(PLAYERBOT_UPD_BOT_POSITION, "UPDATE playerbot_data SET map = ?, x = ?, y = ?, z = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(PLAYERBOT_SEL_BOT_EQUIPMENT, "SELECT slot, item_entry FROM playerbot_equipment WHERE bot_guid = ?", CONNECTION_SYNCH); } ``` **Usage:** ```cpp // Load bot data (synchronous) PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(PLAYERBOT_SEL_BOT_DATA); stmt->SetData(0, botGuid); PreparedQueryResult result = CharacterDatabase.Query(stmt); if (result) { Field* fields = result->Fetch(); uint32 guid = fields[0].Get(); uint32 owner = fields[1].Get(); uint8 aiState = fields[2].Get(); } // Save bot position (asynchronous) PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(PLAYERBOT_UPD_BOT_POSITION); stmt->SetData(0, bot->GetMapId()); stmt->SetData(1, bot->GetPositionX()); stmt->SetData(2, bot->GetPositionY()); stmt->SetData(3, bot->GetPositionZ()); stmt->SetData(4, bot->GetGUID()); CharacterDatabase.AsyncQuery(stmt); ``` ### 4.4 Transactions **Create transaction:** ```cpp SQLTransaction trans = CharacterDatabase.BeginTransaction(); ``` **Add statements:** ```cpp // Add prepared statements PreparedStatement* stmt1 = CharacterDatabase.GetPreparedStatement(PLAYERBOT_INS_BOT); stmt1->SetData(0, guid); stmt1->SetData(1, owner); stmt1->SetData(2, aiState); trans->Append(stmt1); PreparedStatement* stmt2 = CharacterDatabase.GetPreparedStatement(PLAYERBOT_INS_BOT_EQUIPMENT); stmt2->SetData(0, guid); stmt2->SetData(1, slot); stmt2->SetData(2, itemEntry); trans->Append(stmt2); // Add string statements (use sparingly) trans->Append("UPDATE playerbot_stats SET login_count = login_count + 1"); ``` **Commit transaction:** ```cpp // Synchronous commit (blocks until complete) CharacterDatabase.DirectCommitTransaction(trans); // Asynchronous commit (non-blocking) CharacterDatabase.AsyncCommitTransaction(trans) .WithCallback([](bool success) { if (success) { // Transaction committed } else { // Transaction rolled back (error occurred) } }); ``` **Transaction properties:** - **Atomic**: All statements succeed or all fail - **Auto-rollback**: If any statement fails, entire transaction reverts - **Thread-safe**: Can be built on any thread, committed on any thread ### 4.5 Database Schema for Bots **Recommended tables:** ```sql -- Bot persistent data CREATE TABLE playerbot_data ( guid INT UNSIGNED NOT NULL PRIMARY KEY, owner_guid INT UNSIGNED NOT NULL, ai_state TINYINT UNSIGNED NOT NULL DEFAULT 0, last_teleport_time INT UNSIGNED NOT NULL DEFAULT 0, INDEX idx_owner (owner_guid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Bot equipment (separate table for normalization) CREATE TABLE playerbot_equipment ( bot_guid INT UNSIGNED NOT NULL, slot TINYINT UNSIGNED NOT NULL, item_entry INT UNSIGNED NOT NULL, PRIMARY KEY (bot_guid, slot), FOREIGN KEY (bot_guid) REFERENCES characters(guid) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Bot behaviors (state machine) CREATE TABLE playerbot_behaviors ( bot_guid INT UNSIGNED NOT NULL, behavior_type TINYINT UNSIGNED NOT NULL, priority TINYINT UNSIGNED NOT NULL, enabled TINYINT(1) NOT NULL DEFAULT 1, config TEXT, PRIMARY KEY (bot_guid, behavior_type), FOREIGN KEY (bot_guid) REFERENCES characters(guid) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` ### 4.6 Performance Characteristics | Operation | Latency | Use Case | |-----------|---------|----------| | Sync Query | 1-50ms | Init, critical data | | Async Query | Non-blocking | Runtime loading | | Prepared Statement | 50-80% faster | Repeated queries | | Transaction (small) | 5-20ms | Atomic updates | | Transaction (large) | 50-500ms | Batch operations | **Connection pool size:** - Async threads: 2-4 (configured at startup) - Sync threads: 1-2 (one per database) **Query optimization:** ```cpp // BAD: N+1 query problem for (uint32 botGuid : botGuids) { QueryResult result = CharacterDatabase.PQuery( "SELECT * FROM playerbot_data WHERE guid = {}", botGuid ); // Process one bot } // Total: 1000 bots = 1000 queries = 10-50 seconds // GOOD: Single query with IN clause std::ostringstream guidList; for (size_t i = 0; i < botGuids.size(); ++i) { if (i > 0) guidList << ","; guidList << botGuids[i]; } QueryResult result = CharacterDatabase.PQuery( "SELECT * FROM playerbot_data WHERE guid IN ({})", guidList.str() ); // Total: 1000 bots = 1 query = 10-50ms ``` ### 4.7 Thread Safety **Safe Operations:** - `Query()` / `AsyncQuery()` - Thread-safe - `BeginTransaction()` - Thread-safe - `GetPreparedStatement()` - Thread-safe (creates new object) **Unsafe Operations:** - Sharing PreparedStatement* across threads - Each thread needs its own - Accessing QueryResult from multiple threads - NOT thread-safe **Best Practice:** ```cpp // WRONG - Sharing prepared statement PreparedStatement* sharedStmt = CharacterDatabase.GetPreparedStatement(PLAYERBOT_SEL_BOT_DATA); std::thread t1([sharedStmt]() { /* USE */ }); std::thread t2([sharedStmt]() { /* USE */ }); // Race condition! // CORRECT - Each thread gets own statement std::thread t1([]() { PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(PLAYERBOT_SEL_BOT_DATA); // Use stmt }); std::thread t2([]() { PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(PLAYERBOT_SEL_BOT_DATA); // Use stmt }); ``` --- ## 5. DBC/DB2 Data Stores ### 5.1 Overview **File:** `C:\TrinityBots\TrinityCore\src\server\game\DataStores\DB2Stores.h` DBC (Database Client) and DB2 (Database 2.0) files contain static game data compiled into the client. TrinityCore loads these into memory for fast access. ### 5.2 Available Data Stores **Spell Data:** ```cpp extern DB2Storage sSpellNameStore; extern DB2Storage sSpellRangeStore; extern DB2Storage sSpellDurationStore; extern DB2Storage sSpellCastTimesStore; extern DB2Storage sSpellCooldownsStore; extern DB2Storage sSpellPowerStore; extern DB2Storage sSpellEffectStore; ``` **Character Data:** ```cpp extern DB2Storage sChrClassesStore; extern DB2Storage sChrRacesStore; extern DB2Storage sChrSpecializationStore; extern DB2Storage sTalentStore; ``` **World Data:** ```cpp extern DB2Storage sMapStore; extern DB2Storage sAreaTableStore; extern DB2Storage sFactionStore; extern DB2Storage sFactionTemplateStore; ``` **Item Data:** ```cpp extern DB2Storage sItemStore; extern DB2Storage sItemSparseStore; extern DB2Storage sItemEffectStore; ``` ### 5.3 DB2Manager Singleton **Access:** ```cpp #define sDB2Manager DB2Manager::Instance() ``` **Common Methods:** ```cpp class DB2Manager { public: // Spell queries SpellInfo const* GetSpellInfo(uint32 spellId) const; SpellRangeEntry const* GetSpellRangeEntry(uint32 spellId) const; // Character queries static char const* GetChrClassName(uint8 class_, LocaleConstant locale = DEFAULT_LOCALE); static char const* GetChrRaceName(uint8 race, LocaleConstant locale = DEFAULT_LOCALE); ChrSpecializationEntry const* GetChrSpecializationByIndex(uint32 class_, uint32 index) const; // Map queries static LFGDungeonsEntry const* GetLfgDungeon(uint32 mapId, Difficulty difficulty); MapDifficultyEntry const* GetMapDifficultyData(uint32 mapId, Difficulty difficulty) const; // Faction queries std::vector const* GetFactionTeamList(uint32 faction) const; // Item queries ItemModifiedAppearanceEntry const* GetItemModifiedAppearance(uint32 itemId, uint32 appearanceModId) const; // Curve calculations (scaling) float GetCurveValueAt(uint32 curveId, float x) const; // Content tuning (level scaling) Optional GetContentTuningData(uint32 contentTuningId, uint32 redirectFlag, bool forItem = false) const; }; ``` ### 5.4 Spell Information **SpellInfo - Most Important for Bots:** ```cpp SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) return; // Invalid spell // Range information float minRange = spellInfo->GetMinRange(true); // positive = friendly float maxRange = spellInfo->GetMaxRange(true); // positive = friendly // Cast time uint32 castTime = spellInfo->CalcCastTime(); // Cooldown uint32 cooldown = spellInfo->RecoveryTime; uint32 categoryCooldown = spellInfo->CategoryRecoveryTime; // Power cost int32 manaCost = spellInfo->CalcPowerCost(caster, spellInfo->GetSchoolMask()); // Is instant cast? bool isInstant = spellInfo->IsInstant(); // Requires target? bool needsTarget = spellInfo->IsTargetingArea() || spellInfo->GetExplicitTargetMask() != 0; // Damage spell? bool isDamageSpell = spellInfo->HasEffect(SPELL_EFFECT_SCHOOL_DAMAGE) || spellInfo->HasEffect(SPELL_EFFECT_WEAPON_DAMAGE); // Healing spell? bool isHealSpell = spellInfo->HasEffect(SPELL_EFFECT_HEAL) || spellInfo->HasEffect(SPELL_EFFECT_HEAL_PCT); ``` **Usage Example:** ```cpp // Determine optimal casting range uint32 spellId = 133; // Fireball SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); float optimalRange = spellInfo->GetMaxRange(false) * 0.9f; // 90% of max range if (bot->GetDistance(target) > optimalRange) { // Move closer bot->GetMotionMaster()->MoveChase(target, ChaseRange(optimalRange - 5.0f, optimalRange)); } else { // In range, cast bot->CastSpell(target, spellId); } ``` ### 5.5 Class & Spec Information ```cpp // Get class name std::string className = sDB2Manager->GetChrClassName(bot->GetClass()); // Get specialization uint8 specIndex = 0; // First spec ChrSpecializationEntry const* spec = sDB2Manager->GetChrSpecializationByIndex( bot->GetClass(), specIndex ); if (spec) { // spec->ID - Spec ID // spec->Flags - Spec flags // spec->Role - Tank/Healer/DPS switch (spec->Role) { case CHR_SPEC_ROLE_TANK: // Tank behavior break; case CHR_SPEC_ROLE_HEALER: // Healer behavior break; case CHR_SPEC_ROLE_DPS: // DPS behavior break; } } ``` ### 5.6 Map & Zone Information ```cpp // Get current map info uint32 mapId = bot->GetMapId(); MapEntry const* mapEntry = sMapStore.LookupEntry(mapId); if (mapEntry) { // mapEntry->MapType - Instance type // mapEntry->InstanceType - Raid/Dungeon/Scenario // mapEntry->Flags - Various flags if (mapEntry->IsDungeon()) { // Dungeon-specific bot behavior } else if (mapEntry->IsRaid()) { // Raid-specific bot behavior } } // Get area/zone info uint32 zoneId, areaId; bot->GetZoneAndAreaId(zoneId, areaId); AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId); if (area) { // area->AreaName - Zone name // area->Flags - PvP/Sanctuary/etc. if (area->GetFlags().HasFlag(AREA_FLAG_ARENA)) { // Arena-specific behavior } } ``` ### 5.7 Faction Information ```cpp // Get faction for target Unit* target = bot->GetSelectedUnit(); if (!target) return; FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry( target->GetFaction() ); if (!factionTemplate) return; // Check hostility if (bot->IsHostileTo(target)) { // Enemy - attack } else if (bot->IsFriendlyTo(target)) { // Friendly - buff/heal } // Get faction reputation FactionEntry const* faction = sFactionStore.LookupEntry(factionTemplate->Faction); if (faction) { ReputationRank rank = bot->GetReputationMgr().GetRank(faction); if (rank >= REP_EXALTED) { // Exalted - special rewards available } } ``` ### 5.8 Performance Characteristics | Operation | Complexity | Cache | Notes | |-----------|-----------|-------|-------| | DBC store lookup | O(1) | In-memory | Hash table lookup | | sDB2Manager methods | O(1) - O(log n) | Varies | Most are O(1) | | Spell range calc | O(1) | - | Simple arithmetic | | Class name lookup | O(1) | Static array | Very fast | **Memory Usage:** - All DBC/DB2 data: ~500MB - 1GB (loaded at startup) - Stays in memory entire server lifetime - Read-only after initialization ### 5.9 Thread Safety **ALL DBC/DB2 access is thread-safe** - Data is immutable after loading. **Safe from any thread:** - All `sXXXStore.LookupEntry()` - All `sDB2Manager` methods - SpellInfo queries - Map/Zone queries **No synchronization needed** for read-only DBC access. --- ## 6. Combat & Spell System ### 6.1 ThreatManager API **File:** `C:\TrinityBots\TrinityCore\src\server\game\Combat\ThreatManager.h` #### 6.1.1 Threat Query Methods ```cpp class ThreatManager { public: Unit* GetOwner() const; // Never nullptr Unit* GetCurrentVictim(); // Current tank target Unit* GetLastVictim() const; // Previous victim Unit* GetAnyTarget() const; // Any non-offline target bool IsThreatListEmpty(bool includeOffline = false) const; bool IsThreatenedBy(Unit const* who, bool includeOffline = false) const; float GetThreat(Unit const* who, bool includeOffline = false) const; size_t GetThreatListSize() const; // Iterate threat list Trinity::IteratorPair GetSortedThreatList() const; Trinity::IteratorPair GetUnsortedThreatList() const; }; ``` **Usage Example:** ```cpp // Tank: Check current target Creature* boss = GetBoss(); Unit* currentTarget = boss->GetThreatManager().GetCurrentVictim(); if (currentTarget != tank) { // Tank lost aggro - emergency taunt tank->CastSpell(boss, SPELL_TAUNT); } // Check threat on specific target float botThreat = boss->GetThreatManager().GetThreat(bot); if (botThreat > 0.0f) { // Bot has threat - reduce DPS or use threat drop } // Iterate threat list (highest to lowest) for (auto itr = boss->GetThreatManager().GetSortedThreatList().begin(); itr != boss->GetThreatManager().GetSortedThreatList().end(); ++itr) { ThreatReference const* ref = *itr; Unit* unit = ref->GetVictim(); float threat = ref->GetThreat(); // Process each target by threat } ``` #### 6.1.2 Threat Modification ```cpp void AddThreat(Unit* target, float amount, SpellInfo const* spell = nullptr, bool ignoreModifiers = false, bool ignoreRedirects = false); void ScaleThreat(Unit* target, float factor); void ModifyThreatByPercent(Unit* target, int32 percent); void ResetThreat(Unit* target); void ClearThreat(Unit* target); void ClearAllThreat(); ``` **Usage Example:** ```cpp // Tank: Generate extra threat Unit* target = tank->GetTarget(); if (target && target->GetTypeId() == TYPEID_UNIT) { Creature* creature = target->ToCreature(); creature->GetThreatManager().AddThreat(tank, 1000.0f); } // DPS: Threat dump spell if (bot->GetThreatManager().IsThreateningTo(boss)) { float currentThreat = boss->GetThreatManager().GetThreat(bot); if (currentThreat > dangerThreshold) { // Use threat reduction ability bot->CastSpell(bot, SPELL_FEIGN_DEATH); boss->GetThreatManager().ResetThreat(bot); } } // Wipe threat on evade boss->GetThreatManager().ClearAllThreat(); ``` #### 6.1.3 Threat Reference ```cpp class ThreatReference { public: Creature* GetOwner() const; // Creature with threat list Unit* GetVictim() const; // Target on threat list float GetThreat() const; // Current threat amount enum OnlineState { ONLINE_STATE_ONLINE = 2, // Valid target ONLINE_STATE_SUPPRESSED = 1, // CC'd/Immune (can target if no ONLINE) ONLINE_STATE_OFFLINE = 0 // Invalid (GM/dead/etc.) }; OnlineState GetOnlineState() const; bool IsOnline() const; bool IsAvailable() const; // ONLINE or SUPPRESSED bool IsOffline() const; bool IsTaunting() const; // Under taunt effect bool IsDetaunted() const; // Under detaunt effect }; ``` **Usage Example:** ```cpp // Find highest non-CC'd threat target Unit* bestTarget = nullptr; float highestThreat = 0.0f; for (auto itr = boss->GetThreatManager().GetSortedThreatList().begin(); itr != boss->GetThreatManager().GetSortedThreatList().end(); ++itr) { ThreatReference const* ref = *itr; if (ref->IsOnline() && !ref->IsTaunting()) { bestTarget = ref->GetVictim(); highestThreat = ref->GetThreat(); break; // Sorted list - first valid is highest } } ``` ### 6.2 SpellInfo API **Most useful methods for bot spell selection:** ```cpp class SpellInfo { public: uint32 Id; // Spell ID SpellNameEntry const* SpellName; // Spell name // Range float GetMinRange(bool positive = false) const; float GetMaxRange(bool positive = false) const; // Cast time uint32 CalcCastTime(WorldObject const* caster = nullptr, Spell* spell = nullptr) const; bool IsInstant() const; // Power cost int32 CalcPowerCost(WorldObject const* caster, SpellSchoolMask schoolMask) const; // Cooldown uint32 RecoveryTime; uint32 CategoryRecoveryTime; uint32 StartRecoveryTime; // School SpellSchoolMask GetSchoolMask() const; // Effects bool HasEffect(SpellEffectName effect) const; bool HasAura(AuraType aura) const; bool IsTargetingArea() const; // Attributes bool IsPassive() const; bool IsAutoRepeatRangedSpell() const; bool IsChanneled() const; bool NeedsExplicitUnitTarget() const; // Spell type checks bool IsPositive() const; bool IsPositiveEffect(SpellEffIndex effIndex) const; bool IsHealingSpell() const; bool IsDamageSpell() const; }; ``` **Usage Example - Spell Selection:** ```cpp // Find best healing spell for target uint32 SelectHealSpell(Unit* target) { uint32 bestSpell = 0; float healthDeficit = target->GetMaxHealth() - target->GetHealth(); float healthPct = target->GetHealthPct(); // Emergency heal (< 30% HP) if (healthPct < 30.0f) { bestSpell = SPELL_FLASH_HEAL; } // Large heal needed else if (healthDeficit > 5000.0f) { bestSpell = SPELL_GREATER_HEAL; } // Efficient heal else { bestSpell = SPELL_HEAL; } // Verify spell is usable SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(bestSpell); if (!spellInfo) return 0; // Check mana int32 manaCost = spellInfo->CalcPowerCost(bot, spellInfo->GetSchoolMask()); if (bot->GetPower(POWER_MANA) < manaCost) return 0; // Not enough mana // Check range float dist = bot->GetDistance(target); if (dist > spellInfo->GetMaxRange(true)) // true = friendly spell return 0; // Out of range // Check cooldown if (bot->GetSpellHistory()->HasCooldown(bestSpell)) return 0; // On cooldown return bestSpell; } ``` **Usage Example - Combat Range:** ```cpp // Calculate optimal combat distance per class float CalculateOptimalRange(Player* bot, Unit* target) { // Get bot's primary damage spell uint32 primarySpell = GetPrimaryDamageSpell(bot); SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(primarySpell); if (!spellInfo) return 5.0f; // Default melee range // Melee class if (spellInfo->GetMaxRange(false) < 10.0f) { return 5.0f; // Melee range } // Ranged class - use 80% of max range float maxRange = spellInfo->GetMaxRange(false); return maxRange * 0.8f; } // Use in combat movement float optimalRange = CalculateOptimalRange(bot, target); bot->GetMotionMaster()->MoveChase(target, ChaseRange(optimalRange - 5.0f, optimalRange)); ``` ### 6.3 Spell Casting **Basic spell cast:** ```cpp SpellCastResult CastSpell(Unit* victim, uint32 spellId, CastSpellExtraArgs const& args = {}); SpellCastResult CastSpell(SpellCastTargets const& targets, SpellInfo const* spellInfo, CastSpellExtraArgs const& args = {}); ``` **Usage Example:** ```cpp // Simple spell cast bot->CastSpell(target, SPELL_FIREBALL); // With extra args CastSpellExtraArgs args; args.TriggerFlags = TRIGGERED_IGNORE_GCD; // Ignore GCD bot->CastSpell(target, SPELL_INSTANT_PROC, args); // Check if cast succeeded SpellCastResult result = bot->CastSpell(target, SPELL_POLYMORPH); switch (result) { case SPELL_CAST_OK: // Cast started successfully break; case SPELL_FAILED_OUT_OF_RANGE: // Move closer break; case SPELL_FAILED_NOT_READY: // On cooldown break; case SPELL_FAILED_NO_POWER: // Not enough mana break; default: // Other error break; } ``` ### 6.4 Combat State **Check combat state:** ```cpp bool IsInCombat() const; bool IsInCombatWith(Unit const* who) const; void CombatStart(Unit* target, bool initialAggro = true); void CombatStop(bool includingCast = false); ``` **Usage Example:** ```cpp // Check if bot should engage if (!bot->IsInCombat() && ShouldAttack(target)) { bot->CombatStart(target); bot->GetMotionMaster()->MoveChase(target, optimalRange); } // Exit combat if (ShouldEvade()) { bot->CombatStop(true); // true = interrupt current cast bot->GetMotionMaster()->MoveTargetedHome(); } ``` --- ## 7. Integration Recommendations ### 7.1 Movement System Integration **Remove manual deduplication:** ```cpp // BEFORE (in LeaderFollowBehavior.cpp) if (_bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE) { return; // Already following } _bot->GetMotionMaster()->MoveFollow(leader, dist); // AFTER (correct) _bot->GetMotionMaster()->MoveFollow(leader, dist); // MotionMaster handles deduplication internally ``` **Use GetCurrentMovementGeneratorType for state checks:** ```cpp // Check movement state bool IsFollowingLeader() const { return _bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE; } bool IsChasing() const { return _bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE; } ``` ### 7.2 Group Event Integration **Use GroupScript hooks:** ```cpp // In src/modules/Playerbot/Hooks/BotGroupHook.cpp class BotGroupHook : public GroupScript { public: BotGroupHook() : GroupScript("BotGroupHook") { } void OnAddMember(Group* group, ObjectGuid guid) override { Player* player = ObjectAccessor::FindPlayer(guid); if (!player || !IsBotPlayer(player)) return; // Bot joined group - start following Player* leader = ObjectAccessor::FindPlayer(group->GetLeaderGUID()); if (leader && leader != player) { player->GetMotionMaster()->MoveFollow(leader, 5.0f); } } void OnChangeLeader(Group* group, ObjectGuid newLeaderGuid, ObjectGuid oldLeaderGuid) override { // Update all bots to follow new leader Player* newLeader = ObjectAccessor::FindPlayer(newLeaderGuid); if (!newLeader) return; for (GroupReference const& ref : group->GetMembers()) { Player* member = ref.GetSource(); if (IsBotPlayer(member) && member != newLeader) { member->GetMotionMaster()->MoveFollow(newLeader, 5.0f); } } } }; ``` ### 7.3 Database Integration **Use async queries for runtime loading:** ```cpp // Load bot data asynchronously void LoadBotData(uint32 botGuid) { PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(PLAYERBOT_SEL_BOT_DATA); stmt->SetData(0, botGuid); CharacterDatabase.AsyncQuery(stmt) .WithCallback([botGuid](QueryResult result) { if (!result) return; Field* fields = result->Fetch(); // Process bot data BotMgr::RegisterBot(botGuid, fields); }); } ``` **Use transactions for atomic updates:** ```cpp // Save bot state atomically void SaveBotState(Player* bot) { SQLTransaction trans = CharacterDatabase.BeginTransaction(); // Update position PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(PLAYERBOT_UPD_POSITION); stmt->SetData(0, bot->GetMapId()); stmt->SetData(1, bot->GetPositionX()); stmt->SetData(2, bot->GetPositionY()); stmt->SetData(3, bot->GetPositionZ()); stmt->SetData(4, bot->GetGUID()); trans->Append(stmt); // Update state stmt = CharacterDatabase.GetPreparedStatement(PLAYERBOT_UPD_STATE); stmt->SetData(0, GetAIState()); stmt->SetData(1, bot->GetGUID()); trans->Append(stmt); // Commit asynchronously CharacterDatabase.AsyncCommitTransaction(trans); } ``` ### 7.4 Spell System Integration **Use SpellInfo for decision making:** ```cpp // Intelligent spell selection uint32 SelectBestSpell(Player* bot, Unit* target) { std::vector availableSpells = GetAvailableSpells(bot); float bestScore = 0.0f; uint32 bestSpell = 0; for (uint32 spellId : availableSpells) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) continue; // Check usability if (bot->GetSpellHistory()->HasCooldown(spellId)) continue; int32 manaCost = spellInfo->CalcPowerCost(bot, spellInfo->GetSchoolMask()); if (bot->GetPower(POWER_MANA) < manaCost) continue; float dist = bot->GetDistance(target); if (dist > spellInfo->GetMaxRange(false)) continue; // Score spell float score = CalculateSpellScore(spellInfo, target); if (score > bestScore) { bestScore = score; bestSpell = spellId; } } return bestSpell; } ``` ### 7.5 Performance Optimization **Batch database operations:** ```cpp // Load all bot data in one query void LoadAllBotData(std::vector const& botGuids) { if (botGuids.empty()) return; // Build IN clause std::ostringstream ss; ss << "SELECT guid, owner, ai_state FROM playerbot_data WHERE guid IN ("; for (size_t i = 0; i < botGuids.size(); ++i) { if (i > 0) ss << ","; ss << botGuids[i]; } ss << ")"; // Single async query CharacterDatabase.AsyncQuery(ss.str().c_str()) .WithCallback([](QueryResult result) { if (!result) return; do { Field* fields = result->Fetch(); uint32 guid = fields[0].Get(); uint32 owner = fields[1].Get(); uint8 state = fields[2].Get(); BotMgr::RegisterBot(guid, owner, state); } while (result->NextRow()); }); } ``` **Use DBC for static data:** ```cpp // Cache spell ranges at initialization struct SpellRangeCache { float minRange; float maxRange; }; std::unordered_map _spellRangeCache; void InitializeSpellRangeCache() { for (uint32 spellId : GetBotSpells()) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) continue; _spellRangeCache[spellId] = { spellInfo->GetMinRange(false), spellInfo->GetMaxRange(false) }; } } // Fast range check bool IsInSpellRange(uint32 spellId, float distance) const { auto itr = _spellRangeCache.find(spellId); if (itr == _spellRangeCache.end()) return false; return distance >= itr->second.minRange && distance <= itr->second.maxRange; } ``` --- ## Appendix A: File Locations Reference | API Category | Primary File | Path | |-------------|--------------|------| | MotionMaster | MotionMaster.h | `src/server/game/Movement/MotionMaster.h` | | MovementGenerator | MovementGenerator.h | `src/server/game/Movement/MovementGenerator.h` | | MovementDefines | MovementDefines.h | `src/server/game/Movement/MovementDefines.h` | | Group | Group.h | `src/server/game/Groups/Group.h` | | Group.cpp | Group.cpp | `src/server/game/Groups/Group.cpp` | | ScriptMgr | ScriptMgr.h | `src/server/game/Scripting/ScriptMgr.h` | | ThreatManager | ThreatManager.h | `src/server/game/Combat/ThreatManager.h` | | SpellInfo | SpellInfo.h | `src/server/game/Spells/SpellInfo.h` | | Spell | Spell.h | `src/server/game/Spells/Spell.h` | | DB2Stores | DB2Stores.h | `src/server/game/DataStores/DB2Stores.h` | | DatabaseWorkerPool | DatabaseWorkerPool.h | `src/server/database/Database/DatabaseWorkerPool.h` | | Player | Player.h | `src/server/game/Entities/Player/Player.h` | | Unit | Unit.h | `src/server/game/Entities/Unit/Unit.h` | --- ## Appendix B: Common Integration Patterns ### Pattern 1: Bot Initialization Hook ```cpp // In PlayerEventHooks.cpp void OnLogin(Player* player, bool firstLogin) override { if (!IsBotAccount(player->GetSession()->GetAccountId())) return; // Initialize bot AI BotAI* ai = new BotAI(player); player->SetBotAI(ai); // Load bot configuration BotConfig::Load(player); // If in group, start following if (Group* group = player->GetGroup()) { Player* leader = ObjectAccessor::FindPlayer(group->GetLeaderGUID()); if (leader && leader != player) { player->GetMotionMaster()->MoveFollow(leader, 5.0f); } } } ``` ### Pattern 2: Group-Based Movement ```cpp // In BotGroupMgr.cpp void UpdateGroupMovement(Group* group) { Player* leader = ObjectAccessor::FindPlayer(group->GetLeaderGUID()); if (!leader) return; for (GroupReference const& ref : group->GetMembers()) { Player* member = ref.GetSource(); if (!IsBotPlayer(member) || member == leader) continue; // Calculate formation position float dist = CalculateFollowDistance(member); ChaseAngle angle = CalculateFormationAngle(member, group); // Update movement (no deduplication needed) member->GetMotionMaster()->MoveFollow(leader, dist, angle); } } ``` ### Pattern 3: Combat Spell Rotation ```cpp // In CombatAI.cpp void UpdateCombat(uint32 diff) { Unit* target = _bot->GetTarget(); if (!target) return; // Move to optimal range float optimalRange = CalculateOptimalRange(_bot, target); float currentDist = _bot->GetDistance(target); if (std::abs(currentDist - optimalRange) > 2.0f) { _bot->GetMotionMaster()->MoveChase(target, ChaseRange(optimalRange - 2.0f, optimalRange + 2.0f)); } // Select and cast spell if (!_bot->HasUnitState(UNIT_STATE_CASTING)) { uint32 spellId = SelectBestSpell(_bot, target); if (spellId) { _bot->CastSpell(target, spellId); } } } ``` --- ## Document Revision History | Version | Date | Changes | |---------|------|---------| | 1.0 | 2025-10-08 | Initial comprehensive analysis | --- **End of Document**