From 11571c582b3950c8772294bdf05ad9322d225f54 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 28 Mar 2026 14:55:58 -0700 Subject: [PATCH] fix: hearthstone from action bar, far teleport loading screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Action bar hearthstone: the slot was type SPELL (spell 8690) not ITEM. castSpell sends CMSG_CAST_SPELL which the server rejects for item-use spells. Now detects item-use spells via getItemIdForSpell() and routes through useItemById() instead, sending CMSG_USE_ITEM correctly. Far same-map teleport: hearthstone on the same continent (e.g., Westfall → Stormwind on Azeroth) skipped the loading screen, so the player fell through unloaded terrain. Now triggers a full world reload with loading screen for teleports > 500 units, with the warmup ground check ensuring WMO floors are loaded before spawning. --- include/game/game_handler.hpp | 1 + src/core/application.cpp | 20 ++++++++++++++++---- src/game/game_handler.cpp | 28 ++++++++++++++++++++++++++++ src/ui/game_screen.cpp | 11 +++++++++-- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index e4a2c38f..720e3f7a 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1927,6 +1927,7 @@ public: void swapContainerItems(uint8_t srcBag, uint8_t srcSlot, uint8_t dstBag, uint8_t dstSlot); void swapBagSlots(int srcBagIndex, int dstBagIndex); void useItemById(uint32_t itemId); + uint32_t getItemIdForSpell(uint32_t spellId) const; bool isVendorWindowOpen() const; const ListInventoryData& getVendorItems() const; void setVendorCanRepair(bool v); diff --git a/src/core/application.cpp b/src/core/application.cpp index 7b4cadba..4755151c 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -2436,13 +2436,25 @@ void Application::setupUICallbacks() { return; } - // Same-map teleport (taxi landing, GM teleport on same continent): - // just update position, let terrain streamer handle tile loading incrementally. - // A full reload is only needed on first entry or map change. + // Same-map teleport (taxi landing, GM teleport, hearthstone on same continent): if (mapId == loadedMapId_ && renderer && renderer->getTerrainManager()) { - LOG_INFO("Same-map teleport (map ", mapId, "), skipping full world reload"); + // Check if teleport is far enough to need terrain loading (>500 render units) + glm::vec3 oldPos = renderer->getCharacterPosition(); glm::vec3 canonical = core::coords::serverToCanonical(glm::vec3(x, y, z)); glm::vec3 renderPos = core::coords::canonicalToRender(canonical); + float teleportDistSq = glm::dot(renderPos - oldPos, renderPos - oldPos); + bool farTeleport = (teleportDistSq > 500.0f * 500.0f); + + if (farTeleport) { + // Far same-map teleport (hearthstone, etc.): do a full world reload + // with loading screen to prevent falling through unloaded terrain. + LOG_WARNING("Far same-map teleport (dist=", std::sqrt(teleportDistSq), + "), triggering full world reload with loading screen"); + loadOnlineWorldTerrain(mapId, x, y, z); + return; + } + LOG_INFO("Same-map teleport (map ", mapId, "), skipping full world reload"); + // canonical and renderPos already computed above for distance check renderer->getCharacterPosition() = renderPos; if (renderer->getCameraController()) { auto* ft = renderer->getCameraController()->getFollowTargetMutable(); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 0cddcfab..7396de7d 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -8556,6 +8556,34 @@ void GameHandler::useItemById(uint32_t itemId) { if (inventoryHandler_) inventoryHandler_->useItemById(itemId); } +uint32_t GameHandler::getItemIdForSpell(uint32_t spellId) const { + if (spellId == 0) return 0; + // Search backpack and bags for an item whose on-use spell matches + for (int i = 0; i < inventory.getBackpackSize(); i++) { + const auto& slot = inventory.getBackpackSlot(i); + if (slot.empty()) continue; + auto* info = getItemInfo(slot.item.itemId); + if (!info || !info->valid) continue; + for (const auto& sp : info->spells) { + if (sp.spellId == spellId && (sp.spellTrigger == 0 || sp.spellTrigger == 5)) + return slot.item.itemId; + } + } + for (int bag = 0; bag < inventory.NUM_BAG_SLOTS; bag++) { + for (int s = 0; s < inventory.getBagSize(bag); s++) { + const auto& slot = inventory.getBagSlot(bag, s); + if (slot.empty()) continue; + auto* info = getItemInfo(slot.item.itemId); + if (!info || !info->valid) continue; + for (const auto& sp : info->spells) { + if (sp.spellId == spellId && (sp.spellTrigger == 0 || sp.spellTrigger == 5)) + return slot.item.itemId; + } + } + } + return 0; +} + void GameHandler::unstuck() { if (unstuckCallback_) { unstuckCallback_(); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 5b1aa6bb..64ee286a 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -9366,8 +9366,15 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) { actionBarDragIcon_ = 0; } else if (clicked && !slot.isEmpty()) { if (slot.type == game::ActionBarSlot::SPELL && slot.isReady()) { - uint64_t target = gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0; - gameHandler.castSpell(slot.id, target); + // Check if this spell belongs to an item (e.g., Hearthstone spell 8690). + // Item-use spells must go through CMSG_USE_ITEM, not CMSG_CAST_SPELL. + uint32_t itemForSpell = gameHandler.getItemIdForSpell(slot.id); + if (itemForSpell != 0) { + gameHandler.useItemById(itemForSpell); + } else { + uint64_t target = gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0; + gameHandler.castSpell(slot.id, target); + } } else if (slot.type == game::ActionBarSlot::ITEM && slot.id != 0) { gameHandler.useItemById(slot.id); } else if (slot.type == game::ActionBarSlot::MACRO) {