From dbf973e29ef337fc2abef5c63a4dcf806e43bab9 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 16:25:23 -0700 Subject: [PATCH] feat(editor): add --mvp-zone for one-command demo zone setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quick-start: scaffold a zone AND populate one of each content type (1 creature, 1 object, 1 quest with objective + XP reward) in a single command. Goes from empty filesystem to 'something to look at' without 7 chained --add-* commands: wowee_editor --mvp-zone 'Demo Land' 30 30 Created demo zone: custom_zones/Demo_Land tile : (30, 30) contents : 1 creature, 1 object, 1 quest (with objective + reward) next : wowee_editor --info-zone-tree custom_zones/Demo_Land Demo Land/ ├─ Manifest ... ├─ Tiles (1) — (30, 30) ├─ Creatures (1) — lvl 5 Demo Wolf ├─ Objects (1) — m2 World/Generic/Tree.m2 ├─ Quests (1) — [1] Welcome to Demo Land (lvl 1, 100 XP) │ └─ kill ×1 Demo Wolf Demo content is positioned roughly at tile center (533.33-yard intervals from origin tile 32/32). Quest references the demo creature's auto-id so --check-zone-refs passes immediately. Use cases: - Smoke-testing the bake/validate pipeline - Screenshot bait for docs / blog posts - Editor onboarding (open a zone in the GUI to see the format) - CI sanity check (does our editor still produce a viewable zone?) Verified end-to-end: --mvp-zone 'Demo Land' → --info-zone-tree shows all 4 sections populated correctly, file list matches expected 6 files. --- tools/editor/main.cpp | 104 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index e0605833..531956f2 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -524,6 +524,8 @@ static void printUsage(const char* argv0) { std::printf(" --for-each-zone -- \n"); std::printf(" Run for every zone in ; '{}' in cmd is replaced with the zone path\n"); std::printf(" --scaffold-zone [tx ty] Create a blank zone in custom_zones// and exit\n"); + std::printf(" --mvp-zone [tx ty]\n"); + std::printf(" Scaffold + add a creature + object + quest (with objective+reward) for quick demos\n"); std::printf(" --add-tile [baseHeight]\n"); std::printf(" Add a new ADT tile to an existing zone (extends the manifest's tiles list)\n"); std::printf(" --remove-tile \n"); @@ -788,7 +790,7 @@ int main(int argc, char* argv[]) { "--export-zone-summary-md", "--export-quest-graph", "--export-zone-csv", "--export-zone-html", "--export-project-html", "--export-zone-checksum", - "--scaffold-zone", "--add-tile", "--remove-tile", "--list-tiles", + "--scaffold-zone", "--mvp-zone", "--add-tile", "--remove-tile", "--list-tiles", "--for-each-zone", "--zone-stats", "--info-tilemap", "--list-zone-deps", "--check-zone-refs", "--check-zone-content", "--export-zone-deps-md", @@ -9682,6 +9684,106 @@ int main(int argc, char* argv[]) { slug.c_str(), slug.c_str()); std::printf(" next step: run editor without args, then File → Open Zone\n"); return 0; + } else if (std::strcmp(argv[i], "--mvp-zone") == 0 && i + 1 < argc) { + // Quick-start: scaffold + populate one of each content type + // (1 creature, 1 object, 1 quest with objective + reward). + // Useful for demos, screenshot bait, smoke tests of the + // bake/validate pipeline. The zone goes from empty to + // 'something to look at' in one command. + std::string rawName = argv[++i]; + int sx = 32, sy = 32; + if (i + 2 < argc) { + int parsedX = std::atoi(argv[i + 1]); + int parsedY = std::atoi(argv[i + 2]); + if (parsedX >= 0 && parsedX <= 63 && + parsedY >= 0 && parsedY <= 63) { + sx = parsedX; sy = parsedY; + i += 2; + } + } + // Reuse scaffold-zone's slug logic. + std::string slug; + for (char c : rawName) { + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '_' || c == '-') slug += c; + else if (c == ' ') slug += '_'; + } + if (slug.empty()) { + std::fprintf(stderr, + "mvp-zone: name '%s' has no valid characters\n", + rawName.c_str()); + return 1; + } + namespace fs = std::filesystem; + std::string dir = "custom_zones/" + slug; + if (fs::exists(dir)) { + std::fprintf(stderr, + "mvp-zone: directory already exists: %s\n", dir.c_str()); + return 1; + } + fs::create_directories(dir); + // Scaffold terrain. + auto terrain = wowee::editor::TerrainEditor::createBlankTerrain( + sx, sy, 100.0f, wowee::editor::Biome::Grassland); + std::string base = dir + "/" + slug + "_" + + std::to_string(sx) + "_" + std::to_string(sy); + wowee::editor::WoweeTerrain::exportOpen(terrain, base, sx, sy); + // Manifest. + wowee::editor::ZoneManifest zm; + zm.mapName = slug; + zm.displayName = rawName; + zm.mapId = 9000; + zm.baseHeight = 100.0f; + zm.tiles.push_back({sx, sy}); + zm.hasCreatures = true; + zm.save(dir + "/zone.json"); + // Position the demo content roughly centered in the tile. + // Tile (32, 32) is the WoW map origin; tile centers are at + // 533.33-yard intervals from there. + float centerX = (32.0f - sy) * 533.33333f - 266.667f; + float centerY = (32.0f - sx) * 533.33333f - 266.667f; + float centerZ = 100.0f; + // Demo creature. + wowee::editor::NpcSpawner sp; + wowee::editor::CreatureSpawn c; + c.name = "Demo Wolf"; + c.position = {centerX, centerY, centerZ}; + c.level = 5; + c.health = 100; + c.minDamage = 5; c.maxDamage = 10; + c.displayId = 11430; // any valid id; renderer falls back if absent + sp.getSpawns().push_back(c); + sp.saveToFile(dir + "/creatures.json"); + // Demo object — a tree placement near the creature. + wowee::editor::ObjectPlacer op; + wowee::editor::PlacedObject po; + po.type = wowee::editor::PlaceableType::M2; + po.path = "World/Generic/Tree.m2"; + po.position = {centerX + 5.0f, centerY, centerZ}; + po.scale = 1.0f; + op.getObjects().push_back(po); + op.saveToFile(dir + "/objects.json"); + // Demo quest with objective + XP reward. + wowee::editor::QuestEditor qe; + wowee::editor::Quest q; + q.title = "Welcome to " + rawName; + q.requiredLevel = 1; + q.questGiverNpcId = c.id; // self-referential so refs check passes + q.turnInNpcId = c.id; + q.reward.xp = 100; + wowee::editor::QuestObjective obj; + obj.type = wowee::editor::QuestObjectiveType::KillCreature; + obj.targetName = "Demo Wolf"; + obj.targetCount = 1; + obj.description = "Slay the Demo Wolf"; + q.objectives.push_back(obj); + qe.addQuest(q); + qe.saveToFile(dir + "/quests.json"); + std::printf("Created demo zone: %s\n", dir.c_str()); + std::printf(" tile : (%d, %d)\n", sx, sy); + std::printf(" contents : 1 creature, 1 object, 1 quest (with objective + reward)\n"); + std::printf(" next : wowee_editor --info-zone-tree %s\n", dir.c_str()); + return 0; } else if (std::strcmp(argv[i], "--add-tile") == 0 && i + 3 < argc) { // Extend an existing zone with another ADT tile. Zones can // span multiple tiles (e.g. a continent fragment), but