chore(refactor): god-object decomposition and mega-file splits

Split all mega-files by single-responsibility concern and
partially extracting AudioCoordinator and
OverlaySystem from the Renderer facade. No behavioral changes.

Splits:
- game_handler.cpp (5,247 LOC) → core + callbacks + packets (3 files)
- world_packets.cpp (4,453 LOC) → economy/entity/social/world (4 files)
- game_screen.cpp  (5,786 LOC) → core + frames + hud + minimap (4 files)
- m2_renderer.cpp  (3,343 LOC) → core + instance + particles + render (4 files)
- chat_panel.cpp   (3,140 LOC) → core + commands + utils (3 files)
- entity_spawner.cpp (2,750 LOC) → core + player + processing (3 files)

Extractions:
- AudioCoordinator: include/audio/ + src/audio/ (owned by Renderer)
- OverlaySystem: include/rendering/ + src/rendering/overlay_system.*

CMakeLists.txt: registered all 17 new translation units.
Related handler/callback files: minor include fixups post-split.
This commit is contained in:
Paul 2026-04-05 19:30:44 +03:00
parent 6dcc06697b
commit 34c0e3ca28
49 changed files with 29113 additions and 28109 deletions

View file

@ -31,7 +31,7 @@ void CombatHandler::registerOpcodes(DispatchTable& table) {
};
table[Opcode::SMSG_THREAT_CLEAR] = [this](network::Packet& /*packet*/) {
threatLists_.clear();
if (owner_.addonEventCallback_) owner_.addonEventCallback_("UNIT_THREAT_LIST_UPDATE", {});
if (owner_.addonEventCallbackRef()) owner_.addonEventCallbackRef()("UNIT_THREAT_LIST_UPDATE", {});
};
table[Opcode::SMSG_THREAT_REMOVE] = [this](network::Packet& packet) {
if (!packet.hasRemaining(1)) return;
@ -67,10 +67,10 @@ void CombatHandler::registerOpcodes(DispatchTable& table) {
if (autoAttackRequested_ && autoAttackTarget_ != 0) {
auto targetEntity = owner_.getEntityManager().getEntity(autoAttackTarget_);
if (targetEntity) {
float toTargetX = targetEntity->getX() - owner_.movementInfo.x;
float toTargetY = targetEntity->getY() - owner_.movementInfo.y;
float toTargetX = targetEntity->getX() - owner_.movementInfoRef().x;
float toTargetY = targetEntity->getY() - owner_.movementInfoRef().y;
if (std::abs(toTargetX) > 0.01f || std::abs(toTargetY) > 0.01f) {
owner_.movementInfo.orientation = std::atan2(-toTargetY, toTargetX);
owner_.movementInfoRef().orientation = std::atan2(-toTargetY, toTargetX);
owner_.sendMovement(Opcode::MSG_MOVE_SET_FACING);
}
}
@ -96,10 +96,10 @@ void CombatHandler::registerOpcodes(DispatchTable& table) {
if (!packet.hasRemaining(12)) return;
uint64_t guid = packet.readUInt64();
uint32_t reaction = packet.readUInt32();
if (reaction == 2 && owner_.npcAggroCallback_) {
if (reaction == 2 && owner_.npcAggroCallbackRef()) {
auto entity = owner_.getEntityManager().getEntity(guid);
if (entity)
owner_.npcAggroCallback_(guid, glm::vec3(entity->getX(), entity->getY(), entity->getZ()));
owner_.npcAggroCallbackRef()(guid, glm::vec3(entity->getX(), entity->getY(), entity->getZ()));
}
};
table[Opcode::SMSG_SPELLNONMELEEDAMAGELOG] = [this](network::Packet& packet) { handleSpellDamageLog(packet); };
@ -115,7 +115,7 @@ void CombatHandler::registerOpcodes(DispatchTable& table) {
uint32_t dmg = packet.readUInt32();
uint32_t envAbs = packet.readUInt32();
uint32_t envRes = packet.readUInt32();
if (victimGuid == owner_.playerGuid) {
if (victimGuid == owner_.getPlayerGuid()) {
// Environmental damage: pass envType via powerType field for display differentiation
if (dmg > 0)
addCombatText(CombatTextEntry::ENVIRONMENTAL, static_cast<int32_t>(dmg), 0, false, envType, 0, victimGuid);
@ -124,8 +124,8 @@ void CombatHandler::registerOpcodes(DispatchTable& table) {
if (envRes > 0)
addCombatText(CombatTextEntry::RESIST, static_cast<int32_t>(envRes), 0, false, 0, 0, victimGuid);
// Drowning damage → play DROWN one-shot on player
if (envType == 1 && dmg > 0 && owner_.emoteAnimCallback_)
owner_.emoteAnimCallback_(victimGuid, 131); // anim::DROWN
if (envType == 1 && dmg > 0 && owner_.emoteAnimCallbackRef())
owner_.emoteAnimCallbackRef()(victimGuid, 131); // anim::DROWN
}
packet.skipAll();
};
@ -158,8 +158,8 @@ void CombatHandler::registerOpcodes(DispatchTable& table) {
std::sort(list.begin(), list.end(),
[](const ThreatEntry& a, const ThreatEntry& b){ return a.threat > b.threat; });
threatLists_[unitGuid] = std::move(list);
if (owner_.addonEventCallback_)
owner_.addonEventCallback_("UNIT_THREAT_LIST_UPDATE", {});
if (owner_.addonEventCallbackRef())
owner_.addonEventCallbackRef()("UNIT_THREAT_LIST_UPDATE", {});
};
}
@ -198,7 +198,7 @@ void CombatHandler::registerOpcodes(DispatchTable& table) {
void CombatHandler::startAutoAttack(uint64_t targetGuid) {
// Can't attack yourself
if (targetGuid == owner_.playerGuid) return;
if (targetGuid == owner_.getPlayerGuid()) return;
if (targetGuid == 0) return;
// Dismount when entering combat
@ -209,9 +209,9 @@ void CombatHandler::startAutoAttack(uint64_t targetGuid) {
// Client-side melee range gate to avoid starting "swing forever" loops when
// target is already clearly out of range.
if (auto target = owner_.getEntityManager().getEntity(targetGuid)) {
float dx = owner_.movementInfo.x - target->getLatestX();
float dy = owner_.movementInfo.y - target->getLatestY();
float dz = owner_.movementInfo.z - target->getLatestZ();
float dx = owner_.movementInfoRef().x - target->getLatestX();
float dy = owner_.movementInfoRef().y - target->getLatestY();
float dz = owner_.movementInfoRef().z - target->getLatestZ();
float dist3d = std::sqrt(dx * dx + dy * dy + dz * dz);
if (dist3d > 8.0f) {
if (autoAttackRangeWarnCooldown_ <= 0.0f) {
@ -232,9 +232,9 @@ void CombatHandler::startAutoAttack(uint64_t targetGuid) {
autoAttackOutOfRangeTime_ = 0.0f;
autoAttackResendTimer_ = 0.0f;
autoAttackFacingSyncTimer_ = 0.0f;
if (owner_.state == WorldState::IN_WORLD && owner_.socket) {
if (owner_.getState() == WorldState::IN_WORLD && owner_.getSocket()) {
auto packet = AttackSwingPacket::build(targetGuid);
owner_.socket->send(packet);
owner_.getSocket()->send(packet);
}
LOG_INFO("Starting auto-attack on 0x", std::hex, targetGuid, std::dec);
}
@ -249,13 +249,13 @@ void CombatHandler::stopAutoAttack() {
autoAttackOutOfRangeTime_ = 0.0f;
autoAttackResendTimer_ = 0.0f;
autoAttackFacingSyncTimer_ = 0.0f;
if (owner_.state == WorldState::IN_WORLD && owner_.socket) {
if (owner_.getState() == WorldState::IN_WORLD && owner_.getSocket()) {
auto packet = AttackStopPacket::build();
owner_.socket->send(packet);
owner_.getSocket()->send(packet);
}
LOG_INFO("Stopping auto-attack");
if (owner_.addonEventCallback_)
owner_.addonEventCallback_("PLAYER_LEAVE_COMBAT", {});
if (owner_.addonEventCallbackRef())
owner_.addonEventCallbackRef()("PLAYER_LEAVE_COMBAT", {});
}
// ============================================================
@ -292,9 +292,9 @@ void CombatHandler::addCombatText(CombatTextEntry::Type type, int32_t amount, ui
// preserve "unknown/no source" (e.g. environmental damage) instead of
// backfilling from current target.
uint64_t effectiveSrc = (srcGuid != 0) ? srcGuid
: ((dstGuid != 0) ? 0 : (isPlayerSource ? owner_.playerGuid : owner_.targetGuid));
: ((dstGuid != 0) ? 0 : (isPlayerSource ? owner_.getPlayerGuid() : owner_.getTargetGuid()));
uint64_t effectiveDst = (dstGuid != 0) ? dstGuid
: (isPlayerSource ? owner_.targetGuid : owner_.playerGuid);
: (isPlayerSource ? owner_.getTargetGuid() : owner_.getPlayerGuid());
log.sourceName = owner_.lookupName(effectiveSrc);
log.targetName = (effectiveDst != 0) ? owner_.lookupName(effectiveDst) : std::string{};
if (combatLog_.size() >= MAX_COMBAT_LOG)
@ -303,7 +303,7 @@ void CombatHandler::addCombatText(CombatTextEntry::Type type, int32_t amount, ui
// Fire COMBAT_LOG_EVENT_UNFILTERED for Lua addons
// Args: subevent, sourceGUID, sourceName, 0 (sourceFlags), destGUID, destName, 0 (destFlags), spellId, spellName, amount
if (owner_.addonEventCallback_) {
if (owner_.addonEventCallbackRef()) {
static const char* kSubevents[] = {
"SWING_DAMAGE", "SPELL_DAMAGE", "SPELL_HEAL", "SWING_MISSED", "SWING_MISSED",
"SWING_MISSED", "SWING_MISSED", "SWING_MISSED", "SPELL_DAMAGE", "SPELL_HEAL",
@ -320,7 +320,7 @@ void CombatHandler::addCombatText(CombatTextEntry::Type type, int32_t amount, ui
snprintf(dstBuf, sizeof(dstBuf), "0x%016llX", (unsigned long long)effectiveDst);
std::string spellName = (spellId != 0) ? owner_.getSpellName(spellId) : std::string{};
std::string timestamp = std::to_string(static_cast<double>(std::time(nullptr)));
owner_.addonEventCallback_("COMBAT_LOG_EVENT_UNFILTERED", {
owner_.addonEventCallbackRef()("COMBAT_LOG_EVENT_UNFILTERED", {
timestamp, subevent,
srcBuf, log.sourceName, "0",
dstBuf, log.targetName, "0",
@ -370,8 +370,8 @@ void CombatHandler::updateCombatText(float deltaTime) {
// ============================================================
void CombatHandler::autoTargetAttacker(uint64_t attackerGuid) {
if (attackerGuid == 0 || attackerGuid == owner_.playerGuid) return;
if (owner_.targetGuid != 0) return;
if (attackerGuid == 0 || attackerGuid == owner_.getPlayerGuid()) return;
if (owner_.getTargetGuid() != 0) return;
if (!owner_.getEntityManager().hasEntity(attackerGuid)) return;
owner_.setTarget(attackerGuid);
}
@ -380,23 +380,23 @@ void CombatHandler::handleAttackStart(network::Packet& packet) {
AttackStartData data;
if (!AttackStartParser::parse(packet, data)) return;
if (data.attackerGuid == owner_.playerGuid) {
if (data.attackerGuid == owner_.getPlayerGuid()) {
autoAttackRequested_ = true;
autoAttacking_ = true;
autoAttackRetryPending_ = false;
autoAttackTarget_ = data.victimGuid;
if (owner_.addonEventCallback_)
owner_.addonEventCallback_("PLAYER_ENTER_COMBAT", {});
} else if (data.victimGuid == owner_.playerGuid && data.attackerGuid != 0) {
if (owner_.addonEventCallbackRef())
owner_.addonEventCallbackRef()("PLAYER_ENTER_COMBAT", {});
} else if (data.victimGuid == owner_.getPlayerGuid() && data.attackerGuid != 0) {
hostileAttackers_.insert(data.attackerGuid);
autoTargetAttacker(data.attackerGuid);
// Play aggro sound when NPC attacks player
if (owner_.npcAggroCallback_) {
if (owner_.npcAggroCallbackRef()) {
auto entity = owner_.getEntityManager().getEntity(data.attackerGuid);
if (entity && entity->getType() == ObjectType::UNIT) {
glm::vec3 pos(entity->getX(), entity->getY(), entity->getZ());
owner_.npcAggroCallback_(data.attackerGuid, pos);
owner_.npcAggroCallbackRef()(data.attackerGuid, pos);
}
}
}
@ -421,32 +421,32 @@ void CombatHandler::handleAttackStop(network::Packet& packet) {
if (!AttackStopParser::parse(packet, data)) return;
// Keep intent, but clear server-confirmed active state until ATTACKSTART resumes.
if (data.attackerGuid == owner_.playerGuid) {
if (data.attackerGuid == owner_.getPlayerGuid()) {
autoAttacking_ = false;
autoAttackRetryPending_ = autoAttackRequested_;
autoAttackResendTimer_ = 0.0f;
LOG_DEBUG("SMSG_ATTACKSTOP received (keeping auto-attack intent)");
} else if (data.victimGuid == owner_.playerGuid) {
} else if (data.victimGuid == owner_.getPlayerGuid()) {
hostileAttackers_.erase(data.attackerGuid);
}
}
void CombatHandler::handleAttackerStateUpdate(network::Packet& packet) {
AttackerStateUpdateData data;
if (!owner_.packetParsers_->parseAttackerStateUpdate(packet, data)) return;
if (!owner_.getPacketParsers()->parseAttackerStateUpdate(packet, data)) return;
bool isPlayerAttacker = (data.attackerGuid == owner_.playerGuid);
bool isPlayerTarget = (data.targetGuid == owner_.playerGuid);
bool isPlayerAttacker = (data.attackerGuid == owner_.getPlayerGuid());
bool isPlayerTarget = (data.targetGuid == owner_.getPlayerGuid());
if (!isPlayerAttacker && !isPlayerTarget) return; // Not our combat
if (isPlayerAttacker) {
lastMeleeSwingMs_ = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count());
if (owner_.meleeSwingCallback_) owner_.meleeSwingCallback_(0);
if (owner_.meleeSwingCallbackRef()) owner_.meleeSwingCallbackRef()(0);
}
if (!isPlayerAttacker && owner_.npcSwingCallback_) {
owner_.npcSwingCallback_(data.attackerGuid);
if (!isPlayerAttacker && owner_.npcSwingCallbackRef()) {
owner_.npcSwingCallbackRef()(data.attackerGuid);
}
if (isPlayerTarget && data.attackerGuid != 0) {
@ -524,24 +524,24 @@ void CombatHandler::handleAttackerStateUpdate(network::Packet& packet) {
}
// Fire hit reaction animation on the victim
if (owner_.hitReactionCallback_ && !data.isMiss()) {
if (owner_.hitReactionCallbackRef() && !data.isMiss()) {
using HR = GameHandler::HitReaction;
HR reaction = HR::WOUND;
if (data.victimState == 1) reaction = HR::DODGE;
else if (data.victimState == 2) reaction = HR::PARRY;
else if (data.victimState == 4) reaction = HR::BLOCK;
else if (data.isCrit()) reaction = HR::CRIT_WOUND;
owner_.hitReactionCallback_(data.targetGuid, reaction);
owner_.hitReactionCallbackRef()(data.targetGuid, reaction);
}
}
void CombatHandler::handleSpellDamageLog(network::Packet& packet) {
SpellDamageLogData data;
if (!owner_.packetParsers_->parseSpellDamageLog(packet, data)) return;
if (!owner_.getPacketParsers()->parseSpellDamageLog(packet, data)) return;
bool isPlayerSource = (data.attackerGuid == owner_.playerGuid);
bool isPlayerTarget = (data.targetGuid == owner_.playerGuid);
bool isPlayerSource = (data.attackerGuid == owner_.getPlayerGuid());
bool isPlayerTarget = (data.targetGuid == owner_.getPlayerGuid());
if (!isPlayerSource && !isPlayerTarget) return; // Not our combat
if (isPlayerTarget && data.attackerGuid != 0) {
@ -560,10 +560,10 @@ void CombatHandler::handleSpellDamageLog(network::Packet& packet) {
void CombatHandler::handleSpellHealLog(network::Packet& packet) {
SpellHealLogData data;
if (!owner_.packetParsers_->parseSpellHealLog(packet, data)) return;
if (!owner_.getPacketParsers()->parseSpellHealLog(packet, data)) return;
bool isPlayerSource = (data.casterGuid == owner_.playerGuid);
bool isPlayerTarget = (data.targetGuid == owner_.playerGuid);
bool isPlayerSource = (data.casterGuid == owner_.getPlayerGuid());
bool isPlayerTarget = (data.targetGuid == owner_.getPlayerGuid());
if (!isPlayerSource && !isPlayerTarget) return; // Not our combat
auto type = data.isCrit ? CombatTextEntry::CRIT_HEAL : CombatTextEntry::HEAL;
@ -608,9 +608,9 @@ void CombatHandler::updateAutoAttack(float deltaTime) {
const float targetX = targetEntity->getLatestX();
const float targetY = targetEntity->getLatestY();
const float targetZ = targetEntity->getLatestZ();
float dx = owner_.movementInfo.x - targetX;
float dy = owner_.movementInfo.y - targetY;
float dz = owner_.movementInfo.z - targetZ;
float dx = owner_.movementInfoRef().x - targetX;
float dy = owner_.movementInfoRef().y - targetY;
float dz = owner_.movementInfoRef().z - targetZ;
float dist = std::sqrt(dx * dx + dy * dy);
float dist3d = std::sqrt(dx * dx + dy * dy + dz * dz);
const bool classicLike = isPreWotlk();
@ -652,7 +652,7 @@ void CombatHandler::updateAutoAttack(float deltaTime) {
autoAttackResendTimer_ = 0.0f;
autoAttackRetryPending_ = false;
auto pkt = AttackSwingPacket::build(autoAttackTarget_);
owner_.socket->send(pkt);
owner_.getSocket()->send(pkt);
}
// Keep server-facing aligned while trying to acquire melee.
@ -661,16 +661,16 @@ void CombatHandler::updateAutoAttack(float deltaTime) {
if (allowPeriodicFacingSync &&
autoAttackFacingSyncTimer_ >= facingSyncInterval) {
autoAttackFacingSyncTimer_ = 0.0f;
float toTargetX = targetX - owner_.movementInfo.x;
float toTargetY = targetY - owner_.movementInfo.y;
float toTargetX = targetX - owner_.movementInfoRef().x;
float toTargetY = targetY - owner_.movementInfoRef().y;
if (std::abs(toTargetX) > 0.01f || std::abs(toTargetY) > 0.01f) {
float desired = std::atan2(-toTargetY, toTargetX);
float diff = desired - owner_.movementInfo.orientation;
float diff = desired - owner_.movementInfoRef().orientation;
while (diff > static_cast<float>(M_PI)) diff -= 2.0f * static_cast<float>(M_PI);
while (diff < -static_cast<float>(M_PI)) diff += 2.0f * static_cast<float>(M_PI);
const float facingThreshold = classicLike ? 0.035f : 0.12f;
if (std::abs(diff) > facingThreshold) {
owner_.movementInfo.orientation = desired;
owner_.movementInfoRef().orientation = desired;
owner_.sendMovement(Opcode::MSG_MOVE_SET_FACING);
}
}
@ -685,8 +685,8 @@ void CombatHandler::updateAutoAttack(float deltaTime) {
for (uint64_t attackerGuid : hostileAttackers_) {
auto attacker = owner_.getEntityManager().getEntity(attackerGuid);
if (!attacker) continue;
float dx = owner_.movementInfo.x - attacker->getX();
float dy = owner_.movementInfo.y - attacker->getY();
float dx = owner_.movementInfoRef().x - attacker->getX();
float dy = owner_.movementInfoRef().y - attacker->getY();
if (std::abs(dx) < 0.01f && std::abs(dy) < 0.01f) continue;
attacker->setOrientation(std::atan2(-dy, dx));
}
@ -758,7 +758,7 @@ void CombatHandler::handlePowerUpdate(network::Packet& packet) {
auto unitId = owner_.guidToUnitId(guid);
if (!unitId.empty()) {
owner_.fireAddonEvent("UNIT_POWER", {unitId});
if (guid == owner_.playerGuid) {
if (guid == owner_.getPlayerGuid()) {
owner_.fireAddonEvent("ACTIONBAR_UPDATE_USABLE", {});
owner_.fireAddonEvent("SPELL_UPDATE_USABLE", {});
}
@ -771,10 +771,10 @@ void CombatHandler::handleUpdateComboPoints(network::Packet& packet) {
if (!packet.hasRemaining(cpTbc ? 8u : 2u) ) return;
uint64_t target = cpTbc ? packet.readUInt64() : packet.readPackedGuid();
if (!packet.hasRemaining(1)) return;
owner_.comboPoints_ = packet.readUInt8();
owner_.comboTarget_ = target;
owner_.comboPointsRef() = packet.readUInt8();
owner_.comboTargetRef() = target;
LOG_DEBUG("SMSG_UPDATE_COMBO_POINTS: target=0x", std::hex, target,
std::dec, " points=", static_cast<int>(owner_.comboPoints_));
std::dec, " points=", static_cast<int>(owner_.comboPointsRef()));
owner_.fireAddonEvent("PLAYER_COMBO_POINTS", {});
}
@ -787,7 +787,7 @@ void CombatHandler::handlePvpCredit(network::Packet& packet) {
std::string msg = "You gain " + std::to_string(honor) + " honor points.";
owner_.addSystemChatMessage(msg);
if (honor > 0) addCombatText(CombatTextEntry::HONOR_GAIN, static_cast<int32_t>(honor), 0, true);
if (owner_.pvpHonorCallback_) owner_.pvpHonorCallback_(honor, victimGuid, rank);
if (owner_.pvpHonorCallbackRef()) owner_.pvpHonorCallbackRef()(honor, victimGuid, rank);
owner_.fireAddonEvent("CHAT_MSG_COMBAT_HONOR_GAIN", {msg});
}
}
@ -805,8 +805,8 @@ void CombatHandler::handleProcResist(network::Packet& packet) {
uint64_t victim = readPrGuid();
if (!packet.hasRemaining(4)) return;
uint32_t spellId = packet.readUInt32();
if (victim == owner_.playerGuid) addCombatText(CombatTextEntry::RESIST, 0, spellId, false, 0, caster, victim);
else if (caster == owner_.playerGuid) addCombatText(CombatTextEntry::RESIST, 0, spellId, true, 0, caster, victim);
if (victim == owner_.getPlayerGuid()) addCombatText(CombatTextEntry::RESIST, 0, spellId, false, 0, caster, victim);
else if (caster == owner_.getPlayerGuid()) addCombatText(CombatTextEntry::RESIST, 0, spellId, true, 0, caster, victim);
packet.skipAll();
}
@ -846,10 +846,10 @@ void CombatHandler::handleSpellDamageShield(network::Packet& packet) {
/*uint32_t absorbed =*/ packet.readUInt32();
/*uint32_t school =*/ packet.readUInt32();
// Show combat text: damage shield reflect
if (casterGuid == owner_.playerGuid) {
if (casterGuid == owner_.getPlayerGuid()) {
// We have a damage shield that reflected damage
addCombatText(CombatTextEntry::SPELL_DAMAGE, static_cast<int32_t>(damage), shieldSpellId, true, 0, casterGuid, victimGuid);
} else if (victimGuid == owner_.playerGuid) {
} else if (victimGuid == owner_.getPlayerGuid()) {
// A damage shield hit us (e.g. target's Thorns)
addCombatText(CombatTextEntry::SPELL_DAMAGE, static_cast<int32_t>(damage), shieldSpellId, false, 0, casterGuid, victimGuid);
}
@ -878,9 +878,9 @@ void CombatHandler::handleSpellOrDamageImmune(network::Packet& packet) {
/*uint8_t saveType =*/ packet.readUInt8();
// Show IMMUNE text when the player is the caster (we hit an immune target)
// or the victim (we are immune)
if (casterGuid == owner_.playerGuid || victimGuid == owner_.playerGuid) {
if (casterGuid == owner_.getPlayerGuid() || victimGuid == owner_.getPlayerGuid()) {
addCombatText(CombatTextEntry::IMMUNE, 0, immuneSpellId,
casterGuid == owner_.playerGuid, 0, casterGuid, victimGuid);
casterGuid == owner_.getPlayerGuid(), 0, casterGuid, victimGuid);
}
}
@ -916,9 +916,9 @@ void CombatHandler::handleResistLog(network::Packet& packet) {
/*uint32_t targetRes =*/ packet.readUInt32();
int32_t resistedAmount = static_cast<int32_t>(packet.readUInt32());
// Show RESIST when the player is involved on either side.
if (resistedAmount > 0 && victimGuid == owner_.playerGuid) {
if (resistedAmount > 0 && victimGuid == owner_.getPlayerGuid()) {
addCombatText(CombatTextEntry::RESIST, resistedAmount, spellId, false, 0, attackerGuid, victimGuid);
} else if (resistedAmount > 0 && attackerGuid == owner_.playerGuid) {
} else if (resistedAmount > 0 && attackerGuid == owner_.getPlayerGuid()) {
addCombatText(CombatTextEntry::RESIST, resistedAmount, spellId, true, 0, attackerGuid, victimGuid);
}
packet.skipAll();
@ -985,10 +985,10 @@ void CombatHandler::handlePetCastFailed(network::Packet& packet) {
void CombatHandler::handlePetBroken(network::Packet& packet) {
// Pet bond broken (died or forcibly dismissed) — clear pet state
owner_.petGuid_ = 0;
owner_.petSpellList_.clear();
owner_.petAutocastSpells_.clear();
memset(owner_.petActionSlots_, 0, sizeof(owner_.petActionSlots_));
owner_.petGuidRef() = 0;
owner_.petSpellListRef().clear();
owner_.petAutocastSpellsRef().clear();
memset(owner_.petActionSlotsRef(), 0, sizeof(owner_.petActionSlotsRef()));
owner_.addSystemChatMessage("Your pet has died.");
LOG_INFO("SMSG_PET_BROKEN: pet bond broken");
packet.skipAll();
@ -997,7 +997,7 @@ void CombatHandler::handlePetBroken(network::Packet& packet) {
void CombatHandler::handlePetLearnedSpell(network::Packet& packet) {
if (packet.hasRemaining(4)) {
uint32_t spellId = packet.readUInt32();
owner_.petSpellList_.push_back(spellId);
owner_.petSpellListRef().push_back(spellId);
const std::string& sname = owner_.getSpellName(spellId);
owner_.addSystemChatMessage("Your pet has learned " + (sname.empty() ? "a new ability." : sname + "."));
LOG_DEBUG("SMSG_PET_LEARNED_SPELL: spellId=", spellId);
@ -1009,10 +1009,10 @@ void CombatHandler::handlePetLearnedSpell(network::Packet& packet) {
void CombatHandler::handlePetUnlearnedSpell(network::Packet& packet) {
if (packet.hasRemaining(4)) {
uint32_t spellId = packet.readUInt32();
owner_.petSpellList_.erase(
std::remove(owner_.petSpellList_.begin(), owner_.petSpellList_.end(), spellId),
owner_.petSpellList_.end());
owner_.petAutocastSpells_.erase(spellId);
owner_.petSpellListRef().erase(
std::remove(owner_.petSpellListRef().begin(), owner_.petSpellListRef().end(), spellId),
owner_.petSpellListRef().end());
owner_.petAutocastSpellsRef().erase(spellId);
LOG_DEBUG("SMSG_PET_UNLEARNED_SPELL: spellId=", spellId);
}
packet.skipAll();
@ -1024,11 +1024,11 @@ void CombatHandler::handlePetMode(network::Packet& packet) {
if (packet.hasRemaining(12)) {
uint64_t modeGuid = packet.readUInt64();
uint32_t mode = packet.readUInt32();
if (modeGuid == owner_.petGuid_) {
owner_.petCommand_ = static_cast<uint8_t>(mode & 0xFF);
owner_.petReact_ = static_cast<uint8_t>((mode >> 8) & 0xFF);
LOG_DEBUG("SMSG_PET_MODE: command=", static_cast<int>(owner_.petCommand_),
" react=", static_cast<int>(owner_.petReact_));
if (modeGuid == owner_.petGuidRef()) {
owner_.petCommandRef() = static_cast<uint8_t>(mode & 0xFF);
owner_.petReactRef() = static_cast<uint8_t>((mode >> 8) & 0xFF);
LOG_DEBUG("SMSG_PET_MODE: command=", static_cast<int>(owner_.petCommandRef()),
" react=", static_cast<int>(owner_.petReactRef()));
}
}
packet.skipAll();
@ -1054,18 +1054,18 @@ void CombatHandler::handleResurrectFailed(network::Packet& packet) {
// ============================================================
void CombatHandler::setTarget(uint64_t guid) {
if (guid == owner_.targetGuid) return;
if (guid == owner_.getTargetGuid()) return;
// Save previous target
if (owner_.targetGuid != 0) {
owner_.lastTargetGuid = owner_.targetGuid;
if (owner_.getTargetGuid() != 0) {
owner_.lastTargetGuidRef() = owner_.getTargetGuid();
}
owner_.targetGuid = guid;
owner_.setTargetGuidRaw(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_.spellHandler_) for (auto& slot : owner_.spellHandler_->targetAuras_) slot = AuraSlot{};
if (owner_.getSpellHandler()) for (auto& slot : owner_.getSpellHandler()->targetAuras_) slot = AuraSlot{};
// Clear previous target's cast bar on target change
// (the new target's cast state is naturally fetched from spellHandler_->unitCastStates_ by GUID)
@ -1073,7 +1073,7 @@ void CombatHandler::setTarget(uint64_t guid) {
// Inform server of target selection
if (owner_.isInWorld()) {
auto packet = SetSelectionPacket::build(guid);
owner_.socket->send(packet);
owner_.getSocket()->send(packet);
}
if (guid != 0) {
@ -1083,27 +1083,27 @@ void CombatHandler::setTarget(uint64_t guid) {
}
void CombatHandler::clearTarget() {
if (owner_.targetGuid != 0) {
if (owner_.getTargetGuid() != 0) {
LOG_INFO("Target cleared");
// Zero the GUID before firing the event so callbacks/addons that query
// the current target see null (consistent with setTarget which updates
// targetGuid before the event).
owner_.targetGuid = 0;
owner_.setTargetGuidRaw(0);
owner_.fireAddonEvent("PLAYER_TARGET_CHANGED", {});
} else {
owner_.targetGuid = 0;
owner_.setTargetGuidRaw(0);
}
owner_.tabCycleIndex = -1;
owner_.tabCycleStale = true;
owner_.tabCycleIndexRef() = -1;
owner_.tabCycleStaleRef() = true;
}
std::shared_ptr<Entity> CombatHandler::getTarget() const {
if (owner_.targetGuid == 0) return nullptr;
return owner_.getEntityManager().getEntity(owner_.targetGuid);
if (owner_.getTargetGuid() == 0) return nullptr;
return owner_.getEntityManager().getEntity(owner_.getTargetGuid());
}
void CombatHandler::setFocus(uint64_t guid) {
owner_.focusGuid = guid;
owner_.focusGuidRef() = guid;
owner_.fireAddonEvent("PLAYER_FOCUS_CHANGED", {});
if (guid != 0) {
auto entity = owner_.getEntityManager().getEntity(guid);
@ -1122,36 +1122,36 @@ void CombatHandler::setFocus(uint64_t guid) {
}
void CombatHandler::clearFocus() {
if (owner_.focusGuid != 0) {
if (owner_.focusGuidRef() != 0) {
owner_.addSystemChatMessage("Focus cleared.");
LOG_INFO("Focus cleared");
}
owner_.focusGuid = 0;
owner_.focusGuidRef() = 0;
owner_.fireAddonEvent("PLAYER_FOCUS_CHANGED", {});
}
std::shared_ptr<Entity> CombatHandler::getFocus() const {
if (owner_.focusGuid == 0) return nullptr;
return owner_.getEntityManager().getEntity(owner_.focusGuid);
if (owner_.focusGuidRef() == 0) return nullptr;
return owner_.getEntityManager().getEntity(owner_.focusGuidRef());
}
void CombatHandler::setMouseoverGuid(uint64_t guid) {
if (owner_.mouseoverGuid_ != guid) {
owner_.mouseoverGuid_ = guid;
if (owner_.mouseoverGuidRef() != guid) {
owner_.mouseoverGuidRef() = guid;
owner_.fireAddonEvent("UPDATE_MOUSEOVER_UNIT", {});
}
}
void CombatHandler::targetLastTarget() {
if (owner_.lastTargetGuid == 0) {
if (owner_.lastTargetGuidRef() == 0) {
owner_.addSystemChatMessage("No previous target.");
return;
}
// Swap current and last target
uint64_t temp = owner_.targetGuid;
setTarget(owner_.lastTargetGuid);
owner_.lastTargetGuid = temp;
uint64_t temp = owner_.getTargetGuid();
setTarget(owner_.lastTargetGuidRef());
owner_.lastTargetGuidRef() = temp;
}
void CombatHandler::targetEnemy(bool reverse) {
@ -1162,7 +1162,7 @@ void CombatHandler::targetEnemy(bool reverse) {
for (const auto& [guid, entity] : entities) {
if (entity->getType() == ObjectType::UNIT) {
auto unit = std::dynamic_pointer_cast<Unit>(entity);
if (unit && guid != owner_.playerGuid && unit->isHostile()) {
if (unit && guid != owner_.getPlayerGuid() && unit->isHostile()) {
hostiles.push_back(guid);
}
}
@ -1174,7 +1174,7 @@ void CombatHandler::targetEnemy(bool reverse) {
}
// Find current target in list
auto it = std::find(hostiles.begin(), hostiles.end(), owner_.targetGuid);
auto it = std::find(hostiles.begin(), hostiles.end(), owner_.getTargetGuid());
if (it == hostiles.end()) {
// Not currently targeting a hostile, target first one
@ -1204,7 +1204,7 @@ void CombatHandler::targetFriend(bool reverse) {
auto& entities = owner_.getEntityManager().getEntities();
for (const auto& [guid, entity] : entities) {
if (entity->getType() == ObjectType::PLAYER && guid != owner_.playerGuid) {
if (entity->getType() == ObjectType::PLAYER && guid != owner_.getPlayerGuid()) {
friendlies.push_back(guid);
}
}
@ -1215,7 +1215,7 @@ void CombatHandler::targetFriend(bool reverse) {
}
// Find current target in list
auto it = std::find(friendlies.begin(), friendlies.end(), owner_.targetGuid);
auto it = std::find(friendlies.begin(), friendlies.end(), owner_.getTargetGuid());
if (it == friendlies.end()) {
// Not currently targeting a friend, target first one
@ -1247,8 +1247,8 @@ void CombatHandler::tabTarget(float playerX, float playerY, float playerZ) {
auto* unit = dynamic_cast<Unit*>(e.get());
if (!unit) return false;
if (unit->getHealth() == 0) {
auto lootIt = owner_.localLootState_.find(guid);
if (lootIt == owner_.localLootState_.end() || lootIt->second.data.items.empty()) {
auto lootIt = owner_.localLootStateRef().find(guid);
if (lootIt == owner_.localLootStateRef().end() || lootIt->second.data.items.empty()) {
return false;
}
return true;
@ -1260,9 +1260,9 @@ void CombatHandler::tabTarget(float playerX, float playerY, float playerZ) {
};
// Rebuild cycle list if stale (entity added/removed since last tab press).
if (owner_.tabCycleStale) {
owner_.tabCycleList.clear();
owner_.tabCycleIndex = -1;
if (owner_.tabCycleStaleRef()) {
owner_.tabCycleListRef().clear();
owner_.tabCycleIndexRef() = -1;
struct EntityDist { uint64_t guid; float distance; };
std::vector<EntityDist> sortable;
@ -1270,7 +1270,7 @@ void CombatHandler::tabTarget(float playerX, float playerY, float playerZ) {
for (const auto& [guid, entity] : owner_.getEntityManager().getEntities()) {
auto t = entity->getType();
if (t != ObjectType::UNIT && t != ObjectType::PLAYER) continue;
if (guid == owner_.playerGuid) continue;
if (guid == owner_.getPlayerGuid()) continue;
if (!isValidTabTarget(entity)) continue;
float dx = entity->getX() - playerX;
float dy = entity->getY() - playerY;
@ -1282,22 +1282,22 @@ void CombatHandler::tabTarget(float playerX, float playerY, float playerZ) {
[](const EntityDist& a, const EntityDist& b) { return a.distance < b.distance; });
for (const auto& ed : sortable) {
owner_.tabCycleList.push_back(ed.guid);
owner_.tabCycleListRef().push_back(ed.guid);
}
owner_.tabCycleStale = false;
owner_.tabCycleStaleRef() = false;
}
if (owner_.tabCycleList.empty()) {
if (owner_.tabCycleListRef().empty()) {
clearTarget();
return;
}
// Advance through the cycle, skipping any entry that has since died or
// turned friendly (e.g. NPC killed between two tab presses).
int tries = static_cast<int>(owner_.tabCycleList.size());
int tries = static_cast<int>(owner_.tabCycleListRef().size());
while (tries-- > 0) {
owner_.tabCycleIndex = (owner_.tabCycleIndex + 1) % static_cast<int>(owner_.tabCycleList.size());
uint64_t guid = owner_.tabCycleList[owner_.tabCycleIndex];
owner_.tabCycleIndexRef() = (owner_.tabCycleIndexRef() + 1) % static_cast<int>(owner_.tabCycleListRef().size());
uint64_t guid = owner_.tabCycleListRef()[owner_.tabCycleIndexRef()];
auto entity = owner_.getEntityManager().getEntity(guid);
if (isValidTabTarget(entity)) {
setTarget(guid);
@ -1306,17 +1306,17 @@ void CombatHandler::tabTarget(float playerX, float playerY, float playerZ) {
}
// All cached entries are stale — clear target and force a fresh rebuild next time.
owner_.tabCycleStale = true;
owner_.tabCycleStaleRef() = true;
clearTarget();
}
void CombatHandler::assistTarget() {
if (owner_.state != WorldState::IN_WORLD) {
if (owner_.getState() != WorldState::IN_WORLD) {
LOG_WARNING("Cannot assist: not in world");
return;
}
if (owner_.targetGuid == 0) {
if (owner_.getTargetGuid() == 0) {
owner_.addSystemChatMessage("You must target someone to assist.");
return;
}
@ -1373,8 +1373,8 @@ void CombatHandler::togglePvp() {
}
auto packet = TogglePvpPacket::build();
owner_.socket->send(packet);
auto entity = owner_.getEntityManager().getEntity(owner_.playerGuid);
owner_.getSocket()->send(packet);
auto entity = owner_.getEntityManager().getEntity(owner_.getPlayerGuid());
bool currentlyPvp = false;
if (entity) {
// UNIT_FIELD_FLAGS (index 59), bit 0x1000 = UNIT_FLAG_PVP
@ -1393,93 +1393,93 @@ void CombatHandler::togglePvp() {
// ============================================================
void CombatHandler::releaseSpirit() {
if (owner_.socket && owner_.state == WorldState::IN_WORLD) {
if (owner_.getSocket() && owner_.getState() == WorldState::IN_WORLD) {
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
if (owner_.repopPending_ && now - static_cast<int64_t>(owner_.lastRepopRequestMs_) < 1000) {
if (owner_.repopPendingRef() && now - static_cast<int64_t>(owner_.lastRepopRequestMsRef()) < 1000) {
return;
}
auto packet = RepopRequestPacket::build();
owner_.socket->send(packet);
owner_.selfResAvailable_ = false;
owner_.repopPending_ = true;
owner_.lastRepopRequestMs_ = static_cast<uint64_t>(now);
owner_.getSocket()->send(packet);
owner_.selfResAvailableRef() = false;
owner_.repopPendingRef() = true;
owner_.lastRepopRequestMsRef() = static_cast<uint64_t>(now);
LOG_INFO("Sent CMSG_REPOP_REQUEST (Release Spirit)");
network::Packet cq(wireOpcode(Opcode::MSG_CORPSE_QUERY));
owner_.socket->send(cq);
owner_.getSocket()->send(cq);
}
}
bool CombatHandler::canReclaimCorpse() const {
if (!owner_.releasedSpirit_ || owner_.corpseGuid_ == 0 || owner_.corpseMapId_ == 0) return false;
if (owner_.currentMapId_ != owner_.corpseMapId_) return false;
float dx = owner_.movementInfo.x - owner_.corpseY_;
float dy = owner_.movementInfo.y - owner_.corpseX_;
float dz = owner_.movementInfo.z - owner_.corpseZ_;
if (!owner_.releasedSpiritRef() || owner_.corpseGuidRef() == 0 || owner_.corpseMapIdRef() == 0) return false;
if (owner_.currentMapIdRef() != owner_.corpseMapIdRef()) return false;
float dx = owner_.movementInfoRef().x - owner_.corpseYRef();
float dy = owner_.movementInfoRef().y - owner_.corpseXRef();
float dz = owner_.movementInfoRef().z - owner_.corpseZRef();
return (dx*dx + dy*dy + dz*dz) <= (40.0f * 40.0f);
}
float CombatHandler::getCorpseReclaimDelaySec() const {
if (owner_.corpseReclaimAvailableMs_ == 0) return 0.0f;
if (owner_.corpseReclaimAvailableMsRef() == 0) return 0.0f;
auto nowMs = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count());
if (nowMs >= owner_.corpseReclaimAvailableMs_) return 0.0f;
return static_cast<float>(owner_.corpseReclaimAvailableMs_ - nowMs) / 1000.0f;
if (nowMs >= owner_.corpseReclaimAvailableMsRef()) return 0.0f;
return static_cast<float>(owner_.corpseReclaimAvailableMsRef() - nowMs) / 1000.0f;
}
void CombatHandler::reclaimCorpse() {
if (!canReclaimCorpse() || !owner_.socket) return;
if (owner_.corpseGuid_ == 0) {
if (!canReclaimCorpse() || !owner_.getSocket()) return;
if (owner_.corpseGuidRef() == 0) {
LOG_WARNING("reclaimCorpse: corpse GUID not yet known (corpse object not received); cannot reclaim");
return;
}
auto packet = ReclaimCorpsePacket::build(owner_.corpseGuid_);
owner_.socket->send(packet);
LOG_INFO("Sent CMSG_RECLAIM_CORPSE for corpse guid=0x", std::hex, owner_.corpseGuid_, std::dec);
auto packet = ReclaimCorpsePacket::build(owner_.corpseGuidRef());
owner_.getSocket()->send(packet);
LOG_INFO("Sent CMSG_RECLAIM_CORPSE for corpse guid=0x", std::hex, owner_.corpseGuidRef(), std::dec);
}
void CombatHandler::useSelfRes() {
if (!owner_.selfResAvailable_ || !owner_.socket) return;
if (!owner_.selfResAvailableRef() || !owner_.getSocket()) return;
network::Packet pkt(wireOpcode(Opcode::CMSG_SELF_RES));
owner_.socket->send(pkt);
owner_.selfResAvailable_ = false;
owner_.getSocket()->send(pkt);
owner_.selfResAvailableRef() = false;
LOG_INFO("Sent CMSG_SELF_RES (Reincarnation / Twisting Nether)");
}
void CombatHandler::activateSpiritHealer(uint64_t npcGuid) {
if (!owner_.isInWorld()) return;
owner_.pendingSpiritHealerGuid_ = npcGuid;
owner_.pendingSpiritHealerGuidRef() = npcGuid;
auto packet = SpiritHealerActivatePacket::build(npcGuid);
owner_.socket->send(packet);
owner_.resurrectPending_ = true;
owner_.getSocket()->send(packet);
owner_.resurrectPendingRef() = true;
LOG_INFO("Sent CMSG_SPIRIT_HEALER_ACTIVATE for 0x", std::hex, npcGuid, std::dec);
}
void CombatHandler::acceptResurrect() {
if (owner_.state != WorldState::IN_WORLD || !owner_.socket || !owner_.resurrectRequestPending_) return;
if (owner_.resurrectIsSpiritHealer_) {
auto activate = SpiritHealerActivatePacket::build(owner_.resurrectCasterGuid_);
owner_.socket->send(activate);
if (owner_.getState() != WorldState::IN_WORLD || !owner_.getSocket() || !owner_.resurrectRequestPendingRef()) return;
if (owner_.resurrectIsSpiritHealerRef()) {
auto activate = SpiritHealerActivatePacket::build(owner_.resurrectCasterGuidRef());
owner_.getSocket()->send(activate);
LOG_INFO("Sent CMSG_SPIRIT_HEALER_ACTIVATE for 0x",
std::hex, owner_.resurrectCasterGuid_, std::dec);
std::hex, owner_.resurrectCasterGuidRef(), std::dec);
} else {
auto resp = ResurrectResponsePacket::build(owner_.resurrectCasterGuid_, true);
owner_.socket->send(resp);
auto resp = ResurrectResponsePacket::build(owner_.resurrectCasterGuidRef(), true);
owner_.getSocket()->send(resp);
LOG_INFO("Sent CMSG_RESURRECT_RESPONSE (accept) for 0x",
std::hex, owner_.resurrectCasterGuid_, std::dec);
std::hex, owner_.resurrectCasterGuidRef(), std::dec);
}
owner_.resurrectRequestPending_ = false;
owner_.resurrectPending_ = true;
owner_.resurrectRequestPendingRef() = false;
owner_.resurrectPendingRef() = true;
}
void CombatHandler::declineResurrect() {
if (owner_.state != WorldState::IN_WORLD || !owner_.socket || !owner_.resurrectRequestPending_) return;
auto resp = ResurrectResponsePacket::build(owner_.resurrectCasterGuid_, false);
owner_.socket->send(resp);
if (owner_.getState() != WorldState::IN_WORLD || !owner_.getSocket() || !owner_.resurrectRequestPendingRef()) return;
auto resp = ResurrectResponsePacket::build(owner_.resurrectCasterGuidRef(), false);
owner_.getSocket()->send(resp);
LOG_INFO("Sent CMSG_RESURRECT_RESPONSE (decline) for 0x",
std::hex, owner_.resurrectCasterGuid_, std::dec);
owner_.resurrectRequestPending_ = false;
std::hex, owner_.resurrectCasterGuidRef(), std::dec);
owner_.resurrectRequestPendingRef() = false;
}
// ============================================================