fix: hearthstone from action bar, far teleport loading screen

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.
This commit is contained in:
Kelsi 2026-03-28 14:55:58 -07:00
parent 4e709692f1
commit 11571c582b
4 changed files with 54 additions and 6 deletions

View file

@ -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);

View file

@ -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();

View file

@ -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_();

View file

@ -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()) {
// 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) {