mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Implement Death Knight rune tracking and rune bar UI
Parse SMSG_RESYNC_RUNES, SMSG_ADD_RUNE_POWER, and SMSG_CONVERT_RUNE to track the state of all 6 DK runes (Blood/Unholy/Frost/Death type, ready flag, and cooldown fraction). Render a six-square rune bar below the Runic Power bar when the player is class 6, with per-type colors (Blood=red, Unholy=green, Frost=blue, Death=purple) and client-side fill animation so runes visibly refill over the 10s cooldown.
This commit is contained in:
parent
819a38a7ca
commit
c887a460ea
4 changed files with 112 additions and 5 deletions
|
|
@ -902,6 +902,15 @@ public:
|
||||||
uint8_t getComboPoints() const { return comboPoints_; }
|
uint8_t getComboPoints() const { return comboPoints_; }
|
||||||
uint64_t getComboTarget() const { return comboTarget_; }
|
uint64_t getComboTarget() const { return comboTarget_; }
|
||||||
|
|
||||||
|
// Death Knight rune state (6 runes: 0-1=Blood, 2-3=Unholy, 4-5=Frost; may become Death=3)
|
||||||
|
enum class RuneType : uint8_t { Blood = 0, Unholy = 1, Frost = 2, Death = 3 };
|
||||||
|
struct RuneSlot {
|
||||||
|
RuneType type = RuneType::Blood;
|
||||||
|
bool ready = true; // Server-confirmed ready state
|
||||||
|
float readyFraction = 1.0f; // 0.0=depleted → 1.0=full (from server sync)
|
||||||
|
};
|
||||||
|
const std::array<RuneSlot, 6>& getPlayerRunes() const { return playerRunes_; }
|
||||||
|
|
||||||
struct FactionStandingInit {
|
struct FactionStandingInit {
|
||||||
uint8_t flags = 0;
|
uint8_t flags = 0;
|
||||||
int32_t standing = 0;
|
int32_t standing = 0;
|
||||||
|
|
@ -2081,6 +2090,14 @@ private:
|
||||||
float serverPitchRate_ = 3.14159f;
|
float serverPitchRate_ = 3.14159f;
|
||||||
bool playerDead_ = false;
|
bool playerDead_ = false;
|
||||||
bool releasedSpirit_ = false;
|
bool releasedSpirit_ = false;
|
||||||
|
// Death Knight runes (class 6): slots 0-1=Blood, 2-3=Unholy, 4-5=Frost initially
|
||||||
|
std::array<RuneSlot, 6> playerRunes_ = [] {
|
||||||
|
std::array<RuneSlot, 6> r{};
|
||||||
|
r[0].type = r[1].type = RuneType::Blood;
|
||||||
|
r[2].type = r[3].type = RuneType::Unholy;
|
||||||
|
r[4].type = r[5].type = RuneType::Frost;
|
||||||
|
return r;
|
||||||
|
}();
|
||||||
uint64_t pendingSpiritHealerGuid_ = 0;
|
uint64_t pendingSpiritHealerGuid_ = 0;
|
||||||
bool resurrectPending_ = false;
|
bool resurrectPending_ = false;
|
||||||
bool resurrectRequestPending_ = false;
|
bool resurrectRequestPending_ = false;
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,9 @@ private:
|
||||||
bool spellIconDbLoaded_ = false;
|
bool spellIconDbLoaded_ = false;
|
||||||
VkDescriptorSet getSpellIcon(uint32_t spellId, pipeline::AssetManager* am);
|
VkDescriptorSet getSpellIcon(uint32_t spellId, pipeline::AssetManager* am);
|
||||||
|
|
||||||
|
// Death Knight rune bar: client-predicted fill (0.0=depleted, 1.0=ready) for smooth animation
|
||||||
|
float runeClientFill_[6] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
|
||||||
// Action bar drag state (-1 = not dragging)
|
// Action bar drag state (-1 = not dragging)
|
||||||
int actionBarDragSlot_ = -1;
|
int actionBarDragSlot_ = -1;
|
||||||
VkDescriptorSet actionBarDragIcon_ = VK_NULL_HANDLE;
|
VkDescriptorSet actionBarDragIcon_ = VK_NULL_HANDLE;
|
||||||
|
|
|
||||||
|
|
@ -1980,7 +1980,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
case Opcode::SMSG_FORCE_DISPLAY_UPDATE:
|
case Opcode::SMSG_FORCE_DISPLAY_UPDATE:
|
||||||
case Opcode::SMSG_FORCE_SEND_QUEUED_PACKETS:
|
case Opcode::SMSG_FORCE_SEND_QUEUED_PACKETS:
|
||||||
case Opcode::SMSG_FORCE_SET_VEHICLE_REC_ID:
|
case Opcode::SMSG_FORCE_SET_VEHICLE_REC_ID:
|
||||||
case Opcode::SMSG_CONVERT_RUNE:
|
|
||||||
case Opcode::SMSG_CORPSE_MAP_POSITION_QUERY_RESPONSE:
|
case Opcode::SMSG_CORPSE_MAP_POSITION_QUERY_RESPONSE:
|
||||||
case Opcode::SMSG_DAMAGE_CALC_LOG:
|
case Opcode::SMSG_DAMAGE_CALC_LOG:
|
||||||
case Opcode::SMSG_DYNAMIC_DROP_ROLL_RESULT:
|
case Opcode::SMSG_DYNAMIC_DROP_ROLL_RESULT:
|
||||||
|
|
@ -4457,11 +4456,49 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
packet.setReadPos(packet.getSize());
|
packet.setReadPos(packet.getSize());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// ---- DK rune tracking (not yet implemented) ----
|
// ---- DK rune tracking ----
|
||||||
case Opcode::SMSG_ADD_RUNE_POWER:
|
case Opcode::SMSG_CONVERT_RUNE: {
|
||||||
case Opcode::SMSG_RESYNC_RUNES:
|
// uint8 runeIndex + uint8 newRuneType (0=Blood,1=Unholy,2=Frost,3=Death)
|
||||||
packet.setReadPos(packet.getSize());
|
if (packet.getSize() - packet.getReadPos() < 2) {
|
||||||
|
packet.setReadPos(packet.getSize());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint8_t idx = packet.readUInt8();
|
||||||
|
uint8_t type = packet.readUInt8();
|
||||||
|
if (idx < 6) playerRunes_[idx].type = static_cast<RuneType>(type & 0x3);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case Opcode::SMSG_RESYNC_RUNES: {
|
||||||
|
// uint8 runeReadyMask (bit i=1 → rune i is ready)
|
||||||
|
// uint8[6] cooldowns (0=ready, 255=just used → readyFraction = 1 - val/255)
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 7) {
|
||||||
|
packet.setReadPos(packet.getSize());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint8_t readyMask = packet.readUInt8();
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
uint8_t cd = packet.readUInt8();
|
||||||
|
playerRunes_[i].ready = (readyMask & (1u << i)) != 0;
|
||||||
|
playerRunes_[i].readyFraction = 1.0f - cd / 255.0f;
|
||||||
|
if (playerRunes_[i].ready) playerRunes_[i].readyFraction = 1.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Opcode::SMSG_ADD_RUNE_POWER: {
|
||||||
|
// uint32 runeMask (bit i=1 → rune i just became ready)
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 4) {
|
||||||
|
packet.setReadPos(packet.getSize());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint32_t runeMask = packet.readUInt32();
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
if (runeMask & (1u << i)) {
|
||||||
|
playerRunes_[i].ready = true;
|
||||||
|
playerRunes_[i].readyFraction = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Spell combat logs (consume) ----
|
// ---- Spell combat logs (consume) ----
|
||||||
case Opcode::SMSG_AURACASTLOG:
|
case Opcode::SMSG_AURACASTLOG:
|
||||||
|
|
|
||||||
|
|
@ -1815,6 +1815,56 @@ void GameScreen::renderPlayerFrame(game::GameHandler& gameHandler) {
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Death Knight rune bar (class 6) — 6 colored squares with fill fraction
|
||||||
|
if (gameHandler.getPlayerClass() == 6) {
|
||||||
|
const auto& runes = gameHandler.getPlayerRunes();
|
||||||
|
float dt = ImGui::GetIO().DeltaTime;
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImVec2 cursor = ImGui::GetCursorScreenPos();
|
||||||
|
float totalW = ImGui::GetContentRegionAvail().x;
|
||||||
|
float spacing = 3.0f;
|
||||||
|
float squareW = (totalW - spacing * 5.0f) / 6.0f;
|
||||||
|
float squareH = 14.0f;
|
||||||
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
// Client-side prediction: advance fill over ~10s cooldown
|
||||||
|
runeClientFill_[i] = runes[i].ready ? 1.0f
|
||||||
|
: std::min(runeClientFill_[i] + dt / 10.0f, runes[i].readyFraction + 0.02f);
|
||||||
|
runeClientFill_[i] = std::clamp(runeClientFill_[i], 0.0f, runes[i].ready ? 1.0f : 0.97f);
|
||||||
|
|
||||||
|
float x0 = cursor.x + i * (squareW + spacing);
|
||||||
|
float y0 = cursor.y;
|
||||||
|
float x1 = x0 + squareW;
|
||||||
|
float y1 = y0 + squareH;
|
||||||
|
|
||||||
|
// Background (dark)
|
||||||
|
dl->AddRectFilled(ImVec2(x0, y0), ImVec2(x1, y1),
|
||||||
|
IM_COL32(30, 30, 30, 200), 2.0f);
|
||||||
|
|
||||||
|
// Fill color by rune type
|
||||||
|
ImVec4 fc;
|
||||||
|
switch (runes[i].type) {
|
||||||
|
case game::GameHandler::RuneType::Blood: fc = ImVec4(0.85f, 0.12f, 0.12f, 1.0f); break;
|
||||||
|
case game::GameHandler::RuneType::Unholy: fc = ImVec4(0.20f, 0.72f, 0.20f, 1.0f); break;
|
||||||
|
case game::GameHandler::RuneType::Frost: fc = ImVec4(0.30f, 0.55f, 0.90f, 1.0f); break;
|
||||||
|
case game::GameHandler::RuneType::Death: fc = ImVec4(0.55f, 0.20f, 0.70f, 1.0f); break;
|
||||||
|
default: fc = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); break;
|
||||||
|
}
|
||||||
|
float fillX = x0 + (x1 - x0) * runeClientFill_[i];
|
||||||
|
dl->AddRectFilled(ImVec2(x0, y0), ImVec2(fillX, y1),
|
||||||
|
ImGui::ColorConvertFloat4ToU32(fc), 2.0f);
|
||||||
|
|
||||||
|
// Border
|
||||||
|
ImU32 borderCol = runes[i].ready
|
||||||
|
? IM_COL32(220, 220, 220, 180)
|
||||||
|
: IM_COL32(100, 100, 100, 160);
|
||||||
|
dl->AddRect(ImVec2(x0, y0), ImVec2(x1, y1), borderCol, 2.0f);
|
||||||
|
}
|
||||||
|
ImGui::Dummy(ImVec2(totalW, squareH));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue