From d4ea416dd67ba6ca294fabb8b5e42adea09baf83 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Mar 2026 19:24:09 -0700 Subject: [PATCH] Fix spell cast audio to use correct magic school from Spell.dbc Previously all player spell casts played ARCANE school sounds regardless of the actual spell school. Now loadSpellNameCache() reads SchoolMask (bitmask, TBC/WotLK) or SchoolEnum (Vanilla/Classic) from Spell.dbc and stores it in SpellNameEntry. handleSpellStart/handleSpellGo look up the spell's school and select the correct MagicSchool for cast sounds. DBC field indices: WotLK SchoolMask=225 (verified), TBC=215, Classic/Turtle SchoolEnum=1 (Vanilla enum 0-6 converted to bitmask). --- Data/expansions/classic/dbc_layouts.json | 2 +- Data/expansions/tbc/dbc_layouts.json | 2 +- Data/expansions/turtle/dbc_layouts.json | 2 +- Data/expansions/wotlk/dbc_layouts.json | 2 +- include/game/game_handler.hpp | 2 +- src/game/game_handler.cpp | 50 +++++++++++++++++++++--- 6 files changed, 50 insertions(+), 10 deletions(-) diff --git a/Data/expansions/classic/dbc_layouts.json b/Data/expansions/classic/dbc_layouts.json index 1b9bf00f..4ec229d5 100644 --- a/Data/expansions/classic/dbc_layouts.json +++ b/Data/expansions/classic/dbc_layouts.json @@ -1,7 +1,7 @@ { "Spell": { "ID": 0, "Attributes": 5, "IconID": 117, - "Name": 120, "Tooltip": 147, "Rank": 129 + "Name": 120, "Tooltip": 147, "Rank": 129, "SchoolEnum": 1 }, "ItemDisplayInfo": { "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, diff --git a/Data/expansions/tbc/dbc_layouts.json b/Data/expansions/tbc/dbc_layouts.json index af1bf479..d40a5766 100644 --- a/Data/expansions/tbc/dbc_layouts.json +++ b/Data/expansions/tbc/dbc_layouts.json @@ -1,7 +1,7 @@ { "Spell": { "ID": 0, "Attributes": 5, "IconID": 124, - "Name": 127, "Tooltip": 154, "Rank": 136 + "Name": 127, "Tooltip": 154, "Rank": 136, "SchoolMask": 215 }, "ItemDisplayInfo": { "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, diff --git a/Data/expansions/turtle/dbc_layouts.json b/Data/expansions/turtle/dbc_layouts.json index a3499be5..4e86338a 100644 --- a/Data/expansions/turtle/dbc_layouts.json +++ b/Data/expansions/turtle/dbc_layouts.json @@ -1,7 +1,7 @@ { "Spell": { "ID": 0, "Attributes": 5, "IconID": 117, - "Name": 120, "Tooltip": 147, "Rank": 129 + "Name": 120, "Tooltip": 147, "Rank": 129, "SchoolEnum": 1 }, "ItemDisplayInfo": { "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, diff --git a/Data/expansions/wotlk/dbc_layouts.json b/Data/expansions/wotlk/dbc_layouts.json index 5cbcf2eb..859f7542 100644 --- a/Data/expansions/wotlk/dbc_layouts.json +++ b/Data/expansions/wotlk/dbc_layouts.json @@ -1,7 +1,7 @@ { "Spell": { "ID": 0, "Attributes": 4, "IconID": 133, - "Name": 136, "Tooltip": 139, "Rank": 153 + "Name": 136, "Tooltip": 139, "Rank": 153, "SchoolMask": 225 }, "ItemDisplayInfo": { "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index aba5a344..d98af042 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1971,7 +1971,7 @@ private: // Trainer bool trainerWindowOpen_ = false; TrainerListData currentTrainerList_; - struct SpellNameEntry { std::string name; std::string rank; }; + struct SpellNameEntry { std::string name; std::string rank; uint32_t schoolMask = 0; }; std::unordered_map spellNameCache_; bool spellNameCacheLoaded_ = false; std::vector trainerTabs_; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index f1402b57..610048eb 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -12087,6 +12087,16 @@ void GameHandler::handleCastFailed(network::Packet& packet) { addLocalChatMessage(msg); } +static audio::SpellSoundManager::MagicSchool schoolMaskToMagicSchool(uint32_t mask) { + if (mask & 0x04) return audio::SpellSoundManager::MagicSchool::FIRE; + if (mask & 0x10) return audio::SpellSoundManager::MagicSchool::FROST; + if (mask & 0x02) return audio::SpellSoundManager::MagicSchool::HOLY; + if (mask & 0x08) return audio::SpellSoundManager::MagicSchool::NATURE; + if (mask & 0x20) return audio::SpellSoundManager::MagicSchool::SHADOW; + if (mask & 0x40) return audio::SpellSoundManager::MagicSchool::ARCANE; + return audio::SpellSoundManager::MagicSchool::ARCANE; +} + void GameHandler::handleSpellStart(network::Packet& packet) { SpellStartData data; if (!SpellStartParser::parse(packet, data)) return; @@ -12098,10 +12108,15 @@ void GameHandler::handleSpellStart(network::Packet& packet) { castTimeTotal = data.castTime / 1000.0f; castTimeRemaining = castTimeTotal; - // Play precast (channeling) sound + // Play precast (channeling) sound with correct magic school if (auto* renderer = core::Application::getInstance().getRenderer()) { if (auto* ssm = renderer->getSpellSoundManager()) { - ssm->playPrecast(audio::SpellSoundManager::MagicSchool::ARCANE, audio::SpellSoundManager::SpellPower::MEDIUM); + loadSpellNameCache(); + auto it = spellNameCache_.find(data.spellId); + auto school = (it != spellNameCache_.end() && it->second.schoolMask) + ? schoolMaskToMagicSchool(it->second.schoolMask) + : audio::SpellSoundManager::MagicSchool::ARCANE; + ssm->playPrecast(school, audio::SpellSoundManager::SpellPower::MEDIUM); } } } @@ -12113,10 +12128,15 @@ void GameHandler::handleSpellGo(network::Packet& packet) { // Cast completed if (data.casterUnit == playerGuid) { - // Play cast-complete sound before clearing state + // Play cast-complete sound with correct magic school if (auto* renderer = core::Application::getInstance().getRenderer()) { if (auto* ssm = renderer->getSpellSoundManager()) { - ssm->playCast(audio::SpellSoundManager::MagicSchool::ARCANE); + loadSpellNameCache(); + auto it = spellNameCache_.find(data.spellId); + auto school = (it != spellNameCache_.end() && it->second.schoolMask) + ? schoolMaskToMagicSchool(it->second.schoolMask) + : audio::SpellSoundManager::MagicSchool::ARCANE; + ssm->playCast(school); } } @@ -14269,6 +14289,17 @@ void GameHandler::loadSpellNameCache() { } const auto* spellL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("Spell") : nullptr; + + // Determine school field (bitmask for TBC/WotLK, enum for Classic/Vanilla) + uint32_t schoolMaskField = 0, schoolEnumField = 0; + bool hasSchoolMask = false, hasSchoolEnum = false; + if (spellL) { + uint32_t f = spellL->field("SchoolMask"); + if (f != 0xFFFFFFFF && f < dbc->getFieldCount()) { schoolMaskField = f; hasSchoolMask = true; } + f = spellL->field("SchoolEnum"); + if (f != 0xFFFFFFFF && f < dbc->getFieldCount()) { schoolEnumField = f; hasSchoolEnum = true; } + } + uint32_t count = dbc->getRecordCount(); for (uint32_t i = 0; i < count; ++i) { uint32_t id = dbc->getUInt32(i, spellL ? (*spellL)["ID"] : 0); @@ -14276,7 +14307,16 @@ void GameHandler::loadSpellNameCache() { std::string name = dbc->getString(i, spellL ? (*spellL)["Name"] : 136); std::string rank = dbc->getString(i, spellL ? (*spellL)["Rank"] : 153); if (!name.empty()) { - spellNameCache_[id] = {std::move(name), std::move(rank)}; + SpellNameEntry entry{std::move(name), std::move(rank), 0}; + if (hasSchoolMask) { + entry.schoolMask = dbc->getUInt32(i, schoolMaskField); + } else if (hasSchoolEnum) { + // Classic/Vanilla enum: 0=Physical,1=Holy,2=Fire,3=Nature,4=Frost,5=Shadow,6=Arcane + static const uint32_t enumToBitmask[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40}; + uint32_t e = dbc->getUInt32(i, schoolEnumField); + entry.schoolMask = (e < 7) ? enumToBitmask[e] : 0; + } + spellNameCache_[id] = std::move(entry); } } LOG_INFO("Trainer: Loaded ", spellNameCache_.size(), " spell names from Spell.dbc");