diff --git a/Data/expansions/classic/dbc_layouts.json b/Data/expansions/classic/dbc_layouts.json index ea0497a4..459c9046 100644 --- a/Data/expansions/classic/dbc_layouts.json +++ b/Data/expansions/classic/dbc_layouts.json @@ -168,6 +168,7 @@ "AttributesEx": 6, "CastingTimeIndex": 15, "DispelType": 4, + "DurationIndex": 40, "EffectBasePoints0": 80, "EffectBasePoints1": 81, "EffectBasePoints2": 82, diff --git a/Data/expansions/tbc/dbc_layouts.json b/Data/expansions/tbc/dbc_layouts.json index ad9ab574..e11682cf 100644 --- a/Data/expansions/tbc/dbc_layouts.json +++ b/Data/expansions/tbc/dbc_layouts.json @@ -207,6 +207,7 @@ "AttributesEx": 6, "CastingTimeIndex": 22, "DispelType": 3, + "DurationIndex": 40, "EffectBasePoints0": 80, "EffectBasePoints1": 81, "EffectBasePoints2": 82, diff --git a/Data/expansions/turtle/dbc_layouts.json b/Data/expansions/turtle/dbc_layouts.json index be1602f8..2f580109 100644 --- a/Data/expansions/turtle/dbc_layouts.json +++ b/Data/expansions/turtle/dbc_layouts.json @@ -201,6 +201,7 @@ "AttributesEx": 6, "CastingTimeIndex": 15, "DispelType": 4, + "DurationIndex": 40, "EffectBasePoints0": 80, "EffectBasePoints1": 81, "EffectBasePoints2": 82, diff --git a/Data/expansions/wotlk/dbc_layouts.json b/Data/expansions/wotlk/dbc_layouts.json index ab495769..e563d2c9 100644 --- a/Data/expansions/wotlk/dbc_layouts.json +++ b/Data/expansions/wotlk/dbc_layouts.json @@ -223,6 +223,7 @@ "AttributesEx": 5, "CastingTimeIndex": 47, "DispelType": 2, + "DurationIndex": 40, "EffectBasePoints0": 80, "EffectBasePoints1": 81, "EffectBasePoints2": 82, diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 0e77183f..533d9faa 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -2245,6 +2245,7 @@ public: /// Returns the tooltip/description text from Spell.dbc (empty if unknown or has no text). const std::string& getSpellDescription(uint32_t spellId) const; const int32_t* getSpellEffectBasePoints(uint32_t spellId) const; + float getSpellDuration(uint32_t spellId) const; std::string getEnchantName(uint32_t enchantId) const; const std::string& getSkillLineName(uint32_t spellId) const; /// Returns the DispelType for a spell (0=none,1=magic,2=curse,3=disease,4=poison,5+=other) @@ -3327,6 +3328,7 @@ private: std::string name; std::string rank; std::string description; uint32_t schoolMask = 0; uint8_t dispelType = 0; uint32_t attrEx = 0; int32_t effectBasePoints[3] = {0, 0, 0}; + float durationSec = 0.0f; // resolved from DurationIndex → SpellDuration.dbc }; std::unordered_map spellNameCache_; bool spellNameCacheLoaded_ = false; diff --git a/src/addons/lua_engine.cpp b/src/addons/lua_engine.cpp index 5fe5d1e1..e9018643 100644 --- a/src/addons/lua_engine.cpp +++ b/src/addons/lua_engine.cpp @@ -1627,7 +1627,7 @@ static int lua_GetSpellBookItemName(lua_State* L) { // GetSpellDescription(spellId) → description string // Clean spell description template variables for display -static std::string cleanSpellDescription(const std::string& raw, const int32_t effectBase[3] = nullptr) { +static std::string cleanSpellDescription(const std::string& raw, const int32_t effectBase[3] = nullptr, float durationSec = 0.0f) { if (raw.empty() || raw.find('$') == std::string::npos) return raw; std::string result; result.reserve(raw.size()); @@ -1657,8 +1657,15 @@ static std::string cleanSpellDescription(const std::string& raw, const int32_t e i += 1; while (i + 1 < raw.size() && raw[i + 1] >= '0' && raw[i + 1] <= '9') ++i; } else if (next == 'd' || next == 'D') { - // $d = duration — replace with "X sec" - result += "X sec"; + // $d = duration + if (durationSec > 0.0f) { + if (durationSec >= 60.0f) + result += std::to_string(static_cast(durationSec / 60.0f)) + " min"; + else + result += std::to_string(static_cast(durationSec)) + " sec"; + } else { + result += "X sec"; + } ++i; while (i + 1 < raw.size() && raw[i + 1] >= '0' && raw[i + 1] <= '9') ++i; } else if (next == 'a' || next == 'A') { @@ -1698,7 +1705,8 @@ static int lua_GetSpellDescription(lua_State* L) { uint32_t spellId = static_cast(luaL_checknumber(L, 1)); const std::string& desc = gh->getSpellDescription(spellId); const int32_t* ebp = gh->getSpellEffectBasePoints(spellId); - std::string cleaned = cleanSpellDescription(desc, ebp); + float dur = gh->getSpellDuration(spellId); + std::string cleaned = cleanSpellDescription(desc, ebp, dur); lua_pushstring(L, cleaned.c_str()); return 1; } diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index b9988f70..263c0676 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -23200,9 +23200,33 @@ void GameHandler::loadSpellNameCache() { if (f1 != 0xFFFFFFFF) entry.effectBasePoints[1] = static_cast(dbc->getUInt32(i, f1)); if (f2 != 0xFFFFFFFF) entry.effectBasePoints[2] = static_cast(dbc->getUInt32(i, f2)); } + // Duration: read DurationIndex and resolve via SpellDuration.dbc later + if (spellL) { + uint32_t durF = spellL->field("DurationIndex"); + if (durF != 0xFFFFFFFF) + entry.durationSec = static_cast(dbc->getUInt32(i, durF)); // store index temporarily + } spellNameCache_[id] = std::move(entry); } } + // Resolve DurationIndex → seconds via SpellDuration.dbc + auto durDbc = am->loadDBC("SpellDuration.dbc"); + if (durDbc && durDbc->isLoaded()) { + std::unordered_map durMap; + for (uint32_t di = 0; di < durDbc->getRecordCount(); ++di) { + uint32_t durId = durDbc->getUInt32(di, 0); + int32_t baseMs = static_cast(durDbc->getUInt32(di, 1)); + if (baseMs > 0 && baseMs < 100000000) // filter out absurd values + durMap[durId] = baseMs / 1000.0f; + } + for (auto& [sid, entry] : spellNameCache_) { + uint32_t durIdx = static_cast(entry.durationSec); + if (durIdx > 0) { + auto it = durMap.find(durIdx); + entry.durationSec = (it != durMap.end()) ? it->second : 0.0f; + } + } + } LOG_INFO("Trainer: Loaded ", spellNameCache_.size(), " spell names from Spell.dbc"); } @@ -23445,6 +23469,12 @@ const int32_t* GameHandler::getSpellEffectBasePoints(uint32_t spellId) const { return (it != spellNameCache_.end()) ? it->second.effectBasePoints : nullptr; } +float GameHandler::getSpellDuration(uint32_t spellId) const { + const_cast(this)->loadSpellNameCache(); + auto it = spellNameCache_.find(spellId); + return (it != spellNameCache_.end()) ? it->second.durationSec : 0.0f; +} + const std::string& GameHandler::getSpellName(uint32_t spellId) const { auto it = spellNameCache_.find(spellId); return (it != spellNameCache_.end()) ? it->second.name : EMPTY_STRING;