2012-03-31 05:40:54 -06:00
|
|
|
-- Common startup file for all dfhack plugins with lua support
|
2012-04-01 02:50:56 -06:00
|
|
|
-- The global dfhack table is already created by C++ init code.
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-21 10:15:57 -06:00
|
|
|
-- Setup the global environment.
|
|
|
|
-- BASE_G is the original lua global environment,
|
|
|
|
-- preserved as a common denominator for all modules.
|
|
|
|
-- This file uses it instead of the new default one.
|
|
|
|
|
|
|
|
local dfhack = dfhack
|
|
|
|
local base_env = dfhack.BASE_G
|
|
|
|
local _ENV = base_env
|
|
|
|
|
2014-06-10 11:41:01 -06:00
|
|
|
CR_LINK_FAILURE = -3
|
|
|
|
CR_NEEDS_CONSOLE = -2
|
|
|
|
CR_NOT_IMPLEMENTED = -1
|
|
|
|
CR_OK = 0
|
|
|
|
CR_FAILURE = 1
|
|
|
|
CR_WRONG_USAGE = 2
|
|
|
|
CR_NOT_FOUND = 3
|
|
|
|
|
2012-04-05 09:55:59 -06:00
|
|
|
-- Console color constants
|
|
|
|
|
|
|
|
COLOR_RESET = -1
|
|
|
|
COLOR_BLACK = 0
|
|
|
|
COLOR_BLUE = 1
|
|
|
|
COLOR_GREEN = 2
|
|
|
|
COLOR_CYAN = 3
|
|
|
|
COLOR_RED = 4
|
|
|
|
COLOR_MAGENTA = 5
|
|
|
|
COLOR_BROWN = 6
|
|
|
|
COLOR_GREY = 7
|
|
|
|
COLOR_DARKGREY = 8
|
|
|
|
COLOR_LIGHTBLUE = 9
|
|
|
|
COLOR_LIGHTGREEN = 10
|
|
|
|
COLOR_LIGHTCYAN = 11
|
|
|
|
COLOR_LIGHTRED = 12
|
|
|
|
COLOR_LIGHTMAGENTA = 13
|
|
|
|
COLOR_YELLOW = 14
|
|
|
|
COLOR_WHITE = 15
|
|
|
|
|
2012-04-17 01:45:09 -06:00
|
|
|
-- Events
|
|
|
|
|
|
|
|
if dfhack.is_core_context then
|
|
|
|
SC_WORLD_LOADED = 0
|
|
|
|
SC_WORLD_UNLOADED = 1
|
|
|
|
SC_MAP_LOADED = 2
|
|
|
|
SC_MAP_UNLOADED = 3
|
|
|
|
SC_VIEWSCREEN_CHANGED = 4
|
|
|
|
SC_CORE_INITIALIZED = 5
|
2012-07-05 10:03:02 -06:00
|
|
|
SC_PAUSED = 7
|
|
|
|
SC_UNPAUSED = 8
|
2012-04-17 01:45:09 -06:00
|
|
|
end
|
|
|
|
|
2012-04-05 09:55:59 -06:00
|
|
|
-- Error handling
|
|
|
|
|
2012-04-03 10:02:01 -06:00
|
|
|
safecall = dfhack.safecall
|
2012-09-05 07:37:36 -06:00
|
|
|
curry = dfhack.curry
|
2012-04-03 10:02:01 -06:00
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
function dfhack.pcall(f, ...)
|
|
|
|
return xpcall(f, dfhack.onerror, ...)
|
2012-06-22 06:36:50 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function qerror(msg, level)
|
2018-05-20 07:30:46 -06:00
|
|
|
local name = dfhack.current_script_name()
|
|
|
|
if name and not tostring(msg):match(name) then
|
|
|
|
msg = name .. ': ' .. tostring(msg)
|
|
|
|
end
|
2012-06-22 06:36:50 -06:00
|
|
|
dfhack.error(msg, (level or 1) + 1, false)
|
2012-04-04 03:34:07 -06:00
|
|
|
end
|
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
function dfhack.with_finalize(...)
|
|
|
|
return dfhack.call_with_finalizer(0,true,...)
|
|
|
|
end
|
|
|
|
function dfhack.with_onerror(...)
|
|
|
|
return dfhack.call_with_finalizer(0,false,...)
|
|
|
|
end
|
|
|
|
|
|
|
|
local function call_delete(obj)
|
|
|
|
if obj then obj:delete() end
|
|
|
|
end
|
|
|
|
|
|
|
|
function dfhack.with_temp_object(obj,fn,...)
|
|
|
|
return dfhack.call_with_finalizer(1,true,call_delete,obj,fn,obj,...)
|
|
|
|
end
|
|
|
|
|
2012-06-22 10:17:55 -06:00
|
|
|
dfhack.exception.__index = dfhack.exception
|
|
|
|
|
2012-04-05 09:55:59 -06:00
|
|
|
-- Module loading
|
|
|
|
|
2014-06-26 08:11:05 -06:00
|
|
|
local function find_required_module_arg()
|
|
|
|
-- require -> module code -> mkmodule -> find_...
|
|
|
|
if debug.getinfo(4,'f').func == require then
|
|
|
|
return debug.getlocal(4, 1)
|
|
|
|
end
|
|
|
|
-- reload -> dofile -> module code -> mkmodule -> find_...
|
|
|
|
if debug.getinfo(5,'f').func == reload then
|
|
|
|
return debug.getlocal(5, 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-04-01 02:50:56 -06:00
|
|
|
function mkmodule(module,env)
|
2014-06-26 08:11:05 -06:00
|
|
|
-- Verify that the module name is correct
|
|
|
|
local _, rq_modname = find_required_module_arg()
|
|
|
|
if not rq_modname then
|
|
|
|
error('The mkmodule function must be used at the start of a module')
|
|
|
|
end
|
|
|
|
if rq_modname ~= module then
|
|
|
|
error('Found module '..module..' during require '..rq_modname)
|
|
|
|
end
|
|
|
|
-- Reuse the already loaded module table
|
2012-04-01 02:50:56 -06:00
|
|
|
local pkg = package.loaded[module]
|
|
|
|
if pkg == nil then
|
|
|
|
pkg = {}
|
|
|
|
else
|
|
|
|
if type(pkg) ~= 'table' then
|
|
|
|
error("Not a table in package.loaded["..module.."]")
|
|
|
|
end
|
|
|
|
end
|
2014-06-26 08:11:05 -06:00
|
|
|
-- Inject the plugin-exported functions when appropriate
|
2012-09-06 02:37:29 -06:00
|
|
|
local plugname = string.match(module,'^plugins%.([%w%-]+)$')
|
Allow plugins to export functions to lua with safe reload support.
- To ensure reload safety functions have to be wrapped. Every call
checks the loaded state and locks a mutex in Plugin. If the plugin
is unloaded, calling its functions throws a lua error. Therefore,
plugins may not create closures or export yieldable functions.
- The set of function argument and return types supported by
LuaWrapper is severely limited when compared to being compiled
inside the main library.
Currently supported types: numbers, bool, std::string, df::foo,
df::foo*, std::vector<bool>, std::vector<df::foo*>.
- To facilitate postponing initialization until after all plugins
have been loaded, the core sends a SC_CORE_INITIALIZED event.
- As an example, the burrows plugin now exports its functions.
2012-04-14 09:44:07 -06:00
|
|
|
if plugname then
|
|
|
|
dfhack.open_plugin(pkg,plugname)
|
|
|
|
end
|
2012-04-21 10:15:57 -06:00
|
|
|
setmetatable(pkg, { __index = (env or base_env) })
|
2012-04-01 02:50:56 -06:00
|
|
|
return pkg
|
|
|
|
end
|
|
|
|
|
|
|
|
function reload(module)
|
|
|
|
if type(package.loaded[module]) ~= 'table' then
|
|
|
|
error("Module not loaded: "..module)
|
|
|
|
end
|
|
|
|
local path,err = package.searchpath(module,package.path)
|
|
|
|
if not path then
|
|
|
|
error(err)
|
|
|
|
end
|
|
|
|
dofile(path)
|
|
|
|
end
|
|
|
|
|
2012-08-19 07:53:25 -06:00
|
|
|
-- Trivial classes
|
|
|
|
|
2012-08-24 03:28:34 -06:00
|
|
|
function rawset_default(target,source)
|
|
|
|
for k,v in pairs(source) do
|
|
|
|
if rawget(target,k) == nil then
|
|
|
|
rawset(target,k,v)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-09-18 10:30:25 -06:00
|
|
|
DEFAULT_NIL = DEFAULT_NIL or {} -- Unique token
|
|
|
|
|
|
|
|
function defclass(...)
|
|
|
|
return require('class').defclass(...)
|
2012-08-19 07:53:25 -06:00
|
|
|
end
|
|
|
|
|
2012-09-18 10:30:25 -06:00
|
|
|
function mkinstance(...)
|
|
|
|
return require('class').mkinstance(...)
|
2012-08-19 07:53:25 -06:00
|
|
|
end
|
|
|
|
|
2012-04-05 09:55:59 -06:00
|
|
|
-- Misc functions
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
NEWLINE = "\n"
|
|
|
|
COMMA = ","
|
|
|
|
PERIOD = "."
|
|
|
|
|
2012-04-01 06:43:40 -06:00
|
|
|
function printall(table)
|
2012-08-23 09:27:12 -06:00
|
|
|
local ok,f,t,k = pcall(pairs,table)
|
|
|
|
if ok then
|
|
|
|
for k,v in f,t,k do
|
2012-04-06 01:21:28 -06:00
|
|
|
print(string.format("%-23s\t = %s",tostring(k),tostring(v)))
|
|
|
|
end
|
2012-04-01 06:43:40 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-09-08 18:30:18 -06:00
|
|
|
function printall_ipairs(table)
|
|
|
|
local ok,f,t,k = pcall(ipairs,table)
|
|
|
|
if ok then
|
|
|
|
for k,v in f,t,k do
|
|
|
|
print(string.format("%-23s\t = %s",tostring(k),tostring(v)))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-30 05:32:02 -06:00
|
|
|
local do_print_recurse
|
|
|
|
|
|
|
|
local function print_string(printfn, v, seen, indent)
|
|
|
|
local str = tostring(v)
|
|
|
|
printfn(str)
|
|
|
|
return #str;
|
|
|
|
end
|
|
|
|
|
|
|
|
local fill_chars = {
|
|
|
|
__index = function(table, key, value)
|
|
|
|
local rv = string.rep(' ', 23 - key) .. ' = '
|
|
|
|
rawset(table, key, rv)
|
|
|
|
return rv
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
|
|
|
|
setmetatable(fill_chars, fill_chars)
|
|
|
|
|
|
|
|
local function print_fields(value, seen, indent, prefix)
|
|
|
|
local ok,f,t,k = pcall(pairs,value)
|
|
|
|
if not ok then
|
|
|
|
dfhack.print(prefix)
|
|
|
|
dfhack.println('<Type doesn\'t support iteration with pairs>')
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
local prev_value = "not a value"
|
|
|
|
local repeated = 0
|
|
|
|
for k, v in f,t,k do
|
|
|
|
-- Only show set values of bitfields
|
|
|
|
if value._kind ~= "bitfield" or v then
|
|
|
|
local continue = false
|
|
|
|
if type(k) == "number" then
|
|
|
|
if prev_value == v then
|
|
|
|
repeated = repeated + 1
|
|
|
|
continue = true
|
|
|
|
else
|
|
|
|
prev_value = v
|
|
|
|
end
|
|
|
|
else
|
|
|
|
prev_value = "not a value"
|
|
|
|
end
|
|
|
|
if not continue then
|
|
|
|
if repeated > 0 then
|
|
|
|
dfhack.println(prefix .. "<Repeated " .. repeated .. " times>")
|
|
|
|
repeated = 0
|
|
|
|
end
|
|
|
|
dfhack.print(prefix)
|
|
|
|
local len = do_print_recurse(dfhack.print, k, seen, indent + 1)
|
|
|
|
dfhack.print(fill_chars[len <= 23 and len or 23])
|
|
|
|
do_print_recurse(dfhack.println, v, seen, indent + 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if repeated > 0 then
|
|
|
|
dfhack.println(prefix .. "<Repeated " .. repeated .. " times>")
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
|
|
|
|
-- This should be same as print_array but userdata doesn't compare equal even if
|
|
|
|
-- they hold same pointer.
|
|
|
|
local function print_userdata(printfn, value, seen, indent)
|
|
|
|
local prefix = string.rep(' ', indent)
|
|
|
|
local strvalue = tostring(value)
|
|
|
|
dfhack.println(strvalue)
|
|
|
|
if seen[strvalue] then
|
|
|
|
dfhack.print(prefix)
|
|
|
|
dfhack.println('<Cyclic reference! Skipping fields>\n')
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
seen[strvalue] = true
|
|
|
|
return print_fields(value, seen, indent, prefix)
|
|
|
|
end
|
|
|
|
|
|
|
|
local function print_array(printfn, value, seen, indent)
|
|
|
|
local prefix = string.rep(' ', indent)
|
|
|
|
dfhack.println(tostring(value))
|
|
|
|
if seen[value] then
|
|
|
|
dfhack.print(prefix)
|
|
|
|
dfhack.println('<Cyclic reference! skipping fields>\n')
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
seen[value] = true
|
|
|
|
return print_fields(value, seen, indent, prefix)
|
|
|
|
end
|
|
|
|
|
|
|
|
local recurse_type_map = {
|
|
|
|
number = print_string,
|
|
|
|
string = print_string,
|
|
|
|
boolean = print_string,
|
|
|
|
['function'] = print_string,
|
|
|
|
['nil'] = print_string,
|
|
|
|
userdata = print_userdata,
|
|
|
|
table = print_array,
|
|
|
|
}
|
|
|
|
|
|
|
|
do_print_recurse = function(printfn, value, seen, indent)
|
|
|
|
local t = type(value)
|
|
|
|
if not recurse_type_map[t] then
|
|
|
|
printfn("Unknown type " .. t .. " " .. tostring(value))
|
|
|
|
return
|
|
|
|
end
|
|
|
|
return recurse_type_map[t](printfn, value, seen, indent)
|
|
|
|
end
|
|
|
|
|
2020-02-23 08:57:57 -07:00
|
|
|
function printall_recurse(value, seen)
|
|
|
|
local seen = seen or {}
|
2018-06-30 05:32:02 -06:00
|
|
|
do_print_recurse(dfhack.println, value, seen, 0)
|
|
|
|
end
|
|
|
|
|
2012-04-11 09:42:05 -06:00
|
|
|
function copyall(table)
|
|
|
|
local rv = {}
|
|
|
|
for k,v in pairs(table) do rv[k] = v end
|
|
|
|
return rv
|
|
|
|
end
|
|
|
|
|
2012-04-12 08:37:27 -06:00
|
|
|
function pos2xyz(pos)
|
2012-05-13 03:58:41 -06:00
|
|
|
if pos then
|
|
|
|
local x = pos.x
|
|
|
|
if x and x ~= -30000 then
|
|
|
|
return x, pos.y, pos.z
|
|
|
|
end
|
2012-04-12 08:37:27 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function xyz2pos(x,y,z)
|
|
|
|
if x then
|
|
|
|
return {x=x,y=y,z=z}
|
|
|
|
else
|
|
|
|
return {x=-30000,y=-30000,z=-30000}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function same_xyz(a,b)
|
|
|
|
return a and b and a.x == b.x and a.y == b.y and a.z == b.z
|
|
|
|
end
|
|
|
|
|
|
|
|
function get_path_xyz(path,i)
|
|
|
|
return path.x[i], path.y[i], path.z[i]
|
|
|
|
end
|
|
|
|
|
2012-09-07 09:54:32 -06:00
|
|
|
function pos2xy(pos)
|
|
|
|
if pos then
|
|
|
|
local x = pos.x
|
|
|
|
if x and x ~= -30000 then
|
|
|
|
return x, pos.y
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function xy2pos(x,y)
|
|
|
|
if x then
|
|
|
|
return {x=x,y=y}
|
|
|
|
else
|
|
|
|
return {x=-30000,y=-30000}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function same_xy(a,b)
|
|
|
|
return a and b and a.x == b.x and a.y == b.y
|
|
|
|
end
|
|
|
|
|
|
|
|
function get_path_xy(path,i)
|
|
|
|
return path.x[i], path.y[i]
|
|
|
|
end
|
|
|
|
|
2012-04-23 11:30:53 -06:00
|
|
|
function safe_index(obj,idx,...)
|
|
|
|
if obj == nil or idx == nil then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
if type(idx) == 'number' and (idx < 0 or idx >= #obj) then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
obj = obj[idx]
|
|
|
|
if select('#',...) > 0 then
|
|
|
|
return safe_index(obj,...)
|
|
|
|
else
|
|
|
|
return obj
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-04-16 15:13:41 -06:00
|
|
|
function string:startswith(prefix)
|
|
|
|
return self:sub(1, #prefix) == prefix
|
|
|
|
end
|
|
|
|
|
|
|
|
function string:endswith(suffix)
|
|
|
|
return self:sub(-#suffix) == suffix or #suffix == 0
|
|
|
|
end
|
|
|
|
|
2021-06-23 14:59:39 -06:00
|
|
|
-- Inserts newlines into a string so no individual line exceeds the given width.
|
|
|
|
-- Lines are split at space-separated word boundaries. Any existing newlines are
|
|
|
|
-- kept in place. If a single word is longer than width, it is split over
|
|
|
|
-- multiple lines.
|
|
|
|
function string:wrap(width)
|
|
|
|
local wrapped_text = {}
|
|
|
|
for line in self:gmatch('[^\n]*') do
|
|
|
|
local line_start_pos = 1
|
|
|
|
local wrapped_line = line:gsub(
|
|
|
|
'%s*()(%S+)()',
|
|
|
|
function(start_pos, word, end_pos)
|
|
|
|
-- word fits within the current line
|
|
|
|
if end_pos - line_start_pos <= width then return end
|
|
|
|
-- word needs to go on the next line, but is not itself longer
|
|
|
|
-- than the specified width
|
|
|
|
if #word <= width then
|
|
|
|
line_start_pos = start_pos
|
|
|
|
return '\n' .. word
|
|
|
|
end
|
|
|
|
-- word is too long to fit on one line and needs to be split up
|
|
|
|
local num_chars, str = 0, start_pos == 1 and '' or '\n'
|
|
|
|
repeat
|
|
|
|
local word_frag = word:sub(num_chars + 1, num_chars + width)
|
|
|
|
str = str .. word_frag
|
|
|
|
num_chars = num_chars + #word_frag
|
|
|
|
if num_chars < #word then
|
|
|
|
str = str .. '\n'
|
|
|
|
end
|
|
|
|
line_start_pos = start_pos + num_chars
|
|
|
|
until end_pos - line_start_pos <= width
|
|
|
|
return str .. word:sub(num_chars + 1)
|
|
|
|
end)
|
|
|
|
table.insert(wrapped_text, wrapped_line)
|
|
|
|
end
|
|
|
|
return table.concat(wrapped_text, '\n')
|
|
|
|
end
|
|
|
|
|
2012-04-23 11:30:53 -06:00
|
|
|
-- String conversions
|
|
|
|
|
2012-04-01 06:43:40 -06:00
|
|
|
function dfhack.persistent:__tostring()
|
|
|
|
return "<persistent "..self.entry_id..":"..self.key.."=\""
|
|
|
|
..self.value.."\":"..table.concat(self.ints,",")..">"
|
|
|
|
end
|
|
|
|
|
2012-04-06 09:56:19 -06:00
|
|
|
function dfhack.matinfo:__tostring()
|
|
|
|
return "<material "..self.type..":"..self.index.." "..self:getToken()..">"
|
|
|
|
end
|
|
|
|
|
2013-09-30 09:46:39 -06:00
|
|
|
dfhack.random.__index = dfhack.random
|
|
|
|
|
|
|
|
function dfhack.random:__tostring()
|
|
|
|
return "<random generator>"
|
|
|
|
end
|
|
|
|
|
2015-03-27 20:56:20 -06:00
|
|
|
dfhack.penarray.__index = dfhack.penarray
|
|
|
|
|
|
|
|
function dfhack.penarray.__tostring()
|
|
|
|
return "<penarray>"
|
|
|
|
end
|
|
|
|
|
2012-04-11 06:20:16 -06:00
|
|
|
function dfhack.maps.getSize()
|
|
|
|
local map = df.global.world.map
|
|
|
|
return map.x_count_block, map.y_count_block, map.z_count_block
|
|
|
|
end
|
|
|
|
|
|
|
|
function dfhack.maps.getTileSize()
|
|
|
|
local map = df.global.world.map
|
|
|
|
return map.x_count, map.y_count, map.z_count
|
|
|
|
end
|
|
|
|
|
2012-05-01 08:55:30 -06:00
|
|
|
function dfhack.buildings.getSize(bld)
|
|
|
|
local x, y = bld.x1, bld.y1
|
|
|
|
return bld.x2+1-x, bld.y2+1-y, bld.centerx-x, bld.centery-y
|
|
|
|
end
|
|
|
|
|
2015-10-17 13:08:12 -06:00
|
|
|
function dfhack.gui.getViewscreenByType(scr_type, n)
|
|
|
|
-- translated from modules/Gui.cpp
|
|
|
|
if n == nil then
|
|
|
|
n = 1
|
|
|
|
end
|
|
|
|
local limit = (n > 0)
|
|
|
|
local scr = dfhack.gui.getCurViewscreen()
|
|
|
|
while scr do
|
|
|
|
if limit then
|
|
|
|
n = n - 1
|
|
|
|
if n < 0 then
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if scr_type:is_instance(scr) then
|
|
|
|
return scr
|
|
|
|
end
|
|
|
|
scr = scr.parent
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
-- Interactive
|
|
|
|
|
|
|
|
local print_banner = true
|
|
|
|
|
|
|
|
function dfhack.interpreter(prompt,hfile,env)
|
|
|
|
if not dfhack.is_interactive() then
|
|
|
|
return nil, 'not interactive'
|
|
|
|
end
|
|
|
|
|
|
|
|
print("Type quit to exit interactive lua interpreter.")
|
|
|
|
|
|
|
|
if print_banner then
|
|
|
|
print("Shortcuts:\n"..
|
|
|
|
" '= foo' => '_1,_2,... = foo'\n"..
|
|
|
|
" '! foo' => 'print(foo)'\n"..
|
2014-06-07 18:31:14 -06:00
|
|
|
" '~ foo' => 'printall(foo)'\n"..
|
2018-06-30 05:32:02 -06:00
|
|
|
" '^ foo' => 'printall_recurse(foo)'\n"..
|
2014-09-14 09:50:03 -06:00
|
|
|
" '@ foo' => 'printall_ipairs(foo)'\n"..
|
2014-06-07 18:31:14 -06:00
|
|
|
"All of these save the first result as '_'.")
|
2012-04-15 11:50:22 -06:00
|
|
|
print_banner = false
|
|
|
|
end
|
|
|
|
|
|
|
|
local prompt_str = "["..(prompt or 'lua').."]# "
|
2012-04-23 11:30:53 -06:00
|
|
|
local prompt_cont = string.rep(' ',#prompt_str-4)..">>> "
|
2012-04-15 11:50:22 -06:00
|
|
|
local prompt_env = {}
|
2012-04-23 11:30:53 -06:00
|
|
|
local cmdlinelist = {}
|
|
|
|
local t_prompt = nil
|
2012-04-15 11:50:22 -06:00
|
|
|
local vcnt = 1
|
|
|
|
|
2012-04-29 11:07:39 -06:00
|
|
|
local pfix_handlers = {
|
|
|
|
['!'] = function(data)
|
|
|
|
print(table.unpack(data,2,data.n))
|
|
|
|
end,
|
|
|
|
['~'] = function(data)
|
|
|
|
print(table.unpack(data,2,data.n))
|
|
|
|
printall(data[2])
|
|
|
|
end,
|
2014-09-08 18:30:18 -06:00
|
|
|
['@'] = function(data)
|
|
|
|
print(table.unpack(data,2,data.n))
|
|
|
|
printall_ipairs(data[2])
|
|
|
|
end,
|
2018-06-30 05:32:02 -06:00
|
|
|
['^'] = function(data)
|
|
|
|
printall_recurse(data[2])
|
|
|
|
end,
|
2012-04-29 11:07:39 -06:00
|
|
|
['='] = function(data)
|
|
|
|
for i=2,data.n do
|
|
|
|
local varname = '_'..vcnt
|
|
|
|
prompt_env[varname] = data[i]
|
|
|
|
dfhack.print(varname..' = ')
|
|
|
|
safecall(print, data[i])
|
|
|
|
vcnt = vcnt + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
setmetatable(prompt_env, { __index = env or _G })
|
2012-04-23 11:30:53 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
while true do
|
2012-04-16 09:46:20 -06:00
|
|
|
local cmdline = dfhack.lineedit(t_prompt or prompt_str, hfile)
|
2012-04-15 11:50:22 -06:00
|
|
|
|
|
|
|
if cmdline == nil or cmdline == 'quit' then
|
|
|
|
break
|
|
|
|
elseif cmdline ~= '' then
|
|
|
|
local pfix = string.sub(cmdline,1,1)
|
|
|
|
|
2012-04-29 11:07:39 -06:00
|
|
|
if not t_prompt and pfix_handlers[pfix] then
|
2012-04-15 11:50:22 -06:00
|
|
|
cmdline = 'return '..string.sub(cmdline,2)
|
2012-04-23 11:30:53 -06:00
|
|
|
else
|
|
|
|
pfix = nil
|
2012-04-15 11:50:22 -06:00
|
|
|
end
|
2012-04-23 11:30:53 -06:00
|
|
|
|
|
|
|
table.insert(cmdlinelist,cmdline)
|
|
|
|
cmdline = table.concat(cmdlinelist,'\n')
|
|
|
|
|
|
|
|
local code,err = load(cmdline, '=(interactive)', 't', prompt_env)
|
2012-04-15 11:50:22 -06:00
|
|
|
|
|
|
|
if code == nil then
|
2012-04-23 11:30:53 -06:00
|
|
|
if not pfix and err:sub(-5)=="<eof>" then
|
|
|
|
t_prompt=prompt_cont
|
|
|
|
else
|
|
|
|
dfhack.printerr(err)
|
|
|
|
cmdlinelist={}
|
|
|
|
t_prompt=nil
|
|
|
|
end
|
2012-04-15 11:50:22 -06:00
|
|
|
else
|
2012-04-23 11:30:53 -06:00
|
|
|
cmdlinelist={}
|
|
|
|
t_prompt=nil
|
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
local data = table.pack(safecall(code))
|
|
|
|
|
|
|
|
if data[1] and data.n > 1 then
|
|
|
|
prompt_env._ = data[2]
|
2012-04-29 11:07:39 -06:00
|
|
|
safecall(pfix_handlers[pfix], data)
|
2012-04-15 11:50:22 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2012-05-04 09:47:18 -06:00
|
|
|
-- Command scripts
|
|
|
|
|
2012-12-21 03:00:50 -07:00
|
|
|
local internal = dfhack.internal
|
2012-05-04 09:47:18 -06:00
|
|
|
|
2015-03-10 14:53:32 -06:00
|
|
|
Script = defclass(Script)
|
|
|
|
function Script:init(path)
|
|
|
|
self.path = path
|
|
|
|
self.mtime = dfhack.filesystem.mtime(path)
|
2015-03-10 15:32:37 -06:00
|
|
|
self._flags = {}
|
|
|
|
end
|
2015-06-05 17:08:11 -06:00
|
|
|
function Script:needs_update()
|
|
|
|
return (not self.env) or self.mtime ~= dfhack.filesystem.mtime(self.path)
|
|
|
|
end
|
2015-03-10 15:32:37 -06:00
|
|
|
function Script:get_flags()
|
|
|
|
if self.flags_mtime ~= dfhack.filesystem.mtime(self.path) then
|
|
|
|
self.flags_mtime = dfhack.filesystem.mtime(self.path)
|
|
|
|
self._flags = {}
|
|
|
|
local f = io.open(self.path)
|
|
|
|
local contents = f:read('*all')
|
|
|
|
f:close()
|
2015-03-15 14:54:18 -06:00
|
|
|
for line in contents:gmatch('%-%-@([^\n]+)') do
|
2015-03-10 15:32:37 -06:00
|
|
|
local chunk = load(line, self.path, 't', self._flags)
|
2015-03-15 14:54:18 -06:00
|
|
|
if chunk then
|
|
|
|
chunk()
|
|
|
|
else
|
|
|
|
dfhack.printerr('Parse error: ' .. line)
|
|
|
|
end
|
2015-03-10 15:32:37 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
return self._flags
|
2015-03-10 14:53:32 -06:00
|
|
|
end
|
2012-12-21 03:00:50 -07:00
|
|
|
|
2015-03-10 14:53:32 -06:00
|
|
|
internal.scripts = internal.scripts or {}
|
2015-02-01 23:46:01 -07:00
|
|
|
|
2012-06-14 02:46:12 -06:00
|
|
|
local hack_path = dfhack.getHackPath()
|
|
|
|
|
2015-01-31 20:43:54 -07:00
|
|
|
function dfhack.findScript(name)
|
2015-09-06 14:23:02 -06:00
|
|
|
return dfhack.internal.findScript(name .. '.lua')
|
2014-07-07 06:50:40 -06:00
|
|
|
end
|
|
|
|
|
2015-03-10 15:32:37 -06:00
|
|
|
local valid_script_flags = {
|
2015-03-15 14:54:18 -06:00
|
|
|
enable = {required = true, error = 'Does not recognize enable/disable commands'},
|
2015-03-10 15:32:37 -06:00
|
|
|
enable_state = {required = false},
|
2015-05-09 07:21:00 -06:00
|
|
|
module = {
|
|
|
|
required = function(flags)
|
|
|
|
if flags.module_strict == false then return false end
|
|
|
|
return true
|
|
|
|
end,
|
|
|
|
error = 'Cannot be used as a module'
|
|
|
|
},
|
|
|
|
module_strict = {required = false},
|
2015-06-14 09:56:14 -06:00
|
|
|
alias = {required = false},
|
|
|
|
alias_count = {required = false},
|
2021-03-29 12:26:28 -06:00
|
|
|
scripts = {required = false},
|
2015-03-10 15:32:37 -06:00
|
|
|
}
|
|
|
|
|
2014-07-07 06:50:40 -06:00
|
|
|
function dfhack.run_script(name,...)
|
2015-03-10 15:32:37 -06:00
|
|
|
return dfhack.run_script_with_env(nil, name, nil, ...)
|
2015-01-26 19:45:31 -07:00
|
|
|
end
|
|
|
|
|
2015-03-10 16:29:36 -06:00
|
|
|
function dfhack.enable_script(name, state)
|
2015-03-15 14:54:18 -06:00
|
|
|
local res, err = dfhack.pcall(dfhack.run_script_with_env, nil, name, {enable=true, enable_state=state})
|
2015-03-10 16:29:36 -06:00
|
|
|
if not res then
|
2015-03-15 14:54:18 -06:00
|
|
|
dfhack.printerr(err.message)
|
|
|
|
qerror(('Cannot %s Lua script: %s'):format(state and 'enable' or 'disable', name))
|
2015-03-10 16:29:36 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-05-09 07:21:00 -06:00
|
|
|
function dfhack.reqscript(name)
|
2015-06-05 17:08:11 -06:00
|
|
|
return dfhack.script_environment(name, true)
|
2015-01-31 20:43:54 -07:00
|
|
|
end
|
2015-05-09 07:21:00 -06:00
|
|
|
reqscript = dfhack.reqscript
|
|
|
|
|
2021-03-29 13:23:53 -06:00
|
|
|
function dfhack.script_environment(name, strict)
|
|
|
|
local scripts = internal.scripts
|
2015-06-05 17:08:11 -06:00
|
|
|
local path = dfhack.findScript(name)
|
|
|
|
if not scripts[path] or scripts[path]:needs_update() then
|
2021-03-29 13:29:15 -06:00
|
|
|
local _, env = dfhack.run_script_with_env(nil, name, {
|
|
|
|
module=true,
|
|
|
|
module_strict=(strict and true or false) -- ensure that this key is present if 'strict' is nil
|
|
|
|
})
|
2015-06-05 17:08:11 -06:00
|
|
|
return env
|
|
|
|
else
|
|
|
|
if strict and not scripts[path]:get_flags().module then
|
|
|
|
error(('%s: %s'):format(name, valid_script_flags.module.error))
|
|
|
|
end
|
|
|
|
return scripts[path].env
|
|
|
|
end
|
2015-05-09 07:21:00 -06:00
|
|
|
end
|
2015-01-31 20:43:54 -07:00
|
|
|
|
2015-03-10 15:32:37 -06:00
|
|
|
function dfhack.run_script_with_env(envVars, name, flags, ...)
|
|
|
|
if type(flags) ~= 'table' then flags = {} end
|
2015-01-31 20:43:54 -07:00
|
|
|
local file = dfhack.findScript(name)
|
2014-07-07 06:50:40 -06:00
|
|
|
if not file then
|
|
|
|
error('Could not find script ' .. name)
|
|
|
|
end
|
2015-02-01 23:46:01 -07:00
|
|
|
|
2021-03-29 12:26:28 -06:00
|
|
|
local scripts = flags.scripts or internal.scripts
|
2015-03-10 14:53:32 -06:00
|
|
|
if scripts[file] == nil then
|
|
|
|
scripts[file] = Script(file)
|
|
|
|
end
|
2015-03-10 15:32:37 -06:00
|
|
|
local script_flags = scripts[file]:get_flags()
|
2015-06-14 09:56:14 -06:00
|
|
|
if script_flags.alias then
|
|
|
|
flags.alias_count = (flags.alias_count or 0) + 1
|
|
|
|
if flags.alias_count > 10 then
|
|
|
|
error('Too many script aliases: ' .. flags.alias_count)
|
|
|
|
end
|
|
|
|
return dfhack.run_script_with_env(envVars, script_flags.alias, flags, ...)
|
|
|
|
end
|
2015-03-10 15:32:37 -06:00
|
|
|
for flag, value in pairs(flags) do
|
|
|
|
if value then
|
2015-05-09 07:21:00 -06:00
|
|
|
local v = valid_script_flags[flag]
|
|
|
|
if not v then
|
2015-03-10 15:32:37 -06:00
|
|
|
error('Invalid flag: ' .. flag)
|
2015-05-09 07:21:00 -06:00
|
|
|
elseif ((type(v.required) == 'boolean' and v.required) or
|
|
|
|
(type(v.required) == 'function' and v.required(flags))) then
|
|
|
|
if not script_flags[flag] then
|
|
|
|
local msg = v.error or 'Flag "' .. flag .. '" not recognized'
|
|
|
|
error(name .. ': ' .. msg)
|
|
|
|
end
|
2015-03-10 15:32:37 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-03-10 14:53:32 -06:00
|
|
|
local env = scripts[file].env
|
2012-05-04 09:47:18 -06:00
|
|
|
if env == nil then
|
|
|
|
env = {}
|
|
|
|
setmetatable(env, { __index = base_env })
|
|
|
|
end
|
2015-01-26 19:45:31 -07:00
|
|
|
for x,y in pairs(envVars or {}) do
|
|
|
|
env[x] = y
|
|
|
|
end
|
2015-03-10 15:32:37 -06:00
|
|
|
env.dfhack_flags = flags
|
|
|
|
env.moduleMode = flags.module
|
2016-10-19 07:51:48 -06:00
|
|
|
local script_code
|
2015-02-01 23:46:01 -07:00
|
|
|
local perr
|
|
|
|
local time = dfhack.filesystem.mtime(file)
|
2015-03-10 14:53:32 -06:00
|
|
|
if time == scripts[file].mtime and scripts[file].run then
|
2016-10-19 07:51:48 -06:00
|
|
|
script_code = scripts[file].run
|
2015-02-01 23:46:01 -07:00
|
|
|
else
|
|
|
|
--reload
|
2016-10-19 07:51:48 -06:00
|
|
|
script_code, perr = loadfile(file, 't', env)
|
|
|
|
if not script_code then
|
2015-02-10 14:36:08 -07:00
|
|
|
error(perr)
|
2015-02-01 23:46:01 -07:00
|
|
|
end
|
2015-02-10 14:36:08 -07:00
|
|
|
-- avoid updating mtime if the script failed to load
|
2015-03-10 14:53:32 -06:00
|
|
|
scripts[file].mtime = time
|
2012-05-04 09:47:18 -06:00
|
|
|
end
|
2015-03-10 14:53:32 -06:00
|
|
|
scripts[file].env = env
|
2016-10-19 07:51:48 -06:00
|
|
|
scripts[file].run = script_code
|
|
|
|
return script_code(...), env
|
2012-05-04 09:47:18 -06:00
|
|
|
end
|
|
|
|
|
2018-05-20 07:30:46 -06:00
|
|
|
function dfhack.current_script_name()
|
2018-01-25 08:55:00 -07:00
|
|
|
local frame = 1
|
|
|
|
while true do
|
|
|
|
local info = debug.getinfo(frame, 'f')
|
|
|
|
if not info then break end
|
|
|
|
if info.func == dfhack.run_script_with_env then
|
|
|
|
local i = 1
|
|
|
|
while true do
|
|
|
|
local name, value = debug.getlocal(frame, i)
|
|
|
|
if not name then break end
|
|
|
|
if name == 'name' then
|
|
|
|
return value
|
|
|
|
end
|
|
|
|
i = i + 1
|
|
|
|
end
|
|
|
|
break
|
|
|
|
end
|
|
|
|
frame = frame + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function dfhack.script_help(script_name, extension)
|
2018-05-20 07:30:46 -06:00
|
|
|
script_name = script_name or dfhack.current_script_name()
|
2018-01-25 08:55:00 -07:00
|
|
|
extension = extension or 'lua'
|
|
|
|
local full_name = script_name .. '.' .. extension
|
|
|
|
local path = dfhack.internal.findScript(script_name .. '.' .. extension)
|
|
|
|
or error("Could not find script: " .. full_name)
|
|
|
|
local begin_seq, end_seq
|
|
|
|
if extension == 'rb' then
|
|
|
|
begin_seq = '=begin'
|
|
|
|
end_seq = '=end'
|
|
|
|
else
|
|
|
|
begin_seq = '[====['
|
|
|
|
end_seq = ']====]'
|
|
|
|
end
|
|
|
|
local f = io.open(path) or error("Could not open " .. path)
|
|
|
|
local in_help = false
|
|
|
|
local help = ''
|
|
|
|
for line in f:lines() do
|
|
|
|
if line:endswith(begin_seq) then
|
|
|
|
in_help = true
|
|
|
|
elseif in_help then
|
|
|
|
if line:endswith(end_seq) then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
if line ~= script_name and line ~= ('='):rep(#script_name) then
|
|
|
|
help = help .. line .. '\n'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
f:close()
|
|
|
|
help = help:gsub('^\n+', ''):gsub('\n+$', '')
|
|
|
|
return help
|
|
|
|
end
|
|
|
|
|
2020-11-20 15:57:54 -07:00
|
|
|
local function _run_command(args, use_console)
|
2014-06-10 11:41:01 -06:00
|
|
|
if type(args[1]) == 'table' then
|
2014-06-16 09:16:35 -06:00
|
|
|
command = args[1]
|
|
|
|
elseif #args > 1 and type(args[2]) == 'table' then
|
|
|
|
-- {args[1]} + args[2]
|
|
|
|
command = args[2]
|
|
|
|
table.insert(command, 1, args[1])
|
|
|
|
elseif #args == 1 and type(args[1]) == 'string' then
|
|
|
|
command = args[1]
|
|
|
|
elseif #args > 1 and type(args[1]) == 'string' then
|
|
|
|
command = args
|
2014-06-10 11:41:01 -06:00
|
|
|
else
|
2014-06-16 09:16:35 -06:00
|
|
|
error('Invalid arguments')
|
2014-06-10 11:41:01 -06:00
|
|
|
end
|
2020-11-20 15:57:54 -07:00
|
|
|
return internal.runCommand(command, use_console)
|
2014-06-27 19:58:36 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function dfhack.run_command_silent(...)
|
2020-11-20 15:57:54 -07:00
|
|
|
local result = _run_command({...})
|
2014-06-27 19:58:36 -06:00
|
|
|
local output = ""
|
2014-06-10 11:41:01 -06:00
|
|
|
for i, f in pairs(result) do
|
2014-06-10 19:38:21 -06:00
|
|
|
if type(f) == 'table' then
|
|
|
|
output = output .. f[2]
|
|
|
|
end
|
2014-06-07 18:31:14 -06:00
|
|
|
end
|
2014-06-10 19:38:21 -06:00
|
|
|
return output, result.status
|
2014-06-07 18:31:14 -06:00
|
|
|
end
|
|
|
|
|
2014-06-27 19:58:36 -06:00
|
|
|
function dfhack.run_command(...)
|
2020-11-20 15:57:54 -07:00
|
|
|
local result = _run_command({...}, true)
|
2020-01-14 16:53:52 -07:00
|
|
|
return result.status
|
2014-06-27 19:58:36 -06:00
|
|
|
end
|
|
|
|
|
2012-12-21 03:00:50 -07:00
|
|
|
-- Per-save init file
|
|
|
|
|
|
|
|
function dfhack.getSavePath()
|
|
|
|
if dfhack.isWorldLoaded() then
|
2016-08-18 13:47:40 -06:00
|
|
|
return dfhack.getDFPath() .. '/data/save/' .. df.global.world.cur_savegame.save_dir
|
2012-12-21 03:00:50 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if dfhack.is_core_context then
|
2014-03-31 06:00:55 -06:00
|
|
|
local function loadInitFile(path, name)
|
|
|
|
local env = setmetatable({ SAVE_PATH = path }, { __index = base_env })
|
|
|
|
local f,perr = loadfile(name, 't', env)
|
|
|
|
if f == nil then
|
2015-01-30 14:37:23 -07:00
|
|
|
if dfhack.filesystem.exists(name) then
|
2014-03-31 06:00:55 -06:00
|
|
|
dfhack.printerr(perr)
|
|
|
|
end
|
|
|
|
elseif safecall(f) then
|
|
|
|
if not internal.save_init then
|
|
|
|
internal.save_init = {}
|
|
|
|
end
|
|
|
|
table.insert(internal.save_init, env)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-12-21 03:00:50 -07:00
|
|
|
dfhack.onStateChange.DFHACK_PER_SAVE = function(op)
|
|
|
|
if op == SC_WORLD_LOADED or op == SC_WORLD_UNLOADED then
|
|
|
|
if internal.save_init then
|
2014-03-31 06:00:55 -06:00
|
|
|
for k,v in ipairs(internal.save_init) do
|
|
|
|
if v.onUnload then
|
|
|
|
safecall(v.onUnload)
|
|
|
|
end
|
2012-12-21 03:00:50 -07:00
|
|
|
end
|
|
|
|
internal.save_init = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
local path = dfhack.getSavePath()
|
|
|
|
|
|
|
|
if path and op == SC_WORLD_LOADED then
|
2014-03-31 06:00:55 -06:00
|
|
|
loadInitFile(path, path..'/raw/init.lua')
|
|
|
|
|
|
|
|
local dirlist = dfhack.internal.getDir(path..'/raw/init.d/')
|
|
|
|
if dirlist then
|
|
|
|
table.sort(dirlist)
|
|
|
|
for i,name in ipairs(dirlist) do
|
|
|
|
if string.match(name,'%.lua$') then
|
|
|
|
loadInitFile(path, path..'/raw/init.d/'..name)
|
|
|
|
end
|
2012-12-21 03:00:50 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2014-03-31 06:00:55 -06:00
|
|
|
elseif internal.save_init then
|
|
|
|
for k,v in ipairs(internal.save_init) do
|
|
|
|
if v.onStateChange then
|
|
|
|
safecall(v.onStateChange, op)
|
|
|
|
end
|
|
|
|
end
|
2012-12-21 03:00:50 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-04-01 02:50:56 -06:00
|
|
|
-- Feed the table back to the require() mechanism.
|
2012-03-31 05:40:54 -06:00
|
|
|
return dfhack
|