#pragma once #include "game/character.hpp" #include "game/game_services.hpp" #include "pipeline/blp_loader.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace wowee { // Forward declarations namespace rendering { class Renderer; } namespace pipeline { class AssetManager; class DBCLayout; struct M2Model; struct WMOModel; } namespace audio { enum class VoiceType; } namespace game { class GameHandler; } namespace core { class Application; class EntitySpawner { public: EntitySpawner(rendering::Renderer* renderer, pipeline::AssetManager* assetManager, game::GameHandler* gameHandler, pipeline::DBCLayout* dbcLayout, game::GameServices* gameServices); ~EntitySpawner(); EntitySpawner(const EntitySpawner&) = delete; EntitySpawner& operator=(const EntitySpawner&) = delete; // Lifecycle void initialize(); // Build DBC lookups void update(); // Process all spawn/despawn queues (called from Application::update) void shutdown(); // Clear all instances and queues // Queue-based spawn API (called from GameHandler callbacks) void queueCreatureSpawn(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation, float scale = 1.0f); void queuePlayerSpawn(uint64_t guid, uint8_t raceId, uint8_t genderId, uint32_t appearanceBytes, uint8_t facialFeatures, float x, float y, float z, float orientation); void queueGameObjectSpawn(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation, float scale = 1.0f); void queuePlayerEquipment(uint64_t guid, const std::array& displayInfoIds, const std::array& inventoryTypes); // Immediate despawn void despawnCreature(uint64_t guid); void despawnPlayer(uint64_t guid); void despawnGameObject(uint64_t guid); // Clear all queues and instances (logout, reconnect) void clearAllQueues(); void despawnAllCreatures(); void despawnAllPlayers(); void despawnAllGameObjects(); // Full reset — despawns entities, waits for async loads, clears all state. // Used by logoutToLogin() and loadOnlineWorldTerrain() for clean slate. void resetAllState(); // Rebuild lookup tables (for reloadExpansionData after expansion change) void rebuildLookups(); // Check if any spawn/async work is still pending bool hasWorkPending() const; // Status queries bool isCreatureSpawned(uint64_t guid) const { return creatureInstances_.count(guid) > 0; } bool isCreaturePending(uint64_t guid) const { return pendingCreatureSpawnGuids_.count(guid) > 0; } bool isPlayerSpawned(uint64_t guid) const { return playerInstances_.count(guid) > 0; } bool isPlayerPending(uint64_t guid) const { return pendingPlayerSpawnGuids_.count(guid) > 0; } bool isGameObjectSpawned(uint64_t guid) const { return gameObjectInstances_.count(guid) > 0; } // Quick instance ID lookups (returns 0 if not found) uint32_t getCreatureInstanceId(uint64_t guid) const { auto it = creatureInstances_.find(guid); return (it != creatureInstances_.end()) ? it->second : 0; } uint32_t getPlayerInstanceId(uint64_t guid) const { auto it = playerInstances_.find(guid); return (it != playerInstances_.end()) ? it->second : 0; } // Render bounds/position queries (used by click targeting, etc.) bool getRenderBoundsForGuid(uint64_t guid, glm::vec3& outCenter, float& outRadius) const; bool getRenderFootZForGuid(uint64_t guid, float& outFootZ) const; bool getRenderPositionForGuid(uint64_t guid, glm::vec3& outPos) const; // Display data lookups bool areCreatureLookupsBuilt() const { return creatureLookupsBuilt_; } bool areGameObjectLookupsBuilt() const { return gameObjectLookupsBuilt_; } std::string getModelPathForDisplayId(uint32_t displayId) const; std::string getGameObjectModelPathForDisplayId(uint32_t displayId) const; audio::VoiceType detectVoiceTypeFromDisplayId(uint32_t displayId) const; // Weapon attachment for NPC virtual weapons bool tryAttachCreatureVirtualWeapons(uint64_t guid, uint32_t instanceId); // Mount void setMountDisplayId(uint32_t displayId) { pendingMountDisplayId_ = displayId; } uint32_t getMountInstanceId() const { return mountInstanceId_; } uint32_t getMountModelId() const { return mountModelId_; } // Local player GUID exclusion (EntitySpawner skips spawning the local player) void setLocalPlayerGuid(uint64_t guid) { spawnedPlayerGuid_ = guid; } // Weapon model IDs (shared counter for creature weapons + equipment helm/weapon models) uint32_t allocateWeaponModelId() { return nextWeaponModelId_++; } // Maximum weapon attachment operations per tick (used by Application update loop) static constexpr int MAX_WEAPON_ATTACHES_PER_TICK = 2; // Dead creature tracking (spawn in corpse/death pose) void markCreatureDead(uint64_t guid) { deadCreatureGuids_.insert(guid); } void unmarkCreatureDead(uint64_t guid) { deadCreatureGuids_.erase(guid); } void clearDeadCreatureGuids() { deadCreatureGuids_.clear(); } // Mount state management void clearMountState(); // Transport registration API (used by setupUICallbacks transport spawn callback) void queueTransportRegistration(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation); void setTransportPendingMove(uint64_t guid, float x, float y, float z, float orientation); bool hasTransportRegistrationPending(uint64_t guid) const; void updateTransportRegistration(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation); // Test transport setup flag bool isTestTransportSetup() const { return testTransportSetup_; } void setTestTransportSetup(bool v) { testTransportSetup_ = v; } // Creature animation state accessors (for movement sync in Application::update) std::unordered_map& getCreatureInstances() { return creatureInstances_; } const std::unordered_map& getCreatureInstances() const { return creatureInstances_; } std::unordered_map& getCreatureModelIds() { return creatureModelIds_; } std::unordered_map& getCreatureRenderPosCache() { return creatureRenderPosCache_; } std::unordered_map& getCreatureWasMoving() { return creatureWasMoving_; } std::unordered_map& getCreatureWasSwimming() { return creatureWasSwimming_; } std::unordered_map& getCreatureWasFlying() { return creatureWasFlying_; } std::unordered_map& getCreatureWasWalking() { return creatureWasWalking_; } std::unordered_map& getCreatureSwimmingState() { return creatureSwimmingState_; } std::unordered_map& getCreatureWalkingState() { return creatureWalkingState_; } std::unordered_map& getCreatureFlyingState() { return creatureFlyingState_; } std::unordered_set& getCreatureWeaponsAttached() { return creatureWeaponsAttached_; } std::unordered_map& getCreatureWeaponAttachAttempts() { return creatureWeaponAttachAttempts_; } std::unordered_map& getModelIdIsWolfLike() { return modelIdIsWolfLike_; } // Player instance accessors (for movement sync in Application::update) std::unordered_map& getPlayerInstances() { return playerInstances_; } const std::unordered_map& getPlayerInstances() const { return playerInstances_; } // GameObject instance accessors auto& getGameObjectInstances() { return gameObjectInstances_; } const auto& getGameObjectInstances() const { return gameObjectInstances_; } // Display data accessors (needed by Application for gryphon/wyvern display IDs) const auto& getDisplayDataMap() const { return displayDataMap_; } uint32_t getGryphonDisplayId() const { return gryphonDisplayId_; } uint32_t getWyvernDisplayId() const { return wyvernDisplayId_; } // Character section lookups (needed for player character skin compositing) std::string lookupCharSection(uint8_t race, uint8_t sex, uint8_t section, uint8_t variation, uint8_t color, int texIndex = 0) const; const std::unordered_map& getHairGeosetMap() const { return hairGeosetMap_; } struct FacialHairGeosets { uint16_t geoset100 = 0; uint16_t geoset300 = 0; uint16_t geoset200 = 0; }; const std::unordered_map& getFacialHairGeosetMap() const { return facialHairGeosetMap_; } // Creature M2 sync loader (used by spawnPlayerCharacter in Application) pipeline::M2Model loadCreatureM2Sync(const std::string& m2Path); private: // Dependencies (non-owning) rendering::Renderer* renderer_; pipeline::AssetManager* assetManager_; game::GameHandler* gameHandler_; pipeline::DBCLayout* dbcLayout_; game::GameServices* gameServices_; // --- Creature display data (from DBC files) --- struct CreatureDisplayData { uint32_t modelId = 0; std::string skin1, skin2, skin3; // Texture names from CreatureDisplayInfo.dbc uint32_t extraDisplayId = 0; // Link to CreatureDisplayInfoExtra.dbc }; struct HumanoidDisplayExtra { uint8_t raceId = 0; uint8_t sexId = 0; uint8_t skinId = 0; uint8_t faceId = 0; uint8_t hairStyleId = 0; uint8_t hairColorId = 0; uint8_t facialHairId = 0; std::string bakeName; // Pre-baked texture path if available // Equipment display IDs (from columns 8-18) // 0=helm, 1=shoulder, 2=shirt, 3=chest, 4=belt, 5=legs, 6=feet, 7=wrist, 8=hands, 9=tabard, 10=cape uint32_t equipDisplayId[11] = {0}; }; std::unordered_map displayDataMap_; // displayId → display data std::unordered_map humanoidExtraMap_; // extraDisplayId → humanoid data std::unordered_map modelIdToPath_; // modelId → M2 path (from CreatureModelData.dbc) // CharHairGeosets.dbc: key = (raceId<<16)|(sexId<<8)|variationId → geosetId (skinSectionId) std::unordered_map hairGeosetMap_; // CharFacialHairStyles.dbc: key = (raceId<<16)|(sexId<<8)|variationId → {geoset100, geoset300, geoset200} std::unordered_map facialHairGeosetMap_; bool creatureLookupsBuilt_ = false; // CharSections.dbc lookup cache std::unordered_map charSectionsCache_; bool charSectionsCacheBuilt_ = false; void buildCharSectionsCache(); // Creature display lookup building void buildCreatureDisplayLookups(); // Weapon M2 loading helper bool loadWeaponM2(const std::string& m2Path, pipeline::M2Model& outModel); // GameObject display lookups std::unordered_map gameObjectDisplayIdToPath_; bool gameObjectLookupsBuilt_ = false; void buildGameObjectDisplayLookups(); // --- Creature instances and tracking --- std::unordered_map creatureInstances_; // guid → render instanceId std::unordered_map creatureModelIds_; // guid → loaded modelId std::unordered_map creatureRenderPosCache_; // guid → last synced render position std::unordered_map creatureWasMoving_; std::unordered_map creatureWasSwimming_; std::unordered_map creatureWasFlying_; std::unordered_map creatureWasWalking_; std::unordered_map creatureSwimmingState_; std::unordered_map creatureWalkingState_; std::unordered_map creatureFlyingState_; std::unordered_set creatureWeaponsAttached_; std::unordered_map creatureWeaponAttachAttempts_; std::unordered_map modelIdIsWolfLike_; // --- Creature async loads --- struct PreparedCreatureModel { uint64_t guid; uint32_t displayId; uint32_t modelId; float x, y, z, orientation; float scale = 1.0f; std::shared_ptr model; std::unordered_map predecodedTextures; bool valid = false; bool permanent_failure = false; }; struct AsyncCreatureLoad { std::future future; }; std::vector asyncCreatureLoads_; std::unordered_set asyncCreatureDisplayLoads_; void processAsyncCreatureResults(bool unlimited = false); static constexpr int MAX_ASYNC_CREATURE_LOADS = 4; std::unordered_set deadCreatureGuids_; std::unordered_map displayIdModelCache_; std::unordered_set displayIdTexturesApplied_; std::unordered_map> displayIdPredecodedTextures_; mutable std::unordered_set warnedMissingDisplayDataIds_; mutable std::unordered_set warnedMissingModelPathIds_; uint32_t nextCreatureModelId_ = 5000; uint32_t gryphonDisplayId_ = 0; uint32_t wyvernDisplayId_ = 0; // --- Creature spawn queue --- struct PendingCreatureSpawn { uint64_t guid; uint32_t displayId; float x, y, z, orientation; float scale = 1.0f; }; std::deque pendingCreatureSpawns_; static constexpr int MAX_SPAWNS_PER_FRAME = 3; static constexpr int MAX_NEW_CREATURE_MODELS_PER_FRAME = 1; static constexpr uint16_t MAX_CREATURE_SPAWN_RETRIES = 300; std::unordered_set pendingCreatureSpawnGuids_; std::unordered_map creatureSpawnRetryCounts_; std::unordered_set nonRenderableCreatureDisplayIds_; std::unordered_set creaturePermanentFailureGuids_; void processCreatureSpawnQueue(bool unlimited = false); // --- NPC composite loads --- struct DeferredNpcComposite { uint32_t modelId; uint32_t displayId; std::string basePath; std::vector overlayPaths; std::vector> regionLayers; std::vector skinTextureSlots; bool hasComposite = false; bool hasSimpleSkin = false; std::string bakedSkinPath; bool hasBakedSkin = false; std::vector hairTextureSlots; std::string hairTexturePath; bool useBakedForHair = false; }; struct PreparedNpcComposite { DeferredNpcComposite info; std::unordered_map predecodedTextures; }; struct AsyncNpcCompositeLoad { std::future future; }; std::vector asyncNpcCompositeLoads_; void processAsyncNpcCompositeResults(bool unlimited = false); // --- Player instances --- std::unordered_map playerInstances_; // guid → render instanceId struct OnlinePlayerAppearanceState { uint32_t instanceId = 0; uint32_t modelId = 0; uint8_t raceId = 0; uint8_t genderId = 0; uint32_t appearanceBytes = 0; uint8_t facialFeatures = 0; std::string bodySkinPath; std::vector underwearPaths; }; std::unordered_map onlinePlayerAppearance_; std::unordered_map, std::array>> pendingOnlinePlayerEquipment_; std::deque, std::array>>> deferredEquipmentQueue_; void processDeferredEquipmentQueue(); struct PreparedEquipmentUpdate { uint64_t guid; std::array displayInfoIds; std::array inventoryTypes; std::unordered_map predecodedTextures; }; struct AsyncEquipmentLoad { std::future future; }; std::vector asyncEquipmentLoads_; void processAsyncEquipmentResults(); std::vector resolveEquipmentTexturePaths(uint64_t guid, const std::array& displayInfoIds, const std::array& inventoryTypes) const; std::unordered_map playerModelCache_; struct PlayerTextureSlots { int skin = -1; int hair = -1; int underwear = -1; }; std::unordered_map playerTextureSlotsByModelId_; uint32_t nextPlayerModelId_ = 60000; // --- Player spawn queue --- struct PendingPlayerSpawn { uint64_t guid; uint8_t raceId; uint8_t genderId; uint32_t appearanceBytes; uint8_t facialFeatures; float x, y, z, orientation; }; std::deque pendingPlayerSpawns_; std::unordered_set pendingPlayerSpawnGuids_; void processPlayerSpawnQueue(); // --- GameObject instances --- struct GameObjectInstanceInfo { uint32_t modelId = 0; uint32_t instanceId = 0; bool isWmo = false; }; std::unordered_map gameObjectDisplayIdModelCache_; std::unordered_set gameObjectDisplayIdFailedCache_; std::unordered_map gameObjectDisplayIdWmoCache_; std::unordered_map gameObjectInstances_; struct PendingTransportMove { float x = 0.0f, y = 0.0f, z = 0.0f, orientation = 0.0f; }; struct PendingTransportRegistration { uint64_t guid = 0; uint32_t entry = 0; uint32_t displayId = 0; float x = 0.0f, y = 0.0f, z = 0.0f, orientation = 0.0f; }; std::unordered_map pendingTransportMoves_; std::deque pendingTransportRegistrations_; uint32_t nextGameObjectModelId_ = 20000; uint32_t nextGameObjectWmoModelId_ = 40000; bool testTransportSetup_ = false; // --- GameObject spawn queue --- struct PendingGameObjectSpawn { uint64_t guid; uint32_t entry; uint32_t displayId; float x, y, z, orientation; float scale = 1.0f; }; std::deque pendingGameObjectSpawns_; void processGameObjectSpawnQueue(); // --- Async WMO loading for game objects --- struct PreparedGameObjectWMO { uint64_t guid; uint32_t entry; uint32_t displayId; float x, y, z, orientation; float scale = 1.0f; std::shared_ptr wmoModel; std::unordered_map predecodedTextures; bool valid = false; bool isWmo = false; std::string modelPath; }; struct AsyncGameObjectLoad { std::future future; }; std::vector asyncGameObjectLoads_; void processAsyncGameObjectResults(); struct PendingTransportDoodadBatch { uint64_t guid = 0; uint32_t modelId = 0; uint32_t instanceId = 0; size_t nextIndex = 0; size_t doodadBudget = 0; size_t spawnedDoodads = 0; float x = 0.0f, y = 0.0f, z = 0.0f, orientation = 0.0f; }; std::vector pendingTransportDoodadBatches_; static constexpr size_t MAX_TRANSPORT_DOODADS_PER_FRAME = 4; void processPendingTransportRegistrations(); void processPendingTransportDoodads(); // --- Mount --- uint32_t mountInstanceId_ = 0; uint32_t mountModelId_ = 0; uint32_t pendingMountDisplayId_ = 0; void processPendingMount(); // --- Local player GUID exclusion --- uint64_t spawnedPlayerGuid_ = 0; // --- Weapon model ID counter --- uint32_t nextWeaponModelId_ = 1000; // --- Spawn internal methods --- void spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation, float scale = 1.0f); void spawnOnlinePlayer(uint64_t guid, uint8_t raceId, uint8_t genderId, uint32_t appearanceBytes, uint8_t facialFeatures, float x, float y, float z, float orientation); void setOnlinePlayerEquipment(uint64_t guid, const std::array& displayInfoIds, const std::array& inventoryTypes); void spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation, float scale = 1.0f); }; } // namespace core } // namespace wowee