feat(editor): add --add-object CLI for headless M2/WMO placement

Mirrors --add-creature for the object placer:

  wowee_editor --add-object <zoneDir> <m2|wmo> <gamePath> <x> <y> <z> [scale]

Lets shell scripts populate objects/buildings without the GUI:

  for tree in 100,200 150,250 200,300; do
    x=${tree%,*}; y=${tree#*,}
    wowee_editor --add-object "$zone" m2 'World/Doodad/Tree.m2' $x $y 0
  done

Loads existing objects.json first then appends, so multiple
invocations build up. Optional scale slot lets the caller set
non-default size (clamped to >0 by the load-time guard).
Verified end-to-end: scaffolded zone → added m2 + wmo → info-objects
reports total=2 with the correct scale range.
This commit is contained in:
Kelsi 2026-05-06 11:37:10 -07:00
parent 83d20180c3
commit 1017ab4751

View file

@ -34,6 +34,8 @@ static void printUsage(const char* argv0) {
std::printf(" --scaffold-zone <name> [tx ty] Create a blank zone in custom_zones/<name>/ and exit\n");
std::printf(" --add-creature <zoneDir> <name> <x> <y> <z> [displayId] [level]\n");
std::printf(" Append one creature spawn to <zoneDir>/creatures.json and exit\n");
std::printf(" --add-object <zoneDir> <m2|wmo> <gamePath> <x> <y> <z> [scale]\n");
std::printf(" Append one object placement to <zoneDir>/objects.json and exit\n");
std::printf(" --build-woc <wot-base> Generate a WOC collision mesh from WHM/WOT and exit\n");
std::printf(" --regen-collision <zoneDir> Rebuild every WOC under a zone dir and exit\n");
std::printf(" --fix-zone <zoneDir> Re-parse + re-save zone JSONs to apply latest scrubs/caps and exit\n");
@ -85,7 +87,7 @@ int main(int argc, char* argv[]) {
"--info-extract", "--info-zone", "--info-wcp", "--list-wcp",
"--unpack-wcp", "--pack-wcp",
"--validate", "--zone-summary",
"--scaffold-zone", "--add-creature",
"--scaffold-zone", "--add-creature", "--add-object",
"--build-woc", "--regen-collision", "--fix-zone",
"--export-png",
"--convert-m2", "--convert-wmo",
@ -110,6 +112,11 @@ int main(int argc, char* argv[]) {
"--add-creature requires <zoneDir> <name> <x> <y> <z>\n");
return 1;
}
if (std::strcmp(argv[i], "--add-object") == 0 && i + 6 >= argc) {
std::fprintf(stderr,
"--add-object requires <zoneDir> <m2|wmo> <gamePath> <x> <y> <z>\n");
return 1;
}
}
for (int i = 1; i < argc; i++) {
@ -1101,6 +1108,57 @@ int main(int argc, char* argv[]) {
outPath.c_str(),
col.triangles.size(), col.walkableCount(), col.steepCount());
return 0;
} else if (std::strcmp(argv[i], "--add-object") == 0 && i + 5 < argc) {
// Append a single object placement to a zone's objects.json.
// Args: <zoneDir> <m2|wmo> <gamePath> <x> <y> <z> [scale]
std::string zoneDir = argv[++i];
std::string typeStr = argv[++i];
std::string gamePath = argv[++i];
namespace fs = std::filesystem;
if (!fs::exists(zoneDir)) {
std::fprintf(stderr, "add-object: zone '%s' does not exist\n",
zoneDir.c_str());
return 1;
}
wowee::editor::PlaceableType ptype;
if (typeStr == "m2") ptype = wowee::editor::PlaceableType::M2;
else if (typeStr == "wmo") ptype = wowee::editor::PlaceableType::WMO;
else {
std::fprintf(stderr, "add-object: type must be 'm2' or 'wmo'\n");
return 1;
}
glm::vec3 pos;
try {
pos.x = std::stof(argv[++i]);
pos.y = std::stof(argv[++i]);
pos.z = std::stof(argv[++i]);
} catch (const std::exception& e) {
std::fprintf(stderr, "add-object: bad coordinate (%s)\n", e.what());
return 1;
}
wowee::editor::ObjectPlacer placer;
std::string path = zoneDir + "/objects.json";
if (fs::exists(path)) placer.loadFromFile(path);
placer.setActivePath(gamePath, ptype);
placer.placeObject(pos);
// Optional scale after coordinates.
if (i + 1 < argc && argv[i + 1][0] != '-') {
try {
float scale = std::stof(argv[++i]);
if (std::isfinite(scale) && scale > 0.0f) {
// Set scale on the just-placed object (last in list).
placer.getObjects().back().scale = scale;
}
} catch (...) {}
}
if (!placer.saveToFile(path)) {
std::fprintf(stderr, "add-object: failed to write %s\n", path.c_str());
return 1;
}
std::printf("Added %s '%s' to %s (now %zu total)\n",
typeStr.c_str(), gamePath.c_str(), path.c_str(),
placer.getObjects().size());
return 0;
} else if (std::strcmp(argv[i], "--add-creature") == 0 && i + 4 < argc) {
// Append a single creature spawn to a zone's creatures.json.
// Args: <zoneDir> <name> <x> <y> <z> [displayId] [level]