mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-07-31 18:00:46 -04:00
add map trackers and fix spell and mount state
This commit is contained in:
@@ -808,6 +808,7 @@ set(WOWEE_SOURCES
|
||||
src/rendering/world_map/layers/quest_poi_layer.cpp
|
||||
src/rendering/world_map/layers/corpse_marker_layer.cpp
|
||||
src/rendering/world_map/layers/rare_tracker_layer.cpp
|
||||
src/rendering/world_map/layers/chest_tracker_layer.cpp
|
||||
src/rendering/world_map/layers/zone_highlight_layer.cpp
|
||||
src/rendering/world_map/layers/coordinate_display.cpp
|
||||
src/rendering/world_map/layers/subzone_tooltip_layer.cpp
|
||||
@@ -996,6 +997,8 @@ set(WOWEE_HEADERS
|
||||
include/rendering/world_map/layers/poi_marker_layer.hpp
|
||||
include/rendering/world_map/layers/quest_poi_layer.hpp
|
||||
include/rendering/world_map/layers/corpse_marker_layer.hpp
|
||||
include/rendering/world_map/layers/rare_tracker_layer.hpp
|
||||
include/rendering/world_map/layers/chest_tracker_layer.hpp
|
||||
include/rendering/world_map/layers/zone_highlight_layer.hpp
|
||||
include/rendering/world_map/layers/coordinate_display.hpp
|
||||
include/rendering/world_map/layers/subzone_tooltip_layer.hpp
|
||||
|
||||
@@ -134,11 +134,16 @@ inline uint32_t resolveHighestKnownRank(
|
||||
const SpellRankInfo* info = lookup(spellId);
|
||||
if (!info || info->name.empty()) return spellId;
|
||||
|
||||
// Keep the requested name by value. Some DBC adapters reuse one scratch object
|
||||
// for every lookup; retaining its pointer here lets the first candidate overwrite
|
||||
// the requested spell and makes unrelated known spells look like matching ranks.
|
||||
const std::string requestedName = info->name;
|
||||
|
||||
uint32_t best = 0;
|
||||
int bestRank = -1;
|
||||
for (uint32_t known : knownSpells) {
|
||||
const SpellRankInfo* candidate = lookup(known);
|
||||
if (!candidate || candidate->name != info->name) continue;
|
||||
if (!candidate || candidate->name != requestedName) continue;
|
||||
const int rank = rankValue(candidate->rank);
|
||||
if (rank > bestRank) {
|
||||
bestRank = rank;
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace rendering {
|
||||
// (game_screen_hud.cpp, renderer.cpp, etc.)
|
||||
using WorldMapPartyDot = world_map::PartyDot;
|
||||
using WorldMapRareMark = world_map::RareMark;
|
||||
using WorldMapChestMark = world_map::ChestMark;
|
||||
using WorldMapTaxiNode = world_map::TaxiNode;
|
||||
using MapPOI = world_map::POI;
|
||||
|
||||
|
||||
22
include/rendering/world_map/layers/chest_tracker_layer.hpp
Normal file
22
include/rendering/world_map/layers/chest_tracker_layer.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
// chest_tracker_layer.hpp — Nearby spawned chest markers on the world map.
|
||||
#pragma once
|
||||
#include "rendering/world_map/overlay_renderer.hpp"
|
||||
#include "rendering/world_map/world_map_types.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace wowee {
|
||||
namespace rendering {
|
||||
namespace world_map {
|
||||
|
||||
class ChestTrackerLayer : public IOverlayLayer {
|
||||
public:
|
||||
void setChests(const std::vector<ChestMark>& chests) { chests_ = &chests; }
|
||||
void render(const LayerContext& ctx) override;
|
||||
|
||||
private:
|
||||
const std::vector<ChestMark>* chests_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace world_map
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
void setCorpsePos(bool hasCorpse, glm::vec3 renderPos);
|
||||
/// Nearby rare/rare-elite creatures currently spawned near the player.
|
||||
void setRares(std::vector<RareMark> rares);
|
||||
/// Nearby chest-type game objects currently spawned near the player.
|
||||
void setChests(std::vector<ChestMark> chests);
|
||||
|
||||
bool isOpen() const;
|
||||
void close();
|
||||
|
||||
@@ -108,6 +108,13 @@ struct RareMark {
|
||||
int rank = 4; ///< Creature rank: 2 = Rare Elite, 4 = Rare
|
||||
};
|
||||
|
||||
// ── Nearby spawned chest marker (UI layer → world map overlay) ─
|
||||
|
||||
struct ChestMark {
|
||||
glm::vec3 renderPos; ///< Position in render-space coordinates
|
||||
std::string name; ///< Chest/container name (shown as tooltip on hover)
|
||||
};
|
||||
|
||||
// ── Taxi (flight master) node (UI layer → world map overlay) ─
|
||||
|
||||
struct TaxiNode {
|
||||
|
||||
@@ -157,7 +157,8 @@ public:
|
||||
bool showFriendlyNameplates_ = true; // Shift+V toggles friendly player nameplates
|
||||
bool showDPSMeter_ = false;
|
||||
bool showCooldownTracker_ = false;
|
||||
bool showRareTracker_ = false; // Mark nearby spawned rares/rare-elites on the world map
|
||||
bool showRareTracker_ = false; // Mark nearby spawned rares/rare-elites on both maps
|
||||
bool showChestTracker_ = false; // Mark nearby spawned non-gather chests on both maps
|
||||
bool damageFlashEnabled_ = true;
|
||||
bool lowHealthVignetteEnabled_ = true; // Persistent pulsing red vignette below 20% HP
|
||||
|
||||
|
||||
@@ -1844,23 +1844,15 @@ void EntityController::onValuesUpdatePlayer(const UpdateBlock& block, std::share
|
||||
oldFieldsSnapshot = owner_.lastPlayerFieldsRef();
|
||||
}
|
||||
if (block.hasMovement && block.runSpeed > 0.1f && block.runSpeed < 100.0f) {
|
||||
// Speed is independent of mount ownership: slows and ordinary movement
|
||||
// snapshots may report base-speed values while UNIT_FIELD_MOUNTDISPLAYID
|
||||
// still authoritatively says the player is mounted.
|
||||
if (auto* movement = owner_.getMovementHandler()) {
|
||||
movement->applyServerMovementSpeeds(
|
||||
block.walkSpeed, block.runSpeed, block.runBackSpeed,
|
||||
block.swimSpeed, block.swimBackSpeed, block.flightSpeed,
|
||||
block.flightBackSpeed, block.turnRate, block.pitchRate);
|
||||
}
|
||||
// Some server dismount paths update run speed without updating mount display field.
|
||||
const bool onRealTaxiFlight = owner_.getMovementHandler() && owner_.getMovementHandler()->isOnTaxiFlight();
|
||||
if (!onRealTaxiFlight && !owner_.taxiMountActiveRef() &&
|
||||
owner_.currentMountDisplayIdRef() != 0 && block.runSpeed <= 8.5f) {
|
||||
LOG_INFO("Auto-clearing mount from movement speed update: speed=", block.runSpeed,
|
||||
" displayId=", owner_.currentMountDisplayIdRef());
|
||||
owner_.currentMountDisplayIdRef() = 0;
|
||||
if (owner_.mountCallbackRef()) {
|
||||
owner_.mountCallbackRef()(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Merge block fields into the persistent snapshot. Both are sorted, so
|
||||
// a simple insert_or_assign per key keeps the invariant intact.
|
||||
|
||||
@@ -982,16 +982,9 @@ void MovementHandler::handleForceSpeedChange(network::Packet& packet, const char
|
||||
}
|
||||
|
||||
void MovementHandler::handleForceRunSpeedChange(network::Packet& packet) {
|
||||
// A speed change is not a dismount signal. Mount ownership is updated by
|
||||
// SMSG_DISMOUNT / UNIT_FIELD_MOUNTDISPLAYID instead.
|
||||
handleForceSpeedChange(packet, "RUN_SPEED", Opcode::CMSG_FORCE_RUN_SPEED_CHANGE_ACK, &serverRunSpeed_);
|
||||
|
||||
if (!onTaxiFlight_ && !taxiMountActive_ && owner_.currentMountDisplayIdRef() != 0 && serverRunSpeed_ <= 8.5f) {
|
||||
LOG_INFO("Auto-clearing mount from speed change: speed=", serverRunSpeed_,
|
||||
" displayId=", owner_.currentMountDisplayIdRef());
|
||||
owner_.currentMountDisplayIdRef() = 0;
|
||||
if (owner_.mountCallbackRef()) {
|
||||
owner_.mountCallbackRef()(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MovementHandler::handleForceMoveRootState(network::Packet& packet, bool rooted) {
|
||||
|
||||
69
src/rendering/world_map/layers/chest_tracker_layer.cpp
Normal file
69
src/rendering/world_map/layers/chest_tracker_layer.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
// chest_tracker_layer.cpp — Nearby spawned chest markers on the world map.
|
||||
#include "rendering/world_map/layers/chest_tracker_layer.hpp"
|
||||
#include "rendering/world_map/coordinate_projection.hpp"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace wowee {
|
||||
namespace rendering {
|
||||
namespace world_map {
|
||||
|
||||
void ChestTrackerLayer::render(const LayerContext& ctx) {
|
||||
if (!chests_ || chests_->empty()) return;
|
||||
if (ctx.currentZoneIdx < 0) return;
|
||||
if (ctx.viewLevel != ViewLevel::ZONE && ctx.viewLevel != ViewLevel::CONTINENT) return;
|
||||
if (!ctx.zones) return;
|
||||
|
||||
const auto& zone = (*ctx.zones)[ctx.currentZoneIdx];
|
||||
ZoneBounds bounds = zone.bounds;
|
||||
const bool isContinent = zone.areaID == 0;
|
||||
if (isContinent) {
|
||||
float left, right, top, bottom;
|
||||
if (getContinentProjectionBounds(*ctx.zones, ctx.currentZoneIdx,
|
||||
left, right, top, bottom)) {
|
||||
bounds = {left, right, top, bottom};
|
||||
}
|
||||
}
|
||||
|
||||
constexpr ImU32 fill = IM_COL32(205, 125, 35, 255);
|
||||
constexpr ImU32 outline = IM_COL32(45, 25, 5, 230);
|
||||
ImFont* font = ImGui::GetFont();
|
||||
for (const auto& chest : *chests_) {
|
||||
glm::vec2 uv = renderPosToMapUV(chest.renderPos, bounds, isContinent);
|
||||
if (uv.x < 0.0f || uv.x > 1.0f || uv.y < 0.0f || uv.y > 1.0f) continue;
|
||||
const float px = ctx.imgMin.x + uv.x * ctx.displayW;
|
||||
const float py = ctx.imgMin.y + uv.y * ctx.displayH;
|
||||
|
||||
// Compact chest silhouette: box, domed lid line, and bright latch.
|
||||
constexpr float halfW = 6.0f;
|
||||
constexpr float halfH = 4.5f;
|
||||
ctx.drawList->AddRectFilled(ImVec2(px - halfW, py - halfH),
|
||||
ImVec2(px + halfW, py + halfH), fill, 1.5f);
|
||||
ctx.drawList->AddRect(ImVec2(px - halfW, py - halfH),
|
||||
ImVec2(px + halfW, py + halfH), outline, 1.5f, 0, 1.5f);
|
||||
ctx.drawList->AddLine(ImVec2(px - halfW, py - 1.0f),
|
||||
ImVec2(px + halfW, py - 1.0f), outline, 1.2f);
|
||||
ctx.drawList->AddRectFilled(ImVec2(px - 1.2f, py - 1.5f),
|
||||
ImVec2(px + 1.2f, py + 1.5f),
|
||||
IM_COL32(255, 220, 80, 255), 0.5f);
|
||||
|
||||
ImVec2 mouse = ImGui::GetMousePos();
|
||||
if (mouse.x >= px - halfW - 2.0f && mouse.x <= px + halfW + 2.0f &&
|
||||
mouse.y >= py - halfH - 2.0f && mouse.y <= py + halfH + 2.0f &&
|
||||
!chest.name.empty()) {
|
||||
ImGui::SetTooltip("%s\nChest", chest.name.c_str());
|
||||
}
|
||||
if (!chest.name.empty()) {
|
||||
ImVec2 nameSize = font->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, 0.0f,
|
||||
chest.name.c_str());
|
||||
const float tx = px - nameSize.x * 0.5f;
|
||||
const float ty = py - nameSize.y - halfH - 3.0f;
|
||||
ctx.drawList->AddText(ImVec2(tx + 1.0f, ty + 1.0f),
|
||||
IM_COL32(0, 0, 0, 190), chest.name.c_str());
|
||||
ctx.drawList->AddText(ImVec2(tx, ty), fill, chest.name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace world_map
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "rendering/world_map/layers/quest_poi_layer.hpp"
|
||||
#include "rendering/world_map/layers/corpse_marker_layer.hpp"
|
||||
#include "rendering/world_map/layers/rare_tracker_layer.hpp"
|
||||
#include "rendering/world_map/layers/chest_tracker_layer.hpp"
|
||||
#include "rendering/world_map/layers/zone_highlight_layer.hpp"
|
||||
#include "rendering/world_map/layers/coordinate_display.hpp"
|
||||
#include "rendering/world_map/layers/subzone_tooltip_layer.hpp"
|
||||
@@ -121,11 +122,13 @@ struct WorldMapFacade::Impl {
|
||||
// Typed layer pointers for setters (non-owning references into overlay)
|
||||
PartyDotLayer* partyDotLayer = nullptr;
|
||||
std::vector<RareMark> rares;
|
||||
std::vector<ChestMark> chests;
|
||||
TaxiNodeLayer* taxiNodeLayer = nullptr;
|
||||
POIMarkerLayer* poiMarkerLayer = nullptr;
|
||||
QuestPOILayer* questPOILayer = nullptr;
|
||||
CorpseMarkerLayer* corpseMarkerLayer = nullptr;
|
||||
RareTrackerLayer* rareTrackerLayer = nullptr;
|
||||
ChestTrackerLayer* chestTrackerLayer = nullptr;
|
||||
ZoneHighlightLayer* zoneHighlightLayer = nullptr;
|
||||
PlayerMarkerLayer* playerMarkerLayer = nullptr;
|
||||
|
||||
@@ -305,6 +308,11 @@ void WorldMapFacade::Impl::initOverlayLayers() {
|
||||
rareTrackerLayer = rtLayer.get();
|
||||
overlay.addLayer(std::move(rtLayer));
|
||||
|
||||
// Nearby spawned chest tracker
|
||||
auto ctLayer = std::make_unique<ChestTrackerLayer>();
|
||||
chestTrackerLayer = ctLayer.get();
|
||||
overlay.addLayer(std::move(ctLayer));
|
||||
|
||||
// Coordinate display
|
||||
overlay.addLayer(std::make_unique<CoordinateDisplay>());
|
||||
|
||||
@@ -635,6 +643,10 @@ void WorldMapFacade::setRares(std::vector<RareMark> rares) {
|
||||
impl_->rares = std::move(rares);
|
||||
}
|
||||
|
||||
void WorldMapFacade::setChests(std::vector<ChestMark> chests) {
|
||||
impl_->chests = std::move(chests);
|
||||
}
|
||||
|
||||
void WorldMapFacade::setTaxiNodes(std::vector<TaxiNode> nodes) {
|
||||
impl_->taxiNodes = std::move(nodes);
|
||||
}
|
||||
@@ -815,6 +827,7 @@ void WorldMapFacade::Impl::renderImGuiOverlay(const glm::vec3& playerRenderPos,
|
||||
// Update layer data pointers
|
||||
if (partyDotLayer) partyDotLayer->setDots(partyDots);
|
||||
if (rareTrackerLayer) rareTrackerLayer->setRares(rares);
|
||||
if (chestTrackerLayer) chestTrackerLayer->setChests(chests);
|
||||
if (taxiNodeLayer) taxiNodeLayer->setNodes(taxiNodes);
|
||||
if (poiMarkerLayer) poiMarkerLayer->setMarkers(data.poiMarkers());
|
||||
if (questPOILayer) questPOILayer->setPois(questPois);
|
||||
|
||||
@@ -596,6 +596,28 @@ void GameScreen::renderWorldMap(game::GameHandler& gameHandler) {
|
||||
wm->setRares(std::move(rares));
|
||||
}
|
||||
|
||||
// Chest tracker: mark loaded type-3 chest objects while excluding mining and
|
||||
// herbalism nodes, which share GAMEOBJECT_TYPE_CHEST on the wire.
|
||||
{
|
||||
std::vector<rendering::WorldMapChestMark> chests;
|
||||
if (settingsPanel_.showChestTracker_) {
|
||||
for (const auto& [guid, entity] : gameHandler.getEntityManager().getEntities()) {
|
||||
if (!entity || entity->getType() != game::ObjectType::GAMEOBJECT) continue;
|
||||
auto chest = std::static_pointer_cast<game::GameObject>(entity);
|
||||
const auto* info = gameHandler.getCachedGameObjectInfo(chest->getEntry());
|
||||
if (!info || !info->isValid() || info->type != 3) continue;
|
||||
if (gameHandler.isGatherGameObject(guid)) continue;
|
||||
|
||||
rendering::WorldMapChestMark mark;
|
||||
mark.renderPos = core::coords::canonicalToRender(
|
||||
glm::vec3(chest->getX(), chest->getY(), chest->getZ()));
|
||||
mark.name = chest->getName().empty() ? info->name : chest->getName();
|
||||
chests.push_back(std::move(mark));
|
||||
}
|
||||
}
|
||||
wm->setChests(std::move(chests));
|
||||
}
|
||||
|
||||
glm::vec3 playerPos = renderer->getCharacterPosition();
|
||||
float playerYaw = renderer->getCharacterYaw();
|
||||
auto* window = app.getWindow();
|
||||
|
||||
@@ -257,6 +257,46 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
|
||||
}
|
||||
}
|
||||
|
||||
// Rare tracker: use the same live creature-rank classification as the world map.
|
||||
// This is independent of generic NPC dots so enabling rare tracking consistently
|
||||
// shows spawned rares on both maps without adding every nearby creature.
|
||||
if (settingsPanel_.showRareTracker_) {
|
||||
ImVec2 mouse = ImGui::GetMousePos();
|
||||
for (const auto& entity : minimapUnits) {
|
||||
auto unit = std::static_pointer_cast<game::Unit>(entity);
|
||||
if (!unit || unit->getHealth() == 0) continue;
|
||||
|
||||
const int rank = gameHandler.getCreatureRank(unit->getEntry());
|
||||
if (rank != 2 && rank != 4) continue; // 2 = Rare Elite, 4 = Rare
|
||||
|
||||
glm::vec3 rareRender = core::coords::canonicalToRender(
|
||||
glm::vec3(entity->getX(), entity->getY(), entity->getZ()));
|
||||
float sx = 0.0f, sy = 0.0f;
|
||||
if (!projectToMinimap(rareRender, sx, sy)) continue;
|
||||
|
||||
// Match the world-map tracker: gold for Rare, silver for Rare Elite.
|
||||
const bool isElite = rank == 2;
|
||||
const ImU32 fill = isElite
|
||||
? IM_COL32(210, 210, 225, 255)
|
||||
: IM_COL32(255, 190, 60, 255);
|
||||
constexpr float halfSize = 5.0f;
|
||||
const ImVec2 top(sx, sy - halfSize);
|
||||
const ImVec2 right(sx + halfSize, sy);
|
||||
const ImVec2 bottom(sx, sy + halfSize);
|
||||
const ImVec2 left(sx - halfSize, sy);
|
||||
drawList->AddQuadFilled(top, right, bottom, left, fill);
|
||||
drawList->AddQuad(top, right, bottom, left, IM_COL32(0, 0, 0, 220), 1.5f);
|
||||
|
||||
float mdx = mouse.x - sx, mdy = mouse.y - sy;
|
||||
if (mdx * mdx + mdy * mdy <= 49.0f) {
|
||||
const std::string& name = unit->getName();
|
||||
ImGui::SetTooltip("%s\n%s",
|
||||
name.empty() ? "Unknown creature" : name.c_str(),
|
||||
isElite ? "Rare Elite" : "Rare");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nearby other-player dots — shown when NPC dots are enabled.
|
||||
// Party members are already drawn as squares above; other players get a small circle.
|
||||
if (settingsPanel_.minimapNpcDots_) {
|
||||
@@ -366,6 +406,44 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
|
||||
}
|
||||
}
|
||||
|
||||
// Chest tracker: GAMEOBJECT_TYPE_CHEST also covers gathering nodes on WoW
|
||||
// servers, so explicitly exclude the existing mining/herbalism classification.
|
||||
if (settingsPanel_.showChestTracker_) {
|
||||
ImVec2 mouse = ImGui::GetMousePos();
|
||||
for (const auto& entity : minimapGameObjects) {
|
||||
auto chest = std::static_pointer_cast<game::GameObject>(entity);
|
||||
if (!chest) continue;
|
||||
const auto* info = gameHandler.getCachedGameObjectInfo(chest->getEntry());
|
||||
if (!info || !info->isValid() || info->type != 3) continue;
|
||||
if (gameHandler.isGatherGameObject(chest->getGuid())) continue;
|
||||
|
||||
glm::vec3 chestRender = core::coords::canonicalToRender(
|
||||
glm::vec3(entity->getX(), entity->getY(), entity->getZ()));
|
||||
float sx = 0.0f, sy = 0.0f;
|
||||
if (!projectToMinimap(chestRender, sx, sy)) continue;
|
||||
|
||||
constexpr float halfW = 5.5f;
|
||||
constexpr float halfH = 4.0f;
|
||||
constexpr ImU32 fill = IM_COL32(205, 125, 35, 255);
|
||||
constexpr ImU32 outline = IM_COL32(45, 25, 5, 230);
|
||||
drawList->AddRectFilled(ImVec2(sx - halfW, sy - halfH),
|
||||
ImVec2(sx + halfW, sy + halfH), fill, 1.5f);
|
||||
drawList->AddRect(ImVec2(sx - halfW, sy - halfH),
|
||||
ImVec2(sx + halfW, sy + halfH), outline, 1.5f, 0, 1.5f);
|
||||
drawList->AddLine(ImVec2(sx - halfW, sy - 0.8f),
|
||||
ImVec2(sx + halfW, sy - 0.8f), outline, 1.0f);
|
||||
drawList->AddRectFilled(ImVec2(sx - 1.0f, sy - 1.2f),
|
||||
ImVec2(sx + 1.0f, sy + 1.3f),
|
||||
IM_COL32(255, 220, 80, 255), 0.5f);
|
||||
|
||||
if (mouse.x >= sx - halfW - 2.0f && mouse.x <= sx + halfW + 2.0f &&
|
||||
mouse.y >= sy - halfH - 2.0f && mouse.y <= sy + halfH + 2.0f) {
|
||||
const std::string& name = chest->getName().empty() ? info->name : chest->getName();
|
||||
ImGui::SetTooltip("%s\nChest", name.empty() ? "Chest" : name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Party member dots on minimap — small colored squares with name tooltip on hover
|
||||
if (gameHandler.isInGroup()) {
|
||||
const auto& partyData = gameHandler.getPartyData();
|
||||
@@ -1455,6 +1533,7 @@ void GameScreen::saveSettings() {
|
||||
out << "show_dps_meter=" << (settingsPanel_.showDPSMeter_ ? 1 : 0) << "\n";
|
||||
out << "show_cooldown_tracker=" << (settingsPanel_.showCooldownTracker_ ? 1 : 0) << "\n";
|
||||
out << "show_rare_tracker=" << (settingsPanel_.showRareTracker_ ? 1 : 0) << "\n";
|
||||
out << "show_chest_tracker=" << (settingsPanel_.showChestTracker_ ? 1 : 0) << "\n";
|
||||
out << "separate_bags=" << (settingsPanel_.pendingSeparateBags ? 1 : 0) << "\n";
|
||||
out << "show_keyring=" << (settingsPanel_.pendingShowKeyring ? 1 : 0) << "\n";
|
||||
out << "bag_scale=" << settingsPanel_.pendingBagScale << "\n";
|
||||
@@ -1616,6 +1695,8 @@ void GameScreen::loadSettings() {
|
||||
settingsPanel_.showCooldownTracker_ = (std::stoi(val) != 0);
|
||||
} else if (key == "show_rare_tracker") {
|
||||
settingsPanel_.showRareTracker_ = (std::stoi(val) != 0);
|
||||
} else if (key == "show_chest_tracker") {
|
||||
settingsPanel_.showChestTracker_ = (std::stoi(val) != 0);
|
||||
} else if (key == "separate_bags") {
|
||||
settingsPanel_.pendingSeparateBags = (std::stoi(val) != 0);
|
||||
inventoryScreen.setSeparateBags(settingsPanel_.pendingSeparateBags);
|
||||
|
||||
@@ -175,7 +175,13 @@ if (ImGui::Checkbox("Show Rare Tracker", &showRareTracker_)) {
|
||||
saveCallback();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(mark nearby spawned rares on the world map)");
|
||||
ImGui::TextDisabled("(mark nearby spawned rares on the world map and minimap)");
|
||||
|
||||
if (ImGui::Checkbox("Show Chest Tracker", &showChestTracker_)) {
|
||||
saveCallback();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(mark nearby spawned loot chests on the world map and minimap)");
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::SeparatorText("Screen Effects");
|
||||
|
||||
@@ -164,6 +164,27 @@ TEST_CASE("A superseded rank resolves to the highest known rank", "[spell][rank]
|
||||
REQUIRE(resolveHighestKnownRank(11549, known, dbc.lookup()) == 6192);
|
||||
}
|
||||
|
||||
TEST_CASE("Rank resolution tolerates a lookup adapter with reused storage", "[spell][rank]") {
|
||||
const std::unordered_map<uint32_t, SpellRankInfo> entries{
|
||||
{6673, {"Battle Shout", "Rank 1"}},
|
||||
{5242, {"Battle Shout", "Rank 2"}},
|
||||
{6192, {"Battle Shout", "Rank 3"}},
|
||||
{133, {"Fireball", "Rank 14"}},
|
||||
};
|
||||
SpellRankInfo scratch;
|
||||
auto reusedLookup = [&entries, &scratch](uint32_t id) -> const SpellRankInfo* {
|
||||
auto it = entries.find(id);
|
||||
if (it == entries.end()) return nullptr;
|
||||
scratch = it->second;
|
||||
return &scratch;
|
||||
};
|
||||
|
||||
// The production Spell.dbc adapter reuses scratch storage. Looking up Fireball
|
||||
// must not overwrite the requested Battle Shout name and win merely by rank.
|
||||
const std::unordered_set<uint32_t> known{5242, 6192, 133};
|
||||
REQUIRE(resolveHighestKnownRank(6673, known, reusedLookup) == 6192);
|
||||
}
|
||||
|
||||
TEST_CASE("A known spell is left alone", "[spell][rank]") {
|
||||
FakeSpellDbc dbc;
|
||||
dbc.add(772, "Rend", "Rank 1");
|
||||
|
||||
Reference in New Issue
Block a user