mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-07 09:33:51 +00:00
feat(editor): add --info-creatures-by-faction + --info-creatures-by-level
Two analytics commands for combat-balance work. Where --info-creatures
gives totals + behavior counts, these give the distributions:
wowee_editor --info-creatures-by-faction $Z/creatures.json
Creatures by faction: ... (47 total)
faction count share
7 12 25.5%
14 29 61.7%
35 6 12.8%
(factions: 7=human, 14=monster, 35=neutral, etc.)
wowee_editor --info-creatures-by-level $Z/creatures.json
Creatures by level: ... (47 total)
range : 5 to 32 (avg 14.2)
level count bar
5 4 ████████████████████████████████████████
6 3 ██████████████████████████████
...
30 1 ██████████
Faction histogram catches single-faction zones (one giant melee) and
mixed-faction tuning issues. Level histogram catches difficulty-curve
problems (cluster at 5, gap, cluster at 30) and outlier spawns
(level-60 boss accidentally placed in starter area).
ASCII bar chart for level distribution since gameplay tuning is
visual — '60% of mobs are levels 8-12 with a long tail' is more
intuitive as a bar than as numbers. Bars scale to longest bin so
small zones still get usable visualization.
JSON mode emits per-faction / per-level records for dashboards.
Verified on a 4-creature seed (3×faction-14 + 1×faction-35; levels
7/8/12/30): faction percentages and level range/avg both correct.
This commit is contained in:
parent
1eb8232bb8
commit
e0ed2ab58e
1 changed files with 99 additions and 0 deletions
|
|
@ -708,6 +708,10 @@ static void printUsage(const char* argv0) {
|
|||
std::printf(" Print zone.json fields (manifest, tiles, audio, flags) and exit\n");
|
||||
std::printf(" --info-creatures <p> [--json]\n");
|
||||
std::printf(" Print creatures.json summary (counts, behaviors) and exit\n");
|
||||
std::printf(" --info-creatures-by-faction <p> [--json]\n");
|
||||
std::printf(" Histogram of creature counts grouped by faction id\n");
|
||||
std::printf(" --info-creatures-by-level <p> [--json]\n");
|
||||
std::printf(" Distribution of creature levels (min/max/avg + per-level counts)\n");
|
||||
std::printf(" --info-objects <p> [--json]\n");
|
||||
std::printf(" Print objects.json summary (counts, types, scale range) and exit\n");
|
||||
std::printf(" --info-quests <p> [--json]\n");
|
||||
|
|
@ -790,6 +794,7 @@ int main(int argc, char* argv[]) {
|
|||
"--list-quest-objectives", "--list-quest-rewards",
|
||||
"--info-creature", "--info-quest", "--info-object",
|
||||
"--info-quest-graph-stats",
|
||||
"--info-creatures-by-faction", "--info-creatures-by-level",
|
||||
"--unpack-wcp", "--pack-wcp",
|
||||
"--validate", "--validate-wom", "--validate-wob", "--validate-woc",
|
||||
"--validate-whm", "--validate-all", "--validate-glb", "--info-glb",
|
||||
|
|
@ -2529,6 +2534,100 @@ int main(int argc, char* argv[]) {
|
|||
stationary, wander, patrol);
|
||||
std::printf(" unique displayIds: %zu\n", displayIdHist.size());
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--info-creatures-by-faction") == 0 && i + 1 < argc) {
|
||||
// Faction histogram for combat balance analysis. AzerothCore
|
||||
// factions: 7=human, 14=monster, 16=alliance-friendly, 35=neutral,
|
||||
// etc. A zone with all faction=14 is going to be one giant
|
||||
// free-for-all; a mixed-faction zone needs combat-tuning.
|
||||
std::string path = argv[++i];
|
||||
bool jsonOut = (i + 1 < argc &&
|
||||
std::strcmp(argv[i + 1], "--json") == 0);
|
||||
if (jsonOut) i++;
|
||||
wowee::editor::NpcSpawner sp;
|
||||
if (!sp.loadFromFile(path)) {
|
||||
std::fprintf(stderr,
|
||||
"info-creatures-by-faction: failed to load %s\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::map<uint32_t, int> hist;
|
||||
for (const auto& s : sp.getSpawns()) hist[s.faction]++;
|
||||
if (jsonOut) {
|
||||
nlohmann::json j;
|
||||
j["file"] = path;
|
||||
j["totalCreatures"] = sp.spawnCount();
|
||||
j["uniqueFactions"] = hist.size();
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& [f, c] : hist) {
|
||||
arr.push_back({{"faction", f}, {"count", c}});
|
||||
}
|
||||
j["factions"] = arr;
|
||||
std::printf("%s\n", j.dump(2).c_str());
|
||||
return 0;
|
||||
}
|
||||
std::printf("Creatures by faction: %s (%zu total)\n",
|
||||
path.c_str(), sp.spawnCount());
|
||||
std::printf(" faction count share\n");
|
||||
for (const auto& [f, c] : hist) {
|
||||
double pct = sp.spawnCount() > 0 ? 100.0 * c / sp.spawnCount() : 0.0;
|
||||
std::printf(" %7u %5d %5.1f%%\n", f, c, pct);
|
||||
}
|
||||
std::printf(" (factions: 7=human, 14=monster, 35=neutral, etc.)\n");
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--info-creatures-by-level") == 0 && i + 1 < argc) {
|
||||
// Level distribution for difficulty-curve analysis. Min/max/
|
||||
// avg + per-level histogram. A zone with all level-1 spawns
|
||||
// is a starter area; one with all 60s is endgame; spikes in
|
||||
// the middle suggest content-tuning issues.
|
||||
std::string path = argv[++i];
|
||||
bool jsonOut = (i + 1 < argc &&
|
||||
std::strcmp(argv[i + 1], "--json") == 0);
|
||||
if (jsonOut) i++;
|
||||
wowee::editor::NpcSpawner sp;
|
||||
if (!sp.loadFromFile(path)) {
|
||||
std::fprintf(stderr,
|
||||
"info-creatures-by-level: failed to load %s\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::map<uint32_t, int> hist;
|
||||
uint32_t minL = std::numeric_limits<uint32_t>::max();
|
||||
uint32_t maxL = 0;
|
||||
uint64_t sumL = 0;
|
||||
for (const auto& s : sp.getSpawns()) {
|
||||
hist[s.level]++;
|
||||
if (s.level < minL) minL = s.level;
|
||||
if (s.level > maxL) maxL = s.level;
|
||||
sumL += s.level;
|
||||
}
|
||||
double avgL = sp.spawnCount() > 0 ? double(sumL) / sp.spawnCount() : 0.0;
|
||||
if (sp.spawnCount() == 0) minL = 0;
|
||||
if (jsonOut) {
|
||||
nlohmann::json j;
|
||||
j["file"] = path;
|
||||
j["totalCreatures"] = sp.spawnCount();
|
||||
j["minLevel"] = minL;
|
||||
j["maxLevel"] = maxL;
|
||||
j["avgLevel"] = avgL;
|
||||
nlohmann::json arr = nlohmann::json::array();
|
||||
for (const auto& [l, c] : hist) {
|
||||
arr.push_back({{"level", l}, {"count", c}});
|
||||
}
|
||||
j["levels"] = arr;
|
||||
std::printf("%s\n", j.dump(2).c_str());
|
||||
return 0;
|
||||
}
|
||||
std::printf("Creatures by level: %s (%zu total)\n",
|
||||
path.c_str(), sp.spawnCount());
|
||||
std::printf(" range : %u to %u (avg %.1f)\n", minL, maxL, avgL);
|
||||
std::printf("\n level count bar\n");
|
||||
int maxBarCount = 0;
|
||||
for (const auto& [_, c] : hist) maxBarCount = std::max(maxBarCount, c);
|
||||
for (const auto& [l, c] : hist) {
|
||||
int barLen = maxBarCount > 0 ? (40 * c) / maxBarCount : 0;
|
||||
std::printf(" %5u %5d ", l, c);
|
||||
for (int b = 0; b < barLen; ++b) std::printf("█");
|
||||
std::printf("\n");
|
||||
}
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--list-creatures") == 0 && i + 1 < argc) {
|
||||
// Verbose enumeration of every spawn — needed because
|
||||
// --remove-creature takes a 0-based index but --info-creatures
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue