generate meta blueprints

develop
myk002 2022-10-12 17:42:36 -07:00
parent a1a7a93f74
commit c52138b168
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
6 changed files with 100 additions and 6 deletions

@ -42,6 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- `blueprint`: record built constructions in blueprints - `blueprint`: record built constructions in blueprints
- `blueprint`: record stockpile/building/zone names in blueprints - `blueprint`: record stockpile/building/zone names in blueprints
- `blueprint`: record room sizes in blueprints - `blueprint`: record room sizes in blueprints
- `blueprint`: generate meta blueprints to reduce the number of blueprints you have to apply
- `ls`: indent tag listings and wrap them in the right column for better readability - `ls`: indent tag listings and wrap them in the right column for better readability
- `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. - `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output.
- `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down - `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down

@ -99,8 +99,14 @@ Options
Select the output format of the generated files. See the `Output formats`_ Select the output format of the generated files. See the `Output formats`_
section below for options. If not specified, the output format defaults to section below for options. If not specified, the output format defaults to
"minimal", which will produce a small, fast ``.csv`` file. "minimal", which will produce a small, fast ``.csv`` file.
``-h``, ``--help`` ``--nometa``
Show command help text. `Meta blueprints <quickfort-meta>` let you apply all blueprints that can be
replayed at the same time (without unpausing the game) with a single
command. This usually reduces the number of `quickfort` commands you need to
run to rebuild your fort from about 6 to 2 or 3. If you would rather just
have the low-level blueprints, this flag will prevent meta blueprints from
being generated and any low-level blueprints from being
`hidden <quickfort-hidden>` from the ``quickfort list`` command.
``-s``, ``--playback-start <x>,<y>,<comment>`` ``-s``, ``--playback-start <x>,<y>,<comment>``
Specify the column and row offsets (relative to the upper-left corner of the Specify the column and row offsets (relative to the upper-left corner of the
blueprint, which is ``1,1``) where the player should put the cursor when the blueprint, which is ``1,1``) where the player should put the cursor when the

