mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Handle gossip POI, combat clearing, dismount, spell log miss, and loot notifications
- SMSG_GOSSIP_POI: parse map POI markers (x/y/icon/name) from quest NPCs, render as cyan diamonds on the minimap with hover tooltips for quest navigation - SMSG_ATTACKSWING_DEADTARGET: clear auto-attack when target dies mid-swing - SMSG_CANCEL_COMBAT: server-side combat reset (clears autoAttacking + target) - SMSG_BREAK_TARGET / SMSG_CLEAR_TARGET: server-side targeting clears - SMSG_DISMOUNT: server-forced dismount triggers mountCallback(0) - SMSG_MOUNTRESULT / SMSG_DISMOUNTRESULT: mount feedback in system chat - SMSG_LOOT_ALL_PASSED: "Everyone passed on [Item]" system message, clears loot roll - SMSG_LOOT_ITEM_NOTIFY / SMSG_LOOT_SLOT_CHANGED: consumed - SMSG_SPELLLOGMISS: decode miss/dodge/parry/block from spell casts into combat text - SMSG_ENVIRONMENTALDAMAGELOG: environmental damage (drowning/lava/fall) in combat text - GossipPoi struct + gossipPois_ vector in GameHandler with public getters/clearers
This commit is contained in:
parent
bd3bd1b5a6
commit
f89840a6aa
3 changed files with 189 additions and 0 deletions
|
|
@ -825,6 +825,17 @@ public:
|
||||||
bool isQuestDetailsOpen() const { return questDetailsOpen; }
|
bool isQuestDetailsOpen() const { return questDetailsOpen; }
|
||||||
const QuestDetailsData& getQuestDetails() const { return currentQuestDetails; }
|
const QuestDetailsData& getQuestDetails() const { return currentQuestDetails; }
|
||||||
|
|
||||||
|
// Gossip / quest map POI markers (SMSG_GOSSIP_POI)
|
||||||
|
struct GossipPoi {
|
||||||
|
float x = 0.0f; // WoW canonical X (north)
|
||||||
|
float y = 0.0f; // WoW canonical Y (west)
|
||||||
|
uint32_t icon = 0; // POI icon type
|
||||||
|
uint32_t data = 0;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
const std::vector<GossipPoi>& getGossipPois() const { return gossipPois_; }
|
||||||
|
void clearGossipPois() { gossipPois_.clear(); }
|
||||||
|
|
||||||
// Quest turn-in
|
// Quest turn-in
|
||||||
bool isQuestRequestItemsOpen() const { return questRequestItemsOpen_; }
|
bool isQuestRequestItemsOpen() const { return questRequestItemsOpen_; }
|
||||||
const QuestRequestItemsData& getQuestRequestItems() const { return currentQuestRequestItems_; }
|
const QuestRequestItemsData& getQuestRequestItems() const { return currentQuestRequestItems_; }
|
||||||
|
|
@ -1778,6 +1789,7 @@ private:
|
||||||
// Gossip
|
// Gossip
|
||||||
bool gossipWindowOpen = false;
|
bool gossipWindowOpen = false;
|
||||||
GossipMessageData currentGossip;
|
GossipMessageData currentGossip;
|
||||||
|
std::vector<GossipPoi> gossipPois_;
|
||||||
|
|
||||||
void performGameObjectInteractionNow(uint64_t guid);
|
void performGameObjectInteractionNow(uint64_t guid);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1819,6 +1819,157 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Gossip POI (quest map markers) ----
|
||||||
|
case Opcode::SMSG_GOSSIP_POI: {
|
||||||
|
// uint32 flags + float x + float y + uint32 icon + uint32 data + string name
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 20) break;
|
||||||
|
/*uint32_t flags =*/ packet.readUInt32();
|
||||||
|
float poiX = packet.readFloat(); // WoW canonical coords
|
||||||
|
float poiY = packet.readFloat();
|
||||||
|
uint32_t icon = packet.readUInt32();
|
||||||
|
uint32_t data = packet.readUInt32();
|
||||||
|
std::string name = packet.readString();
|
||||||
|
GossipPoi poi;
|
||||||
|
poi.x = poiX;
|
||||||
|
poi.y = poiY;
|
||||||
|
poi.icon = icon;
|
||||||
|
poi.data = data;
|
||||||
|
poi.name = std::move(name);
|
||||||
|
gossipPois_.push_back(std::move(poi));
|
||||||
|
LOG_DEBUG("SMSG_GOSSIP_POI: x=", poiX, " y=", poiY, " icon=", icon);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Combat clearing ----
|
||||||
|
case Opcode::SMSG_ATTACKSWING_DEADTARGET:
|
||||||
|
// Target died mid-swing: clear auto-attack
|
||||||
|
autoAttacking = false;
|
||||||
|
autoAttackTarget = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Opcode::SMSG_CANCEL_COMBAT:
|
||||||
|
// Server-side combat state reset
|
||||||
|
autoAttacking = false;
|
||||||
|
autoAttackTarget = 0;
|
||||||
|
autoAttackRequested_ = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Opcode::SMSG_BREAK_TARGET:
|
||||||
|
// Server breaking our targeting (PvP flag, etc.)
|
||||||
|
// uint64 guid — consume; target cleared if it matches
|
||||||
|
if (packet.getSize() - packet.getReadPos() >= 8) {
|
||||||
|
uint64_t bGuid = packet.readUInt64();
|
||||||
|
if (bGuid == targetGuid) targetGuid = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Opcode::SMSG_CLEAR_TARGET:
|
||||||
|
// uint64 guid — server cleared targeting on a unit (or 0 = clear all)
|
||||||
|
if (packet.getSize() - packet.getReadPos() >= 8) {
|
||||||
|
uint64_t cGuid = packet.readUInt64();
|
||||||
|
if (cGuid == 0 || cGuid == targetGuid) targetGuid = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ---- Server-forced dismount ----
|
||||||
|
case Opcode::SMSG_DISMOUNT:
|
||||||
|
// No payload — server forcing dismount
|
||||||
|
currentMountDisplayId_ = 0;
|
||||||
|
if (mountCallback_) mountCallback_(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Opcode::SMSG_MOUNTRESULT: {
|
||||||
|
// uint32 result: 0=error, 1=invalid, 2=not in range, 3=already mounted, 4=ok
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 4) break;
|
||||||
|
uint32_t result = packet.readUInt32();
|
||||||
|
if (result != 4) {
|
||||||
|
const char* msgs[] = { "Cannot mount here.", "Invalid mount spell.", "Too far away to mount.", "Already mounted." };
|
||||||
|
addSystemChatMessage(result < 4 ? msgs[result] : "Cannot mount.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Opcode::SMSG_DISMOUNTRESULT: {
|
||||||
|
// uint32 result: 0=ok, others=error
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 4) break;
|
||||||
|
uint32_t result = packet.readUInt32();
|
||||||
|
if (result != 0) addSystemChatMessage("Cannot dismount here.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Loot notifications ----
|
||||||
|
case Opcode::SMSG_LOOT_ALL_PASSED: {
|
||||||
|
// uint64 objectGuid + uint32 slot + uint32 itemId + uint32 randSuffix + uint32 randPropId
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 24) break;
|
||||||
|
/*uint64_t objGuid =*/ packet.readUInt64();
|
||||||
|
/*uint32_t slot =*/ packet.readUInt32();
|
||||||
|
uint32_t itemId = packet.readUInt32();
|
||||||
|
auto* info = getItemInfo(itemId);
|
||||||
|
char buf[256];
|
||||||
|
std::snprintf(buf, sizeof(buf), "Everyone passed on [%s].",
|
||||||
|
info ? info->name.c_str() : std::to_string(itemId).c_str());
|
||||||
|
addSystemChatMessage(buf);
|
||||||
|
pendingLootRollActive_ = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Opcode::SMSG_LOOT_ITEM_NOTIFY:
|
||||||
|
// uint64 looterGuid + uint64 lootGuid + uint32 itemId + uint32 count — consume
|
||||||
|
packet.setReadPos(packet.getSize());
|
||||||
|
break;
|
||||||
|
case Opcode::SMSG_LOOT_SLOT_CHANGED:
|
||||||
|
// uint64 objectGuid + uint32 slot + ... — consume
|
||||||
|
packet.setReadPos(packet.getSize());
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ---- Spell log miss ----
|
||||||
|
case Opcode::SMSG_SPELLLOGMISS: {
|
||||||
|
// packed_guid caster + packed_guid target + uint8 isCrit + uint32 count
|
||||||
|
// + count × (uint64 victimGuid + uint8 missInfo)
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 2) break;
|
||||||
|
uint64_t casterGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 2) break;
|
||||||
|
/*uint64_t targetGuidLog =*/ UpdateObjectParser::readPackedGuid(packet);
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 5) break;
|
||||||
|
/*uint8_t isCrit =*/ packet.readUInt8();
|
||||||
|
uint32_t count = packet.readUInt32();
|
||||||
|
count = std::min(count, 32u);
|
||||||
|
for (uint32_t i = 0; i < count && packet.getSize() - packet.getReadPos() >= 9; ++i) {
|
||||||
|
/*uint64_t victimGuid =*/ packet.readUInt64();
|
||||||
|
uint8_t missInfo = packet.readUInt8();
|
||||||
|
// Show combat text only for local player's spell misses
|
||||||
|
if (casterGuid == playerGuid) {
|
||||||
|
static const CombatTextEntry::Type missTypes[] = {
|
||||||
|
CombatTextEntry::MISS, // 0=MISS
|
||||||
|
CombatTextEntry::DODGE, // 1=DODGE
|
||||||
|
CombatTextEntry::PARRY, // 2=PARRY
|
||||||
|
CombatTextEntry::BLOCK, // 3=BLOCK
|
||||||
|
CombatTextEntry::MISS, // 4=EVADE → show as MISS
|
||||||
|
CombatTextEntry::MISS, // 5=IMMUNE → show as MISS
|
||||||
|
CombatTextEntry::MISS, // 6=DEFLECT
|
||||||
|
CombatTextEntry::MISS, // 7=ABSORB
|
||||||
|
CombatTextEntry::MISS, // 8=RESIST
|
||||||
|
};
|
||||||
|
CombatTextEntry::Type ct = (missInfo < 9) ? missTypes[missInfo] : CombatTextEntry::MISS;
|
||||||
|
addCombatText(ct, 0, 0, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Environmental damage log ----
|
||||||
|
case Opcode::SMSG_ENVIRONMENTALDAMAGELOG: {
|
||||||
|
// uint64 victimGuid + uint8 envDamageType + uint32 damage + uint32 absorb + uint32 resist
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 21) break;
|
||||||
|
uint64_t victimGuid = packet.readUInt64();
|
||||||
|
/*uint8_t envType =*/ packet.readUInt8();
|
||||||
|
uint32_t damage = packet.readUInt32();
|
||||||
|
/*uint32_t absorb =*/ packet.readUInt32();
|
||||||
|
/*uint32_t resist =*/ packet.readUInt32();
|
||||||
|
if (victimGuid == playerGuid && damage > 0) {
|
||||||
|
addCombatText(CombatTextEntry::ENVIRONMENTAL, static_cast<int32_t>(damage), 0, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Creature Movement ----
|
// ---- Creature Movement ----
|
||||||
case Opcode::SMSG_MONSTER_MOVE:
|
case Opcode::SMSG_MONSTER_MOVE:
|
||||||
handleMonsterMove(packet);
|
handleMonsterMove(packet);
|
||||||
|
|
|
||||||
|
|
@ -7378,6 +7378,32 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
|
||||||
IM_COL32(0, 0, 0, 255), marker);
|
IM_COL32(0, 0, 0, 255), marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gossip POI markers (quest / NPC navigation targets)
|
||||||
|
for (const auto& poi : gameHandler.getGossipPois()) {
|
||||||
|
// Convert WoW canonical coords to render coords for minimap projection
|
||||||
|
glm::vec3 poiRender = core::coords::canonicalToRender(glm::vec3(poi.x, poi.y, 0.0f));
|
||||||
|
float sx = 0.0f, sy = 0.0f;
|
||||||
|
if (!projectToMinimap(poiRender, sx, sy)) continue;
|
||||||
|
|
||||||
|
// Draw as a cyan diamond with tooltip on hover
|
||||||
|
const float d = 5.0f;
|
||||||
|
ImVec2 pts[4] = {
|
||||||
|
{ sx, sy - d },
|
||||||
|
{ sx + d, sy },
|
||||||
|
{ sx, sy + d },
|
||||||
|
{ sx - d, sy },
|
||||||
|
};
|
||||||
|
drawList->AddConvexPolyFilled(pts, 4, IM_COL32(0, 210, 255, 220));
|
||||||
|
drawList->AddPolyline(pts, 4, IM_COL32(255, 255, 255, 160), true, 1.0f);
|
||||||
|
|
||||||
|
// Show name label if cursor is within ~8px
|
||||||
|
ImVec2 cursorPos = ImGui::GetMousePos();
|
||||||
|
float dx = cursorPos.x - sx, dy = cursorPos.y - sy;
|
||||||
|
if (!poi.name.empty() && (dx * dx + dy * dy) < 64.0f) {
|
||||||
|
ImGui::SetTooltip("%s", poi.name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto applyMuteState = [&]() {
|
auto applyMuteState = [&]() {
|
||||||
auto* activeRenderer = core::Application::getInstance().getRenderer();
|
auto* activeRenderer = core::Application::getInstance().getRenderer();
|
||||||
float masterScale = soundMuted_ ? 0.0f : static_cast<float>(pendingMasterVolume) / 100.0f;
|
float masterScale = soundMuted_ ? 0.0f : static_cast<float>(pendingMasterVolume) / 100.0f;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue