feat(editor): add --gen-blacksmith-pack + extract emitMeshPack

Two changes in one commit because they're co-designed:

  • Extract emitMeshPack(outDir, packName, items) as a shared
    helper that takes a vector of PackItem {flag, fn, leaf}
    and runs each handler with a synthetic argv. Keeps the
    "build a 2-element argv per sub-handler" wiring in one
    place instead of copy-pasted per pack.

  • Add --gen-blacksmith-pack on top: a smithy-themed
    composite emitting forge + anvil + workbench +
    water-trough + crate-stack + hitching-post into outDir.
    The classic forge-floor layout in 6 .wom files.

Refactors --gen-camp-pack to use emitMeshPack (verified
unchanged: 6 .wom files still land in the camp dir).
Pattern is now established for future packs (--gen-village-
pack, --gen-temple-pack, --gen-dock-pack…) — each is just a
list of primitives in 8 lines of code.
This commit is contained in:
Kelsi 2026-05-09 12:31:34 -07:00
parent 006beffb83
commit facaacd0c6
3 changed files with 64 additions and 41 deletions

View file

@ -6446,62 +6446,82 @@ int handleFirepit(int& i, int argc, char** argv) {
return 0;
}
int handleGenCampPack(int& i, int /*argc*/, char** argv) {
// Convenience composite: emit a complete outdoor-camp scene
// (tent, firepit, bedroll, canopy, woodpile, haystack) into
// <outDir> in one command. Each primitive lands as its own
// .wom file using the existing handler — no per-primitive
// tweaking is exposed; users wanting custom dimensions should
// call the individual --gen-mesh-* commands directly.
std::string outDir = argv[++i];
// Composite-pack item: a flag name, the handler that builds it,
// and the basename to write under <outDir>. Used by --gen-camp-pack
// and --gen-blacksmith-pack to enumerate their member primitives.
struct PackItem {
const char* flag;
int (*fn)(int&, int, char**);
const char* leaf;
};
// Emit every PackItem in `items` into <outDir>/. Returns 0 on full
// success or the first non-zero rc the handler produced. Each
// handler runs with a synthetic argv whose index 0 is the flag and
// index 1 is the destination wom-base — handlers do argv[++i] from
// the flag's position to read that base, so this matches the normal
// CLI invocation contract exactly.
int emitMeshPack(const std::string& outDir, const char* packName,
const std::vector<PackItem>& items) {
std::error_code ec;
std::filesystem::create_directories(outDir, ec);
if (ec) {
std::fprintf(stderr,
"gen-camp-pack: cannot create %s: %s\n",
outDir.c_str(), ec.message().c_str());
"%s: cannot create %s: %s\n",
packName, outDir.c_str(), ec.message().c_str());
return 1;
}
auto invoke = [&](int (*fn)(int&, int, char**),
const char* flag, const std::string& path) -> int {
// Build a synthetic argv where index 0 = the flag name and
// index 1 = the wom-base path (handlers do argv[++i] from
// the flag's position to read the base).
const char* args[2];
args[0] = flag;
args[1] = path.c_str();
int produced = 0;
for (const auto& it : items) {
std::string path = outDir + "/" + it.leaf;
const char* args[2] = {it.flag, path.c_str()};
std::vector<char*> mut;
mut.reserve(2);
for (auto* a : args) mut.push_back(const_cast<char*>(a));
int idx = 0;
return fn(idx, 2, mut.data());
};
struct Item {
const char* flag;
int (*fn)(int&, int, char**);
const char* leaf;
};
const Item items[] = {
if (it.fn(idx, 2, mut.data()) != 0) {
std::fprintf(stderr,
"%s: %s sub-handler failed\n", packName, it.flag);
return 1;
}
++produced;
}
std::printf("\nWrote %s to %s/ — %d primitives\n",
packName, outDir.c_str(), produced);
return 0;
}
int handleGenCampPack(int& i, int /*argc*/, char** argv) {
// Outdoor-camp scene: tent, firepit, bedroll, canopy,
// woodpile, haystack. See emitMeshPack for the synthetic-argv
// contract. Users wanting custom dimensions should call the
// individual --gen-mesh-* commands directly.
std::string outDir = argv[++i];
return emitMeshPack(outDir, "camp pack", {
{"--gen-mesh-tent", handleTent, "tent"},
{"--gen-mesh-firepit", handleFirepit, "firepit"},
{"--gen-mesh-bedroll", handleBedroll, "bedroll"},
{"--gen-mesh-canopy", handleCanopy, "canopy"},
{"--gen-mesh-woodpile", handleWoodpile, "woodpile"},
{"--gen-mesh-haystack", handleHaystack, "haystack"},
};
int produced = 0;
for (const auto& it : items) {
std::string path = outDir + "/" + it.leaf;
if (invoke(it.fn, it.flag, path) != 0) {
std::fprintf(stderr,
"gen-camp-pack: %s sub-handler failed\n", it.flag);
return 1;
}
++produced;
}
std::printf("\nWrote camp pack to %s/ — %d primitives\n",
outDir.c_str(), produced);
return 0;
});
}
int handleGenBlacksmithPack(int& i, int /*argc*/, char** argv) {
// Blacksmith / smithy scene: forge (the hot work), anvil
// (where iron gets struck), workbench (where finished tools
// land), water-trough (for tempering hot metal), crate-stack
// (for raw-material storage), hitching-post (for delivery
// mounts that drop off charcoal and ore).
std::string outDir = argv[++i];
return emitMeshPack(outDir, "blacksmith pack", {
{"--gen-mesh-forge", handleForge, "forge"},
{"--gen-mesh-anvil", handleAnvil, "anvil"},
{"--gen-mesh-workbench", handleWorkbench, "workbench"},
{"--gen-mesh-water-trough", handleWaterTrough, "trough"},
{"--gen-mesh-crate-stack", handleCrateStack, "crates"},
{"--gen-mesh-hitching-post", handleHitchingPost, "hitching"},
});
}
} // namespace
@ -6574,6 +6594,7 @@ constexpr MeshEntry kMeshTable[] = {
{"--gen-mesh-outhouse", 1, handleOuthouse},
{"--gen-mesh-forge", 1, handleForge},
{"--gen-camp-pack", 1, handleGenCampPack},
{"--gen-blacksmith-pack", 1, handleGenBlacksmithPack},
{"--gen-mesh-table", 1, handleTable},
{"--gen-mesh-lamppost", 1, handleLamppost},
{"--gen-mesh-bed", 1, handleBed},