diff --git a/include/rendering/minimap.hpp b/include/rendering/minimap.hpp index aa5097ac..7ca87922 100644 --- a/include/rendering/minimap.hpp +++ b/include/rendering/minimap.hpp @@ -35,7 +35,8 @@ public: /// Display quad — call INSIDE the main render pass. void render(VkCommandBuffer cmd, const Camera& playerCamera, - const glm::vec3& centerWorldPos, int screenWidth, int screenHeight); + const glm::vec3& centerWorldPos, int screenWidth, int screenHeight, + float playerOrientation = 0.0f, bool hasPlayerOrientation = false); void setEnabled(bool enabled) { this->enabled = enabled; } bool isEnabled() const { return enabled; } diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index e26589da..07aed962 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -93,6 +93,13 @@ bool isClassicLikeExpansion() { return isActiveExpansion("classic") || isActiveExpansion("turtle"); } +bool envFlagEnabled(const char* key, bool defaultValue = false) { + const char* raw = std::getenv(key); + if (!raw || !*raw) return defaultValue; + return !(raw[0] == '0' || raw[0] == 'f' || raw[0] == 'F' || + raw[0] == 'n' || raw[0] == 'N'); +} + std::string formatCopperAmount(uint32_t amount) { uint32_t gold = amount / 10000; uint32_t silver = (amount / 100) % 100; @@ -4621,6 +4628,7 @@ void GameHandler::setOrientation(float orientation) { } void GameHandler::handleUpdateObject(network::Packet& packet) { + static const bool kVerboseUpdateObject = envFlagEnabled("WOWEE_LOG_UPDATE_OBJECT_VERBOSE", false); UpdateObjectData data; if (!packetParsers_->parseUpdateObject(packet, data)) { LOG_WARNING("Failed to parse SMSG_UPDATE_OBJECT"); @@ -5119,92 +5127,21 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { // Extract XP / inventory slot / skill fields for player entity if (block.guid == playerGuid && block.objectType == ObjectType::PLAYER) { - // Store baseline snapshot on first update - static bool baselineStored = false; - static std::map baselineFields; - - if (!baselineStored) { - baselineFields = block.fields; - baselineStored = true; - LOG_INFO("===== BASELINE PLAYER FIELDS STORED ====="); - LOG_INFO(" Total fields: ", block.fields.size()); - } - - // Diff against baseline to find changes - std::vector changedIndices; - std::vector newIndices; - std::vector removedIndices; - - for (const auto& [idx, val] : block.fields) { - auto it = baselineFields.find(idx); - if (it == baselineFields.end()) { - newIndices.push_back(idx); - } else if (it->second != val) { - changedIndices.push_back(idx); - } - } - - for (const auto& [idx, val] : baselineFields) { - if (block.fields.find(idx) == block.fields.end()) { - removedIndices.push_back(idx); - } - } - // Auto-detect coinage index using the previous snapshot vs this full snapshot. maybeDetectCoinageIndex(lastPlayerFields_, block.fields); lastPlayerFields_ = block.fields; detectInventorySlotBases(block.fields); - // Debug: Show field changes - LOG_INFO("Player update with ", block.fields.size(), " fields"); - - if (!changedIndices.empty() || !newIndices.empty() || !removedIndices.empty()) { - LOG_INFO(" ===== FIELD CHANGES DETECTED ====="); - if (!changedIndices.empty()) { - LOG_INFO(" Changed fields (", changedIndices.size(), "):"); - std::sort(changedIndices.begin(), changedIndices.end()); - for (size_t i = 0; i < std::min(size_t(30), changedIndices.size()); ++i) { - uint16_t idx = changedIndices[i]; - uint32_t oldVal = baselineFields[idx]; - uint32_t newVal = block.fields.at(idx); - LOG_INFO(" [", idx, "]: ", oldVal, " -> ", newVal, - " (0x", std::hex, oldVal, " -> 0x", newVal, std::dec, ")"); - } - if (changedIndices.size() > 30) { - LOG_INFO(" ... (", changedIndices.size() - 30, " more)"); - } - } - if (!newIndices.empty()) { - LOG_INFO(" New fields (", newIndices.size(), "):"); - std::sort(newIndices.begin(), newIndices.end()); - for (size_t i = 0; i < std::min(size_t(20), newIndices.size()); ++i) { - uint16_t idx = newIndices[i]; - uint32_t val = block.fields.at(idx); - LOG_INFO(" [", idx, "]: ", val, " (0x", std::hex, val, std::dec, ")"); - } - if (newIndices.size() > 20) { - LOG_INFO(" ... (", newIndices.size() - 20, " more)"); - } - } - if (!removedIndices.empty()) { - LOG_INFO(" Removed fields (", removedIndices.size(), "):"); - std::sort(removedIndices.begin(), removedIndices.end()); - for (size_t i = 0; i < std::min(size_t(20), removedIndices.size()); ++i) { - uint16_t idx = removedIndices[i]; - uint32_t val = baselineFields.at(idx); - LOG_INFO(" [", idx, "]: was ", val, " (0x", std::hex, val, std::dec, ")"); - } + if (kVerboseUpdateObject) { + uint16_t maxField = 0; + for (const auto& [key, _val] : block.fields) { + if (key > maxField) maxField = key; } + LOG_INFO("Player update with ", block.fields.size(), + " fields (max index=", maxField, ")"); } - uint16_t maxField = 0; - for (const auto& [key, val] : block.fields) { - if (key > maxField) maxField = key; - } - - LOG_INFO(" Highest field index: ", maxField); - bool slotsChanged = false; const uint16_t ufPlayerXp = fieldIndex(UF::PLAYER_XP); const uint16_t ufPlayerNextXp = fieldIndex(UF::PLAYER_NEXT_LEVEL_XP); @@ -5222,11 +5159,11 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { } else if (key == ufCoinage) { playerMoneyCopper_ = val; - LOG_INFO("Money set from update fields: ", val, " copper"); + LOG_DEBUG("Money set from update fields: ", val, " copper"); } else if (ufArmor != 0xFFFF && key == ufArmor) { playerArmorRating_ = static_cast(val); - LOG_INFO("Armor rating from update fields: ", playerArmorRating_); + LOG_DEBUG("Armor rating from update fields: ", playerArmorRating_); } // Do not synthesize quest-log entries from raw update-field slots. // Slot layouts differ on some classic-family realms and can produce @@ -5502,15 +5439,15 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { for (const auto& [key, val] : block.fields) { if (key == ufPlayerXp) { playerXp_ = val; - LOG_INFO("XP updated: ", val); + LOG_DEBUG("XP updated: ", val); } else if (key == ufPlayerNextXp) { playerNextLevelXp_ = val; - LOG_INFO("Next level XP updated: ", val); + LOG_DEBUG("Next level XP updated: ", val); } else if (key == ufPlayerLevel) { serverPlayerLevel_ = val; - LOG_INFO("Level updated: ", val); + LOG_DEBUG("Level updated: ", val); for (auto& ch : characters) { if (ch.guid == playerGuid) { ch.level = val; @@ -5520,7 +5457,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { } else if (key == ufCoinage) { playerMoneyCopper_ = val; - LOG_INFO("Money updated via VALUES: ", val, " copper"); + LOG_DEBUG("Money updated via VALUES: ", val, " copper"); } else if (ufArmor != 0xFFFF && key == ufArmor) { playerArmorRating_ = static_cast(val); diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 3f670fdc..a390e08b 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -2301,7 +2301,8 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const effectiveBlendMode = 3; } if (model.isGroundDetail) { - effectiveBlendMode = 2; + // Treat foliage cards as cutout so they depth-sort correctly. + effectiveBlendMode = 1; } VkPipeline desiredPipeline; @@ -2329,9 +2330,9 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const if (batch.colorKeyBlack) { mat->colorKeyThreshold = (effectiveBlendMode == 4 || effectiveBlendMode == 5) ? 0.7f : 0.08f; } - // Ground detail: override alphaTest and unlit + // Ground detail: force cutout shading for stable two-sided foliage. if (model.isGroundDetail) { - mat->alphaTest = 0; + mat->alphaTest = 1; mat->unlit = 0; } } diff --git a/src/rendering/minimap.cpp b/src/rendering/minimap.cpp index bb513ed0..b47694f3 100644 --- a/src/rendering/minimap.cpp +++ b/src/rendering/minimap.cpp @@ -472,7 +472,8 @@ void Minimap::compositePass(VkCommandBuffer cmd, const glm::vec3& centerWorldPos void Minimap::render(VkCommandBuffer cmd, const Camera& playerCamera, const glm::vec3& centerWorldPos, - int screenWidth, int screenHeight) { + int screenWidth, int screenHeight, + float playerOrientation, bool hasPlayerOrientation) { if (!enabled || !hasCachedFrame || !displayPipeline) return; vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, displayPipeline); @@ -511,8 +512,15 @@ void Minimap::render(VkCommandBuffer cmd, const Camera& playerCamera, float arrowRotation = 0.0f; if (!rotateWithCamera) { - glm::vec3 fwd = playerCamera.getForward(); - arrowRotation = std::atan2(-fwd.x, fwd.y); + // Prefer authoritative player orientation for north-up minimap arrow. + // Canonical yaw already matches minimap rotation convention: + // 0=north, +pi/2=east. + if (hasPlayerOrientation) { + arrowRotation = playerOrientation; + } else { + glm::vec3 fwd = playerCamera.getForward(); + arrowRotation = std::atan2(-fwd.x, fwd.y); + } } MinimapDisplayPush push{}; diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index fa29b333..44d1e163 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -3183,8 +3183,15 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) { glm::vec3 minimapCenter = camera->getPosition(); if (cameraController && cameraController->isThirdPerson()) minimapCenter = characterPosition; + float minimapPlayerOrientation = 0.0f; + bool hasMinimapPlayerOrientation = false; + if (gameHandler) { + minimapPlayerOrientation = gameHandler->getMovementInfo().orientation; + hasMinimapPlayerOrientation = true; + } minimap->render(currentCmd, *camera, minimapCenter, - window->getWidth(), window->getHeight()); + window->getWidth(), window->getHeight(), + minimapPlayerOrientation, hasMinimapPlayerOrientation); } auto renderEnd = std::chrono::steady_clock::now();