diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 4f9f12e2d..6db9af322 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -46,12 +46,13 @@ selected interactively with the ``blueprint gui`` command or, if the GUI is not used, starts at the active cursor location and extends right and down for the requested width and height. -Usage:: +**Usage:** - blueprint [] [ []] [] - blueprint gui [ []] [] + ``blueprint [] [ []] []`` -Examples: + ``blueprint gui [ []] []`` + +**Examples:** ``blueprint gui`` Runs `gui/blueprint`, the interactive frontend, where all configuration for @@ -68,7 +69,7 @@ Examples: the blueprint start coordinate is set to a specific value instead of using the in-game cursor position. -Positional Parameters: +**Positional Parameters:** :``width``: Width of the area (in tiles) to translate. :``height``: Height of the area (in tiles) to translate. @@ -80,7 +81,7 @@ Positional Parameters: string must contain some characters other than numbers so the name won't be confused with the optional ``depth`` parameter. -Phases: +**Phases:** If you want to generate blueprints only for specific phases, add their names to the commandline, anywhere after the blueprint base name. You can list multiple @@ -94,36 +95,45 @@ phases; just separate them with a space. If no phases are specified, all blueprints are created. -Options: +**Options:** -:``-c``, ``--cursor ,,``: +``-c``, ``--cursor ,,``: Use the specified map coordinates instead of the current cursor position for the upper left corner of the blueprint range. If this option is specified, then an active game map cursor is not necessary. -:``-f``, ``--format ``: - Select the output format of the generated files. See the ``Output Formats`` +``-f``, ``--format ``: + 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``: +``-h``, ``--help``: Show command help text. -:``-t``, ``--splitby ``: +``-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 + blueprint is played back with `quickfort`, in + `quickfort start marker ` format, for example: + ``10,10,central stairs``. If there is a space in the comment, you will need + to surround the parameter string in double quotes: ``"-s10,10,central stairs"`` or + ``--playback-start "10,10,central stairs"`` or + ``"--playback-start=10,10,central stairs"``. +``-t``, ``--splitby ``: Split blueprints into multiple files. See the ``Splitting output into multiple files`` section below for details. If not specified, defaults to "none", which will create a standard quickfort `multi-blueprint ` file. -Output formats: +**Output formats:** Here are the values that can be passed to the ``--format`` flag: -:minimal: +:``minimal``: Creates ``.csv`` files with minimal file size that are fast to read and write. This is the default. -:pretty: +:``pretty``: Makes the blueprints in the ``.csv`` files easier to read and edit with a text editor by adding extra spacing and alignment markers. -Splitting output into multiple files: +**Splitting output into multiple files:** The ``--splitby`` flag can take any of the following values: diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index b539279c3..892613c26 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -757,6 +757,8 @@ be "2" if it is not otherwise set, etc. Labels that are explicitly defined must start with a letter to ensure the auto-generated labels don't conflict with user-defined labels. +.. _quickfort-start: + Start positions ``````````````` @@ -787,6 +789,9 @@ to the ``masonw`` blueprint above could look like this:: #meta start(center of workshop) a mason workshop /masonw +You can use semicolons, commas, or spaces to separate the elements of the +``start()`` marker, whatever is most convenient. + .. _quickfort-hidden: Hiding blueprints diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 6faaacfa5..613eda2ef 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -55,6 +55,11 @@ struct blueprint_options { // for it. string format; + // 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); + string playback_start_comment; + // file splitting strategy. this could be an enum if we set up the // boilerplate for it. string split_strategy; @@ -80,19 +85,21 @@ struct blueprint_options { static struct_identity _identity; }; 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, "split_strategy", offsetof(blueprint_options, split_strategy), df::identity_traits::get(), 0, 0 }, - { struct_field_info::PRIMITIVE, "width", offsetof(blueprint_options, width), &df::identity_traits::identity, 0, 0 }, - { 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, "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, "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, "query", offsetof(blueprint_options, query), &df::identity_traits::identity, 0, 0 }, + { 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::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 }, + { struct_field_info::PRIMITIVE, "width", offsetof(blueprint_options, width), &df::identity_traits::identity, 0, 0 }, + { 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, "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, "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, "query", offsetof(blueprint_options, query), &df::identity_traits::identity, 0, 0 }, { struct_field_info::END } }; struct_identity blueprint_options::_identity(sizeof(blueprint_options), &df::allocator_fn, NULL, "blueprint_options", NULL, blueprint_options_fields); @@ -716,9 +723,17 @@ static void write_pretty(ofstream &ofile, const blueprint_options &opts, } } -static string get_modeline(const string &phase) { +static string get_modeline(const blueprint_options &opts, const string &phase) { std::ostringstream modeline; modeline << "#" << phase << " label(" << phase << ")"; + if (opts.playback_start.x > 0) { + modeline << " start(" << opts.playback_start.x + << ";" << opts.playback_start.y; + if (!opts.playback_start_comment.empty()) { + modeline << ";" << opts.playback_start_comment; + } + modeline << ")"; + } return modeline.str(); } @@ -735,7 +750,7 @@ static bool write_blueprint(color_ostream &out, output_files[fname] = new ofstream(fname, ofstream::trunc); ofstream &ofile = *output_files[fname]; - ofile << get_modeline(processor.phase) << endl; + ofile << get_modeline(opts, processor.phase) << endl; if (pretty) write_pretty(ofile, opts, processor.mapdata); diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 405b9de20..1e9113265 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -70,14 +70,36 @@ local function parse_enum(opts, valid, name, val) opts[name] = val end -local function parse_split_strategy(opts, strategy) - parse_enum(opts, valid_split_strategies, 'split_strategy', strategy) -end - local function parse_format(opts, file_format) parse_enum(opts, valid_formats, 'format', file_format) end +local function is_int(val) + return val and val == math.floor(val) +end + +local function is_positive_int(val) + return is_int(val) and val > 0 +end + +local function parse_start(opts, args) + local arg_list = argparse.stringList(args) + local x_str, y_str = table.remove(arg_list, 1), table.remove(arg_list, 1) + local x, y = tonumber(x_str), tonumber(y_str) + if not is_positive_int(x) or not is_positive_int(y) then + qerror(('playback start offsets must be positive integers: "%s", "%s"') + :format(x_str, y_str)) + end + + if not opts.playback_start then opts.playback_start = {} end + opts.playback_start.x, opts.start.y = x, y + opts.playback_start_comment = table.concat(arg_list, ', ') +end + +local function parse_split_strategy(opts, strategy) + parse_enum(opts, valid_split_strategies, 'split_strategy', strategy) +end + local function parse_positionals(opts, args, start_argidx) local argidx = start_argidx or 1 @@ -125,6 +147,8 @@ 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}, + {'s', 'playback-start', hasArg=true, + handler=function(optarg) parse_start(opts, optarg) end}, {'t', 'splitby', hasArg=true, handler=function(optarg) parse_split_strategy(opts, optarg) end}, }) @@ -146,9 +170,8 @@ end -- dimension must be a non-nil integer that is >= 1 (or at least non-zero if -- negative_ok is true) local function is_bad_dim(dim, negative_ok) - return not dim or - (not negative_ok and dim < 1 or dim == 0) or - dim ~= math.floor(dim) + return not is_int(dim) or + (not negative_ok and dim < 1 or dim == 0) end function parse_commandline(opts, ...) @@ -171,6 +194,17 @@ function parse_commandline(opts, ...) opts.depth = depth end + if opts.playback_start and opts.playback_start.x > 0 then + if opts.playback_start.x > width then + qerror(('playback start x offset outside width of blueprint: %d') + :format(opts.playback_start.x)) + end + if opts.playback_start.y > height then + qerror(('playback start y offset outside height of blueprint: %d') + :format(opts.playback_start.y)) + end + end + parse_positionals(opts, positionals, depth and 4 or 3) end