local _ENV = mkmodule('plugins.blueprint') local utils = require('utils') -- the info here is very basic and minimal, so hopefully we won't need to change -- it when features are added and the full blueprint docs in Plugins.rst are -- updated. local help_text = [=[ blueprint ========= Records the structure of a portion of your fortress in quickfort blueprints. Usage: blueprint [] [ []] [] blueprint gui [ []] [] Examples: blueprint gui Runs gui/blueprint, the interactive blueprint frontend, where all configuration can be set visually and interactively. blueprint 30 40 bedrooms Generates blueprints for an area 30 tiles wide by 40 tiles tall, starting from the active cursor on the current z-level. Output is written to the "bedrooms.csv" file in the "blueprints" directory. See the online DFHack documentation for more examples and details. ]=] valid_phases = utils.invert{ 'dig', 'build', 'place', 'query', } function print_help() print(help_text) end local function parse_cursor(opts, arg) local _, _, x, y, z = arg:find('^(%d+),(%d+),(%d+)$') if not x then qerror(('invalid argument for --cursor option: "%s"; expected format' .. ' is ",,", for example: "30,60,150"'):format(arg)) end -- be careful not to replace struct members when called from C++, but also -- create the table as needed when called from lua if not opts.start then opts.start = {} end opts.start.x = tonumber(x) opts.start.y = tonumber(y) opts.start.z = tonumber(z) 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) end local function parse_positionals(opts, args, start_argidx) local argidx = start_argidx -- set defaults opts.name, opts.auto_phase = 'blueprint', true local name = args[argidx] if not name then return end if name == '' then qerror(('invalid basename: "%s"; must be a valid, non-empty pathname') :format(args[argidx])) end argidx = argidx + 1 -- normalize paths to forward slashes opts.name = name:gsub(package.config:sub(1,1), "/") local auto_phase = true local phase = args[argidx] while phase do if valid_phases[phase] then auto_phase = false opts[phase] = true end argidx = argidx + 1 phase = args[argidx] end opts.auto_phase = auto_phase end local function process_args(opts, args) if args[1] == 'help' then opts.help = true return end return utils.processArgsGetopt(args, { {'c', 'cursor', hasArg=true, handler=function(optarg) parse_cursor(opts, optarg) end}, {'h', 'help', handler=function() opts.help = true end}, }) end function parse_gui_commandline(opts, args) local positionals = process_args(opts, args) if opts.help then return end parse_positionals(opts, positionals, 1) end function parse_commandline(opts, ...) local positionals = process_args(opts, {...}) if opts.help then return end local width, height = tonumber(positionals[1]), tonumber(positionals[2]) if is_bad_dim(width) or is_bad_dim(height) then qerror(('invalid width and height: "%s" "%s"; width and height must' .. ' be positive integers'):format(positionals[1], positionals[2])) end opts.width, opts.height, opts.depth = width, height, 1 local depth = tonumber(positionals[3]) if depth then if is_bad_dim(depth, true) then qerror(('invalid depth: "%s"; must be a non-zero integer') :format(positionals[3])) end opts.depth = depth end parse_positionals(opts, positionals, depth and 4 or 3) end function do_gui(command, ...) local args = {...} print(('launching gui/blueprint %s'):format(table.concat(args, ' '))) dfhack.timeout(1, 'frames', function() dfhack.run_script('gui/blueprint', table.unpack(args)) end) end -- compatibility with old exported API. we route the request back through -- run_command so we have a unified path for parameter processing and invariant -- checking. local function do_blueprint(start_pos, end_pos, name, phase) local width = math.abs(start_pos.x - end_pos.x) + 1 local height = math.abs(start_pos.y - end_pos.y) + 1 local depth = math.abs(start_pos.z - end_pos.z) + 1 if start_pos.z > end_pos.z then depth = -depth end local x = math.min(start_pos.x, end_pos.x) local y = math.min(start_pos.y, end_pos.y) local z = start_pos.z local cursor = ('--cursor=%d,%d,%d'):format(x, y, z) return dfhack.run_command{'blueprint', width, height, depth, name, phase, cursor} end for phase in pairs(valid_phases) do _ENV[phase] = function(s, e, n) do_blueprint(s, e, n, phase) end end return _ENV