update stockpiles command and use new data paths
parent
8c0b59c548
commit
e21c55d6ff
@ -1,41 +1,68 @@
|
|||||||
.. _stocksettings:
|
|
||||||
|
|
||||||
stockpiles
|
stockpiles
|
||||||
==========
|
==========
|
||||||
|
|
||||||
.. dfhack-tool::
|
.. dfhack-tool::
|
||||||
:summary: Import and export stockpile settings.
|
:summary: Import, export, or modify stockpile settings and features.
|
||||||
:tags: fort design productivity stockpiles
|
:tags: fort design productivity stockpiles
|
||||||
:no-command:
|
|
||||||
|
|
||||||
.. dfhack-command:: savestock
|
|
||||||
:summary: Exports the configuration of the selected stockpile.
|
|
||||||
|
|
||||||
.. dfhack-command:: loadstock
|
|
||||||
:summary: Imports configuration for the selected stockpile.
|
|
||||||
|
|
||||||
Select a stockpile in the UI first to use these commands.
|
If you are importing or exporting setting and don't want to specify a building
|
||||||
|
ID, select a stockpile in the UI before running the command.
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
``savestock <filename>``
|
::
|
||||||
Saves the currently highlighted stockpile's settings to a file in your
|
|
||||||
Dwarf Fortress folder. This file can be used to copy settings between game
|
|
||||||
saves or players.
|
|
||||||
``loadstock <filename>``
|
|
||||||
Loads a saved stockpile settings file and applies it to the currently
|
|
||||||
selected stockpile.
|
|
||||||
|
|
||||||
Filenames with spaces are not supported. Generated materials, divine metals,
|
stockpiles [status]
|
||||||
etc. are not saved as they are different in every world.
|
stockpiles list [<search>]
|
||||||
|
stockpiles export <name> [<options>]
|
||||||
|
stockpiles import <name> [<options>]
|
||||||
|
|
||||||
|
Exported stockpile settings are saved in the ``dfhack-config/stockpiles``
|
||||||
|
folder, where you can view and delete them, if desired. Names can only
|
||||||
|
contain numbers, letters, periods, underscores, dashes, and spaces. If
|
||||||
|
the name has spaces, be sure to surround it with double quotes (:kbd:`"`).
|
||||||
|
|
||||||
|
The names of library settings files are all prefixed by the string ``library/``.
|
||||||
|
You can specify library files explicitly by including the prefix, or you can
|
||||||
|
just write the short name to use a player-exported file by that name if it
|
||||||
|
exists, and the library file if it doesn't.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
``savestock food``
|
``stockpiles``
|
||||||
Export the stockpile settings for the currently selected stockpile to a
|
Shows the list of all your stockpiles and some relevant statistics.
|
||||||
file named ``food.dfstock``.
|
``stockpiles list``
|
||||||
``loadstock food``
|
Shows the list of previously exported stockpile settings files, including
|
||||||
Set the selected stockpile settings to those saved in the ``food.dfstock``
|
the stockpile configuration library.
|
||||||
file.
|
``stockpiles list plants``
|
||||||
|
Shows the list of exported stockpile settings files that include the
|
||||||
|
substring ``plants``.
|
||||||
|
``stockpiles import library/plants``
|
||||||
|
Imports the library ``plants`` settings file into the currently selected
|
||||||
|
stockpile.
|
||||||
|
``stockpiles import plants``
|
||||||
|
Imports a player-exported settings file named ``plants``, or the library
|
||||||
|
``plants`` settings file if a player-exported file by that name doesn't
|
||||||
|
exist.
|
||||||
|
``stockpiles export mysettings``
|
||||||
|
Export the settings for the currently selected stockpile to a file named
|
||||||
|
``dfhack-config/stockpiles/mysettings.dfstock``.
|
||||||
|
|
||||||
|
Options
|
||||||
|
-------
|
||||||
|
|
||||||
|
``-s``, ``--stockpile <id>``
|
||||||
|
Specify a specific stockpile ID instead of using the one currently selected
|
||||||
|
in the UI.
|
||||||
|
|
||||||
|
.. _stockpiles-library:
|
||||||
|
|
||||||
|
The stockpiles settings library
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
DFHack comes with a library of useful stockpile settings files that are ready
|
||||||
|
for import:
|
||||||
|
|
||||||
|
TODO: port alias library here
|
||||||
|
@ -1,244 +1,135 @@
|
|||||||
local _ENV = mkmodule('plugins.stockpiles')
|
local _ENV = mkmodule('plugins.stockpiles')
|
||||||
|
|
||||||
--[[
|
local argparse = require('argparse')
|
||||||
|
|
||||||
Native functions:
|
local STOCKPILES_DIR = "dfhack-config/stockpiles";
|
||||||
|
local STOCKPILES_LIBRARY_DIR = "hack/data/stockpiles";
|
||||||
|
|
||||||
* stockpiles_list_settings(dir_path), list files in directory
|
local function get_sp_name(name, num)
|
||||||
* stockpiles_load(file), with full path
|
if #name > 0 then return name end
|
||||||
* stockpiles_save(file), with full path
|
return ('Stockpile %d'):format(num)
|
||||||
* isEnabled()
|
|
||||||
|
|
||||||
--]]
|
|
||||||
--
|
|
||||||
function safe_require(module)
|
|
||||||
local status, module = pcall(require, module)
|
|
||||||
return status and module or nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local STATUS_FMT = '%6s %s'
|
||||||
local gui = require 'gui'
|
local function print_status()
|
||||||
local widgets = require('gui.widgets')
|
local sps = df.global.world.buildings.other.STOCKPILE
|
||||||
local dlg = require('gui.dialogs')
|
print(('Current stockpiles: %d'):format(#sps))
|
||||||
local script = require 'gui.script'
|
if #sps > 0 then
|
||||||
local persist = safe_require('persist-table')
|
print()
|
||||||
|
print(STATUS_FMT:format('ID', 'Name'))
|
||||||
|
print(STATUS_FMT:format('------', '----------'))
|
||||||
function ListFilterDialog(args)
|
|
||||||
args.text = args.prompt or 'Type or select an option'
|
|
||||||
args.text_pen = COLOR_WHITE
|
|
||||||
args.with_filter = true
|
|
||||||
args.icon_width = 2
|
|
||||||
|
|
||||||
local choices = {}
|
|
||||||
|
|
||||||
if not args.hide_none then
|
|
||||||
table.insert(choices, {
|
|
||||||
icon = '?', text = args.none_caption or 'none',
|
|
||||||
index = -1, name = -1
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
for _,sp in ipairs(sps) do
|
||||||
local filter = args.item_filter
|
print(STATUS_FMT:format(sp.id, get_sp_name(sp.name, sp.stockpile_number)))
|
||||||
|
|
||||||
for i,v in ipairs(args.items) do
|
|
||||||
if not filter or filter(v,-1) then
|
|
||||||
local name = v
|
|
||||||
local icon
|
|
||||||
table.insert(choices, {
|
|
||||||
icon = icon, search_key = string.lower(name), text = name, index = i
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
args.choices = choices
|
local function list_dir(path, prefix, filters)
|
||||||
|
local paths = dfhack.filesystem.listdir_recursive(path, 0, false)
|
||||||
if args.on_select then
|
if not paths then
|
||||||
local cb = args.on_select
|
dfhack.printerr(('Cannot find stockpile settings directory: "%s"'):format(path))
|
||||||
args.on_select = function(idx, obj)
|
return
|
||||||
return cb(obj.index, args.items[obj.index])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return dlg.ListBox(args)
|
|
||||||
end
|
|
||||||
|
|
||||||
function showFilterPrompt(title, list, text,item_filter,hide_none)
|
|
||||||
ListFilterDialog{
|
|
||||||
frame_title=title,
|
|
||||||
items=list,
|
|
||||||
prompt=text,
|
|
||||||
item_filter=item_filter,
|
|
||||||
hide_none=hide_none,
|
|
||||||
on_select=script.mkresume(true),
|
|
||||||
on_cancel=script.mkresume(false),
|
|
||||||
on_close=script.qresume(nil)
|
|
||||||
}:show()
|
|
||||||
|
|
||||||
return script.wait()
|
|
||||||
end
|
|
||||||
|
|
||||||
function init()
|
|
||||||
if persist == nil then return end
|
|
||||||
if dfhack.isMapLoaded() then
|
|
||||||
if persist.GlobalTable.stockpiles == nil then
|
|
||||||
persist.GlobalTable.stockpiles = {}
|
|
||||||
persist.GlobalTable.stockpiles['settings_path'] = './stocksettings'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
local normalized_filters = {}
|
||||||
function tablify(iterableObject)
|
for _,filter in ipairs(filters or {}) do
|
||||||
t={}
|
table.insert(normalized_filters, filter:lower())
|
||||||
for k,v in ipairs(iterableObject) do
|
|
||||||
t[k] = v~=nil and v or 'nil'
|
|
||||||
end
|
end
|
||||||
return t
|
for _,v in ipairs(paths) do
|
||||||
|
local normalized_path = prefix .. v.path:lower()
|
||||||
|
if v.isdir or not normalized_path:endswith('.dfstock') then goto continue end
|
||||||
|
normalized_path = normalized_path:sub(1, -9)
|
||||||
|
if #normalized_filters > 0 then
|
||||||
|
local matched = false
|
||||||
|
for _,filter in ipairs(normalized_filters) do
|
||||||
|
if normalized_path:find(filter, 1, true) then
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
local filename_invalid_regex = '[^A-Za-z0-9 ._-]'
|
|
||||||
|
|
||||||
function valid_filename(filename)
|
|
||||||
return not filename:match(filename_invalid_regex)
|
|
||||||
end
|
end
|
||||||
|
if not matched then goto continue end
|
||||||
function sanitize_filename(filename)
|
|
||||||
local ret = ''
|
|
||||||
for i = 1, #filename do
|
|
||||||
local ch = filename:sub(i, i)
|
|
||||||
if valid_filename(ch) then
|
|
||||||
ret = ret .. ch
|
|
||||||
else
|
|
||||||
ret = ret .. '-'
|
|
||||||
end
|
end
|
||||||
|
print(('%s%s'):format(prefix, v.path:sub(1, -9)))
|
||||||
|
::continue::
|
||||||
end
|
end
|
||||||
return ret
|
|
||||||
end
|
end
|
||||||
|
|
||||||
FilenameInputBox = defclass(FilenameInputBox, dlg.InputBox)
|
local function list_settings_files(filters)
|
||||||
function FilenameInputBox:onInput(keys)
|
list_dir(STOCKPILES_DIR, '', filters)
|
||||||
if not valid_filename(string.char(keys._STRING or 0)) and not keys.STRING_A000 then
|
list_dir(STOCKPILES_LIBRARY_DIR, 'library/', filters)
|
||||||
keys._STRING = nil
|
|
||||||
end
|
end
|
||||||
FilenameInputBox.super.onInput(self, keys)
|
|
||||||
end
|
|
||||||
|
|
||||||
function showFilenameInputPrompt(title, text, tcolor, input, min_width)
|
|
||||||
FilenameInputBox{
|
|
||||||
frame_title = title,
|
|
||||||
text = text,
|
|
||||||
text_pen = tcolor,
|
|
||||||
input = input,
|
|
||||||
frame_width = min_width,
|
|
||||||
on_input = script.mkresume(true),
|
|
||||||
on_cancel = script.mkresume(false),
|
|
||||||
on_close = script.qresume(nil)
|
|
||||||
}:show()
|
|
||||||
|
|
||||||
return script.wait()
|
local function assert_safe_name(name)
|
||||||
|
if not name or #name == 0 then
|
||||||
|
qerror('name missing or empty')
|
||||||
end
|
end
|
||||||
|
if name:find('[^%a ._-]') then
|
||||||
function load_settings()
|
qerror('name can only contain numbers, letters, periods, underscores, dashes, and spaces')
|
||||||
init()
|
|
||||||
local path = get_path()
|
|
||||||
local ok, list = pcall(stockpiles_list_settings, path)
|
|
||||||
if not ok then
|
|
||||||
show_message_box("Stockpile Settings", "The stockpile settings folder doesn't exist.", true)
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
if #list == 0 then
|
|
||||||
show_message_box("Stockpile Settings", "There are no saved stockpile settings.", true)
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local choice_list = {}
|
local function get_sp_id(opts)
|
||||||
for i,v in ipairs(list) do
|
if opts.id then return opts.id end
|
||||||
choice_list[i] = string.gsub(v, "/", "/ ")
|
local sp = dfhack.gui.getSelectedStockpile()
|
||||||
choice_list[i] = string.gsub(choice_list[i], "-", " - ")
|
if sp then return sp.id end
|
||||||
choice_list[i] = string.gsub(choice_list[i], "_", " ")
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
script.start(function()
|
local function export_stockpile(name, opts)
|
||||||
local ok2,index,name=showFilterPrompt('Stockpile Settings', choice_list, 'Choose a stockpile', function(item) return true end, true)
|
assert_safe_name(name)
|
||||||
if ok2 then
|
name = STOCKPILES_DIR .. '/' .. name
|
||||||
local filename = list[index];
|
stockpiles_export(name, get_sp_id(opts))
|
||||||
stockpiles_load(path..'/'..filename)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function save_settings(stockpile)
|
local function import_stockpile(name, opts)
|
||||||
init()
|
local is_library = false
|
||||||
script.start(function()
|
if name:startswith('library/') then
|
||||||
local suggested = stockpile.name
|
name = name:sub(9)
|
||||||
if #suggested == 0 then
|
is_library = true
|
||||||
suggested = 'Stock1'
|
|
||||||
end
|
end
|
||||||
suggested = sanitize_filename(suggested)
|
assert_safe_name(name)
|
||||||
local path = get_path()
|
if not is_library and dfhack.filesystem.exists(STOCKPILES_DIR .. '/' .. name .. '.dfstock') then
|
||||||
local sok,filename = showFilenameInputPrompt('Stockpile Settings', 'Enter filename', COLOR_WHITE, suggested)
|
name = STOCKPILES_DIR .. '/' .. name
|
||||||
if sok then
|
|
||||||
if filename == nil or filename == '' or not valid_filename(filename) then
|
|
||||||
script.showMessage('Stockpile Settings', 'Invalid File Name', COLOR_RED)
|
|
||||||
else
|
else
|
||||||
if not dfhack.filesystem.exists(path) then
|
name = STOCKPILES_LIBRARY_DIR .. '/' .. name
|
||||||
dfhack.filesystem.mkdir(path)
|
|
||||||
end
|
|
||||||
stockpiles_save(path..'/'..filename)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end)
|
stockpiles_import(name, get_sp_id(opts))
|
||||||
end
|
end
|
||||||
|
|
||||||
function manage_settings(sp)
|
local function process_args(opts, args)
|
||||||
init()
|
if args[1] == 'help' then
|
||||||
if not guard() then return false end
|
opts.help = true
|
||||||
script.start(function()
|
return
|
||||||
local list = {'Load', 'Save'}
|
|
||||||
local tok,i = script.showListPrompt('Stockpile Settings','Load or Save Settings?',COLOR_WHITE,tablify(list))
|
|
||||||
if tok then
|
|
||||||
if i == 1 then
|
|
||||||
load_settings()
|
|
||||||
else
|
|
||||||
save_settings(sp)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function show_message_box(title, msg, iserror)
|
return argparse.processArgsGetopt(args, {
|
||||||
local color = COLOR_WHITE
|
{'h', 'help', handler=function() opts.help = true end},
|
||||||
if iserror then
|
{'s', 'stockpile', has_arg=true,
|
||||||
color = COLOR_RED
|
handler=function(arg) opts.id = argparse.nonnegativeInt(art, 'stockpile') end},
|
||||||
end
|
})
|
||||||
script.start(function()
|
|
||||||
script.showMessage(title, msg, color)
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function guard()
|
function parse_commandline(args)
|
||||||
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Stockpile') then
|
local opts = {}
|
||||||
qerror("This script requires a stockpile selected in the 'q' mode")
|
local positionals = process_args(opts, args)
|
||||||
|
|
||||||
|
if opts.help or not positionals then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function set_path(path)
|
local command = table.remove(positionals, 1)
|
||||||
init()
|
if not command or command == 'status' then
|
||||||
if persist == nil then
|
print_status()
|
||||||
qerror("This version of DFHack doesn't support setting the stockpile settings path. Sorry.")
|
elseif command == 'list' then
|
||||||
return
|
list_settings_files(positionals)
|
||||||
end
|
elseif command == 'export' then
|
||||||
persist.GlobalTable.stockpiles['settings_path'] = path
|
export_stockpile(positionals[1], opts)
|
||||||
|
elseif command == 'import' then
|
||||||
|
import_stockpile(positionals[1], opts)
|
||||||
|
else
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function get_path()
|
return true
|
||||||
init()
|
|
||||||
if persist == nil then
|
|
||||||
return "stocksettings"
|
|
||||||
end
|
|
||||||
return persist.GlobalTable.stockpiles['settings_path']
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return _ENV
|
return _ENV
|
||||||
|
Loading…
Reference in New Issue