diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 044c12c9..9e6ada12 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1,5 +1,6 @@ #pragma once +#include "game/game_interfaces.hpp" #include "game/world_packets.hpp" #include "game/character.hpp" #include "game/opcode_table.hpp" @@ -125,7 +126,11 @@ using WorldConnectFailureCallback = std::function +#include +#include +#include +#include +#include + +namespace wowee::network { class WorldSocket; } + +namespace wowee::game { + +// Forward declarations +class Entity; +class EntityManager; +enum class WorldState; +struct ContactEntry; +struct BgQueueSlot; +struct AvailableBgInfo; +struct BgScoreboardData; +struct BgPlayerPosition; +struct ArenaTeamStats; +struct ArenaTeamRoster; +struct GuildRosterData; +struct GuildInfoData; +struct GuildQueryResponseData; +struct CreatureQueryResponseData; +struct GameObjectQueryResponseData; + +// --------------------------------------------------------------------------- +// IConnectionState — server connection and authentication state +// --------------------------------------------------------------------------- +class IConnectionState { +public: + virtual ~IConnectionState() = default; + + virtual bool isConnected() const = 0; + virtual bool isInWorld() const = 0; + virtual WorldState getState() const = 0; + virtual network::WorldSocket* getSocket() = 0; + virtual const std::vector& getSessionKey() const = 0; +}; + +// --------------------------------------------------------------------------- +// ITargetingState — target, focus, and mouseover management +// --------------------------------------------------------------------------- +class ITargetingState { +public: + virtual ~ITargetingState() = default; + + virtual void setTarget(uint64_t guid) = 0; + virtual void clearTarget() = 0; + virtual uint64_t getTargetGuid() const = 0; + virtual std::shared_ptr getTarget() const = 0; + virtual bool hasTarget() const = 0; + + virtual void setFocus(uint64_t guid) = 0; + virtual void clearFocus() = 0; + virtual uint64_t getFocusGuid() const = 0; + virtual bool hasFocus() const = 0; + + virtual void setMouseoverGuid(uint64_t guid) = 0; + virtual uint64_t getMouseoverGuid() const = 0; +}; + +// --------------------------------------------------------------------------- +// IEntityAccess — entity queries and name/info caching +// --------------------------------------------------------------------------- +class IEntityAccess { +public: + virtual ~IEntityAccess() = default; + + virtual EntityManager& getEntityManager() = 0; + virtual const EntityManager& getEntityManager() const = 0; + + virtual void queryPlayerName(uint64_t guid) = 0; + virtual void queryCreatureInfo(uint32_t entry, uint64_t guid) = 0; + virtual std::string getCachedPlayerName(uint64_t guid) const = 0; + virtual std::string getCachedCreatureName(uint32_t entry) const = 0; + virtual const std::unordered_map& getPlayerNameCache() const = 0; + virtual const std::unordered_map& getCreatureInfoCache() const = 0; + virtual const GameObjectQueryResponseData* getCachedGameObjectInfo(uint32_t entry) const = 0; +}; + +// --------------------------------------------------------------------------- +// ISocialState — friends, ignore list, contacts, guild info +// --------------------------------------------------------------------------- +class ISocialState { +public: + virtual ~ISocialState() = default; + + virtual void addFriend(const std::string& playerName, const std::string& note = "") = 0; + virtual void removeFriend(const std::string& playerName) = 0; + virtual void addIgnore(const std::string& playerName) = 0; + virtual void removeIgnore(const std::string& playerName) = 0; + virtual const std::unordered_map& getIgnoreCache() const = 0; + virtual const std::vector& getContacts() const = 0; + + virtual bool isInGuild() const = 0; + virtual const std::string& getGuildName() const = 0; + virtual const GuildRosterData& getGuildRoster() const = 0; + virtual bool hasGuildRoster() const = 0; +}; + +// --------------------------------------------------------------------------- +// IPvpState — battleground queues, arena teams, scoreboard +// --------------------------------------------------------------------------- +class IPvpState { +public: + virtual ~IPvpState() = default; + + virtual bool hasPendingBgInvite() const = 0; + virtual void acceptBattlefield(uint32_t queueSlot = 0xFFFFFFFF) = 0; + virtual void declineBattlefield(uint32_t queueSlot = 0xFFFFFFFF) = 0; + virtual const std::array& getBgQueues() const = 0; + virtual const std::vector& getAvailableBgs() const = 0; + virtual const BgScoreboardData* getBgScoreboard() const = 0; + virtual const std::vector& getArenaTeamStats() const = 0; +}; + +} // namespace wowee::game diff --git a/include/game/movement_handler.hpp b/include/game/movement_handler.hpp index 3a4649c2..a66305f3 100644 --- a/include/game/movement_handler.hpp +++ b/include/game/movement_handler.hpp @@ -148,6 +148,11 @@ public: uint32_t& monsterMovePacketsThisTickRef() { return monsterMovePacketsThisTick_; } uint32_t& monsterMovePacketsDroppedThisTickRef() { return monsterMovePacketsDroppedThisTick_; } + // Movement clock / fall state setters (formerly accessed via friend) + void resetMovementClock() { movementClockStart_ = std::chrono::steady_clock::now(); lastMovementTimestampMs_ = 0; } + void setFalling(bool falling) { isFalling_ = falling; } + void setFallStartMs(uint32_t ms) { fallStartMs_ = ms; } + // Taxi state references for GameHandler update/processing bool& onTaxiFlightRef() { return onTaxiFlight_; } bool& taxiMountActiveRef() { return taxiMountActive_; } @@ -197,8 +202,6 @@ private: void buildTaxiCostMap(); void startClientTaxiPath(const std::vector& pathNodes); - friend class GameHandler; - GameHandler& owner_; // --- Movement state --- diff --git a/include/game/spell_handler.hpp b/include/game/spell_handler.hpp index e93f6281..fe6f223c 100644 --- a/include/game/spell_handler.hpp +++ b/include/game/spell_handler.hpp @@ -80,6 +80,23 @@ public: return (it != unitCastStates_.end() && it->second.casting) ? &it->second : nullptr; } void clearUnitCastStates() { unitCastStates_.clear(); } + void removeUnitCastState(uint64_t guid) { unitCastStates_.erase(guid); } + + // Aura cache mutation (formerly accessed via friend) + void clearUnitAurasCache() { unitAurasCache_.clear(); } + void removeUnitAuraCache(uint64_t guid) { unitAurasCache_.erase(guid); } + + // Known spells mutation (formerly accessed via friend) + void addKnownSpell(uint32_t spellId) { knownSpells_.insert(spellId); } + bool hasKnownSpell(uint32_t spellId) const { return knownSpells_.count(spellId) > 0; } + + // Target aura mutation (formerly accessed via friend) + void clearTargetAuras() { for (auto& slot : targetAuras_) slot = AuraSlot{}; } + + // Player aura mutation (formerly accessed via friend) + void resetPlayerAuras(size_t capacity) { playerAuras_.clear(); playerAuras_.resize(capacity); } + AuraSlot& getPlayerAuraSlotRef(size_t slot) { return playerAuras_[slot]; } + std::vector& getPlayerAurasMut() { return playerAuras_; } // Target cast helpers bool isTargetCasting() const; @@ -204,6 +221,20 @@ public: // Update per-frame timers (call from GameHandler::update) void updateTimers(float dt); + // Packet handlers dispatched from GameHandler's opcode table + void handlePetSpells(network::Packet& packet); + void handleListStabledPets(network::Packet& packet); + + // Pet stable commands (called via GameHandler delegation) + void requestStabledPetList(); + void stablePet(uint8_t slot); + void unstablePet(uint32_t petNumber); + + // DBC cache loading (called from GameHandler during login) + void loadSpellNameCache() const; + void loadSkillLineAbilityDbc(); + void categorizeTrainerSpells(); + private: // --- Packet handlers --- void handleInitialSpells(network::Packet& packet); @@ -214,13 +245,6 @@ private: 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); @@ -252,20 +276,12 @@ private: // 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 --- diff --git a/src/game/combat_handler.cpp b/src/game/combat_handler.cpp index 47d36a0b..6dc004f6 100644 --- a/src/game/combat_handler.cpp +++ b/src/game/combat_handler.cpp @@ -1065,7 +1065,7 @@ void CombatHandler::setTarget(uint64_t guid) { // Clear stale aura data from the previous target so the buff bar shows // an empty state until the server sends SMSG_AURA_UPDATE_ALL for the new target. - if (owner_.getSpellHandler()) for (auto& slot : owner_.getSpellHandler()->targetAuras_) slot = AuraSlot{}; + if (owner_.getSpellHandler()) owner_.getSpellHandler()->clearTargetAuras(); // Clear previous target's cast bar on target change // (the new target's cast state is naturally fetched from spellHandler_->unitCastStates_ by GUID) diff --git a/src/game/entity_controller.cpp b/src/game/entity_controller.cpp index ac67bef4..ce5ed2e3 100644 --- a/src/game/entity_controller.cpp +++ b/src/game/entity_controller.cpp @@ -449,15 +449,14 @@ void EntityController::syncClassicAurasFromFields(const std::shared_ptr& } if (!hasAuraField) return; - owner_.getSpellHandler()->playerAuras_.clear(); - owner_.getSpellHandler()->playerAuras_.resize(48); + owner_.getSpellHandler()->resetPlayerAuras(48); uint64_t nowMs = static_cast( std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count()); for (int slot = 0; slot < 48; ++slot) { auto it = allFields.find(static_cast(ufAuras + slot)); if (it != allFields.end() && it->second != 0) { - AuraSlot& a = owner_.getSpellHandler()->playerAuras_[slot]; + AuraSlot& a = owner_.getSpellHandler()->getPlayerAuraSlotRef(slot); a.spellId = it->second; // Read aura flag byte: packed 4-per-uint32 at ufAuraFlags uint8_t aFlag = 0; @@ -492,7 +491,7 @@ void EntityController::detectPlayerMountChange(uint32_t newMountDisplayId, if (old == 0 && newMountDisplayId != 0) { // Just mounted — find the mount aura (indefinite duration, self-cast) owner_.mountAuraSpellIdRef() = 0; - if (owner_.getSpellHandler()) for (const auto& a : owner_.getSpellHandler()->playerAuras_) { + if (owner_.getSpellHandler()) for (const auto& a : owner_.getSpellHandler()->getPlayerAuras()) { if (!a.isEmpty() && a.maxDurationMs < 0 && a.casterGuid == owner_.getPlayerGuid()) { owner_.mountAuraSpellIdRef() = a.spellId; } @@ -518,7 +517,7 @@ void EntityController::detectPlayerMountChange(uint32_t newMountDisplayId, uint32_t mountSpell = owner_.mountAuraSpellIdRef(); owner_.mountAuraSpellIdRef() = 0; if (mountSpell != 0 && owner_.getSpellHandler()) { - for (auto& a : owner_.getSpellHandler()->playerAuras_) { + for (auto& a : owner_.getSpellHandler()->getPlayerAurasMut()) { if (!a.isEmpty() && a.spellId == mountSpell) { a = AuraSlot{}; break; @@ -1950,9 +1949,9 @@ void EntityController::handleDestroyObject(network::Packet& packet) { if (owner_.getCombatHandler()) owner_.getCombatHandler()->removeCombatTextForGuid(data.guid); // Clean up unit cast owner_.getState() (cast bar) for the destroyed unit - if (owner_.getSpellHandler()) owner_.getSpellHandler()->unitCastStates_.erase(data.guid); + if (owner_.getSpellHandler()) owner_.getSpellHandler()->removeUnitCastState(data.guid); // Clean up cached auras - if (owner_.getSpellHandler()) owner_.getSpellHandler()->unitAurasCache_.erase(data.guid); + if (owner_.getSpellHandler()) owner_.getSpellHandler()->removeUnitAuraCache(data.guid); owner_.tabCycleStaleRef() = true; } diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 14d510ba..a693575f 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -272,8 +272,8 @@ void GameHandler::disconnect() { otherPlayerVisibleItemEntries_.clear(); otherPlayerVisibleDirty_.clear(); otherPlayerMoveTimeMs_.clear(); - if (spellHandler_) spellHandler_->unitCastStates_.clear(); - if (spellHandler_) spellHandler_->unitAurasCache_.clear(); + if (spellHandler_) spellHandler_->clearUnitCastStates(); + if (spellHandler_) spellHandler_->clearUnitAurasCache(); if (combatHandler_) combatHandler_->clearCombatText(); entityController_->clearAll(); setState(WorldState::DISCONNECTED); @@ -315,8 +315,8 @@ bool GameHandler::isConnected() const { void GameHandler::updateNetworking(float deltaTime) { // Reset per-tick monster-move budget tracking (Classic/Turtle flood protection). if (movementHandler_) { - movementHandler_->monsterMovePacketsThisTick_ = 0; - movementHandler_->monsterMovePacketsDroppedThisTick_ = 0; + movementHandler_->monsterMovePacketsThisTickRef() = 0; + movementHandler_->monsterMovePacketsDroppedThisTickRef() = 0; } // Update socket (processes incoming data and triggers callbacks) @@ -649,7 +649,7 @@ void GameHandler::updateTimers(float deltaTime) { if (isInWorld()) { // Avoid sending CMSG_LOOT while a timed cast is active (e.g. gathering). // handleSpellGo will trigger loot after the cast completes. - if (spellHandler_ && spellHandler_->casting_ && spellHandler_->currentCastSpellId_ != 0) { + if (spellHandler_ && spellHandler_->isCasting() && spellHandler_->getCurrentCastSpellId() != 0) { it->timer = 0.20f; ++it; continue; @@ -785,7 +785,7 @@ void GameHandler::update(float deltaTime) { // Send periodic heartbeat if in world if (state == WorldState::IN_WORLD) { timeSinceLastPing += deltaTime; - if (movementHandler_) movementHandler_->timeSinceLastMoveHeartbeat_ += deltaTime; + if (movementHandler_) movementHandler_->timeSinceLastMoveHeartbeatRef() += deltaTime; const float currentPingInterval = (isPreWotlk()) ? 10.0f : pingInterval; @@ -823,9 +823,9 @@ void GameHandler::update(float deltaTime) { : (classicLikeStationaryCombatSync ? 0.75f : (classicLikeCombatSync ? 0.20f : moveHeartbeatInterval_)); - if (movementHandler_ && movementHandler_->timeSinceLastMoveHeartbeat_ >= heartbeatInterval) { + if (movementHandler_ && movementHandler_->timeSinceLastMoveHeartbeatRef() >= heartbeatInterval) { sendMovement(Opcode::MSG_MOVE_HEARTBEAT); - movementHandler_->timeSinceLastMoveHeartbeat_ = 0.0f; + movementHandler_->timeSinceLastMoveHeartbeatRef() = 0.0f; } // Check area triggers (instance portals, tavern rests, etc.) @@ -845,7 +845,7 @@ void GameHandler::update(float deltaTime) { } // Check if client-side cast timer expired (tick-down is in SpellHandler::updateTimers). // Two paths depending on whether this is a GO interaction cast: - if (spellHandler_ && spellHandler_->casting_ && spellHandler_->castTimeRemaining_ <= 0.0f) { + if (spellHandler_ && spellHandler_->isCasting() && spellHandler_->getCastTimeRemaining() <= 0.0f) { if (pendingGameObjectInteractGuid_ != 0) { // GO interaction cast: do NOT call resetCastState() here. The server // sends SMSG_SPELL_GO when the cast completes server-side (~50-200ms diff --git a/src/game/game_handler_callbacks.cpp b/src/game/game_handler_callbacks.cpp index bd97e42a..67f2cc44 100644 --- a/src/game/game_handler_callbacks.cpp +++ b/src/game/game_handler_callbacks.cpp @@ -573,13 +573,12 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) { movementInfo.flags = 0; movementInfo.flags2 = 0; if (movementHandler_) { - movementHandler_->movementClockStart_ = std::chrono::steady_clock::now(); - movementHandler_->lastMovementTimestampMs_ = 0; + movementHandler_->resetMovementClock(); } movementInfo.time = nextMovementTimestampMs(); if (movementHandler_) { - movementHandler_->isFalling_ = false; - movementHandler_->fallStartMs_ = 0; + movementHandler_->setFalling(false); + movementHandler_->setFallStartMs(0); } movementInfo.fallTime = 0; movementInfo.jumpVelocity = 0.0f; @@ -1945,8 +1944,8 @@ void GameHandler::interactWithGameObject(uint64_t guid) { if (guid == 0) { LOG_WARNING("[GO-DIAG] BLOCKED: guid==0"); return; } if (!isInWorld()) { LOG_WARNING("[GO-DIAG] BLOCKED: not in world"); return; } // Do not overlap an actual spell cast. - if (spellHandler_ && spellHandler_->casting_ && spellHandler_->currentCastSpellId_ != 0) { - LOG_WARNING("[GO-DIAG] BLOCKED: already casting spellId=", spellHandler_->currentCastSpellId_); + if (spellHandler_ && spellHandler_->isCasting() && spellHandler_->getCurrentCastSpellId() != 0) { + LOG_WARNING("[GO-DIAG] BLOCKED: already casting spellId=", spellHandler_->getCurrentCastSpellId()); return; } // Always clear melee intent before GO interactions. diff --git a/src/game/game_handler_packets.cpp b/src/game/game_handler_packets.cpp index 16132bfd..f2b44533 100644 --- a/src/game/game_handler_packets.cpp +++ b/src/game/game_handler_packets.cpp @@ -1019,10 +1019,11 @@ void GameHandler::registerOpcodeHandlers() { // SMSG_SPELL_COOLDOWN often arrives before SMSG_ACTION_BUTTONS during login, // so the per-slot cooldownRemaining would be 0 without this sync. if (spellHandler_) { + const auto& cooldowns = spellHandler_->getSpellCooldowns(); for (auto& slot : actionBar) { if (slot.type == ActionBarSlot::SPELL && slot.id != 0) { - auto cdIt = spellHandler_->spellCooldowns_.find(slot.id); - if (cdIt != spellHandler_->spellCooldowns_.end() && cdIt->second > 0.0f) { + auto cdIt = cooldowns.find(slot.id); + if (cdIt != cooldowns.end() && cdIt->second > 0.0f) { slot.cooldownRemaining = cdIt->second; slot.cooldownTotal = cdIt->second; } @@ -1033,8 +1034,8 @@ void GameHandler::registerOpcodeHandlers() { if (qi && qi->valid) { for (const auto& sp : qi->spells) { if (sp.spellId == 0) continue; - auto cdIt = spellHandler_->spellCooldowns_.find(sp.spellId); - if (cdIt != spellHandler_->spellCooldowns_.end() && cdIt->second > 0.0f) { + auto cdIt = cooldowns.find(sp.spellId); + if (cdIt != cooldowns.end() && cdIt->second > 0.0f) { slot.cooldownRemaining = cdIt->second; slot.cooldownTotal = cdIt->second; break; diff --git a/src/game/inventory_handler.cpp b/src/game/inventory_handler.cpp index 53fb6a51..9526ca79 100644 --- a/src/game/inventory_handler.cpp +++ b/src/game/inventory_handler.cpp @@ -3219,8 +3219,8 @@ void InventoryHandler::emitAllOtherPlayerEquipment() { void InventoryHandler::handleTrainerBuySucceeded(network::Packet& packet) { /*uint64_t guid =*/ packet.readUInt64(); uint32_t spellId = packet.readUInt32(); - if (owner_.getSpellHandler() && !owner_.getSpellHandler()->knownSpells_.count(spellId)) { - owner_.getSpellHandler()->knownSpells_.insert(spellId); + if (owner_.getSpellHandler() && !owner_.getSpellHandler()->hasKnownSpell(spellId)) { + owner_.getSpellHandler()->addKnownSpell(spellId); } const std::string& name = owner_.getSpellName(spellId); if (!name.empty())