From c52138b16874e59665dd75261132ca27167d0194 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 17:42:36 -0700 Subject: [PATCH 1/2] generate meta blueprints --- docs/changelog.txt | 1 + docs/plugins/blueprint.rst | 10 ++++-- plugins/blueprint.cpp | 70 ++++++++++++++++++++++++++++++++++-- plugins/lua/blueprint.lua | 17 +++++++++ test/plugins/blueprint.lua | 6 ++++ test/quickfort/ecosystem.lua | 2 +- 6 files changed, 100 insertions(+), 6 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 71d843764..c2173d5e1 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -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 stockpile/building/zone names 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`: 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 diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index 262a3bd5b..3a5e5a0c5 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -99,8 +99,14 @@ Options Select the output format of the generated files. See the `Output formats`_ section below for options. If not specified, the output format defaults to "minimal", which will produce a small, fast ``.csv`` file. -``-h``, ``--help`` - Show command help text. +``--nometa`` + `Meta blueprints ` 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 ` from the ``quickfort list`` command. ``-s``, ``--playback-start ,,`` 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 diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 2f42e2798..b08f4d074 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -14,6 +14,7 @@ #include "DataDefs.h" #include "DataFuncs.h" #include "DataIdentity.h" +#include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" #include "TileTypes.h" @@ -47,6 +48,10 @@ using namespace DFHack; DFHACK_PLUGIN("blueprint"); REQUIRE_GLOBAL(world); +namespace DFHack { + DBG_DECLARE(blueprint,log); +} + struct blueprint_options { // whether to display help bool help = false; @@ -59,6 +64,9 @@ struct blueprint_options { // for it. string format; + // whether to skip generating meta blueprints + bool nometa = false; + // 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 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::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::get(), 0, 0 }, + { struct_field_info::PRIMITIVE, "nometa", offsetof(blueprint_options, nometa), &df::identity_traits::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::get(), 0, 0 }, { struct_field_info::PRIMITIVE, "split_strategy", offsetof(blueprint_options, split_strategy), df::identity_traits::get(), 0, 0 }, @@ -1115,6 +1124,32 @@ static bool get_filename(string &fname, 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, const bp_volume &mapdata) { 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, - const string &phase) { +static string get_modeline(color_ostream &out, const blueprint_options &opts, + const string &mode, const string &phase) { std::ostringstream modeline; modeline << "#" << mode << " label(" << phase << ")"; if (opts.playback_start.x > 0) { @@ -1183,6 +1218,8 @@ static string get_modeline(const blueprint_options &opts, const string &mode, } modeline << ")"; } + if (is_meta_phase(out, opts, phase)) + modeline << " hidden()"; return modeline.str(); } @@ -1199,7 +1236,7 @@ static bool write_blueprint(color_ostream &out, output_files[fname] = new ofstream(fname, ofstream::trunc); 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) write_pretty(ofile, opts, processor.mapdata); @@ -1209,6 +1246,27 @@ static bool write_blueprint(color_ostream &out, return true; } +static void write_meta_blueprint(color_ostream &out, + std::map &output_files, + const blueprint_options &opts, + const std::vector & 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) { if (ctx.b) return; @@ -1301,12 +1359,18 @@ static bool do_transform(color_ostream &out, } std::map output_files; + std::vector meta_phases; for (blueprint_processor &processor : processors) { if (processor.mapdata.empty() && !processor.force_create) continue; + if (is_meta_phase(out, opts, processor.phase)) + meta_phases.push_back(processor.phase); if (!write_blueprint(out, output_files, opts, processor, pretty)) break; } + if (meta_phases.size()) { + write_meta_blueprint(out, output_files, opts, meta_phases); + } for (auto &it : output_files) { filenames.push_back(it.first); diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 6fe869d3b..e3b36d9fe 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -15,6 +15,14 @@ local 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 = { 'minimal', 'pretty', @@ -123,6 +131,7 @@ local function process_args(opts, args) {'f', 'format', hasArg=true, handler=function(optarg) parse_format(opts, optarg) end}, {'h', 'help', handler=function() opts.help = true end}, + {nil, 'nometa', handler=function() opts.nometa = true end}, {'s', 'playback-start', hasArg=true, handler=function(optarg) parse_start(opts, optarg) 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) 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 function get_filename(opts, phase) local fullname = 'blueprints/' .. opts.name @@ -197,6 +211,9 @@ function get_filename(opts, phase) fullname = fullname .. basename end if opts.split_strategy == 'phase' then + if is_meta_phase(opts, phase) then + phase = 'meta' + end return ('%s-%s.csv'):format(fullname, phase) end -- no splitting diff --git a/test/plugins/blueprint.lua b/test/plugins/blueprint.lua index a19d519b8..73ec33eda 100644 --- a/test/plugins/blueprint.lua +++ b/test/plugins/blueprint.lua @@ -20,6 +20,12 @@ function test.parse_gui_commandline() b.parse_gui_commandline(opts, {'-h'}) 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 = {} mock.patch(dfhack.maps, 'isValidTilePos', mock.func(true), function() diff --git a/test/quickfort/ecosystem.lua b/test/quickfort/ecosystem.lua index 27dddc5d5..05f531e2e 100644 --- a/test/quickfort/ecosystem.lua +++ b/test/quickfort/ecosystem.lua @@ -244,7 +244,7 @@ end local function run_blueprint(basename, spec, pos) local args = {tostring(spec.width), tostring(spec.height), 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) if playback_start_arg then table.insert(args, playback_start_arg) From b4986aad97f73c6f961874d6543001af1f9e08cf Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 12 Oct 2022 17:49:37 -0700 Subject: [PATCH 2/2] create meta bp only if it will reduce the bp count --- plugins/blueprint.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index b08f4d074..361aaa1ca 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -1285,7 +1285,7 @@ static void add_processor(vector &processors, static bool do_transform(color_ostream &out, const df::coord &start, const df::coord &end, - const blueprint_options &opts, + blueprint_options &opts, vector &filenames) { // empty map instances to pass to emplace() below static const bp_area EMPTY_AREA; @@ -1358,17 +1358,24 @@ static bool do_transform(color_ostream &out, } } - std::map output_files; std::vector meta_phases; for (blueprint_processor &processor : processors) { if (processor.mapdata.empty() && !processor.force_create) continue; if (is_meta_phase(out, opts, processor.phase)) meta_phases.push_back(processor.phase); + } + if (meta_phases.size() <= 1) + opts.nometa = true; + + std::map output_files; + for (blueprint_processor &processor : processors) { + if (processor.mapdata.empty() && !processor.force_create) + continue; if (!write_blueprint(out, output_files, opts, processor, pretty)) break; } - if (meta_phases.size()) { + if (!opts.nometa) { write_meta_blueprint(out, output_files, opts, meta_phases); }