diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index a846aa35..d38f67d0 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1401,6 +1401,7 @@ private: TaxiOrientationCallback taxiOrientationCallback_; TaxiFlightStartCallback taxiFlightStartCallback_; uint32_t currentMountDisplayId_ = 0; + uint32_t mountAuraSpellId_ = 0; // Spell ID of the aura that caused mounting (for CMSG_CANCEL_AURA fallback) float serverRunSpeed_ = 7.0f; bool playerDead_ = false; bool releasedSpirit_ = false; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index b5926d7e..04266f2d 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -3122,7 +3122,18 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { uint32_t old = currentMountDisplayId_; currentMountDisplayId_ = val; if (val != old && mountCallback_) mountCallback_(val); + if (old == 0 && val != 0) { + // Just mounted — find the mount aura (indefinite duration, self-cast) + mountAuraSpellId_ = 0; + for (const auto& a : playerAuras) { + if (!a.isEmpty() && a.maxDurationMs < 0 && a.casterGuid == playerGuid) { + mountAuraSpellId_ = a.spellId; + } + } + LOG_INFO("Mount detected: displayId=", val, " auraSpellId=", mountAuraSpellId_); + } if (old != 0 && val == 0) { + mountAuraSpellId_ = 0; for (auto& a : playerAuras) if (!a.isEmpty() && a.maxDurationMs < 0) a = AuraSlot{}; } @@ -3502,7 +3513,17 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { uint32_t old = currentMountDisplayId_; currentMountDisplayId_ = val; if (val != old && mountCallback_) mountCallback_(val); + if (old == 0 && val != 0) { + mountAuraSpellId_ = 0; + for (const auto& a : playerAuras) { + if (!a.isEmpty() && a.maxDurationMs < 0 && a.casterGuid == playerGuid) { + mountAuraSpellId_ = a.spellId; + } + } + LOG_INFO("Mount detected (values update): displayId=", val, " auraSpellId=", mountAuraSpellId_); + } if (old != 0 && val == 0) { + mountAuraSpellId_ = 0; for (auto& a : playerAuras) if (!a.isEmpty() && a.maxDurationMs < 0) a = AuraSlot{}; } @@ -5973,6 +5994,7 @@ void GameHandler::dismount() { if (!socket) return; // Clear local mount state immediately (optimistic dismount). // Server will confirm via SMSG_UPDATE_OBJECT with mountDisplayId=0. + uint32_t savedMountAura = mountAuraSpellId_; if (currentMountDisplayId_ != 0 || taxiMountActive_) { if (mountCallback_) { mountCallback_(0); @@ -5980,11 +6002,31 @@ void GameHandler::dismount() { currentMountDisplayId_ = 0; taxiMountActive_ = false; taxiMountDisplayId_ = 0; + mountAuraSpellId_ = 0; LOG_INFO("Dismount: cleared local mount state"); } - network::Packet pkt(wireOpcode(Opcode::CMSG_CANCEL_MOUNT_AURA)); - socket->send(pkt); - LOG_INFO("Sent CMSG_CANCEL_MOUNT_AURA"); + // CMSG_CANCEL_MOUNT_AURA exists in TBC+ (0x0375). Classic/Vanilla doesn't have it. + uint16_t cancelMountWire = wireOpcode(Opcode::CMSG_CANCEL_MOUNT_AURA); + if (cancelMountWire != 0xFFFF) { + network::Packet pkt(cancelMountWire); + socket->send(pkt); + LOG_INFO("Sent CMSG_CANCEL_MOUNT_AURA"); + } else if (savedMountAura != 0) { + // Fallback for Classic/Vanilla: cancel the mount aura by spell ID + auto pkt = CancelAuraPacket::build(savedMountAura); + socket->send(pkt); + LOG_INFO("Sent CMSG_CANCEL_AURA (mount spell ", savedMountAura, ") — Classic fallback"); + } else { + // No tracked mount aura — try cancelling all indefinite self-cast auras + // (mount aura detection may have missed if aura arrived after mount field) + for (const auto& a : playerAuras) { + if (!a.isEmpty() && a.maxDurationMs < 0 && a.casterGuid == playerGuid) { + auto pkt = CancelAuraPacket::build(a.spellId); + socket->send(pkt); + LOG_INFO("Sent CMSG_CANCEL_AURA (spell ", a.spellId, ") — brute force dismount"); + } + } + } } void GameHandler::handleForceRunSpeedChange(network::Packet& packet) { @@ -6623,6 +6665,17 @@ void GameHandler::handleAuraUpdate(network::Packet& packet, bool isAll) { } (*auraList)[slot] = aura; } + + // If player is mounted but we haven't identified the mount aura yet, + // check newly added auras (aura update may arrive after mountDisplayId) + if (data.guid == playerGuid && currentMountDisplayId_ != 0 && mountAuraSpellId_ == 0) { + for (const auto& [slot, aura] : data.updates) { + if (!aura.isEmpty() && aura.maxDurationMs < 0 && aura.casterGuid == playerGuid) { + mountAuraSpellId_ = aura.spellId; + LOG_INFO("Mount aura detected from aura update: spellId=", aura.spellId); + } + } + } } } diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 109d1f12..955c2f0e 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -770,12 +770,51 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) { continue; } - // Failed to parse as item link — render the |c literally and continue - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextWrapped("|c"); - ImGui::PopStyleColor(); - ImGui::SameLine(0, 0); - pos = nextSpecial + 2; + // Not an item link — treat as colored text: |cAARRGGBB...text...|r + if (nextSpecial == linkStart && text.size() > linkStart + 10) { + ImVec4 cColor = parseWowColor(text, linkStart); + size_t textStart = linkStart + 10; // after |cAARRGGBB + size_t resetPos2 = text.find("|r", textStart); + std::string coloredText; + if (resetPos2 != std::string::npos) { + coloredText = text.substr(textStart, resetPos2 - textStart); + pos = resetPos2 + 2; // skip |r + } else { + coloredText = text.substr(textStart); + pos = text.size(); + } + // Strip any remaining WoW markup from the colored segment + // (e.g. |H...|h pairs that aren't item links) + std::string clean; + for (size_t i = 0; i < coloredText.size(); i++) { + if (coloredText[i] == '|' && i + 1 < coloredText.size()) { + char next = coloredText[i + 1]; + if (next == 'H') { + // Skip |H...|h + size_t hEnd = coloredText.find("|h", i + 2); + if (hEnd != std::string::npos) { i = hEnd + 1; continue; } + } else if (next == 'h') { + i += 1; continue; // skip |h + } else if (next == 'r') { + i += 1; continue; // skip |r + } + } + clean += coloredText[i]; + } + if (!clean.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, cColor); + ImGui::TextWrapped("%s", clean.c_str()); + ImGui::PopStyleColor(); + ImGui::SameLine(0, 0); + } + } else { + // Bare |c without enough chars for color — render literally + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextWrapped("|c"); + ImGui::PopStyleColor(); + ImGui::SameLine(0, 0); + pos = nextSpecial + 2; + } continue; }