dfhack/plugins/blueprint.cpp

935 lines
32 KiB
C++

/**
* 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> &parameters)
{
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 &param : 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> &parameters,
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> &parameters) {
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
};