/** * Translates a region of tiles specified by the cursor and arguments/prompts * into a series of blueprint files suitable for replay via quickfort. * * Written by cdombroski. */ #include <algorithm> #include <sstream> #include "Console.h" #include "DataDefs.h" #include "DataFuncs.h" #include "DataIdentity.h" #include "LuaTools.h" #include "PluginManager.h" #include "TileTypes.h" #include "modules/Buildings.h" #include "modules/Filesystem.h" #include "modules/Gui.h" #include "df/building_axle_horizontalst.h" #include "df/building_bridgest.h" #include "df/building_constructionst.h" #include "df/building_furnacest.h" #include "df/building_rollersst.h" #include "df/building_screw_pumpst.h" #include "df/building_siegeenginest.h" #include "df/building_trapst.h" #include "df/building_water_wheelst.h" #include "df/building_workshopst.h" #include "df/world.h" using std::endl; using std::ofstream; using std::pair; using std::map; using std::string; using std::vector; using namespace DFHack; DFHACK_PLUGIN("blueprint"); REQUIRE_GLOBAL(world); struct blueprint_options { // whether to display help bool help = false; // starting tile coordinate of the translation area (if not set then all // coordinates are set to -30000) df::coord start; // output file format. this could be an enum if we set up the boilerplate // for it. string format; // file splitting strategy. this could be an enum if we set up the // boilerplate for it. string split_strategy; // dimensions of translation area. width and height are guaranteed to be // greater than 0. depth can be positive or negative, but not zero. int32_t width = 0; int32_t height = 0; int32_t depth = 0; // base name to use for generated files string name; // whether to autodetect which phases to output bool auto_phase = false; // if not autodetecting, which phases to output bool dig = false; bool build = false; bool place = false; bool query = false; static struct_identity _identity; }; static const struct_field_info blueprint_options_fields[] = { { struct_field_info::PRIMITIVE, "help", offsetof(blueprint_options, help), &df::identity_traits<bool>::identity, 0, 0 }, { struct_field_info::SUBSTRUCT, "start", offsetof(blueprint_options, start), &df::coord::_identity, 0, 0 }, { struct_field_info::PRIMITIVE, "format", offsetof(blueprint_options, format), df::identity_traits<string>::get(), 0, 0 }, { struct_field_info::PRIMITIVE, "split_strategy", offsetof(blueprint_options, split_strategy), df::identity_traits<string>::get(), 0, 0 }, { struct_field_info::PRIMITIVE, "width", offsetof(blueprint_options, width), &df::identity_traits<int32_t>::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "height", offsetof(blueprint_options, height), &df::identity_traits<int32_t>::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "depth", offsetof(blueprint_options, depth), &df::identity_traits<int32_t>::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "name", offsetof(blueprint_options, name), df::identity_traits<string>::get(), 0, 0 }, { struct_field_info::PRIMITIVE, "auto_phase", offsetof(blueprint_options, auto_phase), &df::identity_traits<bool>::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "dig", offsetof(blueprint_options, dig), &df::identity_traits<bool>::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "build", offsetof(blueprint_options, build), &df::identity_traits<bool>::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "place", offsetof(blueprint_options, place), &df::identity_traits<bool>::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "query", offsetof(blueprint_options, query), &df::identity_traits<bool>::identity, 0, 0 }, { struct_field_info::END } }; struct_identity blueprint_options::_identity(sizeof(blueprint_options), &df::allocator_fn<blueprint_options>, NULL, "blueprint_options", NULL, blueprint_options_fields); command_result blueprint(color_ostream &, vector<string> &); DFhackCExport command_result plugin_init(color_ostream &, vector<PluginCommand> &commands) { commands.push_back(PluginCommand("blueprint", "Record the structure of a live game map in a quickfort blueprint file", blueprint, false)); return CR_OK; } DFhackCExport command_result plugin_shutdown(color_ostream &) { return CR_OK; } struct tile_context { bool pretty = false; df::building* b = NULL; }; static pair<uint32_t, uint32_t> get_building_size(df::building *b) { return pair<uint32_t, uint32_t>(b->x2 - b->x1 + 1, b->y2 - b->y1 + 1); } static const char * if_pretty(const tile_context &ctx, const char *c) { return ctx.pretty ? c : ""; } static void get_tile_dig(const df::coord &pos, const tile_context &, string &str) { df::tiletype *tt = Maps::getTileType(pos); switch (tileShape(tt ? *tt : tiletype::Void)) { case tiletype_shape::EMPTY: case tiletype_shape::RAMP_TOP: str = "h"; break; case tiletype_shape::FLOOR: case tiletype_shape::BOULDER: case tiletype_shape::PEBBLES: case tiletype_shape::BROOK_TOP: str = "d"; break; case tiletype_shape::FORTIFICATION: str = "F"; break; case tiletype_shape::STAIR_UP: str = "u"; break; case tiletype_shape::STAIR_DOWN: str = "j"; break; case tiletype_shape::STAIR_UPDOWN: str = "i"; break; case tiletype_shape::RAMP: str = "r"; break; case tiletype_shape::WALL: default: break; } } static void do_block_building(const tile_context &ctx, string &str, string s, bool at_target_pos, bool *add_size = NULL) { if(!at_target_pos) { str = if_pretty(ctx, "`"); return; } str = s; if (add_size) *add_size = true; } static string get_bridge_str(df::building *b) { df::building_bridgest *bridge = virtual_cast<df::building_bridgest>(b); if (!bridge) return "g"; switch(bridge->direction) { case df::building_bridgest::T_direction::Retracting: return "gs"; case df::building_bridgest::T_direction::Left: return "ga"; case df::building_bridgest::T_direction::Right: return "gd"; case df::building_bridgest::T_direction::Up: return "gw"; case df::building_bridgest::T_direction::Down: return "gx"; default: return "g"; } } static string get_siege_str(df::building *b) { df::building_siegeenginest *se = virtual_cast<df::building_siegeenginest>(b); return !se || se->type == df::siegeengine_type::Catapult ? "ic" : "ib"; } static string get_workshop_str(df::building *b) { df::building_workshopst *ws = virtual_cast<df::building_workshopst>(b); if (!ws) return "~"; switch (ws->type) { case workshop_type::Leatherworks: return "we"; case workshop_type::Quern: return "wq"; case workshop_type::Millstone: return "wM"; case workshop_type::Loom: return "wo"; case workshop_type::Clothiers: return "wk"; case workshop_type::Bowyers: return "wb"; case workshop_type::Carpenters: return "wc"; case workshop_type::MetalsmithsForge: return "wf"; case workshop_type::MagmaForge: return "wv"; case workshop_type::Jewelers: return "wj"; case workshop_type::Masons: return "wm"; case workshop_type::Butchers: return "wu"; case workshop_type::Tanners: return "wn"; case workshop_type::Craftsdwarfs: return "wr"; case workshop_type::Siege: return "ws"; case workshop_type::Mechanics: return "wt"; case workshop_type::Still: return "wl"; case workshop_type::Farmers: return "ww"; case workshop_type::Kitchen: return "wz"; case workshop_type::Fishery: return "wh"; case workshop_type::Ashery: return "wy"; case workshop_type::Dyers: return "wd"; case workshop_type::Kennels: return "k"; case workshop_type::Custom: case workshop_type::Tool: default: return "~"; } } static string get_furnace_str(df::building *b) { df::building_furnacest *furnace = virtual_cast<df::building_furnacest>(b); if (!furnace) return "~"; switch (furnace->type) { case furnace_type::WoodFurnace: return "ew"; case furnace_type::Smelter: return "es"; case furnace_type::GlassFurnace: return "eg"; case furnace_type::Kiln: return "ek"; case furnace_type::MagmaSmelter: return "el"; case furnace_type::MagmaGlassFurnace: return "ea"; case furnace_type::MagmaKiln: return "en"; case furnace_type::Custom: default: return "~"; } } static string get_construction_str(df::building *b) { df::building_constructionst *cons = virtual_cast<df::building_constructionst>(b); if (!cons) return "~"; switch (cons->type) { case construction_type::Fortification: return "CF"; case construction_type::Wall: return "CW"; case construction_type::Floor: return "Cf"; case construction_type::UpStair: return "Cu"; case construction_type::DownStair: return "Cj"; case construction_type::UpDownStair: return "Cx"; case construction_type::Ramp: return "Cr"; case construction_type::TrackN: return "trackN"; case construction_type::TrackS: return "trackS"; case construction_type::TrackE: return "trackE"; case construction_type::TrackW: return "trackW"; case construction_type::TrackNS: return "trackNS"; case construction_type::TrackNE: return "trackNE"; case construction_type::TrackNW: return "trackNW"; case construction_type::TrackSE: return "trackSE"; case construction_type::TrackSW: return "trackSW"; case construction_type::TrackEW: return "trackEW"; case construction_type::TrackNSE: return "trackNSE"; case construction_type::TrackNSW: return "trackNSW"; case construction_type::TrackNEW: return "trackNEW"; case construction_type::TrackSEW: return "trackSEW"; case construction_type::TrackNSEW: return "trackNSEW"; case construction_type::TrackRampN: return "trackrampN"; case construction_type::TrackRampS: return "trackrampS"; case construction_type::TrackRampE: return "trackrampE"; case construction_type::TrackRampW: return "trackrampW"; case construction_type::TrackRampNS: return "trackrampNS"; case construction_type::TrackRampNE: return "trackrampNE"; case construction_type::TrackRampNW: return "trackrampNW"; case construction_type::TrackRampSE: return "trackrampSE"; case construction_type::TrackRampSW: return "trackrampSW"; case construction_type::TrackRampEW: return "trackrampEW"; case construction_type::TrackRampNSE: return "trackrampNSE"; case construction_type::TrackRampNSW: return "trackrampNSW"; case construction_type::TrackRampNEW: return "trackrampNEW"; case construction_type::TrackRampSEW: return "trackrampSEW"; case construction_type::TrackRampNSEW: return "trackrampNSEW"; case construction_type::NONE: default: return "~"; } } static string get_trap_str(df::building *b) { df::building_trapst *trap = virtual_cast<df::building_trapst>(b); if (!trap) return "~"; switch (trap->trap_type) { case trap_type::StoneFallTrap: return "Ts"; case trap_type::WeaponTrap: return "Tw"; case trap_type::Lever: return "Tl"; case trap_type::PressurePlate: return "Tp"; case trap_type::CageTrap: return "Tc"; case trap_type::TrackStop: { std::ostringstream buf; buf << "CS"; if (trap->use_dump) { if (trap->dump_x_shift == 0) { buf << "d"; if (trap->dump_y_shift > 0) buf << "d"; } else { buf << "ddd"; if (trap->dump_x_shift < 0) buf << "d"; } } // each case falls through and is additive switch (trap->friction) { case 10: buf << "a"; case 50: buf << "a"; case 500: buf << "a"; case 10000: buf << "a"; } return buf.str(); } default: return "~"; } } static string get_screw_pump_str(df::building *b) { df::building_screw_pumpst *sp = virtual_cast<df::building_screw_pumpst>(b); if (!sp) return "~"; switch (sp->direction) { case screw_pump_direction::FromNorth: return "Msu"; case screw_pump_direction::FromEast: return "Msk"; case screw_pump_direction::FromSouth: return "Msm"; case screw_pump_direction::FromWest: return "Msh"; default: return "~"; } } static string get_water_wheel_str(df::building *b) { df::building_water_wheelst *ww = virtual_cast<df::building_water_wheelst>(b); if (!ww) return "~"; return ww->is_vertical ? "Mw" : "Mws"; } static string get_axle_str(df::building *b) { df::building_axle_horizontalst *ah = virtual_cast<df::building_axle_horizontalst>(b); if (!ah) return "~"; return ah->is_vertical ? "Mhs" : "Mh"; } static string get_roller_str(df::building *b) { df::building_rollersst *r = virtual_cast<df::building_rollersst>(b); if (!r) return "~"; switch (r->direction) { case screw_pump_direction::FromNorth: return "Mr"; case screw_pump_direction::FromEast: return "Mrs"; case screw_pump_direction::FromSouth: return "Mrss"; case screw_pump_direction::FromWest: return "Mrsss"; default: return "~"; } } static string get_expansion_str(df::building *b) { pair<uint32_t, uint32_t> size = get_building_size(b); std::ostringstream s; s << "(" << size.first << "x" << size.second << ")"; return s.str(); } static void get_tile_build(const df::coord &pos, const tile_context &ctx, string &str) { if (!ctx.b || ctx.b->getType() == building_type::Stockpile) { return; } bool at_nw_corner = static_cast<int32_t>(pos.x) == ctx.b->x1 && static_cast<int32_t>(pos.y) == ctx.b->y1; bool at_se_corner = static_cast<int32_t>(pos.x) == ctx.b->x2 && static_cast<int32_t>(pos.y) == ctx.b->y2; bool at_center = static_cast<int32_t>(pos.x) == ctx.b->centerx && static_cast<int32_t>(pos.y) == ctx.b->centery; bool add_size = false; switch(ctx.b->getType()) { case building_type::Armorstand: str = "a"; break; case building_type::Bed: str = "b"; break; case building_type::Chair: str = "c"; break; case building_type::Door: str = "d"; break; case building_type::Floodgate: str = "x"; break; case building_type::Cabinet: str = "f"; break; case building_type::Box: str = "h"; break; //case building_type::Kennel is missing case building_type::FarmPlot: do_block_building(ctx, str, "p", at_nw_corner, &add_size); break; case building_type::Weaponrack: str = "r"; break; case building_type::Statue: str = "s"; break; case building_type::Table: str = "t"; break; case building_type::RoadPaved: do_block_building(ctx, str, "o", at_nw_corner, &add_size); break; case building_type::RoadDirt: do_block_building(ctx, str, "O", at_nw_corner, &add_size); break; case building_type::Bridge: do_block_building(ctx, str, get_bridge_str(ctx.b), at_nw_corner, &add_size); break; case building_type::Well: str = "l"; break; case building_type::SiegeEngine: do_block_building(ctx, str, get_siege_str(ctx.b), at_center); break; case building_type::Workshop: do_block_building(ctx, str, get_workshop_str(ctx.b), at_center); break; case building_type::Furnace: do_block_building(ctx, str, get_furnace_str(ctx.b), at_center); break; case building_type::WindowGlass: str = "y"; break; case building_type::WindowGem: str = "Y"; break; case building_type::Construction: str = get_construction_str(ctx.b); break; case building_type::Shop: do_block_building(ctx, str, "z", at_center); break; case building_type::AnimalTrap: str = "m"; break; case building_type::Chain: str = "v"; break; case building_type::Cage: str = "j"; break; case building_type::TradeDepot: do_block_building(ctx, str, "D", at_center); break; case building_type::Trap: str = get_trap_str(ctx.b); break; case building_type::ScrewPump: do_block_building(ctx, str, get_screw_pump_str(ctx.b), at_se_corner); break; case building_type::WaterWheel: do_block_building(ctx, str, get_water_wheel_str(ctx.b), at_center); break; case building_type::Windmill: do_block_building(ctx, str, "Mm", at_center); break; case building_type::GearAssembly: str = "Mg"; break; case building_type::AxleHorizontal: do_block_building(ctx, str, get_axle_str(ctx.b), at_nw_corner, &add_size); break; case building_type::AxleVertical: str = "Mv"; break; case building_type::Rollers: do_block_building(ctx, str, get_roller_str(ctx.b), at_nw_corner, &add_size); break; case building_type::Support: str = "S"; break; case building_type::ArcheryTarget: str = "A"; break; case building_type::TractionBench: str = "R"; break; case building_type::Hatch: str = "H"; break; case building_type::Slab: //how to mine alt key?!? //alt+s str = "~"; break; case building_type::NestBox: str = "N"; break; case building_type::Hive: //alt+h str = "~"; break; case building_type::GrateWall: str = "W"; break; case building_type::GrateFloor: str = "G"; break; case building_type::BarsVertical: str = "B"; break; case building_type::BarsFloor: //alt+b str = "~"; break; default: str = if_pretty(ctx, "~"); break; } if (add_size) str.append(get_expansion_str(ctx.b)); } static void get_tile_place(const df::coord &pos, const tile_context &ctx, string &str) { if (!ctx.b || ctx.b->getType() != building_type::Stockpile) return; if (ctx.b->x1 != static_cast<int32_t>(pos.x) || ctx.b->y1 != static_cast<int32_t>(pos.y)) { str = if_pretty(ctx, "`"); return; } df::building_stockpilest* sp = virtual_cast<df::building_stockpilest>(ctx.b); if (!sp) { str = "~"; return; } switch (sp->settings.flags.whole) { case df::stockpile_group_set::mask_animals: str = "a"; break; case df::stockpile_group_set::mask_food: str = "f"; break; case df::stockpile_group_set::mask_furniture: str = "u"; break; case df::stockpile_group_set::mask_corpses: str = "y"; break; case df::stockpile_group_set::mask_refuse: str = "r"; break; case df::stockpile_group_set::mask_wood: str = "w"; break; case df::stockpile_group_set::mask_stone: str = "s"; break; case df::stockpile_group_set::mask_gems: str = "e"; break; case df::stockpile_group_set::mask_bars_blocks: str = "b"; break; case df::stockpile_group_set::mask_cloth: str = "h"; break; case df::stockpile_group_set::mask_leather: str = "l"; break; case df::stockpile_group_set::mask_ammo: str = "z"; break; case df::stockpile_group_set::mask_coins: str = "n"; break; case df::stockpile_group_set::mask_finished_goods: str = "g"; break; case df::stockpile_group_set::mask_weapons: str = "p"; break; case df::stockpile_group_set::mask_armor: str = "d"; break; default: // multiple stockpile types str = "~"; return; } str.append(get_expansion_str(ctx.b)); } static void get_tile_query(const df::coord &, const tile_context &ctx, string &str) { if (ctx.b && ctx.b->is_room) str = "r+"; } static bool create_output_dir(color_ostream &out, const blueprint_options &opts) { string basename = "blueprints/" + opts.name; size_t last_slash = basename.find_last_of("/"); string parent_path = basename.substr(0, last_slash); // create output directory if it doesn't already exist if (!Filesystem::mkdir_recursive(parent_path)) { out.printerr("could not create output directory: '%s'\n", parent_path.c_str()); return false; } return true; } static bool get_filename(string &fname, color_ostream &out, blueprint_options opts, // copy because we can't const const string &phase) { auto L = Lua::Core::State; Lua::StackUnwinder top(L); if (!lua_checkstack(L, 3) || !Lua::PushModulePublic( out, L, "plugins.blueprint", "get_filename")) { out.printerr("Failed to load blueprint Lua code\n"); return false; } Lua::Push(L, &opts); Lua::Push(L, phase); if (!Lua::SafeCall(out, L, 2, 1)) { out.printerr("Failed Lua call to get_filename\n"); return false; } const char *s = lua_tostring(L, -1); if (!s) { out.printerr("Failed to retrieve filename from get_filename\n"); return false; } fname = s; return true; } typedef map<int16_t /* x */, string> bp_row; typedef map<int16_t /* y */, bp_row> bp_area; typedef map<int16_t /* z */, bp_area> bp_volume; static const bp_area NEW_AREA; static const bp_row NEW_ROW; typedef void (get_tile_fn)(const df::coord &pos, const tile_context &ctx, string &str); typedef void (init_ctx_fn)(const df::coord &pos, tile_context &ctx); struct blueprint_processor { bp_volume mapdata; string phase; get_tile_fn *get_tile; init_ctx_fn *init_ctx; blueprint_processor(const string &phase, get_tile_fn *get_tile, init_ctx_fn *init_ctx = NULL) : phase(phase), get_tile(get_tile), init_ctx(init_ctx) { } }; static void write_minimal(ofstream &ofile, const blueprint_options &opts, const bp_volume &mapdata) { if (mapdata.begin() == mapdata.end()) return; const string z_key = opts.depth > 0 ? "#<" : "#>"; int16_t zprev = 0; for (auto area : mapdata) { for ( ; zprev < area.first; ++zprev) ofile << z_key << endl; int16_t yprev = 0; for (auto row : area.second) { for ( ; yprev < row.first; ++yprev) ofile << endl; int16_t xprev = 0; for (auto tile : row.second) { for ( ; xprev < tile.first; ++xprev) ofile << ","; ofile << tile.second; } } ofile << endl; } } static void write_pretty(ofstream &ofile, const blueprint_options &opts, const bp_volume &mapdata) { const string z_key = opts.depth > 0 ? "#<" : "#>"; int16_t absdepth = abs(opts.depth); for (int16_t z = 0; z < absdepth; ++z) { const bp_area *area = NULL; if (mapdata.count(z)) area = &mapdata.at(z); for (int16_t y = 0; y < opts.height; ++y) { const bp_row *row = NULL; if (area && area->count(y)) row = &area->at(y); for (int16_t x = 0; x < opts.width; ++x) { const string *tile = NULL; if (row && row->count(x)) tile = &row->at(x); ofile << (tile ? *tile : " ") << ","; } ofile << "#" << endl; } if (z < absdepth - 1) ofile << z_key << endl; } } static string get_modeline(const string &phase) { std::ostringstream modeline; modeline << "#" << phase << " label(" << phase << ")"; return modeline.str(); } static bool write_blueprint(color_ostream &out, std::map<string, ofstream*> &output_files, const blueprint_options &opts, const blueprint_processor &processor, bool pretty) { string fname; if (!get_filename(fname, out, opts, processor.phase)) return false; if (!output_files.count(fname)) output_files[fname] = new ofstream(fname, ofstream::trunc); ofstream &ofile = *output_files[fname]; ofile << get_modeline(processor.phase) << endl; if (pretty) write_pretty(ofile, opts, processor.mapdata); else write_minimal(ofile, opts, processor.mapdata); return true; } void ensure_building(const df::coord &pos, tile_context &ctx) { if (ctx.b) return; ctx.b = Buildings::findAtTile(pos); } static bool do_transform(color_ostream &out, const df::coord &start, const df::coord &end, const blueprint_options &opts, vector<string> &filenames) { vector<blueprint_processor> processors; if (opts.auto_phase || opts.dig) processors.push_back(blueprint_processor("dig", get_tile_dig)); if (opts.auto_phase || opts.build) processors.push_back(blueprint_processor("build", get_tile_build, ensure_building)); if (opts.auto_phase || opts.place) processors.push_back(blueprint_processor("place", get_tile_place, ensure_building)); if (opts.auto_phase || opts.query) processors.push_back(blueprint_processor("query", get_tile_query, ensure_building)); if (processors.empty()) { out.printerr("no phases requested! nothing to do!\n"); return false; } if (!create_output_dir(out, opts)) return false; const bool pretty = opts.format != "minimal"; const int32_t z_inc = start.z < end.z ? 1 : -1; for (int32_t z = start.z; z != end.z; z += z_inc) { for (int32_t y = start.y; y < end.y; y++) { for (int32_t x = start.x; x < end.x; x++) { df::coord pos(x, y, z); tile_context ctx; ctx.pretty = pretty; for (blueprint_processor &processor : processors) { if (processor.init_ctx) processor.init_ctx(pos, ctx); string tile_str; processor.get_tile(pos, ctx, tile_str); if (!tile_str.empty()) { // ensure our z-index is in the order we want to write auto area = processor.mapdata.emplace(abs(z - start.z), NEW_AREA); auto row = area.first->second.emplace(y - start.y, NEW_ROW); row.first->second[x - start.x] = tile_str; } } } } } std::map<string, ofstream*> output_files; for (blueprint_processor &processor : processors) { if (!write_blueprint(out, output_files, opts, processor, pretty)) return false; } for (auto &it : output_files) { filenames.push_back(it.first); it.second->close(); delete(it.second); } output_files.clear(); return true; } static bool get_options(color_ostream &out, blueprint_options &opts, const vector<string> ¶meters) { auto L = Lua::Core::State; Lua::StackUnwinder top(L); if (!lua_checkstack(L, parameters.size() + 2) || !Lua::PushModulePublic( out, L, "plugins.blueprint", "parse_commandline")) { out.printerr("Failed to load blueprint Lua code\n"); return false; } Lua::Push(L, &opts); for (const string ¶m : parameters) Lua::Push(L, param); if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) return false; return true; } static void print_help(color_ostream &out) { auto L = Lua::Core::State; Lua::StackUnwinder top(L); if (!lua_checkstack(L, 1) || !Lua::PushModulePublic(out, L, "plugins.blueprint", "print_help") || !Lua::SafeCall(out, L, 0, 0)) { out.printerr("Failed to load blueprint Lua code\n"); } } // returns whether blueprint generation was successful. populates files with the // names of the files that were generated static bool do_blueprint(color_ostream &out, const vector<string> ¶meters, vector<string> &files) { CoreSuspender suspend; if (parameters.size() >= 1 && parameters[0] == "gui") { std::ostringstream command; command << "gui/blueprint"; for (size_t i = 1; i < parameters.size(); ++i) { command << " " << parameters[i]; } string command_str = command.str(); out.print("launching %s\n", command_str.c_str()); Core::getInstance().setHotkeyCmd(command_str); return CR_OK; } blueprint_options options; if (!get_options(out, options, parameters) || options.help) { print_help(out); return options.help; } if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); return false; } // start coordinates can come from either the commandline or the map cursor df::coord start(options.start); if (start.x == -30000) { if (!Gui::getCursorCoords(start)) { out.printerr("Can't get cursor coords! Make sure you specify the" " --cursor parameter or have an active cursor in DF.\n"); return false; } } if (!Maps::isValidTilePos(start)) { out.printerr("Invalid start position: %d,%d,%d\n", start.x, start.y, start.z); return false; } // end coords are one beyond the last processed coordinate. note that // options.depth can be negative. df::coord end(start.x + options.width, start.y + options.height, start.z + options.depth); // crop end coordinate to map bounds. we've already verified that start is // a valid coordinate, and width, height, and depth are non-zero, so our // final area is always going to be at least 1x1x1. df::world::T_map &map = df::global::world->map; if (end.x > map.x_count) end.x = map.x_count; if (end.y > map.y_count) end.y = map.y_count; if (end.z > map.z_count) end.z = map.z_count; if (end.z < -1) end.z = -1; return do_transform(out, start, end, options, files); } // entrypoint when called from Lua. returns the names of the generated files static int run(lua_State *L) { int argc = lua_gettop(L); vector<string> argv; for (int i = 1; i <= argc; ++i) { const char *s = lua_tostring(L, i); if (s == NULL) luaL_error(L, "all parameters must be strings"); argv.push_back(s); } vector<string> files; color_ostream *out = Lua::GetOutput(L); if (!out) out = &Core::getInstance().getConsole(); if (do_blueprint(*out, argv, files)) { Lua::PushVector(L, files); return 1; } return 0; } command_result blueprint(color_ostream &out, vector<string> ¶meters) { vector<string> files; if (do_blueprint(out, parameters, files)) { out.print("Generated blueprint file(s):\n"); for (string &fname : files) out.print(" %s\n", fname.c_str()); return CR_OK; } return CR_FAILURE; } DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(run), DFHACK_LUA_END };