mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-07-30 17:29:14 -04:00
Layout files are looked up by name, so a field that goes missing degrades silently rather than failing: a Spell layout with no RangeIndex makes every spell's range unknown, which quietly disables melee range checks and self-cast detection across all four expansions. Verified load-bearing: dropping RangeIndex from the WotLK layout fails the test at the assertion that guards Battle Shout.
109 lines
4.0 KiB
C++
109 lines
4.0 KiB
C++
#include <catch_amalgamated.hpp>
|
|
|
|
#include "pipeline/dbc_layout.hpp"
|
|
|
|
#include <filesystem>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// The spell code reads DBC fields by name, resolving them through the per-expansion
|
|
// layout files. If a layout loses a field the code depends on, the lookup returns
|
|
// "missing" and the behaviour silently degrades rather than failing: a Spell layout
|
|
// with no RangeIndex makes every spell's range unknown, which turns off melee range
|
|
// checks and self-cast detection across the board. Nothing else would catch that, so
|
|
// pin the fields the spell logic actually depends on.
|
|
|
|
using wowee::pipeline::DBCLayout;
|
|
|
|
namespace {
|
|
|
|
constexpr uint32_t kMissing = 0xFFFFFFFF;
|
|
|
|
const std::vector<std::string>& expansions() {
|
|
static const std::vector<std::string> kExpansions{"classic", "tbc", "wotlk", "turtle"};
|
|
return kExpansions;
|
|
}
|
|
|
|
std::string layoutPath(const std::string& expansion) {
|
|
return (std::filesystem::path(WOWEE_SOURCE_DIR) /
|
|
"Data" / "expansions" / expansion / "dbc_layouts.json").string();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Every expansion ships a loadable DBC layout", "[dbc][layout]") {
|
|
for (const auto& expansion : expansions()) {
|
|
INFO("expansion: " << expansion);
|
|
DBCLayout layout;
|
|
REQUIRE(layout.loadFromJson(layoutPath(expansion)));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Spell layout exposes the fields the spell logic reads", "[dbc][layout]") {
|
|
for (const auto& expansion : expansions()) {
|
|
INFO("expansion: " << expansion);
|
|
DBCLayout layout;
|
|
REQUIRE(layout.loadFromJson(layoutPath(expansion)));
|
|
|
|
const auto* spell = layout.getLayout("Spell");
|
|
REQUIRE(spell != nullptr);
|
|
|
|
// Identity and display.
|
|
REQUIRE(spell->field("ID") != kMissing);
|
|
REQUIRE(spell->field("Name") != kMissing);
|
|
// Rank drives superseded-rank resolution ("Rank 3" -> 3).
|
|
REQUIRE(spell->field("Rank") != kMissing);
|
|
|
|
// RangeIndex resolves against SpellRange.dbc and decides whether a spell is
|
|
// self-cast (0 yards) or melee (Combat Range, 5 yards). Lose it and Battle
|
|
// Shout reads as a melee ability again.
|
|
REQUIRE(spell->field("RangeIndex") != kMissing);
|
|
|
|
// Duration drives aura timers.
|
|
REQUIRE(spell->field("DurationIndex") != kMissing);
|
|
|
|
// School is per-expansion: a bitmask on TBC/WotLK, a 0-6 enum on Classic/Turtle.
|
|
const bool hasSchool = spell->field("SchoolMask") != kMissing ||
|
|
spell->field("SchoolEnum") != kMissing;
|
|
REQUIRE(hasSchool);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("SpellRange layout exposes MaxRange", "[dbc][layout]") {
|
|
for (const auto& expansion : expansions()) {
|
|
INFO("expansion: " << expansion);
|
|
DBCLayout layout;
|
|
REQUIRE(layout.loadFromJson(layoutPath(expansion)));
|
|
|
|
const auto* range = layout.getLayout("SpellRange");
|
|
REQUIRE(range != nullptr);
|
|
|
|
// Without MaxRange the RangeIndex cannot be resolved into yards, and every
|
|
// spell falls back to "unknown range".
|
|
REQUIRE(range->field("MaxRange") != kMissing);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Spell field indices are distinct", "[dbc][layout]") {
|
|
// A copy-paste slip that points two fields at the same column reads the wrong
|
|
// data rather than failing, so check the ones that sit next to each other.
|
|
for (const auto& expansion : expansions()) {
|
|
INFO("expansion: " << expansion);
|
|
DBCLayout layout;
|
|
REQUIRE(layout.loadFromJson(layoutPath(expansion)));
|
|
const auto* spell = layout.getLayout("Spell");
|
|
REQUIRE(spell != nullptr);
|
|
|
|
const uint32_t id = spell->field("ID");
|
|
const uint32_t name = spell->field("Name");
|
|
const uint32_t rank = spell->field("Rank");
|
|
const uint32_t rangeIndex = spell->field("RangeIndex");
|
|
const uint32_t durationIndex = spell->field("DurationIndex");
|
|
|
|
REQUIRE(id != name);
|
|
REQUIRE(name != rank);
|
|
REQUIRE(rangeIndex != durationIndex);
|
|
REQUIRE(rangeIndex != rank);
|
|
}
|
|
}
|