mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
fix(render): code quality cleanup
Magic number elimination: - Create protocol_constants.hpp, warden_constants.hpp, render_constants.hpp, ui_constants.hpp - Replace ~55 magic numbers across game_handler, warden_handler, m2_renderer_render Reduce nesting depth: - Extract 5 parseEffect* methods from handleSpellLogExecute (max indent 52 → 16 cols) - Extract resolveSpellSchool/playSpellCastSound/playSpellImpactSound from 3× duplicate audio blocks in handleSpellGo - Flatten SMSG_INVENTORY_CHANGE_FAILURE with early-return guards - Extract drawScreenEdgeVignette() for 3 duplicate vignette blocks DRY extract patterns: - Replace 12 compound expansion checks with isPreWotlk() across movement_handler (9), chat_handler (1), social_handler (1) const to constexpr: - Promote 23+ static const arrays/scalars to static constexpr across 12 source files Error handling: - Convert PIN auth from exceptions to std::optional<PinProof> - Add [[nodiscard]] to 15+ initialize/parse methods - Wrap ~20 unchecked initialize() calls with LOG_WARNING/LOG_ERROR Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
This commit is contained in:
parent
2e8856bacd
commit
97106bd6ae
41 changed files with 849 additions and 424 deletions
|
|
@ -378,7 +378,7 @@ void ChatHandler::sendTextEmote(uint32_t textEmoteId, uint64_t targetGuid) {
|
|||
}
|
||||
|
||||
void ChatHandler::handleTextEmote(network::Packet& packet) {
|
||||
const bool legacyFormat = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool legacyFormat = isPreWotlk();
|
||||
TextEmoteData data;
|
||||
if (!TextEmoteParser::parse(packet, data, legacyFormat)) {
|
||||
LOG_WARNING("Failed to parse SMSG_TEXT_EMOTE");
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "pipeline/asset_manager.hpp"
|
||||
#include "pipeline/dbc_loader.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include "game/protocol_constants.hpp"
|
||||
#include "rendering/animation/animation_ids.hpp"
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <algorithm>
|
||||
|
|
@ -79,9 +80,9 @@ const char* worldStateName(WorldState state) {
|
|||
} // end anonymous namespace
|
||||
|
||||
std::string formatCopperAmount(uint32_t amount) {
|
||||
uint32_t gold = amount / 10000;
|
||||
uint32_t silver = (amount / 100) % 100;
|
||||
uint32_t copper = amount % 100;
|
||||
uint32_t gold = amount / game::COPPER_PER_GOLD;
|
||||
uint32_t silver = (amount / game::COPPER_PER_SILVER) % 100;
|
||||
uint32_t copper = amount % game::COPPER_PER_SILVER;
|
||||
|
||||
std::ostringstream oss;
|
||||
bool wrote = false;
|
||||
|
|
@ -150,9 +151,9 @@ GameHandler::GameHandler(GameServices& services)
|
|||
|
||||
// Default action bar layout
|
||||
actionBar[0].type = ActionBarSlot::SPELL;
|
||||
actionBar[0].id = 6603; // Attack in slot 1
|
||||
actionBar[0].id = game::SPELL_ID_ATTACK; // Attack in slot 1
|
||||
actionBar[11].type = ActionBarSlot::SPELL;
|
||||
actionBar[11].id = 8690; // Hearthstone in slot 12
|
||||
actionBar[11].id = game::SPELL_ID_HEARTHSTONE; // Hearthstone in slot 12
|
||||
|
||||
// Build the opcode dispatch table (replaces switch(*logicalOp) in handlePacket)
|
||||
registerOpcodeHandlers();
|
||||
|
|
@ -365,11 +366,11 @@ void GameHandler::updateNetworking(float deltaTime) {
|
|||
lastRxTime_.time_since_epoch().count() > 0) {
|
||||
auto silenceMs = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - lastRxTime_).count();
|
||||
if (silenceMs > 10000 && !rxSilenceLogged_) {
|
||||
if (silenceMs > game::RX_SILENCE_WARNING_MS && !rxSilenceLogged_) {
|
||||
rxSilenceLogged_ = true;
|
||||
LOG_WARNING("RX SILENCE: No packets from server for ", silenceMs, "ms — possible soft disconnect");
|
||||
}
|
||||
if (silenceMs > 15000 && !rxSilence15sLogged_) {
|
||||
if (silenceMs > game::RX_SILENCE_CRITICAL_MS && !rxSilence15sLogged_) {
|
||||
rxSilence15sLogged_ = true;
|
||||
LOG_WARNING("RX SILENCE: 15s — server appears to have stopped sending");
|
||||
}
|
||||
|
|
@ -393,7 +394,7 @@ void GameHandler::updateNetworking(float deltaTime) {
|
|||
LOG_DEBUG("Warden gate status: elapsed=", wardenGateElapsed_,
|
||||
"s connected=", socket->isConnected() ? "yes" : "no",
|
||||
" packetsAfterGate=", wardenPacketsAfterGate_);
|
||||
wardenGateNextStatusLog_ += 30.0f;
|
||||
wardenGateNextStatusLog_ += game::WARDEN_GATE_LOG_INTERVAL_SEC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -420,7 +421,7 @@ if (onTaxiFlight_) {
|
|||
auto playerEntity = entityController_->getEntityManager().getEntity(playerGuid);
|
||||
auto unit = std::dynamic_pointer_cast<Unit>(playerEntity);
|
||||
if (unit &&
|
||||
(unit->getUnitFlags() & 0x00000100) == 0 &&
|
||||
(unit->getUnitFlags() & game::UNIT_FLAG_TAXI_FLIGHT) == 0 &&
|
||||
!taxiClientActive_ &&
|
||||
!taxiActivatePending_ &&
|
||||
taxiStartGrace_ <= 0.0f) {
|
||||
|
|
@ -452,7 +453,7 @@ if (!onTaxiFlight_ && taxiMountActive_) {
|
|||
auto playerEntity = entityController_->getEntityManager().getEntity(playerGuid);
|
||||
auto playerUnit = std::dynamic_pointer_cast<Unit>(playerEntity);
|
||||
if (playerUnit) {
|
||||
serverStillTaxi = (playerUnit->getUnitFlags() & 0x00000100) != 0;
|
||||
serverStillTaxi = (playerUnit->getUnitFlags() & game::UNIT_FLAG_TAXI_FLIGHT) != 0;
|
||||
}
|
||||
|
||||
if (taxiStartGrace_ > 0.0f || serverStillTaxi || taxiClientActive_ || taxiActivatePending_) {
|
||||
|
|
@ -541,7 +542,7 @@ void GameHandler::updateAutoAttack(float deltaTime) {
|
|||
void GameHandler::updateEntityInterpolation(float deltaTime) {
|
||||
// Update entity movement interpolation (keeps targeting in sync with visuals)
|
||||
// Only update entities within reasonable distance for performance
|
||||
const float updateRadiusSq = 150.0f * 150.0f; // 150 unit radius
|
||||
const float updateRadiusSq = game::ENTITY_UPDATE_RADIUS * game::ENTITY_UPDATE_RADIUS; // 150 unit radius
|
||||
auto playerEntity = entityController_->getEntityManager().getEntity(playerGuid);
|
||||
glm::vec3 playerPos = playerEntity ? glm::vec3(playerEntity->getX(), playerEntity->getY(), playerEntity->getZ()) : glm::vec3(0.0f);
|
||||
|
||||
|
|
@ -788,7 +789,7 @@ void GameHandler::update(float deltaTime) {
|
|||
if (movementHandler_) movementHandler_->timeSinceLastMoveHeartbeatRef() += deltaTime;
|
||||
|
||||
const float currentPingInterval =
|
||||
(isPreWotlk()) ? 10.0f : pingInterval;
|
||||
(isPreWotlk()) ? game::CLASSIC_PING_INTERVAL_SEC : pingInterval;
|
||||
if (timeSinceLastPing >= currentPingInterval) {
|
||||
if (socket) {
|
||||
sendPing();
|
||||
|
|
@ -819,9 +820,9 @@ void GameHandler::update(float deltaTime) {
|
|||
!taxiClientActive_ &&
|
||||
(movementInfo.flags & locomotionFlags) == 0;
|
||||
float heartbeatInterval = (onTaxiFlight_ || taxiActivatePending_ || taxiClientActive_)
|
||||
? 0.25f
|
||||
: (classicLikeStationaryCombatSync ? 0.75f
|
||||
: (classicLikeCombatSync ? 0.20f
|
||||
? game::HEARTBEAT_INTERVAL_TAXI
|
||||
: (classicLikeStationaryCombatSync ? game::HEARTBEAT_INTERVAL_STATIONARY_COMBAT
|
||||
: (classicLikeCombatSync ? game::HEARTBEAT_INTERVAL_MOVING_COMBAT
|
||||
: moveHeartbeatInterval_));
|
||||
if (movementHandler_ && movementHandler_->timeSinceLastMoveHeartbeatRef() >= heartbeatInterval) {
|
||||
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
||||
|
|
@ -830,7 +831,7 @@ void GameHandler::update(float deltaTime) {
|
|||
|
||||
// Check area triggers (instance portals, tavern rests, etc.)
|
||||
areaTriggerCheckTimer_ += deltaTime;
|
||||
if (areaTriggerCheckTimer_ >= 0.25f) {
|
||||
if (areaTriggerCheckTimer_ >= game::AREA_TRIGGER_CHECK_INTERVAL) {
|
||||
areaTriggerCheckTimer_ = 0.0f;
|
||||
checkAreaTriggers();
|
||||
}
|
||||
|
|
@ -894,7 +895,7 @@ void GameHandler::update(float deltaTime) {
|
|||
if (!npc) return;
|
||||
float dx = movementInfo.x - npc->getX();
|
||||
float dy = movementInfo.y - npc->getY();
|
||||
if (std::sqrt(dx * dx + dy * dy) > 15.0f) {
|
||||
if (std::sqrt(dx * dx + dy * dy) > game::NPC_INTERACT_MAX_DISTANCE) {
|
||||
closeFn();
|
||||
LOG_INFO(label, " closed: walked too far from NPC");
|
||||
}
|
||||
|
|
@ -918,7 +919,7 @@ void GameHandler::update(float deltaTime) {
|
|||
// ============================================================
|
||||
|
||||
// WotLK 3.3.5a XP-to-next-level table (from player_xp_for_level)
|
||||
static const uint32_t XP_TABLE[] = {
|
||||
static constexpr uint32_t XP_TABLE[] = {
|
||||
0, // level 0 (unused)
|
||||
400, 900, 1400, 2100, 2800, 3600, 4500, 5400, 6500, 7600, // 1-10
|
||||
8700, 9800, 11000, 12300, 13600, 15000, 16400, 17800, 19300, 20800, // 11-20
|
||||
|
|
|
|||
|
|
@ -308,33 +308,37 @@ void InventoryHandler::registerOpcodes(DispatchTable& table) {
|
|||
};
|
||||
|
||||
table[Opcode::SMSG_INVENTORY_CHANGE_FAILURE] = [this](network::Packet& packet) {
|
||||
if ((packet.getRemainingSize()) >= 1) {
|
||||
uint8_t error = packet.readUInt8();
|
||||
if (error != 0) {
|
||||
LOG_WARNING("SMSG_INVENTORY_CHANGE_FAILURE: error=", (int)error);
|
||||
uint32_t requiredLevel = 0;
|
||||
if (packet.hasRemaining(17)) {
|
||||
packet.readUInt64();
|
||||
packet.readUInt64();
|
||||
packet.readUInt8();
|
||||
if (error == 1 && packet.hasRemaining(4))
|
||||
requiredLevel = packet.readUInt32();
|
||||
}
|
||||
const char* errMsg = nullptr;
|
||||
char levelBuf[64];
|
||||
switch (error) {
|
||||
case 1:
|
||||
if (requiredLevel > 0) {
|
||||
std::snprintf(levelBuf, sizeof(levelBuf),
|
||||
"You must reach level %u to use that item.", requiredLevel);
|
||||
owner_.addUIError(levelBuf);
|
||||
owner_.addSystemChatMessage(levelBuf);
|
||||
} else {
|
||||
owner_.addUIError("You must reach a higher level to use that item.");
|
||||
owner_.addSystemChatMessage("You must reach a higher level to use that item.");
|
||||
}
|
||||
return;
|
||||
case 2: errMsg = "You don't have the required skill."; break;
|
||||
if (packet.getRemainingSize() < 1) return;
|
||||
uint8_t error = packet.readUInt8();
|
||||
if (error == 0) return;
|
||||
|
||||
LOG_WARNING("SMSG_INVENTORY_CHANGE_FAILURE: error=", (int)error);
|
||||
uint32_t requiredLevel = 0;
|
||||
if (packet.hasRemaining(17)) {
|
||||
packet.readUInt64();
|
||||
packet.readUInt64();
|
||||
packet.readUInt8();
|
||||
if (error == 1 && packet.hasRemaining(4))
|
||||
requiredLevel = packet.readUInt32();
|
||||
}
|
||||
|
||||
// Level requirement has its own formatting
|
||||
if (error == 1) {
|
||||
char levelBuf[64];
|
||||
if (requiredLevel > 0) {
|
||||
std::snprintf(levelBuf, sizeof(levelBuf),
|
||||
"You must reach level %u to use that item.", requiredLevel);
|
||||
} else {
|
||||
std::snprintf(levelBuf, sizeof(levelBuf),
|
||||
"You must reach a higher level to use that item.");
|
||||
}
|
||||
owner_.addUIError(levelBuf);
|
||||
owner_.addSystemChatMessage(levelBuf);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* errMsg = nullptr;
|
||||
switch (error) {
|
||||
case 3: errMsg = "That item doesn't go in that slot."; break;
|
||||
case 4: errMsg = "That bag is full."; break;
|
||||
case 5: errMsg = "Can't put bags in bags."; break;
|
||||
|
|
@ -392,14 +396,12 @@ void InventoryHandler::registerOpcodes(DispatchTable& table) {
|
|||
case 88: errMsg = "Requires the right talent."; break;
|
||||
default: break;
|
||||
}
|
||||
std::string msg = errMsg ? errMsg : "Inventory error (" + std::to_string(error) + ").";
|
||||
owner_.addUIError(msg);
|
||||
owner_.addSystemChatMessage(msg);
|
||||
if (auto* ac = owner_.services().audioCoordinator) {
|
||||
if (auto* sfx = ac->getUiSoundManager())
|
||||
sfx->playError();
|
||||
}
|
||||
}
|
||||
std::string msg = errMsg ? errMsg : "Inventory error (" + std::to_string(error) + ").";
|
||||
owner_.addUIError(msg);
|
||||
owner_.addSystemChatMessage(msg);
|
||||
if (auto* ac = owner_.services().audioCoordinator) {
|
||||
if (auto* sfx = ac->getUiSoundManager())
|
||||
sfx->playError();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ void MovementHandler::sendMovement(Opcode opcode) {
|
|||
movementInfo.time = movementTime;
|
||||
|
||||
if (opcode == Opcode::MSG_MOVE_SET_FACING &&
|
||||
(isClassicLikeExpansion() || isActiveExpansion("tbc"))) {
|
||||
isPreWotlk()) {
|
||||
const float facingDelta = core::coords::normalizeAngleRad(
|
||||
movementInfo.orientation - lastFacingSentOrientation_);
|
||||
const uint32_t sinceLastFacingMs =
|
||||
|
|
@ -833,7 +833,7 @@ network::Packet MovementHandler::buildForceAck(Opcode ackOpcode, uint32_t counte
|
|||
|
||||
void MovementHandler::handleForceSpeedChange(network::Packet& packet, const char* name,
|
||||
Opcode ackOpcode, float* speedStorage) {
|
||||
const bool fscTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool fscTbcLike = isPreWotlk();
|
||||
uint64_t guid = fscTbcLike
|
||||
? packet.readUInt64() : packet.readPackedGuid();
|
||||
uint32_t counter = packet.readUInt32();
|
||||
|
|
@ -881,7 +881,7 @@ void MovementHandler::handleForceRunSpeedChange(network::Packet& packet) {
|
|||
}
|
||||
|
||||
void MovementHandler::handleForceMoveRootState(network::Packet& packet, bool rooted) {
|
||||
const bool rootTbc = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool rootTbc = isPreWotlk();
|
||||
if (packet.getRemainingSize() < (rootTbc ? 8u : 2u)) return;
|
||||
uint64_t guid = rootTbc
|
||||
? packet.readUInt64() : packet.readPackedGuid();
|
||||
|
|
@ -907,7 +907,7 @@ void MovementHandler::handleForceMoveRootState(network::Packet& packet, bool roo
|
|||
|
||||
void MovementHandler::handleForceMoveFlagChange(network::Packet& packet, const char* name,
|
||||
Opcode ackOpcode, uint32_t flag, bool set) {
|
||||
const bool fmfTbcLike = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool fmfTbcLike = isPreWotlk();
|
||||
if (packet.getRemainingSize() < (fmfTbcLike ? 8u : 2u)) return;
|
||||
uint64_t guid = fmfTbcLike
|
||||
? packet.readUInt64() : packet.readPackedGuid();
|
||||
|
|
@ -932,7 +932,7 @@ void MovementHandler::handleForceMoveFlagChange(network::Packet& packet, const c
|
|||
}
|
||||
|
||||
void MovementHandler::handleMoveSetCollisionHeight(network::Packet& packet) {
|
||||
const bool legacyGuid = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool legacyGuid = isPreWotlk();
|
||||
if (packet.getRemainingSize() < (legacyGuid ? 8u : 2u)) return;
|
||||
uint64_t guid = legacyGuid ? packet.readUInt64() : packet.readPackedGuid();
|
||||
if (!packet.hasRemaining(8)) return;
|
||||
|
|
@ -954,7 +954,7 @@ void MovementHandler::handleMoveSetCollisionHeight(network::Packet& packet) {
|
|||
}
|
||||
|
||||
void MovementHandler::handleMoveKnockBack(network::Packet& packet) {
|
||||
const bool mkbTbc = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool mkbTbc = isPreWotlk();
|
||||
if (packet.getRemainingSize() < (mkbTbc ? 8u : 2u)) return;
|
||||
uint64_t guid = mkbTbc
|
||||
? packet.readUInt64() : packet.readPackedGuid();
|
||||
|
|
@ -985,7 +985,7 @@ void MovementHandler::handleMoveKnockBack(network::Packet& packet) {
|
|||
// ============================================================
|
||||
|
||||
void MovementHandler::handleMoveSetSpeed(network::Packet& packet) {
|
||||
const bool useFull = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool useFull = isPreWotlk();
|
||||
uint64_t moverGuid = useFull
|
||||
? packet.readUInt64() : packet.readPackedGuid();
|
||||
|
||||
|
|
@ -1010,7 +1010,7 @@ void MovementHandler::handleMoveSetSpeed(network::Packet& packet) {
|
|||
}
|
||||
|
||||
void MovementHandler::handleOtherPlayerMovement(network::Packet& packet) {
|
||||
const bool otherMoveTbc = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool otherMoveTbc = isPreWotlk();
|
||||
uint64_t moverGuid = otherMoveTbc
|
||||
? packet.readUInt64() : packet.readPackedGuid();
|
||||
if (moverGuid == owner_.getPlayerGuid() || moverGuid == 0) {
|
||||
|
|
@ -1646,7 +1646,7 @@ void MovementHandler::handleMonsterMoveTransport(network::Packet& packet) {
|
|||
// ============================================================
|
||||
|
||||
void MovementHandler::handleTeleportAck(network::Packet& packet) {
|
||||
const bool taTbc = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool taTbc = isPreWotlk();
|
||||
if (packet.getRemainingSize() < (taTbc ? 8u : 4u)) {
|
||||
LOG_WARNING("MSG_MOVE_TELEPORT_ACK too short");
|
||||
return;
|
||||
|
|
@ -1657,7 +1657,7 @@ void MovementHandler::handleTeleportAck(network::Packet& packet) {
|
|||
if (!packet.hasRemaining(4)) return;
|
||||
uint32_t counter = packet.readUInt32();
|
||||
|
||||
const bool taNoFlags2 = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool taNoFlags2 = isPreWotlk();
|
||||
const size_t minMoveSz = taNoFlags2 ? (4 + 4 + 4 * 4) : (4 + 2 + 4 + 4 * 4);
|
||||
if (packet.getRemainingSize() < minMoveSz) {
|
||||
LOG_WARNING("MSG_MOVE_TELEPORT_ACK: not enough data for movement info");
|
||||
|
|
@ -2048,8 +2048,8 @@ void MovementHandler::applyTaxiMountForCurrentNode() {
|
|||
else if (it->second.mountDisplayIdHorde != 0) mountId = it->second.mountDisplayIdHorde;
|
||||
}
|
||||
if (mountId == 0) {
|
||||
static const uint32_t kAllianceTaxiDisplays[] = {1210u, 1211u, 1212u, 1213u};
|
||||
static const uint32_t kHordeTaxiDisplays[] = {1310u, 1311u, 1312u};
|
||||
static constexpr uint32_t kAllianceTaxiDisplays[] = {1210u, 1211u, 1212u, 1213u};
|
||||
static constexpr uint32_t kHordeTaxiDisplays[] = {1310u, 1311u, 1312u};
|
||||
mountId = isAlliance ? kAllianceTaxiDisplays[0] : kHordeTaxiDisplays[0];
|
||||
}
|
||||
if (mountId == 0) {
|
||||
|
|
|
|||
|
|
@ -612,7 +612,7 @@ void SocialHandler::handleInspectResults(network::Packet& packet) {
|
|||
}
|
||||
|
||||
// talentType == 1: inspect result
|
||||
const bool talentTbc = isClassicLikeExpansion() || isActiveExpansion("tbc");
|
||||
const bool talentTbc = isPreWotlk();
|
||||
if (packet.getRemainingSize() < (talentTbc ? 8u : 2u)) return;
|
||||
uint64_t guid = talentTbc
|
||||
? packet.readUInt64() : packet.readPackedGuid();
|
||||
|
|
|
|||
|
|
@ -62,6 +62,33 @@ static audio::SpellSoundManager::MagicSchool schoolMaskToMagicSchool(uint32_t ma
|
|||
return audio::SpellSoundManager::MagicSchool::ARCANE;
|
||||
}
|
||||
|
||||
// ---- Extracted helpers to reduce nesting in handleSpellGo ----
|
||||
|
||||
audio::SpellSoundManager::MagicSchool SpellHandler::resolveSpellSchool(uint32_t spellId) {
|
||||
owner_.loadSpellNameCache();
|
||||
auto it = owner_.spellNameCacheRef().find(spellId);
|
||||
if (it != owner_.spellNameCacheRef().end() && it->second.schoolMask)
|
||||
return schoolMaskToMagicSchool(it->second.schoolMask);
|
||||
return audio::SpellSoundManager::MagicSchool::ARCANE;
|
||||
}
|
||||
|
||||
void SpellHandler::playSpellCastSound(uint32_t spellId) {
|
||||
auto* ac = owner_.services().audioCoordinator;
|
||||
if (!ac) return;
|
||||
auto* ssm = ac->getSpellSoundManager();
|
||||
if (!ssm) return;
|
||||
ssm->playCast(resolveSpellSchool(spellId));
|
||||
}
|
||||
|
||||
void SpellHandler::playSpellImpactSound(uint32_t spellId) {
|
||||
auto* ac = owner_.services().audioCoordinator;
|
||||
if (!ac) return;
|
||||
auto* ssm = ac->getSpellSoundManager();
|
||||
if (!ssm) return;
|
||||
ssm->playImpact(resolveSpellSchool(spellId),
|
||||
audio::SpellSoundManager::SpellPower::MEDIUM);
|
||||
}
|
||||
|
||||
|
||||
static std::string displaySpellName(GameHandler& handler, uint32_t spellId) {
|
||||
if (spellId == 0) return {};
|
||||
|
|
@ -929,18 +956,8 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
|
|||
|
||||
if (data.casterUnit == owner_.getPlayerGuid()) {
|
||||
// Play cast-complete sound
|
||||
if (!owner_.isProfessionSpell(data.spellId)) {
|
||||
if (auto* ac = owner_.services().audioCoordinator) {
|
||||
if (auto* ssm = ac->getSpellSoundManager()) {
|
||||
owner_.loadSpellNameCache();
|
||||
auto it = owner_.spellNameCacheRef().find(data.spellId);
|
||||
auto school = (it != owner_.spellNameCacheRef().end() && it->second.schoolMask)
|
||||
? schoolMaskToMagicSchool(it->second.schoolMask)
|
||||
: audio::SpellSoundManager::MagicSchool::ARCANE;
|
||||
ssm->playCast(school);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!owner_.isProfessionSpell(data.spellId))
|
||||
playSpellCastSound(data.spellId);
|
||||
|
||||
// Instant melee abilities → trigger attack animation
|
||||
uint32_t sid = data.spellId;
|
||||
|
|
@ -1039,18 +1056,8 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
|
|||
for (const auto& tgt : data.hitTargets) {
|
||||
if (tgt == owner_.getPlayerGuid()) { targetsPlayer = true; break; }
|
||||
}
|
||||
if (targetsPlayer) {
|
||||
if (auto* ac = owner_.services().audioCoordinator) {
|
||||
if (auto* ssm = ac->getSpellSoundManager()) {
|
||||
owner_.loadSpellNameCache();
|
||||
auto it = owner_.spellNameCacheRef().find(data.spellId);
|
||||
auto school = (it != owner_.spellNameCacheRef().end() && it->second.schoolMask)
|
||||
? schoolMaskToMagicSchool(it->second.schoolMask)
|
||||
: audio::SpellSoundManager::MagicSchool::ARCANE;
|
||||
ssm->playCast(school);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetsPlayer)
|
||||
playSpellCastSound(data.spellId);
|
||||
}
|
||||
|
||||
// Clear unit cast bar
|
||||
|
|
@ -1085,18 +1092,8 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
|
|||
owner_.addonEventCallbackRef()("UNIT_SPELLCAST_SUCCEEDED", {unitId, std::to_string(data.spellId)});
|
||||
}
|
||||
|
||||
if (playerIsHit || playerHitEnemy) {
|
||||
if (auto* ac = owner_.services().audioCoordinator) {
|
||||
if (auto* ssm = ac->getSpellSoundManager()) {
|
||||
owner_.loadSpellNameCache();
|
||||
auto it = owner_.spellNameCacheRef().find(data.spellId);
|
||||
auto school = (it != owner_.spellNameCacheRef().end() && it->second.schoolMask)
|
||||
? schoolMaskToMagicSchool(it->second.schoolMask)
|
||||
: audio::SpellSoundManager::MagicSchool::ARCANE;
|
||||
ssm->playImpact(school, audio::SpellSoundManager::SpellPower::MEDIUM);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (playerIsHit || playerHitEnemy)
|
||||
playSpellImpactSound(data.spellId);
|
||||
}
|
||||
|
||||
void SpellHandler::handleSpellCooldown(network::Packet& packet) {
|
||||
|
|
@ -1225,7 +1222,7 @@ void SpellHandler::handleAuraUpdate(network::Packet& packet, bool isAll) {
|
|||
|
||||
// Sprint aura detection — check if any sprint/dash speed buff is active
|
||||
if (data.guid == owner_.getPlayerGuid() && owner_.sprintAuraCallbackRef()) {
|
||||
static const uint32_t sprintSpells[] = {
|
||||
static constexpr uint32_t sprintSpells[] = {
|
||||
2983, 8696, 11305, // Rogue Sprint (ranks 1-3)
|
||||
1850, 9821, 33357, // Druid Dash (ranks 1-3)
|
||||
36554, // Shadowstep (speed component)
|
||||
|
|
@ -1816,7 +1813,7 @@ void SpellHandler::loadSpellNameCache() const {
|
|||
if (hasSchoolMask) {
|
||||
entry.schoolMask = dbc->getUInt32(i, schoolMaskField);
|
||||
} else if (hasSchoolEnum) {
|
||||
static const uint32_t enumToBitmask[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40};
|
||||
static constexpr uint32_t enumToBitmask[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40};
|
||||
uint32_t e = dbc->getUInt32(i, schoolEnumField);
|
||||
entry.schoolMask = (e < 7) ? enumToBitmask[e] : 0;
|
||||
}
|
||||
|
|
@ -2941,6 +2938,163 @@ void SpellHandler::handleSpellInstaKillLog(network::Packet& packet) {
|
|||
packet.skipAll();
|
||||
}
|
||||
|
||||
// ---- handleSpellLogExecute per-effect parsers (extracted to reduce nesting) ----
|
||||
|
||||
void SpellHandler::parseEffectPowerDrain(network::Packet& packet, uint32_t effectLogCount,
|
||||
uint64_t caster, uint32_t spellId,
|
||||
bool isPlayerCaster, bool usesFullGuid) {
|
||||
// SPELL_EFFECT_POWER_DRAIN: packed_guid target + uint32 amount + uint32 powerType + float multiplier
|
||||
const uint64_t playerGuid = owner_.getPlayerGuid();
|
||||
for (uint32_t li = 0; li < effectLogCount; ++li) {
|
||||
if (!packet.hasRemaining(usesFullGuid ? 8u : 1u)
|
||||
|| (!usesFullGuid && !packet.hasFullPackedGuid())) {
|
||||
packet.skipAll(); break;
|
||||
}
|
||||
uint64_t drainTarget = usesFullGuid ? packet.readUInt64() : packet.readPackedGuid();
|
||||
if (!packet.hasRemaining(12)) { packet.skipAll(); break; }
|
||||
uint32_t drainAmount = packet.readUInt32();
|
||||
uint32_t drainPower = packet.readUInt32(); // 0=mana,1=rage,3=energy,6=runic
|
||||
float drainMult = packet.readFloat();
|
||||
|
||||
LOG_DEBUG("SMSG_SPELLLOGEXECUTE POWER_DRAIN: spell=", spellId,
|
||||
" power=", drainPower, " amount=", drainAmount,
|
||||
" multiplier=", drainMult);
|
||||
if (drainAmount == 0) continue;
|
||||
|
||||
const auto powerByte = static_cast<uint8_t>(drainPower);
|
||||
if (drainTarget == playerGuid)
|
||||
owner_.addCombatText(CombatTextEntry::POWER_DRAIN,
|
||||
static_cast<int32_t>(drainAmount), spellId, false,
|
||||
powerByte, caster, drainTarget);
|
||||
if (!isPlayerCaster) continue;
|
||||
if (drainTarget != playerGuid)
|
||||
owner_.addCombatText(CombatTextEntry::POWER_DRAIN,
|
||||
static_cast<int32_t>(drainAmount), spellId, true,
|
||||
powerByte, caster, drainTarget);
|
||||
if (drainMult <= 0.0f || !std::isfinite(drainMult)) continue;
|
||||
const uint32_t gained = static_cast<uint32_t>(
|
||||
std::lround(static_cast<double>(drainAmount) * static_cast<double>(drainMult)));
|
||||
if (gained > 0)
|
||||
owner_.addCombatText(CombatTextEntry::ENERGIZE,
|
||||
static_cast<int32_t>(gained), spellId, true,
|
||||
powerByte, caster, caster);
|
||||
}
|
||||
}
|
||||
|
||||
void SpellHandler::parseEffectHealthLeech(network::Packet& packet, uint32_t effectLogCount,
|
||||
uint64_t caster, uint32_t spellId,
|
||||
bool isPlayerCaster, bool usesFullGuid) {
|
||||
// SPELL_EFFECT_HEALTH_LEECH: packed_guid target + uint32 amount + float multiplier
|
||||
const uint64_t playerGuid = owner_.getPlayerGuid();
|
||||
for (uint32_t li = 0; li < effectLogCount; ++li) {
|
||||
if (!packet.hasRemaining(usesFullGuid ? 8u : 1u)
|
||||
|| (!usesFullGuid && !packet.hasFullPackedGuid())) {
|
||||
packet.skipAll(); break;
|
||||
}
|
||||
uint64_t leechTarget = usesFullGuid ? packet.readUInt64() : packet.readPackedGuid();
|
||||
if (!packet.hasRemaining(8)) { packet.skipAll(); break; }
|
||||
uint32_t leechAmount = packet.readUInt32();
|
||||
float leechMult = packet.readFloat();
|
||||
|
||||
LOG_DEBUG("SMSG_SPELLLOGEXECUTE HEALTH_LEECH: spell=", spellId,
|
||||
" amount=", leechAmount, " multiplier=", leechMult);
|
||||
if (leechAmount == 0) continue;
|
||||
|
||||
if (leechTarget == playerGuid) {
|
||||
owner_.addCombatText(CombatTextEntry::SPELL_DAMAGE,
|
||||
static_cast<int32_t>(leechAmount), spellId, false, 0,
|
||||
caster, leechTarget);
|
||||
} else if (isPlayerCaster) {
|
||||
owner_.addCombatText(CombatTextEntry::SPELL_DAMAGE,
|
||||
static_cast<int32_t>(leechAmount), spellId, true, 0,
|
||||
caster, leechTarget);
|
||||
}
|
||||
if (!isPlayerCaster || leechMult <= 0.0f || !std::isfinite(leechMult)) continue;
|
||||
const uint32_t gained = static_cast<uint32_t>(
|
||||
std::lround(static_cast<double>(leechAmount) * static_cast<double>(leechMult)));
|
||||
if (gained > 0)
|
||||
owner_.addCombatText(CombatTextEntry::HEAL,
|
||||
static_cast<int32_t>(gained), spellId, true, 0,
|
||||
caster, caster);
|
||||
}
|
||||
}
|
||||
|
||||
void SpellHandler::parseEffectCreateItem(network::Packet& packet, uint32_t effectLogCount,
|
||||
uint64_t /*caster*/, uint32_t spellId,
|
||||
bool isPlayerCaster) {
|
||||
// SPELL_EFFECT_CREATE_ITEM / CREATE_ITEM2: uint32 itemEntry per log entry
|
||||
for (uint32_t li = 0; li < effectLogCount; ++li) {
|
||||
if (!packet.hasRemaining(4)) break;
|
||||
uint32_t itemEntry = packet.readUInt32();
|
||||
if (!isPlayerCaster || itemEntry == 0) continue;
|
||||
|
||||
owner_.ensureItemInfo(itemEntry);
|
||||
const ItemQueryResponseData* info = owner_.getItemInfo(itemEntry);
|
||||
std::string itemName = (info && !info->name.empty())
|
||||
? info->name : ("item #" + std::to_string(itemEntry));
|
||||
const auto& spellName = owner_.getSpellName(spellId);
|
||||
std::string msg = spellName.empty()
|
||||
? ("You create: " + itemName + ".")
|
||||
: ("You create " + itemName + " using " + spellName + ".");
|
||||
owner_.addSystemChatMessage(msg);
|
||||
LOG_DEBUG("SMSG_SPELLLOGEXECUTE CREATE_ITEM: spell=", spellId,
|
||||
" item=", itemEntry, " name=", itemName);
|
||||
|
||||
// Repeat-craft queue: re-cast if more crafts remaining
|
||||
if (craftQueueRemaining_ > 0 && craftQueueSpellId_ == spellId) {
|
||||
--craftQueueRemaining_;
|
||||
if (craftQueueRemaining_ > 0)
|
||||
castSpell(craftQueueSpellId_, 0);
|
||||
else
|
||||
craftQueueSpellId_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpellHandler::parseEffectInterruptCast(network::Packet& packet, uint32_t effectLogCount,
|
||||
uint64_t caster, uint32_t spellId,
|
||||
bool isPlayerCaster, bool usesFullGuid) {
|
||||
// SPELL_EFFECT_INTERRUPT_CAST: packed_guid target + uint32 interrupted_spell_id
|
||||
const uint64_t playerGuid = owner_.getPlayerGuid();
|
||||
for (uint32_t li = 0; li < effectLogCount; ++li) {
|
||||
if (!packet.hasRemaining(usesFullGuid ? 8u : 1u)
|
||||
|| (!usesFullGuid && !packet.hasFullPackedGuid())) {
|
||||
packet.skipAll(); break;
|
||||
}
|
||||
uint64_t icTarget = usesFullGuid ? packet.readUInt64() : packet.readPackedGuid();
|
||||
if (!packet.hasRemaining(4)) { packet.skipAll(); break; }
|
||||
uint32_t icSpellId = packet.readUInt32();
|
||||
// Clear the interrupted unit's cast bar immediately
|
||||
unitCastStates_.erase(icTarget);
|
||||
// Record interrupt in combat log when player is involved
|
||||
if (isPlayerCaster || icTarget == playerGuid)
|
||||
owner_.addCombatText(CombatTextEntry::INTERRUPT, 0, icSpellId, isPlayerCaster, 0,
|
||||
caster, icTarget);
|
||||
LOG_DEBUG("SMSG_SPELLLOGEXECUTE INTERRUPT_CAST: spell=", spellId,
|
||||
" interrupted=", icSpellId, " target=0x", std::hex, icTarget, std::dec);
|
||||
}
|
||||
}
|
||||
|
||||
void SpellHandler::parseEffectFeedPet(network::Packet& packet, uint32_t effectLogCount,
|
||||
uint64_t /*caster*/, uint32_t /*spellId*/,
|
||||
bool isPlayerCaster) {
|
||||
// SPELL_EFFECT_FEED_PET: uint32 itemEntry per log entry
|
||||
for (uint32_t li = 0; li < effectLogCount; ++li) {
|
||||
if (!packet.hasRemaining(4)) break;
|
||||
uint32_t feedItem = packet.readUInt32();
|
||||
if (!isPlayerCaster || feedItem == 0) continue;
|
||||
|
||||
owner_.ensureItemInfo(feedItem);
|
||||
const ItemQueryResponseData* info = owner_.getItemInfo(feedItem);
|
||||
std::string itemName = (info && !info->name.empty())
|
||||
? info->name : ("item #" + std::to_string(feedItem));
|
||||
uint32_t feedQuality = info ? info->quality : 1u;
|
||||
owner_.addSystemChatMessage("You feed your pet " +
|
||||
buildItemLink(feedItem, feedQuality, itemName) + ".");
|
||||
LOG_DEBUG("SMSG_SPELLLOGEXECUTE FEED_PET: item=", feedItem, " name=", itemName);
|
||||
}
|
||||
}
|
||||
|
||||
void SpellHandler::handleSpellLogExecute(network::Packet& packet) {
|
||||
// WotLK/Classic/Turtle: packed_guid caster + uint32 spellId + uint32 effectCount
|
||||
// TBC: uint64 caster + uint32 spellId + uint32 effectCount
|
||||
|
|
@ -2973,142 +3127,22 @@ void SpellHandler::handleSpellLogExecute(network::Packet& packet) {
|
|||
uint8_t effectType = packet.readUInt8();
|
||||
uint32_t effectLogCount = packet.readUInt32();
|
||||
effectLogCount = std::min(effectLogCount, 64u); // sanity
|
||||
if (effectType == SpellEffect::POWER_DRAIN) {
|
||||
// SPELL_EFFECT_POWER_DRAIN: packed_guid target + uint32 amount + uint32 powerType + float multiplier
|
||||
for (uint32_t li = 0; li < effectLogCount; ++li) {
|
||||
if (!packet.hasRemaining(exeUsesFullGuid ? 8u : 1u)
|
||||
|| (!exeUsesFullGuid && !packet.hasFullPackedGuid())) {
|
||||
packet.skipAll(); break;
|
||||
}
|
||||
uint64_t drainTarget = exeUsesFullGuid
|
||||
? packet.readUInt64()
|
||||
: packet.readPackedGuid();
|
||||
if (!packet.hasRemaining(12)) { packet.skipAll(); break; }
|
||||
uint32_t drainAmount = packet.readUInt32();
|
||||
uint32_t drainPower = packet.readUInt32(); // 0=mana,1=rage,3=energy,6=runic
|
||||
float drainMult = packet.readFloat();
|
||||
if (drainAmount > 0) {
|
||||
if (drainTarget == owner_.getPlayerGuid())
|
||||
owner_.addCombatText(CombatTextEntry::POWER_DRAIN, static_cast<int32_t>(drainAmount), exeSpellId, false,
|
||||
static_cast<uint8_t>(drainPower),
|
||||
exeCaster, drainTarget);
|
||||
if (isPlayerCaster) {
|
||||
if (drainTarget != owner_.getPlayerGuid()) {
|
||||
owner_.addCombatText(CombatTextEntry::POWER_DRAIN, static_cast<int32_t>(drainAmount), exeSpellId, true,
|
||||
static_cast<uint8_t>(drainPower), exeCaster, drainTarget);
|
||||
}
|
||||
if (drainMult > 0.0f && std::isfinite(drainMult)) {
|
||||
const uint32_t gainedAmount = static_cast<uint32_t>(
|
||||
std::lround(static_cast<double>(drainAmount) * static_cast<double>(drainMult)));
|
||||
if (gainedAmount > 0) {
|
||||
owner_.addCombatText(CombatTextEntry::ENERGIZE, static_cast<int32_t>(gainedAmount), exeSpellId, true,
|
||||
static_cast<uint8_t>(drainPower), exeCaster, exeCaster);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("SMSG_SPELLLOGEXECUTE POWER_DRAIN: spell=", exeSpellId,
|
||||
" power=", drainPower, " amount=", drainAmount,
|
||||
" multiplier=", drainMult);
|
||||
}
|
||||
} else if (effectType == SpellEffect::HEALTH_LEECH) {
|
||||
// SPELL_EFFECT_HEALTH_LEECH: packed_guid target + uint32 amount + float multiplier
|
||||
for (uint32_t li = 0; li < effectLogCount; ++li) {
|
||||
if (!packet.hasRemaining(exeUsesFullGuid ? 8u : 1u)
|
||||
|| (!exeUsesFullGuid && !packet.hasFullPackedGuid())) {
|
||||
packet.skipAll(); break;
|
||||
}
|
||||
uint64_t leechTarget = exeUsesFullGuid
|
||||
? packet.readUInt64()
|
||||
: packet.readPackedGuid();
|
||||
if (!packet.hasRemaining(8)) { packet.skipAll(); break; }
|
||||
uint32_t leechAmount = packet.readUInt32();
|
||||
float leechMult = packet.readFloat();
|
||||
if (leechAmount > 0) {
|
||||
if (leechTarget == owner_.getPlayerGuid()) {
|
||||
owner_.addCombatText(CombatTextEntry::SPELL_DAMAGE, static_cast<int32_t>(leechAmount), exeSpellId, false, 0,
|
||||
exeCaster, leechTarget);
|
||||
} else if (isPlayerCaster) {
|
||||
owner_.addCombatText(CombatTextEntry::SPELL_DAMAGE, static_cast<int32_t>(leechAmount), exeSpellId, true, 0,
|
||||
exeCaster, leechTarget);
|
||||
}
|
||||
if (isPlayerCaster && leechMult > 0.0f && std::isfinite(leechMult)) {
|
||||
const uint32_t gainedAmount = static_cast<uint32_t>(
|
||||
std::lround(static_cast<double>(leechAmount) * static_cast<double>(leechMult)));
|
||||
if (gainedAmount > 0) {
|
||||
owner_.addCombatText(CombatTextEntry::HEAL, static_cast<int32_t>(gainedAmount), exeSpellId, true, 0,
|
||||
exeCaster, exeCaster);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("SMSG_SPELLLOGEXECUTE HEALTH_LEECH: spell=", exeSpellId,
|
||||
" amount=", leechAmount, " multiplier=", leechMult);
|
||||
}
|
||||
} else if (effectType == SpellEffect::CREATE_ITEM || effectType == SpellEffect::CREATE_ITEM2) {
|
||||
// SPELL_EFFECT_CREATE_ITEM / CREATE_ITEM2: uint32 itemEntry per log entry
|
||||
for (uint32_t li = 0; li < effectLogCount; ++li) {
|
||||
if (!packet.hasRemaining(4)) break;
|
||||
uint32_t itemEntry = packet.readUInt32();
|
||||
if (isPlayerCaster && itemEntry != 0) {
|
||||
owner_.ensureItemInfo(itemEntry);
|
||||
const ItemQueryResponseData* info = owner_.getItemInfo(itemEntry);
|
||||
std::string itemName = info && !info->name.empty()
|
||||
? info->name : ("item #" + std::to_string(itemEntry));
|
||||
const auto& spellName = owner_.getSpellName(exeSpellId);
|
||||
std::string msg = spellName.empty()
|
||||
? ("You create: " + itemName + ".")
|
||||
: ("You create " + itemName + " using " + spellName + ".");
|
||||
owner_.addSystemChatMessage(msg);
|
||||
LOG_DEBUG("SMSG_SPELLLOGEXECUTE CREATE_ITEM: spell=", exeSpellId,
|
||||
" item=", itemEntry, " name=", itemName);
|
||||
|
||||
// Repeat-craft queue: re-cast if more crafts remaining
|
||||
if (craftQueueRemaining_ > 0 && craftQueueSpellId_ == exeSpellId) {
|
||||
--craftQueueRemaining_;
|
||||
if (craftQueueRemaining_ > 0) {
|
||||
castSpell(craftQueueSpellId_, 0);
|
||||
} else {
|
||||
craftQueueSpellId_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (effectType == SpellEffect::POWER_DRAIN) {
|
||||
parseEffectPowerDrain(packet, effectLogCount, exeCaster, exeSpellId,
|
||||
isPlayerCaster, exeUsesFullGuid);
|
||||
} else if (effectType == SpellEffect::HEALTH_LEECH) {
|
||||
parseEffectHealthLeech(packet, effectLogCount, exeCaster, exeSpellId,
|
||||
isPlayerCaster, exeUsesFullGuid);
|
||||
} else if (effectType == SpellEffect::CREATE_ITEM || effectType == SpellEffect::CREATE_ITEM2) {
|
||||
parseEffectCreateItem(packet, effectLogCount, exeCaster, exeSpellId,
|
||||
isPlayerCaster);
|
||||
} else if (effectType == SpellEffect::INTERRUPT_CAST) {
|
||||
// SPELL_EFFECT_INTERRUPT_CAST: packed_guid target + uint32 interrupted_spell_id
|
||||
for (uint32_t li = 0; li < effectLogCount; ++li) {
|
||||
if (!packet.hasRemaining(exeUsesFullGuid ? 8u : 1u)
|
||||
|| (!exeUsesFullGuid && !packet.hasFullPackedGuid())) {
|
||||
packet.skipAll(); break;
|
||||
}
|
||||
uint64_t icTarget = exeUsesFullGuid
|
||||
? packet.readUInt64()
|
||||
: packet.readPackedGuid();
|
||||
if (!packet.hasRemaining(4)) { packet.skipAll(); break; }
|
||||
uint32_t icSpellId = packet.readUInt32();
|
||||
// Clear the interrupted unit's cast bar immediately
|
||||
unitCastStates_.erase(icTarget);
|
||||
// Record interrupt in combat log when player is involved
|
||||
if (isPlayerCaster || icTarget == owner_.getPlayerGuid())
|
||||
owner_.addCombatText(CombatTextEntry::INTERRUPT, 0, icSpellId, isPlayerCaster, 0,
|
||||
exeCaster, icTarget);
|
||||
LOG_DEBUG("SMSG_SPELLLOGEXECUTE INTERRUPT_CAST: spell=", exeSpellId,
|
||||
" interrupted=", icSpellId, " target=0x", std::hex, icTarget, std::dec);
|
||||
}
|
||||
parseEffectInterruptCast(packet, effectLogCount, exeCaster, exeSpellId,
|
||||
isPlayerCaster, exeUsesFullGuid);
|
||||
} else if (effectType == SpellEffect::FEED_PET) {
|
||||
// SPELL_EFFECT_FEED_PET: uint32 itemEntry per log entry
|
||||
for (uint32_t li = 0; li < effectLogCount; ++li) {
|
||||
if (!packet.hasRemaining(4)) break;
|
||||
uint32_t feedItem = packet.readUInt32();
|
||||
if (isPlayerCaster && feedItem != 0) {
|
||||
owner_.ensureItemInfo(feedItem);
|
||||
const ItemQueryResponseData* info = owner_.getItemInfo(feedItem);
|
||||
std::string itemName = info && !info->name.empty()
|
||||
? info->name : ("item #" + std::to_string(feedItem));
|
||||
uint32_t feedQuality = info ? info->quality : 1u;
|
||||
owner_.addSystemChatMessage("You feed your pet " + buildItemLink(feedItem, feedQuality, itemName) + ".");
|
||||
LOG_DEBUG("SMSG_SPELLLOGEXECUTE FEED_PET: item=", feedItem, " name=", itemName);
|
||||
}
|
||||
}
|
||||
parseEffectFeedPet(packet, effectLogCount, exeCaster, exeSpellId,
|
||||
isPlayerCaster);
|
||||
} else {
|
||||
// Unknown effect type — stop parsing to avoid misalignment
|
||||
packet.skipAll();
|
||||
|
|
|
|||
|
|
@ -1282,14 +1282,14 @@ uint32_t TransportManager::pickFallbackMovingPath(uint32_t entry, uint32_t displ
|
|||
(displayId == 3031u || displayId == 7546u || displayId == 1587u || displayId == 807u || displayId == 808u);
|
||||
|
||||
if (looksLikeShip) {
|
||||
static const uint32_t kShipCandidates[] = {176080u, 176081u, 176082u, 176083u, 176084u, 176085u, 194675u};
|
||||
static constexpr uint32_t kShipCandidates[] = {176080u, 176081u, 176082u, 176083u, 176084u, 176085u, 194675u};
|
||||
for (uint32_t id : kShipCandidates) {
|
||||
if (isUsableMovingPath(id)) return id;
|
||||
}
|
||||
}
|
||||
|
||||
if (looksLikeZeppelin) {
|
||||
static const uint32_t kZeppelinCandidates[] = {193182u, 193183u, 188360u, 190587u};
|
||||
static constexpr uint32_t kZeppelinCandidates[] = {193182u, 193183u, 188360u, 190587u};
|
||||
for (uint32_t id : kZeppelinCandidates) {
|
||||
if (isUsableMovingPath(id)) return id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ uint32_t WardenEmulator::hookAPI(const std::string& dllName,
|
|||
// Write a RET (0xC3) at the stub address as a safe fallback in case
|
||||
// the code hook fires after EIP has already advanced past our intercept.
|
||||
if (uc_) {
|
||||
static const uint8_t retInstr = 0xC3;
|
||||
static constexpr uint8_t retInstr = 0xC3;
|
||||
uc_mem_write(uc_, stubAddr, &retInstr, 1);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "core/application.hpp"
|
||||
#include "pipeline/asset_manager.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include "game/warden_constants.hpp"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
|
|
@ -355,7 +356,7 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
};
|
||||
|
||||
switch (wardenOpcode) {
|
||||
case 0x00: { // WARDEN_SMSG_MODULE_USE
|
||||
case WARDEN_SMSG_MODULE_USE: { // MODULE_USE
|
||||
// Format: [1 opcode][16 moduleHash][16 moduleKey][4 moduleSize]
|
||||
if (decrypted.size() < 37) {
|
||||
LOG_ERROR("Warden: MODULE_USE too short (", decrypted.size(), " bytes, need 37)");
|
||||
|
|
@ -379,15 +380,15 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
loadWardenCRFile(hashHex);
|
||||
}
|
||||
|
||||
// Respond with MODULE_MISSING (opcode 0x00) to request the module data
|
||||
std::vector<uint8_t> resp = { 0x00 }; // WARDEN_CMSG_MODULE_MISSING
|
||||
// Respond with MODULE_MISSING to request the module data
|
||||
std::vector<uint8_t> resp = { WARDEN_CMSG_MODULE_MISSING };
|
||||
sendWardenResponse(resp);
|
||||
wardenState_ = WardenState::WAIT_MODULE_CACHE;
|
||||
LOG_DEBUG("Warden: Sent MODULE_MISSING for module size=", wardenModuleSize_, ", waiting for data chunks");
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x01: { // WARDEN_SMSG_MODULE_CACHE (module data chunk)
|
||||
case WARDEN_SMSG_MODULE_CACHE: { // MODULE_CACHE (module data chunk)
|
||||
// Format: [1 opcode][2 chunkSize LE][chunkSize bytes data]
|
||||
if (decrypted.size() < 3) {
|
||||
LOG_ERROR("Warden: MODULE_CACHE too short");
|
||||
|
|
@ -463,8 +464,8 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
wardenLoadedModule_.reset();
|
||||
}
|
||||
|
||||
// Send MODULE_OK (opcode 0x01)
|
||||
std::vector<uint8_t> resp = { 0x01 }; // WARDEN_CMSG_MODULE_OK
|
||||
// Send MODULE_OK
|
||||
std::vector<uint8_t> resp = { WARDEN_CMSG_MODULE_OK };
|
||||
sendWardenResponse(resp);
|
||||
LOG_DEBUG("Warden: Sent MODULE_OK");
|
||||
}
|
||||
|
|
@ -472,7 +473,7 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
break;
|
||||
}
|
||||
|
||||
case 0x05: { // WARDEN_SMSG_HASH_REQUEST
|
||||
case WARDEN_SMSG_HASH_REQUEST: { // HASH_REQUEST
|
||||
// Format: [1 opcode][16 seed]
|
||||
if (decrypted.size() < 17) {
|
||||
LOG_ERROR("Warden: HASH_REQUEST too short (", decrypted.size(), " bytes, need 17)");
|
||||
|
|
@ -506,9 +507,9 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
if (match) {
|
||||
LOG_DEBUG("Warden: HASH_REQUEST — CR entry MATCHED, sending pre-computed reply");
|
||||
|
||||
// Send HASH_RESULT (opcode 0x04 + 20-byte reply)
|
||||
// Send HASH_RESULT
|
||||
std::vector<uint8_t> resp;
|
||||
resp.push_back(0x04);
|
||||
resp.push_back(WARDEN_CMSG_HASH_RESULT);
|
||||
resp.insert(resp.end(), match->reply, match->reply + 20);
|
||||
sendWardenResponse(resp);
|
||||
|
||||
|
|
@ -576,7 +577,7 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
break;
|
||||
}
|
||||
|
||||
case 0x02: { // WARDEN_SMSG_CHEAT_CHECKS_REQUEST
|
||||
case WARDEN_SMSG_CHEAT_CHECKS_REQUEST: { // CHEAT_CHECKS_REQUEST
|
||||
LOG_DEBUG("Warden: CHEAT_CHECKS_REQUEST (", decrypted.size(), " bytes)");
|
||||
|
||||
if (decrypted.size() < 3) {
|
||||
|
|
@ -660,9 +661,9 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
HMAC(EVP_sha1(), seed, 4, pat, patLen, out, &outLen);
|
||||
return outLen == SHA_DIGEST_LENGTH && !std::memcmp(out, hash, SHA_DIGEST_LENGTH);
|
||||
};
|
||||
static const uint8_t p1[] = {0x33,0xD2,0x33,0xC9,0xE8,0x87,0x07,0x1B,0x00,0xE8};
|
||||
static constexpr uint8_t p1[] = {0x33,0xD2,0x33,0xC9,0xE8,0x87,0x07,0x1B,0x00,0xE8};
|
||||
if (off == 13856 && len == sizeof(p1) && tryMatch(p1, sizeof(p1))) return true;
|
||||
static const uint8_t p2[] = {0x56,0x57,0xFC,0x8B,0x54,0x24,0x14,0x8B,
|
||||
static constexpr uint8_t p2[] = {0x56,0x57,0xFC,0x8B,0x54,0x24,0x14,0x8B,
|
||||
0x74,0x24,0x10,0x8B,0x44,0x24,0x0C,0x8B,0xCA,0x8B,0xF8,0xC1,
|
||||
0xE9,0x02,0x74,0x02,0xF3,0xA5,0xB1,0x03,0x23,0xCA,0x74,0x02,
|
||||
0xF3,0xA4,0x5F,0x5E,0xC3};
|
||||
|
|
@ -706,7 +707,7 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
LOG_WARNING("Warden: MEM offset=0x", [&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}(),
|
||||
" len=", (int)readLen,
|
||||
(strIdx ? " module=\"" + moduleName + "\"" : ""));
|
||||
if (offset == 0x00CF0BC8 && readLen == 4 && wardenMemory_ && wardenMemory_->isLoaded()) {
|
||||
if (offset == WARDEN_TICKCOUNT_ADDRESS && readLen == 4 && wardenMemory_ && wardenMemory_->isLoaded()) {
|
||||
uint32_t now = static_cast<uint32_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count());
|
||||
|
|
@ -717,25 +718,25 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
wardenMemory_->readMemory(offset, readLen, memBuf.data());
|
||||
if (memOk) {
|
||||
const char* region = "?";
|
||||
if (offset >= 0x7FFE0000 && offset < 0x7FFF0000) region = "KUSER";
|
||||
else if (offset >= 0x400000 && offset < 0x800000) region = ".text/.code";
|
||||
else if (offset >= 0x7FF000 && offset < 0x827000) region = ".rdata";
|
||||
else if (offset >= 0x827000 && offset < 0x883000) region = ".data(raw)";
|
||||
else if (offset >= 0x883000 && offset < 0xD06000) region = ".data(BSS)";
|
||||
if (offset >= KUSER_SHARED_DATA_BASE && offset < KUSER_SHARED_DATA_END) region = "KUSER";
|
||||
else if (offset >= PE_TEXT_SECTION_BASE && offset < PE_TEXT_SECTION_END) region = ".text/.code";
|
||||
else if (offset >= PE_RDATA_SECTION_BASE && offset < PE_DATA_RAW_SECTION_BASE) region = ".rdata";
|
||||
else if (offset >= PE_DATA_RAW_SECTION_BASE && offset < PE_BSS_SECTION_BASE) region = ".data(raw)";
|
||||
else if (offset >= PE_BSS_SECTION_BASE && offset < PE_BSS_SECTION_END) region = ".data(BSS)";
|
||||
bool allZero = true;
|
||||
for (int i = 0; i < (int)readLen; i++) { if (memBuf[i] != 0) { allZero = false; break; } }
|
||||
std::string hexDump;
|
||||
for (int i = 0; i < (int)readLen; i++) { char hx[4]; snprintf(hx,4,"%02x ",memBuf[i]); hexDump += hx; }
|
||||
LOG_WARNING("Warden: MEM_CHECK served: [", hexDump, "] region=", region,
|
||||
(allZero && offset >= 0x883000 ? " \xe2\x98\x85""BSS_ZERO\xe2\x98\x85" : ""));
|
||||
if (offset == 0x7FFE026C && readLen == 12)
|
||||
(allZero && offset >= PE_BSS_SECTION_BASE ? " \xe2\x98\x85""BSS_ZERO\xe2\x98\x85" : ""));
|
||||
if (offset == WARDEN_WIN_VERSION_ADDRESS && readLen == 12)
|
||||
LOG_WARNING("Warden: Applying 4-byte ULONG alignment padding for WinVersionGet");
|
||||
resultData.push_back(0x00);
|
||||
resultData.push_back(WARDEN_MEM_CHECK_SUCCESS);
|
||||
resultData.insert(resultData.end(), memBuf.begin(), memBuf.end());
|
||||
} else {
|
||||
LOG_WARNING("Warden: MEM_CHECK -> 0xE9 (unmapped 0x",
|
||||
[&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}(), ")");
|
||||
resultData.push_back(0xE9);
|
||||
resultData.push_back(WARDEN_MEM_CHECK_UNMAPPED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -936,7 +937,7 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
};
|
||||
|
||||
// DB sanity check: "Warden packet process code search sanity check" (id=85)
|
||||
static const uint8_t kPacketProcessSanityPattern[] = {
|
||||
static constexpr uint8_t kPacketProcessSanityPattern[] = {
|
||||
0x33, 0xD2, 0x33, 0xC9, 0xE8, 0x87, 0x07, 0x1B, 0x00, 0xE8
|
||||
};
|
||||
if (offset == 13856 && length == sizeof(kPacketProcessSanityPattern) &&
|
||||
|
|
@ -945,7 +946,7 @@ void WardenHandler::handleWardenData(network::Packet& packet) {
|
|||
}
|
||||
|
||||
// Scripted sanity check: "Warden Memory Read check" in wardenwin.cpp
|
||||
static const uint8_t kWardenMemoryReadPattern[] = {
|
||||
static constexpr uint8_t kWardenMemoryReadPattern[] = {
|
||||
0x56, 0x57, 0xFC, 0x8B, 0x54, 0x24, 0x14, 0x8B,
|
||||
0x74, 0x24, 0x10, 0x8B, 0x44, 0x24, 0x0C, 0x8B,
|
||||
0xCA, 0x8B, 0xF8, 0xC1, 0xE9, 0x02, 0x74, 0x02,
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ void WardenMemory::patchRuntimeGlobals() {
|
|||
// FIND_CODE_BY_HASH (PAGE_B) brute-force search can find it.
|
||||
// This is the pattern VMaNGOS's "Warden Memory Read check" looks for.
|
||||
constexpr uint32_t MEMCPY_PATTERN_VA = 0xCE8700;
|
||||
static const uint8_t kWardenMemcpyPattern[37] = {
|
||||
static constexpr uint8_t kWardenMemcpyPattern[37] = {
|
||||
0x56, 0x57, 0xFC, 0x8B, 0x54, 0x24, 0x14, 0x8B,
|
||||
0x74, 0x24, 0x10, 0x8B, 0x44, 0x24, 0x0C, 0x8B,
|
||||
0xCA, 0x8B, 0xF8, 0xC1, 0xE9, 0x02, 0x74, 0x02,
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ network::Packet AuthSessionPacket::build(uint32_t build,
|
|||
"Blizzard_InspectUI", "Blizzard_MacroUI", "Blizzard_RaidUI",
|
||||
"Blizzard_TalentUI", "Blizzard_TradeSkillUI", "Blizzard_TrainerUI"
|
||||
};
|
||||
static const uint32_t standardModulusCRC = 0x4C1C776D;
|
||||
static constexpr uint32_t standardModulusCRC = 0x4C1C776D;
|
||||
for (const char* name : vanillaAddons) {
|
||||
// string (null-terminated)
|
||||
size_t len = strlen(name);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue