mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-08 10:03:51 +00:00
feat(editor): add --export-zone-csv for spreadsheet design workflows
Designers often prefer spreadsheets over JSON for read-only analysis
('which 5 quests give the most XP?', 'how many lvl 10+ creatures?',
'pivot table by faction'). This emits creatures.csv / objects.csv /
quests.csv in standard CSV that LibreOffice / Excel / Numbers / Python
pandas all consume natively:
wowee_editor --export-zone-csv custom_zones/MyZone
wrote custom_zones/MyZone/creatures.csv (47 rows)
wrote custom_zones/MyZone/objects.csv (12 rows)
wrote custom_zones/MyZone/quests.csv (8 rows)
CSV columns chosen to be designer-actionable:
- creatures: index/id/name/displayId/level/health/mana/faction/
position/orientation/scale + hostile/questgiver/vendor/trainer flags
- objects: index/type/path/position/rotation/scale
- quests: index/id/title/requiredLevel/giver/turnIn/reward fields/
objectiveCount + objectives/itemRewards joined by '; ' for keep-
one-row-per-quest sortability
RFC 4180 quoting: fields with comma/quote/newline get wrapped in
double quotes with internal quotes doubled. Verified on a creature
named 'Big, Bad Bear' — comes out as '"Big, Bad Bear"'.
Round-trip back into the editor isn't supported yet (would need
schema-aware CSV parsing); this is read-only-export for now.
This commit is contained in:
parent
8dc91adc52
commit
17b83858c1
1 changed files with 138 additions and 0 deletions
|
|
@ -529,6 +529,8 @@ static void printUsage(const char* argv0) {
|
|||
std::printf(" Render a hierarchical tree view of a zone's contents (no --json)\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(" --export-zone-csv <zoneDir> [outDir]\n");
|
||||
std::printf(" Emit creatures.csv / objects.csv / quests.csv for spreadsheet workflows\n");
|
||||
std::printf(" --export-quest-graph <zoneDir> [out.dot]\n");
|
||||
std::printf(" Render quest-chain DAG as Graphviz DOT (pipe to `dot -Tpng -o quests.png`)\n");
|
||||
std::printf(" --info <wom-base> [--json]\n");
|
||||
|
|
@ -635,6 +637,7 @@ int main(int argc, char* argv[]) {
|
|||
"--validate-jsondbc", "--check-glb-bounds", "--validate-stl",
|
||||
"--zone-summary", "--info-zone-tree",
|
||||
"--export-zone-summary-md", "--export-quest-graph",
|
||||
"--export-zone-csv",
|
||||
"--scaffold-zone", "--add-tile", "--remove-tile", "--list-tiles",
|
||||
"--for-each-zone", "--zone-stats", "--list-zone-deps",
|
||||
"--check-zone-refs",
|
||||
|
|
@ -3470,6 +3473,141 @@ int main(int argc, char* argv[]) {
|
|||
zm.mapName.c_str(), zm.tiles.size(), sp.spawnCount(),
|
||||
op.getObjects().size(), qe.questCount());
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--export-zone-csv") == 0 && i + 1 < argc) {
|
||||
// Emit creatures.csv / objects.csv / quests.csv for designers
|
||||
// who prefer spreadsheets over JSON. Round-trip back into the
|
||||
// editor isn't supported yet, but for read-only analysis (sort
|
||||
// by XP, group by faction, pivot tables in LibreOffice) CSV is
|
||||
// the lingua franca of design data.
|
||||
std::string zoneDir = argv[++i];
|
||||
std::string outDir;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') outDir = argv[++i];
|
||||
namespace fs = std::filesystem;
|
||||
if (!fs::exists(zoneDir + "/zone.json")) {
|
||||
std::fprintf(stderr,
|
||||
"export-zone-csv: %s has no zone.json\n", zoneDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (outDir.empty()) outDir = zoneDir;
|
||||
// CSV-escape: wrap any field containing comma/quote/newline in
|
||||
// double quotes; double up internal quotes per RFC 4180.
|
||||
auto csvEsc = [](const std::string& s) {
|
||||
bool needs = s.find(',') != std::string::npos ||
|
||||
s.find('"') != std::string::npos ||
|
||||
s.find('\n') != std::string::npos;
|
||||
if (!needs) return s;
|
||||
std::string out = "\"";
|
||||
for (char c : s) {
|
||||
if (c == '"') out += "\"\"";
|
||||
else out += c;
|
||||
}
|
||||
out += "\"";
|
||||
return out;
|
||||
};
|
||||
int filesWritten = 0;
|
||||
// Creatures
|
||||
wowee::editor::NpcSpawner sp;
|
||||
if (sp.loadFromFile(zoneDir + "/creatures.json")) {
|
||||
std::string out = outDir + "/creatures.csv";
|
||||
std::ofstream f(out);
|
||||
if (!f) {
|
||||
std::fprintf(stderr, "cannot write %s\n", out.c_str());
|
||||
return 1;
|
||||
}
|
||||
f << "index,id,name,displayId,level,health,mana,faction,"
|
||||
"x,y,z,orientation,scale,hostile,questgiver,vendor,trainer\n";
|
||||
for (size_t k = 0; k < sp.spawnCount(); ++k) {
|
||||
const auto& s = sp.getSpawns()[k];
|
||||
f << k << "," << s.id << "," << csvEsc(s.name) << ","
|
||||
<< s.displayId << "," << s.level << ","
|
||||
<< s.health << "," << s.mana << "," << s.faction << ","
|
||||
<< s.position.x << "," << s.position.y << ","
|
||||
<< s.position.z << "," << s.orientation << ","
|
||||
<< s.scale << ","
|
||||
<< (s.hostile ? 1 : 0) << ","
|
||||
<< (s.questgiver ? 1 : 0) << ","
|
||||
<< (s.vendor ? 1 : 0) << ","
|
||||
<< (s.trainer ? 1 : 0) << "\n";
|
||||
}
|
||||
std::printf(" wrote %s (%zu rows)\n", out.c_str(), sp.spawnCount());
|
||||
filesWritten++;
|
||||
}
|
||||
// Objects
|
||||
wowee::editor::ObjectPlacer op;
|
||||
if (op.loadFromFile(zoneDir + "/objects.json")) {
|
||||
std::string out = outDir + "/objects.csv";
|
||||
std::ofstream f(out);
|
||||
if (!f) return 1;
|
||||
f << "index,type,path,x,y,z,rotX,rotY,rotZ,scale\n";
|
||||
for (size_t k = 0; k < op.getObjects().size(); ++k) {
|
||||
const auto& o = op.getObjects()[k];
|
||||
f << k << ","
|
||||
<< (o.type == wowee::editor::PlaceableType::M2 ? "m2" : "wmo") << ","
|
||||
<< csvEsc(o.path) << ","
|
||||
<< o.position.x << "," << o.position.y << "," << o.position.z << ","
|
||||
<< o.rotation.x << "," << o.rotation.y << "," << o.rotation.z << ","
|
||||
<< o.scale << "\n";
|
||||
}
|
||||
std::printf(" wrote %s (%zu rows)\n", out.c_str(),
|
||||
op.getObjects().size());
|
||||
filesWritten++;
|
||||
}
|
||||
// Quests — flatten to one row per quest. Objectives + items
|
||||
// are joined into a single semicolon-separated cell so the
|
||||
// CSV stays one-row-per-quest (designer-friendly for sorting).
|
||||
wowee::editor::QuestEditor qe;
|
||||
if (qe.loadFromFile(zoneDir + "/quests.json")) {
|
||||
std::string out = outDir + "/quests.csv";
|
||||
std::ofstream f(out);
|
||||
if (!f) return 1;
|
||||
f << "index,id,title,requiredLevel,giverNpcId,turnInNpcId,"
|
||||
"xp,gold,silver,copper,nextQuestId,objectiveCount,"
|
||||
"objectives,itemRewards\n";
|
||||
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];
|
||||
std::string objs;
|
||||
for (size_t o = 0; o < q.objectives.size(); ++o) {
|
||||
if (o) objs += "; ";
|
||||
objs += std::string(typeName(q.objectives[o].type)) + ":" +
|
||||
q.objectives[o].targetName + "x" +
|
||||
std::to_string(q.objectives[o].targetCount);
|
||||
}
|
||||
std::string items;
|
||||
for (size_t r = 0; r < q.reward.itemRewards.size(); ++r) {
|
||||
if (r) items += "; ";
|
||||
items += q.reward.itemRewards[r];
|
||||
}
|
||||
f << k << "," << q.id << "," << csvEsc(q.title) << ","
|
||||
<< q.requiredLevel << ","
|
||||
<< q.questGiverNpcId << "," << q.turnInNpcId << ","
|
||||
<< q.reward.xp << "," << q.reward.gold << ","
|
||||
<< q.reward.silver << "," << q.reward.copper << ","
|
||||
<< q.nextQuestId << ","
|
||||
<< q.objectives.size() << ","
|
||||
<< csvEsc(objs) << "," << csvEsc(items) << "\n";
|
||||
}
|
||||
std::printf(" wrote %s (%zu rows)\n", out.c_str(), qe.questCount());
|
||||
filesWritten++;
|
||||
}
|
||||
if (filesWritten == 0) {
|
||||
std::fprintf(stderr,
|
||||
"export-zone-csv: zone has no creatures/objects/quests to emit\n");
|
||||
return 1;
|
||||
}
|
||||
std::printf("Exported %d CSV file(s) to %s\n", filesWritten, outDir.c_str());
|
||||
return 0;
|
||||
} else if (std::strcmp(argv[i], "--export-quest-graph") == 0 && i + 1 < argc) {
|
||||
// Render quest chains as a Graphviz DOT graph. Visualizing
|
||||
// quest dependencies in plain text rapidly becomes unreadable
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue