#pragma once #include "game/world_packets.hpp" #include "game/opcode_table.hpp" #include "game/spell_defines.hpp" #include "game/handler_types.hpp" #include "network/packet.hpp" #include #include #include #include #include #include #include #include namespace wowee { namespace game { class GameHandler; class SpellHandler { public: using PacketHandler = std::function; using DispatchTable = std::unordered_map; explicit SpellHandler(GameHandler& owner); void registerOpcodes(DispatchTable& table); // Talent data structures (aliased from handler_types.hpp) using TalentEntry = game::TalentEntry; using TalentTabEntry = game::TalentTabEntry; // --- Spell book tabs --- struct SpellBookTab { std::string name; std::string texture; // icon path std::vector spellIds; // spells in this tab }; // Unit cast state (aliased from handler_types.hpp) using UnitCastState = game::UnitCastState; // Equipment set info (aliased from handler_types.hpp) using EquipmentSetInfo = game::EquipmentSetInfo; // --- Public API (delegated from GameHandler) --- void castSpell(uint32_t spellId, uint64_t targetGuid = 0); void cancelCast(); void cancelAura(uint32_t spellId); // Known spells const std::unordered_set& getKnownSpells() const { return knownSpells_; } const std::unordered_map& getSpellCooldowns() const { return spellCooldowns_; } float getSpellCooldown(uint32_t spellId) const; // Cast state bool isCasting() const { return casting_; } bool isChanneling() const { return casting_ && castIsChannel_; } bool isGameObjectInteractionCasting() const; uint32_t getCurrentCastSpellId() const { return currentCastSpellId_; } float getCastProgress() const { return castTimeTotal_ > 0 ? (castTimeTotal_ - castTimeRemaining_) / castTimeTotal_ : 0.0f; } float getCastTimeRemaining() const { return castTimeRemaining_; } float getCastTimeTotal() const { return castTimeTotal_; } // Repeat-craft queue void startCraftQueue(uint32_t spellId, int count); void cancelCraftQueue(); int getCraftQueueRemaining() const { return craftQueueRemaining_; } uint32_t getCraftQueueSpellId() const { return craftQueueSpellId_; } // Spell queue (400ms window) uint32_t getQueuedSpellId() const { return queuedSpellId_; } void cancelQueuedSpell() { queuedSpellId_ = 0; queuedSpellTarget_ = 0; } // Unit cast state (tracked per GUID for target frame + boss frames) const UnitCastState* getUnitCastState(uint64_t guid) const { auto it = unitCastStates_.find(guid); return (it != unitCastStates_.end() && it->second.casting) ? &it->second : nullptr; } void clearUnitCastStates() { unitCastStates_.clear(); } // Target cast helpers bool isTargetCasting() const; uint32_t getTargetCastSpellId() const; float getTargetCastProgress() const; float getTargetCastTimeRemaining() const; bool isTargetCastInterruptible() const; // Talents uint8_t getActiveTalentSpec() const { return activeTalentSpec_; } uint8_t getUnspentTalentPoints() const { return unspentTalentPoints_[activeTalentSpec_]; } uint8_t getUnspentTalentPoints(uint8_t spec) const { return spec < 2 ? unspentTalentPoints_[spec] : 0; } const std::unordered_map& getLearnedTalents() const { return learnedTalents_[activeTalentSpec_]; } const std::unordered_map& getLearnedTalents(uint8_t spec) const { static std::unordered_map empty; return spec < 2 ? learnedTalents_[spec] : empty; } static constexpr uint8_t MAX_GLYPH_SLOTS = 6; const std::array& getGlyphs() const { return learnedGlyphs_[activeTalentSpec_]; } const std::array& getGlyphs(uint8_t spec) const { static std::array empty{}; return spec < 2 ? learnedGlyphs_[spec] : empty; } uint8_t getTalentRank(uint32_t talentId) const { auto it = learnedTalents_[activeTalentSpec_].find(talentId); return (it != learnedTalents_[activeTalentSpec_].end()) ? it->second : 0; } void learnTalent(uint32_t talentId, uint32_t requestedRank); void switchTalentSpec(uint8_t newSpec); // Talent DBC access const TalentEntry* getTalentEntry(uint32_t talentId) const { auto it = talentCache_.find(talentId); return (it != talentCache_.end()) ? &it->second : nullptr; } const TalentTabEntry* getTalentTabEntry(uint32_t tabId) const { auto it = talentTabCache_.find(tabId); return (it != talentTabCache_.end()) ? &it->second : nullptr; } const std::unordered_map& getAllTalents() const { return talentCache_; } const std::unordered_map& getAllTalentTabs() const { return talentTabCache_; } void loadTalentDbc(); // Auras const std::vector& getPlayerAuras() const { return playerAuras_; } const std::vector& getTargetAuras() const { return targetAuras_; } const std::vector* getUnitAuras(uint64_t guid) const { auto it = unitAurasCache_.find(guid); return (it != unitAurasCache_.end()) ? &it->second : nullptr; } // Global Cooldown (GCD) float getGCDRemaining() const { if (gcdTotal_ <= 0.0f) return 0.0f; auto elapsed = std::chrono::duration_cast( std::chrono::steady_clock::now() - gcdStartedAt_).count() / 1000.0f; float rem = gcdTotal_ - elapsed; return rem > 0.0f ? rem : 0.0f; } float getGCDTotal() const { return gcdTotal_; } bool isGCDActive() const { return getGCDRemaining() > 0.0f; } // Spell book tabs const std::vector& getSpellBookTabs(); // Talent wipe confirm dialog bool showTalentWipeConfirmDialog() const { return talentWipePending_; } uint32_t getTalentWipeCost() const { return talentWipeCost_; } void confirmTalentWipe(); void cancelTalentWipe() { talentWipePending_ = false; } // Pet talent respec confirm bool showPetUnlearnDialog() const { return petUnlearnPending_; } uint32_t getPetUnlearnCost() const { return petUnlearnCost_; } void confirmPetUnlearn(); void cancelPetUnlearn() { petUnlearnPending_ = false; } // Item use void useItemBySlot(int backpackIndex); void useItemInBag(int bagIndex, int slotIndex); void useItemById(uint32_t itemId); // Equipment sets — canonical data owned by InventoryHandler; // GameHandler::getEquipmentSets() delegates to inventoryHandler_. // Pet spells void sendPetAction(uint32_t action, uint64_t targetGuid = 0); void dismissPet(); void togglePetSpellAutocast(uint32_t spellId); void renamePet(const std::string& newName); // Spell DBC accessors const int32_t* getSpellEffectBasePoints(uint32_t spellId) const; float getSpellDuration(uint32_t spellId) const; const std::string& getSpellName(uint32_t spellId) const; const std::string& getSpellRank(uint32_t spellId) const; const std::string& getSpellDescription(uint32_t spellId) const; std::string getEnchantName(uint32_t enchantId) const; uint8_t getSpellDispelType(uint32_t spellId) const; bool isSpellInterruptible(uint32_t spellId) const; uint32_t getSpellSchoolMask(uint32_t spellId) const; const std::string& getSkillLineName(uint32_t spellId) const; // Cast state void stopCasting(); void resetCastState(); void resetTalentState(); // Full per-character reset (spells, cooldowns, auras, cast state, talents). // Called from GameHandler::selectCharacter so spell state doesn't bleed between characters. void resetAllState(); void clearUnitCaches(); // Aura duration void handleUpdateAuraDuration(uint8_t slot, uint32_t durationMs); // Skill DBC void loadSkillLineDbc(); void extractSkillFields(const std::map& fields); void extractExploredZoneFields(const std::map& fields); // Update per-frame timers (call from GameHandler::update) void updateTimers(float dt); private: // --- Packet handlers --- void handleInitialSpells(network::Packet& packet); void handleCastFailed(network::Packet& packet); void handleSpellStart(network::Packet& packet); void handleSpellGo(network::Packet& packet); void handleSpellCooldown(network::Packet& packet); void handleCooldownEvent(network::Packet& packet); void handleAuraUpdate(network::Packet& packet, bool isAll); void handleLearnedSpell(network::Packet& packet); void handlePetSpells(network::Packet& packet); void handleListStabledPets(network::Packet& packet); // Pet stable void requestStabledPetList(); void stablePet(uint8_t slot); void unstablePet(uint32_t petNumber); void handleCastResult(network::Packet& packet); void handleSpellFailedOther(network::Packet& packet); void handleClearCooldown(network::Packet& packet); void handleModifyCooldown(network::Packet& packet); void handlePlaySpellVisual(network::Packet& packet); void handleSpellModifier(network::Packet& packet, bool isFlat); void handleSpellDelayed(network::Packet& packet); void handleSpellLogMiss(network::Packet& packet); void handleSpellFailure(network::Packet& packet); void handleItemCooldown(network::Packet& packet); void handleDispelFailed(network::Packet& packet); void handleTotemCreated(network::Packet& packet); void handlePeriodicAuraLog(network::Packet& packet); void handleSpellEnergizeLog(network::Packet& packet); void handleExtraAuraInfo(network::Packet& packet, bool isInit); void handleSpellDispelLog(network::Packet& packet); void handleSpellStealLog(network::Packet& packet); void handleSpellChanceProcLog(network::Packet& packet); void handleSpellInstaKillLog(network::Packet& packet); void handleSpellLogExecute(network::Packet& packet); void handleClearExtraAuraInfo(network::Packet& packet); void handleItemEnchantTimeUpdate(network::Packet& packet); void handleResumeCastBar(network::Packet& packet); void handleChannelStart(network::Packet& packet); void handleChannelUpdate(network::Packet& packet); // --- Internal helpers --- // Find the on-use spell for an item (trigger=0 Use or trigger=5 NoDelay). // CMSG_USE_ITEM requires a valid spellId or the server silently ignores it. uint32_t findOnUseSpellId(uint32_t itemId) const; void loadSpellNameCache() const; void loadSkillLineAbilityDbc(); void categorizeTrainerSpells(); void handleSupercededSpell(network::Packet& packet); void handleRemovedSpell(network::Packet& packet); void handleUnlearnSpells(network::Packet& packet); void handleTalentsInfo(network::Packet& packet); void handleAchievementEarned(network::Packet& packet); friend class GameHandler; friend class InventoryHandler; friend class CombatHandler; friend class EntityController; GameHandler& owner_; // --- Spell state --- std::unordered_set knownSpells_; std::unordered_map spellCooldowns_; // spellId -> remaining seconds uint8_t castCount_ = 0; bool casting_ = false; bool castIsChannel_ = false; uint32_t currentCastSpellId_ = 0; float castTimeRemaining_ = 0.0f; float castTimeTotal_ = 0.0f; // Repeat-craft queue uint32_t craftQueueSpellId_ = 0; int craftQueueRemaining_ = 0; // Spell queue (400ms window) uint32_t queuedSpellId_ = 0; uint64_t queuedSpellTarget_ = 0; // Per-unit cast state std::unordered_map unitCastStates_; // Talents (dual-spec support) uint8_t activeTalentSpec_ = 0; uint8_t unspentTalentPoints_[2] = {0, 0}; std::unordered_map learnedTalents_[2]; std::array, 2> learnedGlyphs_{}; std::unordered_map talentCache_; std::unordered_map talentTabCache_; bool talentDbcLoaded_ = false; bool talentsInitialized_ = false; // Auras std::vector playerAuras_; std::vector targetAuras_; std::unordered_map> unitAurasCache_; // Global Cooldown float gcdTotal_ = 0.0f; std::chrono::steady_clock::time_point gcdStartedAt_{}; // Spell book tabs std::vector spellBookTabs_; size_t lastSpellCount_ = 0; bool spellBookTabsDirty_ = true; // Talent wipe confirm dialog bool talentWipePending_ = false; uint64_t talentWipeNpcGuid_ = 0; uint32_t talentWipeCost_ = 0; // Pet talent respec confirm dialog bool petUnlearnPending_ = false; uint64_t petUnlearnGuid_ = 0; uint32_t petUnlearnCost_ = 0; }; } // namespace game } // namespace wowee