diff --git a/data/blueprints/library/test/ecosystem/golden/tracks-track.csv b/data/blueprints/library/test/ecosystem/golden/tracks-carve.csv similarity index 84% rename from data/blueprints/library/test/ecosystem/golden/tracks-track.csv rename to data/blueprints/library/test/ecosystem/golden/tracks-carve.csv index fdf373019..c5b430961 100644 --- a/data/blueprints/library/test/ecosystem/golden/tracks-track.csv +++ b/data/blueprints/library/test/ecosystem/golden/tracks-carve.csv @@ -1,4 +1,4 @@ -#dig label(track) +#dig label(carve) trackSE,trackEW,trackEW,trackEW,trackSW trackNE,,,,trackNW #> diff --git a/data/blueprints/library/test/ecosystem/in/basic-track.csv b/data/blueprints/library/test/ecosystem/in/basic-carve.csv similarity index 81% rename from data/blueprints/library/test/ecosystem/in/basic-track.csv rename to data/blueprints/library/test/ecosystem/in/basic-carve.csv index 23e7897e7..250ea2381 100644 --- a/data/blueprints/library/test/ecosystem/in/basic-track.csv +++ b/data/blueprints/library/test/ecosystem/in/basic-carve.csv @@ -1,4 +1,4 @@ -#dig label(track) +#dig label(carve) ,,trackS ,,trackNS trackE,trackEW,trackNSEW,trackEW,trackW diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-carve.csv b/data/blueprints/library/test/ecosystem/in/fortifications-carve.csv new file mode 100644 index 000000000..c174cc918 --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/fortifications-carve.csv @@ -0,0 +1,4 @@ +#dig label(carve) +,F +F,,F +,F diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-dig.csv b/data/blueprints/library/test/ecosystem/in/fortifications-dig.csv new file mode 100644 index 000000000..dc40f07b2 --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/fortifications-dig.csv @@ -0,0 +1,3 @@ +#dig label(dig) + +,d diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-smooth.csv b/data/blueprints/library/test/ecosystem/in/fortifications-smooth.csv new file mode 100644 index 000000000..bf91a2328 --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/fortifications-smooth.csv @@ -0,0 +1,4 @@ +#dig label(smooth) +,s +s,,s +,s diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-spec.csv b/data/blueprints/library/test/ecosystem/in/fortifications-spec.csv new file mode 100644 index 000000000..462a2e6db --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/fortifications-spec.csv @@ -0,0 +1,4 @@ +#notes +description=test carving fortifications +width=3 +height=3 diff --git a/data/blueprints/library/test/ecosystem/in/tracks-track.csv b/data/blueprints/library/test/ecosystem/in/tracks-carve.csv similarity index 63% rename from data/blueprints/library/test/ecosystem/in/tracks-track.csv rename to data/blueprints/library/test/ecosystem/in/tracks-carve.csv index b0ceb0fc7..0646e5934 100644 --- a/data/blueprints/library/test/ecosystem/in/tracks-track.csv +++ b/data/blueprints/library/test/ecosystem/in/tracks-carve.csv @@ -1,4 +1,4 @@ -#dig label(track) +#dig label(carve) T(5x2) ,,,,T(-5x-2) #> diff --git a/docs/changelog.txt b/docs/changelog.txt index 244274b3f..d7fcb9617 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -55,6 +55,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `manipulator`: Tweak colors to make the cursor easier to locate - `autochop`: only designate the amount of trees required to reach ``max_logs`` - `autochop`: preferably designate larger trees over smaller ones +- `blueprint`: ``track`` phase renamed to ``carve``. carved fortifications and (optionally) engravings are now captured in blueprints ## Documentation - Add more examples to the plugin skeleton files so they are more informative for a newbie diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 048e84914..1401333eb 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "Console.h" #include "DataDefs.h" @@ -31,6 +32,7 @@ #include "df/building_trapst.h" #include "df/building_water_wheelst.h" #include "df/building_workshopst.h" +#include "df/engraving.h" #include "df/world.h" using std::endl; @@ -74,12 +76,15 @@ struct blueprint_options { // base name to use for generated files string name; + // whether to capture engravings and smooth the tiles that will be engraved + bool engrave = false; + // whether to autodetect which phases to output bool auto_phase = false; // if not autodetecting, which phases to output bool dig = false; - bool track = false; + bool carve = false; bool build = false; bool place = false; bool zone = false; @@ -98,9 +103,10 @@ static const struct_field_info blueprint_options_fields[] = { { struct_field_info::PRIMITIVE, "height", offsetof(blueprint_options, height), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "depth", offsetof(blueprint_options, depth), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "name", offsetof(blueprint_options, name), df::identity_traits::get(), 0, 0 }, + { struct_field_info::PRIMITIVE, "engrave", offsetof(blueprint_options, engrave), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "auto_phase", offsetof(blueprint_options, auto_phase), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "dig", offsetof(blueprint_options, dig), &df::identity_traits::identity, 0, 0 }, - { struct_field_info::PRIMITIVE, "track", offsetof(blueprint_options, track), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "carve", offsetof(blueprint_options, carve), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "build", offsetof(blueprint_options, build), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "place", offsetof(blueprint_options, place), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "zone", offsetof(blueprint_options, zone), &df::identity_traits::identity, 0, 0 }, @@ -125,6 +131,14 @@ struct tile_context { df::building* b = NULL; }; +// global engravings cache, cleared when the string cache is cleared +struct PosHash { + size_t operator()(const df::coord &c) const { + return c.x * 65537 + c.y * 513 + c.z; + } +}; +static std::unordered_map engravings_cache; + // We use const char * throughout this code instead of std::string to avoid // having to allocate memory for all the small string literals. This // significantly speeds up processing and allows us to handle very large maps @@ -140,6 +154,7 @@ static const char * cache(const char *str) { static std::set _cache; if (!str) { _cache.clear(); + engravings_cache.clear(); return NULL; } return _cache.emplace(str).first->c_str(); @@ -170,8 +185,6 @@ static const char * get_tile_dig(const df::coord &pos, const tile_context &) { case tiletype_shape::PEBBLES: case tiletype_shape::BROOK_TOP: return "d"; - case tiletype_shape::FORTIFICATION: - return "F"; case tiletype_shape::STAIR_UP: return "u"; case tiletype_shape::STAIR_DOWN: @@ -186,6 +199,48 @@ static const char * get_tile_dig(const df::coord &pos, const tile_context &) { } } +static const char * get_tile_smooth_minimal(const df::coord &pos, + const tile_context &) { + df::tiletype *tt = Maps::getTileType(pos); + if (!tt) + return NULL; + + switch (tileShape(*tt)) + { + case tiletype_shape::FORTIFICATION: + return "s"; + default: + break; + } + + return NULL; +} + +static const char * get_tile_smooth(const df::coord &pos, + const tile_context &tc) { + const char * smooth_minimal = get_tile_smooth_minimal(pos, tc); + if (smooth_minimal) + return smooth_minimal; + + df::tiletype *tt = Maps::getTileType(pos); + if (!tt) + return NULL; + + switch (tileShape(*tt)) + { + case tiletype_shape::FLOOR: + case tiletype_shape::WALL: + if (tileSpecial(*tt) == tiletype_special::SMOOTH && + engravings_cache.count(pos)) + return "s"; + break; + default: + break; + } + + return NULL; +} + static const char * get_track_str(const char *prefix, df::tiletype tt) { TileDirection tdir = tileDirection(tt); @@ -198,7 +253,8 @@ static const char * get_track_str(const char *prefix, df::tiletype tt) { return cache(prefix + dir); } -static const char * get_tile_track(const df::coord &pos, const tile_context &) { +static const char * get_tile_carve_minimal(const df::coord &pos, + const tile_context &) { df::tiletype *tt = Maps::getTileType(pos); if (!tt) return NULL; @@ -213,6 +269,32 @@ static const char * get_tile_track(const df::coord &pos, const tile_context &) { if (tileSpecial(*tt) == tiletype_special::TRACK) return get_track_str("trackramp", *tt); break; + case tiletype_shape::FORTIFICATION: + return "F"; + default: + break; + } + + return NULL; +} + +static const char * get_tile_carve(const df::coord &pos, const tile_context &tc) { + const char * tile_carve_minimal = get_tile_carve_minimal(pos, tc); + if (tile_carve_minimal) + return tile_carve_minimal; + + df::tiletype *tt = Maps::getTileType(pos); + if (!tt) + return NULL; + + switch (tileShape(*tt)) + { + case tiletype_shape::FLOOR: + case tiletype_shape::WALL: + if (tileSpecial(*tt) == tiletype_special::SMOOTH && + engravings_cache.count(pos)) + return "e"; + break; default: break; } @@ -1002,10 +1084,20 @@ static bool do_transform(color_ostream &out, static const bp_area EMPTY_AREA; static const bp_row EMPTY_ROW; + if (opts.engrave) { + // initialize the engravings cache + for (auto engraving : world->engravings) { + engravings_cache.emplace(engraving->pos, engraving); + } + } + vector processors; add_processor(processors, opts, "dig", "dig", opts.dig, get_tile_dig); - add_processor(processors, opts, "dig", "track", opts.track, get_tile_track); + add_processor(processors, opts, "dig", "smooth", opts.carve, + opts.engrave ? get_tile_smooth : get_tile_smooth_minimal); + add_processor(processors, opts, "dig", "carve", opts.carve, + opts.engrave ? get_tile_carve : get_tile_carve_minimal); add_processor(processors, opts, "build", "build", opts.build, get_tile_build, ensure_building); add_processor(processors, opts, "place", "place", opts.place, diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 2c5733c01..7b0076e14 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -36,7 +36,7 @@ function print_help() print(help_text) end local valid_phase_list = { 'dig', - 'track', + 'carve', 'build', 'place', 'zone', @@ -148,6 +148,7 @@ local function process_args(opts, args) local positionals = argparse.processArgsGetopt(args, { {'c', 'cursor', hasArg=true, handler=function(optarg) parse_cursor(opts, optarg) end}, + {'e', 'engrave', handler=function() opts.engrave = true end}, {'f', 'format', hasArg=true, handler=function(optarg) parse_format(opts, optarg) end}, {'h', 'help', handler=function() opts.help = true end}, diff --git a/test/plugins/blueprint.lua b/test/plugins/blueprint.lua index 8d3dde9ec..27a2634b4 100644 --- a/test/plugins/blueprint.lua +++ b/test/plugins/blueprint.lua @@ -29,6 +29,18 @@ function test.parse_gui_commandline() name='blueprint', start={x=1,y=2,z=3}}, opts) + opts = {} + b.parse_gui_commandline(opts, {'-e'}) + expect.table_eq({auto_phase=true, format='minimal', split_strategy='none', + name='blueprint', engrave=true,}, + opts) + + opts = {} + b.parse_gui_commandline(opts, {'--engrave'}) + expect.table_eq({auto_phase=true, format='minimal', split_strategy='none', + name='blueprint', engrave=true,}, + opts) + opts = {} b.parse_gui_commandline(opts, {'-fminimal'}) expect.table_eq({auto_phase=true, format='minimal', split_strategy='none', diff --git a/test/quickfort/ecosystem.lua b/test/quickfort/ecosystem.lua index 5b863f9ac..320dc9979 100644 --- a/test/quickfort/ecosystem.lua +++ b/test/quickfort/ecosystem.lua @@ -264,6 +264,23 @@ local function reset_area(area, spec) dfhack.run_command('tiletypes-here', '--quiet', get_cursor_arg(pos)) end +local function do_phase(phase_data, area, spec) + quickfort_run(phase_data.listnum, area.pos, spec.start) +end + +-- run a #dig blueprint (or just designate the whole block if there is no data) +-- and then run dig-now to materialize the designations +local function do_dig_phase(phase_data, area, spec) + if phase_data then + do_phase(phase_data, area, spec) + else + designate_area(area.pos, spec) + end + + -- run dig-now to dig out designated tiles + run_dig_now(area) +end + function test.end_to_end() -- read in test plan local sets = get_blueprint_sets() @@ -282,27 +299,14 @@ function test.end_to_end() goto continue end - -- quickfort run #dig blueprint (or just designate the whole block if - -- there is no #dig blueprint) local phases = set.phases - if phases.dig then - quickfort_run(phases.dig.listnum, area.pos, spec.start) - else - designate_area(area.pos, spec) - end - - -- run dig-now to dig out designated tiles - run_dig_now(area) - - -- quickfort run remaining blueprints - for _,phase_name in ipairs(phase_names) do - if phase_name ~= 'dig' and phases[phase_name] then - quickfort_run(phases[phase_name].listnum, area.pos, spec.start) - if phase_name == 'track' then - run_dig_now(area) - end - end - end + do_dig_phase(phases.dig, area, spec) + if phases.smooth then do_dig_phase(phases.smooth, area, spec) end + if phases.carve then do_dig_phase(phases.carve, area, spec) end + if phases.build then do_phase(phases.build, area, spec) end + if phases.place then do_phase(phases.place, area, spec) end + if phases.zone then do_phase(phases.zone, area, spec) end + if phases.query then do_phase(phases.query, area, spec) end -- run blueprint to generate files in output dir run_blueprint(basename, set, area.pos)