@ -14,6 +14,7 @@
#include "DataDefs.h" #include "DataDefs.h"
#include "DataFuncs.h" #include "DataFuncs.h"
#include "DataIdentity.h" #include "DataIdentity.h"
#include "Debug.h"
#include "LuaTools.h" #include "LuaTools.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "TileTypes.h" #include "TileTypes.h"
@ -47,6 +48,10 @@ using namespace DFHack;
DFHACK_PLUGIN("blueprint"); DFHACK_PLUGIN("blueprint");
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
namespace DFHack {
DBG_DECLARE(blueprint,log);
}
struct blueprint_options { struct blueprint_options {
// whether to display help // whether to display help
bool help = false; bool help = false;
@ -59,6 +64,9 @@ struct blueprint_options {
// for it. // for it.
string format; string format;
// whether to skip generating meta blueprints
bool nometa = false;
// offset and comment to write in the quickfort start() modeline marker // offset and comment to write in the quickfort start() modeline marker
// if not set, coordinates are set to 0 and the comment will be empty // if not set, coordinates are set to 0 and the comment will be empty
df::coord2d playback_start = df::coord2d(0, 0); df::coord2d playback_start = df::coord2d(0, 0);
@ -101,6 +109,7 @@ 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::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::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, "format", offsetof(blueprint_options, format), df::identity_traits<string>::get(), 0, 0 },
{ struct_field_info::PRIMITIVE, "nometa", offsetof(blueprint_options, nometa), &df::identity_traits<bool>::identity, 0, 0 },
{ struct_field_info::SUBSTRUCT, "playback_start", offsetof(blueprint_options, playback_start), &df::coord2d::_identity, 0, 0 }, { struct_field_info::SUBSTRUCT, "playback_start", offsetof(blueprint_options, playback_start), &df::coord2d::_identity, 0, 0 },
{ struct_field_info::PRIMITIVE, "playback_start_comment", offsetof(blueprint_options, playback_start_comment), df::identity_traits<string>::get(), 0, 0 }, { struct_field_info::PRIMITIVE, "playback_start_comment", offsetof(blueprint_options, playback_start_comment), 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, "split_strategy", offsetof(blueprint_options, split_strategy), df::identity_traits<string>::get(), 0, 0 },
@ -1115,6 +1124,32 @@ static bool get_filename(string &fname,
return true; return true;
} }
// returns true if we could interface with lua and could verify that the given
// phase is a meta phase
static bool is_meta_phase(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", "is_meta_phase")) {
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 is_meta_phase\n");
return false;
}
return lua_toboolean(L, -1);
}
static void write_minimal(ofstream &ofile, const blueprint_options &opts, static void write_minimal(ofstream &ofile, const blueprint_options &opts,
const bp_volume &mapdata) { const bp_volume &mapdata) {
if (mapdata.begin() == mapdata.end()) if (mapdata.begin() == mapdata.end())
@ -1171,8 +1206,8 @@ static void write_pretty(ofstream &ofile, const blueprint_options &opts,
} }
} }
static string get_modeline(const blueprint_options &opts, const string &mode, static string get_modeline(color_ostream &out, const blueprint_options &opts,
const string &phase) { const string &mode, const string &phase) {
std::ostringstream modeline; std::ostringstream modeline;
modeline << "#" << mode << " label(" << phase << ")"; modeline << "#" << mode << " label(" << phase << ")";
if (opts.playback_start.x > 0) { if (opts.playback_start.x > 0) {
@ -1183,6 +1218,8 @@ static string get_modeline(const blueprint_options &opts, const string &mode,
} }
modeline << ")"; modeline << ")";
} }
if (is_meta_phase(out, opts, phase))
modeline << " hidden()";
return modeline.str(); return modeline.str();
} }
@ -1199,7 +1236,7 @@ static bool write_blueprint(color_ostream &out,
output_files[fname] = new ofstream(fname, ofstream::trunc); output_files[fname] = new ofstream(fname, ofstream::trunc);
ofstream &ofile = *output_files[fname]; ofstream &ofile = *output_files[fname];
ofile << get_modeline(opts, processor.mode, processor.phase) << endl; ofile << get_modeline(out, opts, processor.mode, processor.phase) << endl;
if (pretty) if (pretty)
write_pretty(ofile, opts, processor.mapdata); write_pretty(ofile, opts, processor.mapdata);
@ -1209,6 +1246,27 @@ static bool write_blueprint(color_ostream &out,
return true; return true;
} }
static void write_meta_blueprint(color_ostream &out,
std::map<string, ofstream*> &output_files,
const blueprint_options &opts,
const std::vector<string> & meta_phases) {
string fname;
get_filename(fname, out, opts, meta_phases.front());
ofstream &ofile = *output_files[fname];
ofile << "#meta label(";
for (string phase : meta_phases) {
ofile << phase;
if (phase != meta_phases.back())
ofile << "_";
}
ofile << ")" << endl;
for (string phase : meta_phases) {
ofile << "/" << phase << endl;
}
}
static void ensure_building(const df::coord &pos, tile_context &ctx) { static void ensure_building(const df::coord &pos, tile_context &ctx) {
if (ctx.b) if (ctx.b)
return; return;
@ -1301,12 +1359,18 @@ static bool do_transform(color_ostream &out,
} }
std::map<string, ofstream*> output_files; std::map<string, ofstream*> output_files;
std::vector<string> meta_phases;
for (blueprint_processor &processor : processors) { for (blueprint_processor &processor : processors) {
if (processor.mapdata.empty() && !processor.force_create) if (processor.mapdata.empty() && !processor.force_create)
continue; continue;
if (is_meta_phase(out, opts, processor.phase))
meta_phases.push_back(processor.phase);
if (!write_blueprint(out, output_files, opts, processor, pretty)) if (!write_blueprint(out, output_files, opts, processor, pretty))
break; break;
} }
if (meta_phases.size()) {
write_meta_blueprint(out, output_files, opts, meta_phases);
}
for (auto &it : output_files) { for (auto &it : output_files) {
filenames.push_back(it.first); filenames.push_back(it.first);

@ -15,6 +15,14 @@ local valid_phase_list = {
} }
valid_phases = utils.invert(valid_phase_list) valid_phases = utils.invert(valid_phase_list)
local meta_phase_list = {
'build',
'place',
'zone',
'query',
}
meta_phases = utils.invert(meta_phase_list)
local valid_formats_list = { local valid_formats_list = {
'minimal', 'minimal',
'pretty', 'pretty',
@ -123,6 +131,7 @@ local function process_args(opts, args)
{'f', 'format', hasArg=true, {'f', 'format', hasArg=true,
handler=function(optarg) parse_format(opts, optarg) end}, handler=function(optarg) parse_format(opts, optarg) end},
{'h', 'help', handler=function() opts.help = true end}, {'h', 'help', handler=function() opts.help = true end},
{nil, 'nometa', handler=function() opts.nometa = true end},
{'s', 'playback-start', hasArg=true, {'s', 'playback-start', hasArg=true,
handler=function(optarg) parse_start(opts, optarg) end}, handler=function(optarg) parse_start(opts, optarg) end},
{nil, 'smooth', handler=function() opts.smooth = true end}, {nil, 'smooth', handler=function() opts.smooth = true end},
@ -185,6 +194,11 @@ function parse_commandline(opts, ...)
parse_positionals(opts, positionals, depth and 4 or 3) parse_positionals(opts, positionals, depth and 4 or 3)
end end
function is_meta_phase(opts, phase)
-- this is called directly by cpp so ensure we return a boolean, not nil
return not opts.nometa and meta_phases[phase] or false
end
-- returns the name of the output file for the given context -- returns the name of the output file for the given context
function get_filename(opts, phase) function get_filename(opts, phase)
local fullname = 'blueprints/' .. opts.name local fullname = 'blueprints/' .. opts.name
@ -197,6 +211,9 @@ function get_filename(opts, phase)
fullname = fullname .. basename fullname = fullname .. basename
end end
if opts.split_strategy == 'phase' then if opts.split_strategy == 'phase' then
if is_meta_phase(opts, phase) then
phase = 'meta'
end
return ('%s-%s.csv'):format(fullname, phase) return ('%s-%s.csv'):format(fullname, phase)
end end
-- no splitting -- no splitting

@ -20,6 +20,12 @@ function test.parse_gui_commandline()
b.parse_gui_commandline(opts, {'-h'}) b.parse_gui_commandline(opts, {'-h'})
expect.table_eq({help=true, format='minimal', split_strategy='none'}, opts) expect.table_eq({help=true, format='minimal', split_strategy='none'}, opts)
opts = {}
b.parse_gui_commandline(opts, {'--nometa'})
expect.table_eq({auto_phase=true, format='minimal', split_strategy='none',
name='blueprint', nometa=true},
opts)
opts = {} opts = {}
mock.patch(dfhack.maps, 'isValidTilePos', mock.func(true), mock.patch(dfhack.maps, 'isValidTilePos', mock.func(true),
function() function()

@ -244,7 +244,7 @@ end
local function run_blueprint(basename, spec, pos) local function run_blueprint(basename, spec, pos)
local args = {tostring(spec.width), tostring(spec.height), local args = {tostring(spec.width), tostring(spec.height),
tostring(-spec.depth), output_dir..basename, tostring(-spec.depth), output_dir..basename,
get_cursor_arg(pos), '-tphase'} get_cursor_arg(pos), '-tphase', '--nometa'}
local playback_start_arg = get_playback_start_arg(spec.start) local playback_start_arg = get_playback_start_arg(spec.start)
if playback_start_arg then if playback_start_arg then
table.insert(args, playback_start_arg) table.insert(args, playback_start_arg)