dfhack/plugins/lua/blueprint.lua

170 lines
5.1 KiB
Lua

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 <width> <height> [<depth>] [<name> [<phases>]] [<options>]
blueprint gui [<name> [<phases>]] [<options>]
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 "<x>,<y>,<z>", 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