#pragma once #include "game/world_packets.hpp" #include "game/entity.hpp" #include "game/opcode_table.hpp" #include "network/packet.hpp" #include #include #include #include #include #include #include #include namespace wowee { namespace game { class GameHandler; class EntityController { public: using PacketHandler = std::function; using DispatchTable = std::unordered_map; explicit EntityController(GameHandler& owner); void registerOpcodes(DispatchTable& table); // --- Entity Manager access --- EntityManager& getEntityManager() { return entityManager; } const EntityManager& getEntityManager() const { return entityManager; } // --- Name / info cache queries --- void queryPlayerName(uint64_t guid); void queryCreatureInfo(uint32_t entry, uint64_t guid); void queryGameObjectInfo(uint32_t entry, uint64_t guid); std::string getCachedPlayerName(uint64_t guid) const; std::string getCachedCreatureName(uint32_t entry) const; void invalidatePlayerName(uint64_t guid) { playerNameCache.erase(guid); } // Read-only cache access for other handlers const std::unordered_map& getPlayerNameCache() const { return playerNameCache; } const std::unordered_map& getCreatureInfoCache() const { return creatureInfoCache; } std::string getCachedCreatureSubName(uint32_t entry) const { auto it = creatureInfoCache.find(entry); return (it != creatureInfoCache.end()) ? it->second.subName : ""; } int getCreatureRank(uint32_t entry) const { auto it = creatureInfoCache.find(entry); return (it != creatureInfoCache.end()) ? static_cast(it->second.rank) : -1; } uint32_t getCreatureType(uint32_t entry) const { auto it = creatureInfoCache.find(entry); return (it != creatureInfoCache.end()) ? it->second.creatureType : 0; } uint32_t getCreatureFamily(uint32_t entry) const { auto it = creatureInfoCache.find(entry); return (it != creatureInfoCache.end()) ? it->second.family : 0; } const GameObjectQueryResponseData* getCachedGameObjectInfo(uint32_t entry) const { auto it = gameObjectInfoCache_.find(entry); return (it != gameObjectInfoCache_.end()) ? &it->second : nullptr; } // Name lookup (checks cache then entity manager) const std::string& lookupName(uint64_t guid) const { static const std::string kEmpty; auto it = playerNameCache.find(guid); if (it != playerNameCache.end()) return it->second; auto entity = entityManager.getEntity(guid); if (entity) { if (auto* unit = dynamic_cast(entity.get())) { if (!unit->getName().empty()) return unit->getName(); } } return kEmpty; } uint8_t lookupPlayerClass(uint64_t guid) const { auto it = playerClassRaceCache_.find(guid); return it != playerClassRaceCache_.end() ? it->second.classId : 0; } uint8_t lookupPlayerRace(uint64_t guid) const { auto it = playerClassRaceCache_.find(guid); return it != playerClassRaceCache_.end() ? it->second.raceId : 0; } // --- Transport GUID tracking --- bool isTransportGuid(uint64_t guid) const { return transportGuids_.count(guid) > 0; } bool hasServerTransportUpdate(uint64_t guid) const { return serverUpdatedTransportGuids_.count(guid) > 0; } // --- Update object work queue --- void enqueueUpdateObjectWork(UpdateObjectData&& data); void processPendingUpdateObjectWork(const std::chrono::steady_clock::time_point& start, float budgetMs); bool hasPendingUpdateObjectWork() const { return !pendingUpdateObjectWork_.empty(); } // --- Reset all state (called on disconnect / character switch) --- void clearAll(); private: GameHandler& owner_; // --- Entity tracking --- EntityManager entityManager; // Manages all entities in view // ---- Name caches ---- std::unordered_map playerNameCache; // Class/race cache from SMSG_NAME_QUERY_RESPONSE (guid → {classId, raceId}) struct PlayerClassRace { uint8_t classId = 0; uint8_t raceId = 0; }; std::unordered_map playerClassRaceCache_; std::unordered_set pendingNameQueries; std::unordered_map creatureInfoCache; std::unordered_set pendingCreatureQueries; std::unordered_map gameObjectInfoCache_; std::unordered_set pendingGameObjectQueries_; // --- Update Object work queue --- struct PendingUpdateObjectWork { UpdateObjectData data; size_t nextBlockIndex = 0; bool outOfRangeProcessed = false; bool newItemCreated = false; }; std::deque pendingUpdateObjectWork_; // --- Transport GUID tracking --- std::unordered_set transportGuids_; // GUIDs of known transport GameObjects std::unordered_set serverUpdatedTransportGuids_; // --- Packet handlers --- void handleUpdateObject(network::Packet& packet); void handleCompressedUpdateObject(network::Packet& packet); void handleDestroyObject(network::Packet& packet); void handleNameQueryResponse(network::Packet& packet); void handleCreatureQueryResponse(network::Packet& packet); void handleGameObjectQueryResponse(network::Packet& packet); void handleGameObjectPageText(network::Packet& packet); void handlePageTextQueryResponse(network::Packet& packet); // --- Entity lifecycle --- void processOutOfRangeObjects(const std::vector& guids); void applyUpdateObjectBlock(const UpdateBlock& block, bool& newItemCreated); void finalizeUpdateObjectBatch(bool newItemCreated); bool extractPlayerAppearance(const std::map& fields, uint8_t& outRace, uint8_t& outGender, uint32_t& outAppearanceBytes, uint8_t& outFacial) const; void maybeDetectCoinageIndex(const std::map& oldFields, const std::map& newFields); void handleCreateObject(const UpdateBlock& block, bool& newItemCreated); void handleValuesUpdate(const UpdateBlock& block); void handleMovementUpdate(const UpdateBlock& block); // Update transport-relative child attachment (non-player entities). // Consolidates identical logic from CREATE/VALUES/MOVEMENT handlers. void updateNonPlayerTransportAttachment(const UpdateBlock& block, const std::shared_ptr& entity, ObjectType entityType); // Rebuild playerAuras_ from UNIT_FIELD_AURAS (Classic/vanilla only). // Consolidates identical logic from CREATE and VALUES handlers. void syncClassicAurasFromFields(const std::shared_ptr& entity); // Detect mount/dismount from UNIT_FIELD_MOUNTDISPLAYID changes (self-player only). // Consolidates identical logic from CREATE and VALUES handlers. void detectPlayerMountChange(uint32_t newMountDisplayId, const std::map& blockFields); // Shared player-death handler: caches corpse position, sets death state. void markPlayerDead(const char* source); // Cached field indices resolved once per handler call to avoid repeated lookups. struct UnitFieldIndices { uint16_t health, maxHealth, powerBase, maxPowerBase; uint16_t level, faction, flags, dynFlags; uint16_t displayId, mountDisplayId, npcFlags; uint16_t bytes0, bytes1; static UnitFieldIndices resolve(); }; struct PlayerFieldIndices { uint16_t xp, nextXp, restedXp, level; uint16_t coinage, honor, arena; uint16_t playerFlags, armor; uint16_t pBytes, pBytes2, chosenTitle; uint16_t stats[5]; uint16_t meleeAP, rangedAP; uint16_t spDmg1, healBonus; uint16_t blockPct, dodgePct, parryPct, critPct, rangedCritPct; uint16_t sCrit1, rating1; static PlayerFieldIndices resolve(); }; struct UnitFieldUpdateResult { bool healthChanged = false; bool powerChanged = false; bool displayIdChanged = false; bool npcDeathNotified = false; bool npcRespawnNotified = false; uint32_t oldDisplayId = 0; }; // Entity factory — creates the correct Entity subclass for the given block. std::shared_ptr createEntityFromBlock(const UpdateBlock& block); // Track player-on-transport state from movement blocks. void applyPlayerTransportState(const UpdateBlock& block, const std::shared_ptr& entity, const glm::vec3& canonicalPos, float oCanonical, bool updateMovementInfoPos); // Apply unit fields during CREATE — returns true if entity is initially dead. bool applyUnitFieldsOnCreate(const UpdateBlock& block, std::shared_ptr& unit, const UnitFieldIndices& ufi); // Apply unit fields during VALUES — returns change tracking result. UnitFieldUpdateResult applyUnitFieldsOnUpdate(const UpdateBlock& block, const std::shared_ptr& entity, std::shared_ptr& unit, const UnitFieldIndices& ufi); // Apply player stat fields (XP, inventory, skills, etc.). isCreate=true for CREATE path. bool applyPlayerStatFields(const std::map& fields, const PlayerFieldIndices& pfi, bool isCreate); // Dispatch spawn callbacks (creature/player) — deduplicates CREATE and VALUES paths. void dispatchEntitySpawn(uint64_t guid, ObjectType objectType, const std::shared_ptr& entity, const std::shared_ptr& unit, bool isDead); // Track item/container on CREATE. void trackItemOnCreate(const UpdateBlock& block, bool& newItemCreated); // Update item fields on VALUES update. void updateItemOnValuesUpdate(const UpdateBlock& block, const std::shared_ptr& entity); // Allows extending object-type handling without modifying handler dispatch. struct IObjectTypeHandler { virtual ~IObjectTypeHandler() = default; virtual void onCreate(const UpdateBlock& /*block*/, std::shared_ptr& /*entity*/, bool& /*newItemCreated*/) {} virtual void onValuesUpdate(const UpdateBlock& /*block*/, std::shared_ptr& /*entity*/) {} virtual void onMovementUpdate(const UpdateBlock& /*block*/, std::shared_ptr& /*entity*/) {} }; struct UnitTypeHandler; struct PlayerTypeHandler; struct GameObjectTypeHandler; struct ItemTypeHandler; struct CorpseTypeHandler; std::unordered_map> typeHandlers_; void initTypeHandlers(); IObjectTypeHandler* getTypeHandler(ObjectType type) const; void onCreateUnit(const UpdateBlock& block, std::shared_ptr& entity); void onCreatePlayer(const UpdateBlock& block, std::shared_ptr& entity); void onCreateGameObject(const UpdateBlock& block, std::shared_ptr& entity); void onCreateItem(const UpdateBlock& block, bool& newItemCreated); void onCreateCorpse(const UpdateBlock& block); void handleDisplayIdChange(const UpdateBlock& block, const std::shared_ptr& entity, const std::shared_ptr& unit, const UnitFieldUpdateResult& result); void onValuesUpdateUnit(const UpdateBlock& block, std::shared_ptr& entity); void onValuesUpdatePlayer(const UpdateBlock& block, std::shared_ptr& entity); void onValuesUpdateItem(const UpdateBlock& block, std::shared_ptr& entity); void onValuesUpdateGameObject(const UpdateBlock& block, std::shared_ptr& entity); // Collects addon events during block processing, flushes at the end. struct PendingEvents { std::vector>> events; void emit(const std::string& name, const std::vector& args = {}) { events.emplace_back(name, args); } void clear() { events.clear(); } }; PendingEvents pendingEvents_; void flushPendingEvents(); }; } // namespace game } // namespace wowee