mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Handle missing WotLK packets: health/power updates, mirror timers, combo points, loot roll, titles, phase shift
- SMSG_HEALTH_UPDATE / SMSG_POWER_UPDATE: update entity HP/power via entityManager - SMSG_UPDATE_WORLD_STATE: single world state variable update (companion to INIT) - SMSG_UPDATE_COMBO_POINTS: store comboPoints_/comboTarget_ in GameHandler - SMSG_START_MIRROR_TIMER / SMSG_STOP_MIRROR_TIMER / SMSG_PAUSE_MIRROR_TIMER: breath/fatigue/feign timer state - MirrorTimer struct + getMirrorTimer() public getter; renderMirrorTimers() draws colored breath/fatigue bars above cast bar - SMSG_CAST_RESULT: WotLK extended cast result; clear cast bar and show reason on failure (result != 0) - SMSG_SPELL_FAILED_OTHER / SMSG_PROCRESIST: consume silently - SMSG_LOOT_START_ROLL: correct trigger for Need/Greed popup (replaces rollType=128 heuristic) - SMSG_STABLE_RESULT: show pet stable feedback in system chat (store/retrieve/buy slot/error) - SMSG_TITLE_EARNED: system chat notification for title earned/removed - SMSG_PLAYERBOUND / SMSG_BINDER_CONFIRM: hearthstone binding notification - SMSG_SET_PHASE_SHIFT: consume (WotLK phasing, no client action needed) - SMSG_TOGGLE_XP_GAIN: system chat notification
This commit is contained in:
parent
6df36f4588
commit
bd3bd1b5a6
4 changed files with 301 additions and 0 deletions
|
|
@ -865,6 +865,23 @@ public:
|
|||
uint32_t getWorldStateMapId() const { return worldStateMapId_; }
|
||||
uint32_t getWorldStateZoneId() const { return worldStateZoneId_; }
|
||||
|
||||
// Mirror timers (0=fatigue, 1=breath, 2=feigndeath)
|
||||
struct MirrorTimer {
|
||||
int32_t value = 0;
|
||||
int32_t maxValue = 0;
|
||||
int32_t scale = 0; // +1 = counting up, -1 = counting down
|
||||
bool paused = false;
|
||||
bool active = false;
|
||||
};
|
||||
const MirrorTimer& getMirrorTimer(int type) const {
|
||||
static MirrorTimer empty;
|
||||
return (type >= 0 && type < 3) ? mirrorTimers_[type] : empty;
|
||||
}
|
||||
|
||||
// Combo points
|
||||
uint8_t getComboPoints() const { return comboPoints_; }
|
||||
uint64_t getComboTarget() const { return comboTarget_; }
|
||||
|
||||
struct FactionStandingInit {
|
||||
uint8_t flags = 0;
|
||||
int32_t standing = 0;
|
||||
|
|
@ -1655,6 +1672,13 @@ private:
|
|||
uint32_t instanceDifficulty_ = 0;
|
||||
bool instanceIsHeroic_ = false;
|
||||
|
||||
// Mirror timers (0=fatigue, 1=breath, 2=feigndeath)
|
||||
MirrorTimer mirrorTimers_[3];
|
||||
|
||||
// Combo points (rogues/druids)
|
||||
uint8_t comboPoints_ = 0;
|
||||
uint64_t comboTarget_ = 0;
|
||||
|
||||
// Instance / raid lockouts
|
||||
std::vector<InstanceLockout> instanceLockouts_;
|
||||
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ private:
|
|||
void renderBagBar(game::GameHandler& gameHandler);
|
||||
void renderXpBar(game::GameHandler& gameHandler);
|
||||
void renderCastBar(game::GameHandler& gameHandler);
|
||||
void renderMirrorTimers(game::GameHandler& gameHandler);
|
||||
void renderCombatText(game::GameHandler& gameHandler);
|
||||
void renderPartyFrames(game::GameHandler& gameHandler);
|
||||
void renderGroupInvitePopup(game::GameHandler& gameHandler);
|
||||
|
|
|
|||
|
|
@ -1596,6 +1596,229 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
break;
|
||||
}
|
||||
|
||||
// ---- Entity health/power delta updates ----
|
||||
case Opcode::SMSG_HEALTH_UPDATE: {
|
||||
// packed_guid + uint32 health
|
||||
if (packet.getSize() - packet.getReadPos() < 2) break;
|
||||
uint64_t guid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() < 4) break;
|
||||
uint32_t hp = packet.readUInt32();
|
||||
auto entity = entityManager.getEntity(guid);
|
||||
if (auto* unit = dynamic_cast<Unit*>(entity.get())) {
|
||||
unit->setHealth(hp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_POWER_UPDATE: {
|
||||
// packed_guid + uint8 powerType + uint32 value
|
||||
if (packet.getSize() - packet.getReadPos() < 2) break;
|
||||
uint64_t guid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() < 5) break;
|
||||
uint8_t powerType = packet.readUInt8();
|
||||
uint32_t value = packet.readUInt32();
|
||||
auto entity = entityManager.getEntity(guid);
|
||||
if (auto* unit = dynamic_cast<Unit*>(entity.get())) {
|
||||
unit->setPowerByType(powerType, value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- World state single update ----
|
||||
case Opcode::SMSG_UPDATE_WORLD_STATE: {
|
||||
// uint32 field + uint32 value
|
||||
if (packet.getSize() - packet.getReadPos() < 8) break;
|
||||
uint32_t field = packet.readUInt32();
|
||||
uint32_t value = packet.readUInt32();
|
||||
worldStates_[field] = value;
|
||||
LOG_DEBUG("SMSG_UPDATE_WORLD_STATE: field=", field, " value=", value);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- Combo points ----
|
||||
case Opcode::SMSG_UPDATE_COMBO_POINTS: {
|
||||
// packed_guid (target) + uint8 points
|
||||
if (packet.getSize() - packet.getReadPos() < 2) break;
|
||||
uint64_t target = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
comboPoints_ = packet.readUInt8();
|
||||
comboTarget_ = target;
|
||||
LOG_DEBUG("SMSG_UPDATE_COMBO_POINTS: target=0x", std::hex, target,
|
||||
std::dec, " points=", static_cast<int>(comboPoints_));
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- Mirror timers (breath/fatigue/feign death) ----
|
||||
case Opcode::SMSG_START_MIRROR_TIMER: {
|
||||
// uint32 type + int32 value + int32 maxValue + int32 scale + uint32 tracker + uint8 paused
|
||||
if (packet.getSize() - packet.getReadPos() < 21) break;
|
||||
uint32_t type = packet.readUInt32();
|
||||
int32_t value = static_cast<int32_t>(packet.readUInt32());
|
||||
int32_t maxV = static_cast<int32_t>(packet.readUInt32());
|
||||
int32_t scale = static_cast<int32_t>(packet.readUInt32());
|
||||
/*uint32_t tracker =*/ packet.readUInt32();
|
||||
uint8_t paused = packet.readUInt8();
|
||||
if (type < 3) {
|
||||
mirrorTimers_[type].value = value;
|
||||
mirrorTimers_[type].maxValue = maxV;
|
||||
mirrorTimers_[type].scale = scale;
|
||||
mirrorTimers_[type].paused = (paused != 0);
|
||||
mirrorTimers_[type].active = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_STOP_MIRROR_TIMER: {
|
||||
// uint32 type
|
||||
if (packet.getSize() - packet.getReadPos() < 4) break;
|
||||
uint32_t type = packet.readUInt32();
|
||||
if (type < 3) {
|
||||
mirrorTimers_[type].active = false;
|
||||
mirrorTimers_[type].value = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_PAUSE_MIRROR_TIMER: {
|
||||
// uint32 type + uint8 paused
|
||||
if (packet.getSize() - packet.getReadPos() < 5) break;
|
||||
uint32_t type = packet.readUInt32();
|
||||
uint8_t paused = packet.readUInt8();
|
||||
if (type < 3) {
|
||||
mirrorTimers_[type].paused = (paused != 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- Cast result (WotLK extended cast failed) ----
|
||||
case Opcode::SMSG_CAST_RESULT:
|
||||
// WotLK: uint8 castCount + uint32 spellId + uint8 result [+ optional extra]
|
||||
// If result == 0, the spell successfully began; otherwise treat like SMSG_CAST_FAILED.
|
||||
if (packet.getSize() - packet.getReadPos() >= 6) {
|
||||
/*uint8_t castCount =*/ packet.readUInt8();
|
||||
/*uint32_t spellId =*/ packet.readUInt32();
|
||||
uint8_t result = packet.readUInt8();
|
||||
if (result != 0) {
|
||||
// Failure — clear cast bar and show message
|
||||
casting = false;
|
||||
currentCastSpellId = 0;
|
||||
castTimeRemaining = 0.0f;
|
||||
const char* reason = getSpellCastResultString(result, -1);
|
||||
MessageChatData msg;
|
||||
msg.type = ChatType::SYSTEM;
|
||||
msg.language = ChatLanguage::UNIVERSAL;
|
||||
msg.message = reason ? reason
|
||||
: ("Spell cast failed (error " + std::to_string(result) + ")");
|
||||
addLocalChatMessage(msg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// ---- Spell failed on another unit ----
|
||||
case Opcode::SMSG_SPELL_FAILED_OTHER:
|
||||
// packed_guid + uint8 castCount + uint32 spellId + uint8 reason — just consume
|
||||
packet.setReadPos(packet.getSize());
|
||||
break;
|
||||
|
||||
// ---- Spell proc resist log ----
|
||||
case Opcode::SMSG_PROCRESIST:
|
||||
// guid(8) + guid(8) + uint32 spellId + uint8 logSchoolMask — just consume
|
||||
packet.setReadPos(packet.getSize());
|
||||
break;
|
||||
|
||||
// ---- Loot start roll (Need/Greed popup trigger) ----
|
||||
case Opcode::SMSG_LOOT_START_ROLL: {
|
||||
// uint64 objectGuid + uint32 mapId + uint32 lootSlot + uint32 itemId
|
||||
// + uint32 randomSuffix + uint32 randomPropId + uint32 countdown + uint8 voteMask
|
||||
if (packet.getSize() - packet.getReadPos() < 33) break;
|
||||
uint64_t objectGuid = packet.readUInt64();
|
||||
/*uint32_t mapId =*/ packet.readUInt32();
|
||||
uint32_t slot = packet.readUInt32();
|
||||
uint32_t itemId = packet.readUInt32();
|
||||
/*uint32_t randSuffix =*/ packet.readUInt32();
|
||||
/*uint32_t randProp =*/ packet.readUInt32();
|
||||
/*uint32_t countdown =*/ packet.readUInt32();
|
||||
/*uint8_t voteMask =*/ packet.readUInt8();
|
||||
// Trigger the roll popup for local player
|
||||
pendingLootRollActive_ = true;
|
||||
pendingLootRoll_.objectGuid = objectGuid;
|
||||
pendingLootRoll_.slot = slot;
|
||||
pendingLootRoll_.itemId = itemId;
|
||||
auto* info = getItemInfo(itemId);
|
||||
pendingLootRoll_.itemName = info ? info->name : std::to_string(itemId);
|
||||
pendingLootRoll_.itemQuality = info ? static_cast<uint8_t>(info->quality) : 0;
|
||||
LOG_INFO("SMSG_LOOT_START_ROLL: item=", itemId, " (", pendingLootRoll_.itemName,
|
||||
") slot=", slot);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- Pet stable result ----
|
||||
case Opcode::SMSG_STABLE_RESULT: {
|
||||
// uint8 result
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
uint8_t result = packet.readUInt8();
|
||||
const char* msg = nullptr;
|
||||
switch (result) {
|
||||
case 0x01: msg = "Pet stored in stable."; break;
|
||||
case 0x06: msg = "Pet retrieved from stable."; break;
|
||||
case 0x07: msg = "Stable slot purchased."; break;
|
||||
case 0x08: msg = "Stable list updated."; break;
|
||||
case 0x09: msg = "Stable failed: not enough money or other error."; break;
|
||||
default: break;
|
||||
}
|
||||
if (msg) addSystemChatMessage(msg);
|
||||
LOG_INFO("SMSG_STABLE_RESULT: result=", static_cast<int>(result));
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- Title earned ----
|
||||
case Opcode::SMSG_TITLE_EARNED: {
|
||||
// uint32 titleBitIndex + uint32 isLost
|
||||
if (packet.getSize() - packet.getReadPos() < 8) break;
|
||||
uint32_t titleBit = packet.readUInt32();
|
||||
uint32_t isLost = packet.readUInt32();
|
||||
char buf[128];
|
||||
std::snprintf(buf, sizeof(buf),
|
||||
isLost ? "Title removed (ID %u)." : "Title earned (ID %u)!",
|
||||
titleBit);
|
||||
addSystemChatMessage(buf);
|
||||
LOG_INFO("SMSG_TITLE_EARNED: id=", titleBit, " lost=", isLost);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- Hearthstone binding ----
|
||||
case Opcode::SMSG_PLAYERBOUND: {
|
||||
// uint64 binderGuid + uint32 mapId + uint32 zoneId
|
||||
if (packet.getSize() - packet.getReadPos() < 16) break;
|
||||
/*uint64_t binderGuid =*/ packet.readUInt64();
|
||||
uint32_t mapId = packet.readUInt32();
|
||||
uint32_t zoneId = packet.readUInt32();
|
||||
char buf[128];
|
||||
std::snprintf(buf, sizeof(buf),
|
||||
"Your home location has been set (map %u, zone %u).", mapId, zoneId);
|
||||
addSystemChatMessage(buf);
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_BINDER_CONFIRM: {
|
||||
// uint64 npcGuid — server asking client to confirm bind at innkeeper
|
||||
packet.setReadPos(packet.getSize());
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- Phase shift (WotLK phasing) ----
|
||||
case Opcode::SMSG_SET_PHASE_SHIFT: {
|
||||
// uint32 phaseFlags [+ packed guid + uint16 count + repeated uint16 phaseIds]
|
||||
// Just consume; phasing doesn't require action from client in WotLK
|
||||
packet.setReadPos(packet.getSize());
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- XP gain toggle ----
|
||||
case Opcode::SMSG_TOGGLE_XP_GAIN: {
|
||||
// uint8 enabled
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
uint8_t enabled = packet.readUInt8();
|
||||
addSystemChatMessage(enabled ? "XP gain enabled." : "XP gain disabled.");
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- Creature Movement ----
|
||||
case Opcode::SMSG_MONSTER_MOVE:
|
||||
handleMonsterMove(packet);
|
||||
|
|
|
|||
|
|
@ -393,6 +393,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
renderBagBar(gameHandler);
|
||||
renderXpBar(gameHandler);
|
||||
renderCastBar(gameHandler);
|
||||
renderMirrorTimers(gameHandler);
|
||||
renderCombatText(gameHandler);
|
||||
renderPartyFrames(gameHandler);
|
||||
renderGroupInvitePopup(gameHandler);
|
||||
|
|
@ -4176,6 +4177,58 @@ void GameScreen::renderCastBar(game::GameHandler& gameHandler) {
|
|||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Mirror Timers (breath / fatigue / feign death)
|
||||
// ============================================================
|
||||
|
||||
void GameScreen::renderMirrorTimers(game::GameHandler& gameHandler) {
|
||||
auto* window = core::Application::getInstance().getWindow();
|
||||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
||||
|
||||
static const struct { const char* label; ImVec4 color; } kTimerInfo[3] = {
|
||||
{ "Fatigue", ImVec4(0.8f, 0.4f, 0.1f, 1.0f) },
|
||||
{ "Breath", ImVec4(0.2f, 0.5f, 1.0f, 1.0f) },
|
||||
{ "Feign", ImVec4(0.6f, 0.6f, 0.6f, 1.0f) },
|
||||
};
|
||||
|
||||
float barW = 280.0f;
|
||||
float barH = 36.0f;
|
||||
float barX = (screenW - barW) / 2.0f;
|
||||
float baseY = screenH - 160.0f; // Just above the cast bar slot
|
||||
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoInputs;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const auto& t = gameHandler.getMirrorTimer(i);
|
||||
if (!t.active || t.maxValue <= 0) continue;
|
||||
|
||||
float frac = static_cast<float>(t.value) / static_cast<float>(t.maxValue);
|
||||
frac = std::max(0.0f, std::min(1.0f, frac));
|
||||
|
||||
char winId[32];
|
||||
std::snprintf(winId, sizeof(winId), "##MirrorTimer%d", i);
|
||||
ImGui::SetNextWindowPos(ImVec2(barX, baseY - i * (barH + 4.0f)), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(barW, barH), ImGuiCond_Always);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.05f, 0.05f, 0.05f, 0.88f));
|
||||
if (ImGui::Begin(winId, nullptr, flags)) {
|
||||
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, kTimerInfo[i].color);
|
||||
char overlay[48];
|
||||
float sec = static_cast<float>(t.value) / 1000.0f;
|
||||
std::snprintf(overlay, sizeof(overlay), "%s %.0fs", kTimerInfo[i].label, sec);
|
||||
ImGui::ProgressBar(frac, ImVec2(-1, 20), overlay);
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Floating Combat Text (Phase 2)
|
||||
// ============================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue