diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 47dd523d..a596c959 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -966,6 +966,12 @@ public: const std::vector& getQuestLog() const { return questLog_; } void abandonQuest(uint32_t questId); bool requestQuestQuery(uint32_t questId, bool force = false); + bool isQuestTracked(uint32_t questId) const { return trackedQuestIds_.count(questId) > 0; } + void setQuestTracked(uint32_t questId, bool tracked) { + if (tracked) trackedQuestIds_.insert(questId); + else trackedQuestIds_.erase(questId); + } + const std::unordered_set& getTrackedQuestIds() const { return trackedQuestIds_; } bool isQuestQueryPending(uint32_t questId) const { return pendingQuestQueryIds_.count(questId) > 0; } @@ -1981,6 +1987,7 @@ private: // Quest log std::vector questLog_; std::unordered_set pendingQuestQueryIds_; + std::unordered_set trackedQuestIds_; bool pendingLoginQuestResync_ = false; float pendingLoginQuestResyncTimeout_ = 0.0f; diff --git a/src/core/application.cpp b/src/core/application.cpp index f52be835..ca528bb2 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1714,6 +1714,21 @@ void Application::setupUICallbacks() { // (e.g. Hearthstone pre-loaded them) so they're GPU-uploaded before // the first frame at the new position. renderer->getTerrainManager()->processAllReadyTiles(); + + // Queue all remaining tiles within the load radius (8 tiles = 17x17) + // at the new position. precacheTiles skips already-loaded/pending tiles, + // so this only enqueues tiles that aren't yet in the pipeline. + // This ensures background workers immediately start loading everything + // visible from the new position (hearthstone may land far from old location). + { + auto [tileX, tileY] = core::coords::worldToTile(renderPos.x, renderPos.y); + std::vector> nearbyTiles; + nearbyTiles.reserve(289); + for (int dy = -8; dy <= 8; dy++) + for (int dx = -8; dx <= 8; dx++) + nearbyTiles.push_back({tileX + dx, tileY + dy}); + renderer->getTerrainManager()->precacheTiles(nearbyTiles); + } return; } @@ -1976,13 +1991,15 @@ void Application::setupUICallbacks() { if (mapId == loadedMapId_) { // Same map: pre-enqueue tiles around the bind point so workers start // loading them now. Uses render-space coords (canonicalToRender). + // Use radius 4 (9x9=81 tiles) — hearthstone cast is ~10s, enough time + // for workers to parse most of these before the player arrives. glm::vec3 renderPos = core::coords::canonicalToRender(glm::vec3(x, y, z)); auto [tileX, tileY] = core::coords::worldToTile(renderPos.x, renderPos.y); std::vector> tiles; - tiles.reserve(25); - for (int dy = -2; dy <= 2; dy++) - for (int dx = -2; dx <= 2; dx++) + tiles.reserve(81); + for (int dy = -4; dy <= 4; dy++) + for (int dx = -4; dx <= 4; dx++) tiles.push_back({tileX + dx, tileY + dy}); terrainMgr->precacheTiles(tiles); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 50c2a062..8e608990 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -4549,13 +4549,34 @@ void GameScreen::renderQuestObjectiveTracker(game::GameHandler& gameHandler) { constexpr float RIGHT_MARGIN = 10.0f; constexpr int MAX_QUESTS = 5; + // Build display list: tracked quests only, or all quests if none tracked + const auto& trackedIds = gameHandler.getTrackedQuestIds(); + std::vector toShow; + toShow.reserve(MAX_QUESTS); + if (!trackedIds.empty()) { + for (const auto& q : questLog) { + if (q.questId == 0) continue; + if (trackedIds.count(q.questId)) toShow.push_back(&q); + if (static_cast(toShow.size()) >= MAX_QUESTS) break; + } + } + // Fallback: show all quests if nothing is tracked + if (toShow.empty()) { + for (const auto& q : questLog) { + if (q.questId == 0) continue; + toShow.push_back(&q); + if (static_cast(toShow.size()) >= MAX_QUESTS) break; + } + } + if (toShow.empty()) return; + float x = screenW - TRACKER_W - RIGHT_MARGIN; float y = 200.0f; // below minimap area ImGui::SetNextWindowPos(ImVec2(x, y), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(TRACKER_W, 0), ImGuiCond_Always); - ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoBringToFrontOnFocus; @@ -4564,15 +4585,23 @@ void GameScreen::renderQuestObjectiveTracker(game::GameHandler& gameHandler) { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 2.0f)); if (ImGui::Begin("##QuestTracker", nullptr, flags)) { - int shown = 0; - for (const auto& q : questLog) { - if (q.questId == 0) continue; - if (shown >= MAX_QUESTS) break; + for (int i = 0; i < static_cast(toShow.size()); ++i) { + const auto& q = *toShow[i]; - // Quest title in yellow (gold) if complete, white if in progress + // Clickable quest title — opens quest log + ImGui::PushID(q.questId); ImVec4 titleCol = q.complete ? ImVec4(1.0f, 0.84f, 0.0f, 1.0f) : ImVec4(1.0f, 1.0f, 0.85f, 1.0f); - ImGui::TextColored(titleCol, "%s", q.title.c_str()); + ImGui::PushStyleColor(ImGuiCol_Text, titleCol); + if (ImGui::Selectable(q.title.c_str(), false, + ImGuiSelectableFlags_None, ImVec2(TRACKER_W - 12.0f, 0))) { + questLogScreen.setOpen(true); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Click to open Quest Log"); + } + ImGui::PopStyleColor(); + ImGui::PopID(); // Objectives line (condensed) if (q.complete) { @@ -4599,7 +4628,6 @@ void GameScreen::renderQuestObjectiveTracker(game::GameHandler& gameHandler) { } } if (q.killCounts.empty() && q.itemCounts.empty() && !q.objectives.empty()) { - // Show the raw objectives text, truncated if needed const std::string& obj = q.objectives; if (obj.size() > 40) { ImGui::TextColored(ImVec4(0.75f, 0.75f, 0.75f, 1.0f), @@ -4611,10 +4639,9 @@ void GameScreen::renderQuestObjectiveTracker(game::GameHandler& gameHandler) { } } - if (shown < MAX_QUESTS - 1 && shown < static_cast(questLog.size()) - 1) { + if (i < static_cast(toShow.size()) - 1) { ImGui::Spacing(); } - ++shown; } } ImGui::End(); diff --git a/src/ui/quest_log_screen.cpp b/src/ui/quest_log_screen.cpp index ad7df081..4f7f0dec 100644 --- a/src/ui/quest_log_screen.cpp +++ b/src/ui/quest_log_screen.cpp @@ -373,11 +373,17 @@ void QuestLogScreen::render(game::GameHandler& gameHandler) { } } - // Abandon button + // Track / Abandon buttons + ImGui::Separator(); + bool isTracked = gameHandler.isQuestTracked(sel.questId); + if (ImGui::Button(isTracked ? "Untrack" : "Track", ImVec2(100.0f, 0.0f))) { + gameHandler.setQuestTracked(sel.questId, !isTracked); + } if (!sel.complete) { - ImGui::Separator(); + ImGui::SameLine(); if (ImGui::Button("Abandon Quest", ImVec2(150.0f, 0.0f))) { gameHandler.abandonQuest(sel.questId); + gameHandler.setQuestTracked(sel.questId, false); selectedIndex = -1; } }