mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-03 20:03:50 +00:00
- Restore 0x02→0x80 Classic harmful-to-WotLK debuff bit mapping in syncClassicAurasFromFields so downstream checks work across expansions - Extract handleDisplayIdChange helper to deduplicate identical logic in onValuesUpdateUnit and onValuesUpdatePlayer - Remove unused newItemCreated parameter from handleValuesUpdate - Fix indentation on PLAYER_DEAD/PLAYER_ALIVE/PLAYER_UNGHOST emit calls
279 lines
13 KiB
C++
279 lines
13 KiB
C++
#pragma once
|
|
|
|
#include "game/world_packets.hpp"
|
|
#include "game/entity.hpp"
|
|
#include "game/opcode_table.hpp"
|
|
#include "network/packet.hpp"
|
|
#include <chrono>
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <glm/vec3.hpp>
|
|
|
|
namespace wowee {
|
|
namespace game {
|
|
|
|
class GameHandler;
|
|
|
|
class EntityController {
|
|
public:
|
|
using PacketHandler = std::function<void(network::Packet&)>;
|
|
using DispatchTable = std::unordered_map<LogicalOpcode, PacketHandler>;
|
|
|
|
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<uint64_t, std::string>& getPlayerNameCache() const { return playerNameCache; }
|
|
const std::unordered_map<uint32_t, CreatureQueryResponseData>& 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<int>(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<const Unit*>(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<uint64_t, std::string> 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<uint64_t, PlayerClassRace> playerClassRaceCache_;
|
|
std::unordered_set<uint64_t> pendingNameQueries;
|
|
std::unordered_map<uint32_t, CreatureQueryResponseData> creatureInfoCache;
|
|
std::unordered_set<uint32_t> pendingCreatureQueries;
|
|
std::unordered_map<uint32_t, GameObjectQueryResponseData> gameObjectInfoCache_;
|
|
std::unordered_set<uint32_t> pendingGameObjectQueries_;
|
|
|
|
// --- Update Object work queue ---
|
|
struct PendingUpdateObjectWork {
|
|
UpdateObjectData data;
|
|
size_t nextBlockIndex = 0;
|
|
bool outOfRangeProcessed = false;
|
|
bool newItemCreated = false;
|
|
};
|
|
std::deque<PendingUpdateObjectWork> pendingUpdateObjectWork_;
|
|
|
|
// --- Transport GUID tracking ---
|
|
std::unordered_set<uint64_t> transportGuids_; // GUIDs of known transport GameObjects
|
|
std::unordered_set<uint64_t> 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<uint64_t>& guids);
|
|
void applyUpdateObjectBlock(const UpdateBlock& block, bool& newItemCreated);
|
|
void finalizeUpdateObjectBatch(bool newItemCreated);
|
|
|
|
// --- Phase 1: Extracted helper methods ---
|
|
bool extractPlayerAppearance(const std::map<uint16_t, uint32_t>& fields,
|
|
uint8_t& outRace, uint8_t& outGender,
|
|
uint32_t& outAppearanceBytes, uint8_t& outFacial) const;
|
|
void maybeDetectCoinageIndex(const std::map<uint16_t, uint32_t>& oldFields,
|
|
const std::map<uint16_t, uint32_t>& newFields);
|
|
|
|
// --- Phase 2: Update type handlers ---
|
|
void handleCreateObject(const UpdateBlock& block, bool& newItemCreated);
|
|
void handleValuesUpdate(const UpdateBlock& block);
|
|
void handleMovementUpdate(const UpdateBlock& block);
|
|
|
|
// --- Phase 3: Concern-specific helpers ---
|
|
// 3i: 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>& entity,
|
|
ObjectType entityType);
|
|
// 3f: Rebuild playerAuras_ from UNIT_FIELD_AURAS (Classic/vanilla only).
|
|
// Consolidates identical logic from CREATE and VALUES handlers.
|
|
void syncClassicAurasFromFields(const std::shared_ptr<Entity>& entity);
|
|
// 3h: 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<uint16_t, uint32_t>& blockFields);
|
|
|
|
// --- Phase 4: Field index cache structs ---
|
|
// 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;
|
|
};
|
|
|
|
// --- Phase 3: Extracted concern-specific helpers (continued) ---
|
|
// 3a: Entity factory — creates the correct Entity subclass for the given block.
|
|
std::shared_ptr<Entity> createEntityFromBlock(const UpdateBlock& block);
|
|
// 3b: Track player-on-transport state from movement blocks.
|
|
void applyPlayerTransportState(const UpdateBlock& block,
|
|
const std::shared_ptr<Entity>& entity,
|
|
const glm::vec3& canonicalPos, float oCanonical,
|
|
bool updateMovementInfoPos);
|
|
// 3c: Apply unit fields during CREATE — returns true if entity is initially dead.
|
|
bool applyUnitFieldsOnCreate(const UpdateBlock& block,
|
|
std::shared_ptr<Unit>& unit,
|
|
const UnitFieldIndices& ufi);
|
|
// 3c: Apply unit fields during VALUES — returns change tracking result.
|
|
UnitFieldUpdateResult applyUnitFieldsOnUpdate(const UpdateBlock& block,
|
|
const std::shared_ptr<Entity>& entity,
|
|
std::shared_ptr<Unit>& unit,
|
|
const UnitFieldIndices& ufi);
|
|
// 3d: Apply player stat fields (XP, inventory, skills, etc.). isCreate=true for CREATE path.
|
|
bool applyPlayerStatFields(const std::map<uint16_t, uint32_t>& fields,
|
|
const PlayerFieldIndices& pfi, bool isCreate);
|
|
// 3e: Dispatch spawn callbacks (creature/player) — deduplicates CREATE and VALUES paths.
|
|
void dispatchEntitySpawn(uint64_t guid, ObjectType objectType,
|
|
const std::shared_ptr<Entity>& entity,
|
|
const std::shared_ptr<Unit>& unit, bool isDead);
|
|
// 3g: Track item/container on CREATE.
|
|
void trackItemOnCreate(const UpdateBlock& block, bool& newItemCreated);
|
|
// 3g: Update item fields on VALUES update.
|
|
void updateItemOnValuesUpdate(const UpdateBlock& block,
|
|
const std::shared_ptr<Entity>& entity);
|
|
|
|
// --- Phase 5: Strategy pattern — object-type handler interface ---
|
|
// Allows extending object-type handling without modifying handler dispatch.
|
|
struct IObjectTypeHandler {
|
|
virtual ~IObjectTypeHandler() = default;
|
|
virtual void onCreate(const UpdateBlock& block, std::shared_ptr<Entity>& entity,
|
|
bool& newItemCreated) {}
|
|
virtual void onValuesUpdate(const UpdateBlock& block, std::shared_ptr<Entity>& entity) {}
|
|
virtual void onMovementUpdate(const UpdateBlock& block, std::shared_ptr<Entity>& entity) {}
|
|
};
|
|
struct UnitTypeHandler;
|
|
struct PlayerTypeHandler;
|
|
struct GameObjectTypeHandler;
|
|
struct ItemTypeHandler;
|
|
struct CorpseTypeHandler;
|
|
std::unordered_map<uint8_t, std::unique_ptr<IObjectTypeHandler>> typeHandlers_;
|
|
void initTypeHandlers();
|
|
IObjectTypeHandler* getTypeHandler(ObjectType type) const;
|
|
|
|
// --- Phase 5: Type-specific handler implementations (trampolined from handlers) ---
|
|
void onCreateUnit(const UpdateBlock& block, std::shared_ptr<Entity>& entity);
|
|
void onCreatePlayer(const UpdateBlock& block, std::shared_ptr<Entity>& entity);
|
|
void onCreateGameObject(const UpdateBlock& block, std::shared_ptr<Entity>& entity);
|
|
void onCreateItem(const UpdateBlock& block, bool& newItemCreated);
|
|
void onCreateCorpse(const UpdateBlock& block);
|
|
void handleDisplayIdChange(const UpdateBlock& block,
|
|
const std::shared_ptr<Entity>& entity,
|
|
const std::shared_ptr<Unit>& unit,
|
|
const UnitFieldUpdateResult& result);
|
|
void onValuesUpdateUnit(const UpdateBlock& block, std::shared_ptr<Entity>& entity);
|
|
void onValuesUpdatePlayer(const UpdateBlock& block, std::shared_ptr<Entity>& entity);
|
|
void onValuesUpdateItem(const UpdateBlock& block, std::shared_ptr<Entity>& entity);
|
|
void onValuesUpdateGameObject(const UpdateBlock& block, std::shared_ptr<Entity>& entity);
|
|
|
|
// --- Phase 6: Deferred event bus ---
|
|
// Collects addon events during block processing, flushes at the end.
|
|
struct PendingEvents {
|
|
std::vector<std::pair<std::string, std::vector<std::string>>> events;
|
|
void emit(const std::string& name, const std::vector<std::string>& args = {}) {
|
|
events.emplace_back(name, args);
|
|
}
|
|
void clear() { events.clear(); }
|
|
};
|
|
PendingEvents pendingEvents_;
|
|
void flushPendingEvents();
|
|
};
|
|
|
|
} // namespace game
|
|
} // namespace wowee
|