mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-08 10:03:51 +00:00
feat(editor): add --export-zone-summary-md for markdown documentation
Renders a human-readable markdown page summarizing a zone's manifest + content. Useful for designers tracking changes between versions, PR reviews, or generating GitHub Pages docs without cracking open the GUI: wowee_editor --export-zone-summary-md custom_zones/MyZone # -> custom_zones/MyZone/ZONE.md (default path) wowee_editor --export-zone-summary-md custom_zones/MyZone docs/Zone.md # -> custom Markdown destination Sections rendered: - Manifest (table): mapName, displayName, mapId, biome, baseHeight, tile count, gameplay flags (flying/PvP/indoor/sanctuary), audio (music/ambient day/ambient night), description. - Tiles (table): every (tx, ty). - Creatures (table): index, name, level, displayId, position, flags. - Objects (table): index, m2/wmo, path, position, scale. - Quests (sections): title, level, giver/turnIn IDs, XP/coin reward, objectives bulleted with type/target/count/description, item rewards bulleted. Designed to be diff-friendly so PR review highlights actual changes (adding 'Bear' creature shows up as one row added, not whole-file churn). Output is GitHub-Flavored Markdown so it renders cleanly on GitHub Pages and in PR previews. Verified end-to-end: scaffolded zone, populated 2 creatures + 1 object + 1 quest with full objectives + item reward; ZONE.md generated correctly with all tables and sections, quest details including bulleted objective + item list.
This commit is contained in:
parent
605af53c98
commit
bbdbce2ec4
1 changed files with 155 additions and 0 deletions
|
|
@ -480,6 +480,8 @@ static void printUsage(const char* argv0) {
|
|||
std::printf(" Recursively run all per-format validators on every file\n");
|
||||
std::printf(" --zone-summary <zoneDir> [--json]\n");
|
||||
std::printf(" One-shot validate + creature/object/quest counts and exit\n");
|
||||
std::printf(" --export-zone-summary-md <zoneDir> [out.md]\n");
|
||||
std::printf(" Render a markdown documentation page for a zone (manifest + content)\n");
|
||||
std::printf(" --info <wom-base> [--json]\n");
|
||||
std::printf(" Print WOM file metadata (version, counts) and exit\n");
|
||||
std::printf(" --info-wob <wob-base> [--json]\n");
|
||||
|
|
@ -555,6 +557,7 @@ int main(int argc, char* argv[]) {
|
|||
"--unpack-wcp", "--pack-wcp",
|
||||
"--validate", "--validate-wom", "--validate-wob", "--validate-woc",
|
||||
"--validate-whm", "--validate-all", "--zone-summary",
|
||||
"--export-zone-summary-md",
|
||||
"--scaffold-zone", "--add-tile", "--remove-tile", "--list-tiles",
|
||||
"--for-each-zone",
|
||||
"--add-creature", "--add-object", "--add-quest",
|
||||
|
|
@ -2357,6 +2360,158 @@ int main(int argc, char* argv[]) {
|
|||
questTotal, chainWarnings);
|
||||
}
|
||||
return v.openFormatScore() == 7 ? 0 : 1;
|
||||
} else if (std::strcmp(argv[i], "--export-zone-summary-md") == 0 && i + 1 < argc) {
|
||||
// Render a Markdown documentation page for a zone. Useful for
|
||||
// designers tracking changes between versions, generating
|
||||
// GitHub Pages docs, or reviewing zones in PRs without
|
||||
// round-tripping through the GUI.
|
||||
std::string zoneDir = argv[++i];
|
||||
std::string outPath;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
||||
outPath = argv[++i];
|
||||
}
|
||||
namespace fs = std::filesystem;
|
||||
std::string manifestPath = zoneDir + "/zone.json";
|
||||
if (!fs::exists(manifestPath)) {
|
||||
std::fprintf(stderr,
|
||||
"export-zone-summary-md: %s has no zone.json\n", zoneDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
wowee::editor::ZoneManifest zm;
|
||||
if (!zm.load(manifestPath)) {
|
||||
std::fprintf(stderr,
|
||||
"export-zone-summary-md: failed to parse %s\n", manifestPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
// Default output: ZONE.md sitting next to zone.json.
|
||||
if (outPath.empty()) outPath = zoneDir + "/ZONE.md";
|
||||
// Load content sub-files; missing ones contribute 0 entries.
|
||||
wowee::editor::NpcSpawner sp;
|
||||
sp.loadFromFile(zoneDir + "/creatures.json");
|
||||
wowee::editor::ObjectPlacer op;
|
||||
op.loadFromFile(zoneDir + "/objects.json");
|
||||
wowee::editor::QuestEditor qe;
|
||||
qe.loadFromFile(zoneDir + "/quests.json");
|
||||
std::ofstream md(outPath);
|
||||
if (!md) {
|
||||
std::fprintf(stderr,
|
||||
"export-zone-summary-md: cannot write %s\n", outPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
md << "# " << (zm.displayName.empty() ? zm.mapName : zm.displayName) << "\n\n";
|
||||
md << "*Auto-generated by `wowee_editor --export-zone-summary-md`. "
|
||||
"Do not edit by hand.*\n\n";
|
||||
md << "## Manifest\n\n";
|
||||
md << "| Field | Value |\n";
|
||||
md << "|---|---|\n";
|
||||
md << "| Map name | `" << zm.mapName << "` |\n";
|
||||
md << "| Display name | " << zm.displayName << " |\n";
|
||||
md << "| Map ID | " << zm.mapId << " |\n";
|
||||
if (!zm.biome.empty()) md << "| Biome | " << zm.biome << " |\n";
|
||||
md << "| Base height | " << zm.baseHeight << " |\n";
|
||||
md << "| Tile count | " << zm.tiles.size() << " |\n";
|
||||
md << "| Allow flying | " << (zm.allowFlying ? "yes" : "no") << " |\n";
|
||||
md << "| PvP enabled | " << (zm.pvpEnabled ? "yes" : "no") << " |\n";
|
||||
md << "| Indoor | " << (zm.isIndoor ? "yes" : "no") << " |\n";
|
||||
md << "| Sanctuary | " << (zm.isSanctuary ? "yes" : "no") << " |\n";
|
||||
if (!zm.musicTrack.empty()) md << "| Music | `" << zm.musicTrack << "` |\n";
|
||||
if (!zm.ambienceDay.empty()) md << "| Ambient (day) | `" << zm.ambienceDay << "` |\n";
|
||||
if (!zm.ambienceNight.empty())md << "| Ambient (night) | `" << zm.ambienceNight << "` |\n";
|
||||
if (!zm.description.empty()) {
|
||||
md << "\n### Description\n\n" << zm.description << "\n";
|
||||
}
|
||||
md << "\n## Tiles\n\n";
|
||||
md << "| tx | ty |\n|---|---|\n";
|
||||
for (const auto& [tx, ty] : zm.tiles) {
|
||||
md << "| " << tx << " | " << ty << " |\n";
|
||||
}
|
||||
md << "\n## Creatures (" << sp.spawnCount() << ")\n\n";
|
||||
if (sp.spawnCount() == 0) {
|
||||
md << "*No creature spawns.*\n";
|
||||
} else {
|
||||
md << "| # | Name | Lvl | DisplayId | Pos (x, y, z) | Flags |\n";
|
||||
md << "|---|---|---|---|---|---|\n";
|
||||
for (size_t k = 0; k < sp.spawnCount(); ++k) {
|
||||
const auto& s = sp.getSpawns()[k];
|
||||
md << "| " << k << " | " << s.name << " | " << s.level << " | "
|
||||
<< s.displayId << " | ("
|
||||
<< s.position.x << ", " << s.position.y << ", " << s.position.z
|
||||
<< ") |";
|
||||
if (s.hostile) md << " hostile";
|
||||
if (s.questgiver) md << " quest";
|
||||
if (s.vendor) md << " vendor";
|
||||
if (s.trainer) md << " trainer";
|
||||
md << " |\n";
|
||||
}
|
||||
}
|
||||
md << "\n## Objects (" << op.getObjects().size() << ")\n\n";
|
||||
if (op.getObjects().empty()) {
|
||||
md << "*No object placements.*\n";
|
||||
} else {
|
||||
md << "| # | Type | Path | Pos | Scale |\n";
|
||||
md << "|---|---|---|---|---|\n";
|
||||
for (size_t k = 0; k < op.getObjects().size(); ++k) {
|
||||
const auto& o = op.getObjects()[k];
|
||||
md << "| " << k << " | "
|
||||
<< (o.type == wowee::editor::PlaceableType::M2 ? "m2" : "wmo")
|
||||
<< " | `" << o.path << "` | ("
|
||||
<< o.position.x << ", " << o.position.y << ", " << o.position.z
|
||||
<< ") | " << o.scale << " |\n";
|
||||
}
|
||||
}
|
||||
md << "\n## Quests (" << qe.questCount() << ")\n\n";
|
||||
if (qe.questCount() == 0) {
|
||||
md << "*No quests.*\n";
|
||||
} else {
|
||||
using OT = wowee::editor::QuestObjectiveType;
|
||||
auto typeName = [](OT t) {
|
||||
switch (t) {
|
||||
case OT::KillCreature: return "kill";
|
||||
case OT::CollectItem: return "collect";
|
||||
case OT::TalkToNPC: return "talk";
|
||||
case OT::ExploreArea: return "explore";
|
||||
case OT::EscortNPC: return "escort";
|
||||
case OT::UseObject: return "use";
|
||||
}
|
||||
return "?";
|
||||
};
|
||||
for (size_t k = 0; k < qe.questCount(); ++k) {
|
||||
const auto& q = qe.getQuests()[k];
|
||||
md << "### " << k << ". " << q.title << "\n\n";
|
||||
md << "- Required level: " << q.requiredLevel << "\n";
|
||||
md << "- Quest giver NPC ID: " << q.questGiverNpcId << "\n";
|
||||
md << "- Turn-in NPC ID: " << q.turnInNpcId << "\n";
|
||||
md << "- XP: " << q.reward.xp << "\n";
|
||||
if (q.reward.gold || q.reward.silver || q.reward.copper) {
|
||||
md << "- Coin: " << q.reward.gold << "g "
|
||||
<< q.reward.silver << "s " << q.reward.copper << "c\n";
|
||||
}
|
||||
if (!q.objectives.empty()) {
|
||||
md << "- Objectives:\n";
|
||||
for (const auto& obj : q.objectives) {
|
||||
md << " - **" << typeName(obj.type) << "** "
|
||||
<< obj.targetName << " ×" << obj.targetCount;
|
||||
if (!obj.description.empty()) {
|
||||
md << " — *" << obj.description << "*";
|
||||
}
|
||||
md << "\n";
|
||||
}
|
||||
}
|
||||
if (!q.reward.itemRewards.empty()) {
|
||||
md << "- Item rewards:\n";
|
||||
for (const auto& it : q.reward.itemRewards) {
|
||||
md << " - `" << it << "`\n";
|
||||
}
|
||||
}
|
||||
md << "\n";
|
||||
}
|
||||
}
|
||||
md.close();
|
||||
std::printf("Wrote %s\n", outPath.c_str());
|
||||
std::printf(" zone=%s, %zu tiles, %zu creatures, %zu objects, %zu quests\n",
|
||||
zm.mapName.c_str(), zm.tiles.size(), sp.spawnCount(),
|
||||
op.getObjects().size(), qe.questCount());
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--validate") == 0 && i + 1 < argc) {
|
||||
std::string zoneDir = argv[++i];
|
||||
// Optional --json after the dir for machine-readable output
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue