First the maybe bug:
DFHacks init functions are totally broken for every 40.x version I tested (40.10 r1 and 40.8 r2):
ALL init functions run both when the save is loaded AND when it is unloaded.
A way to confim this bug is to create the onload.init file and add "workflow enable", if the bug is in effect when you load and then immediately save the region you will see something like this:
Enabling the plugin.
Protecting 1 jobs.
The plugin is enabled.
Unprotecting 1 jobs.
Protecting 1 jobs.
<at this point the world is loaded>
Unprotecting 1 jobs.
The plugin is enabled.
Unprotecting 1 jobs.
Protecting 1 jobs.
Unprotecting 1 jobs.
This sequence leads me to believe something really weird is going on, particularly as DFHack 34.11 r5e1 reports:
Enabling the plugin.
Protecting 1 jobs.
The plugin is enabled.
Unprotecting 1 jobs.
Protecting 1 jobs.
<at this point the world is loaded>
Unprotecting 1 jobs.
For the EXACT SAME SETUP, clearly anything that is supposed to run when a region is loaded is ALSO run when it is unloaded in 40.x.
And now the definatly bug:
Scripts use dfhack.timeout for delays that should be persistent:
Several default scripts use dfhack.timeout to make delays, this will not persist across a save/load, so, for example, anything that uses the default "modtools/transform-unit" with a duration can and will get "stuck" if the game is saved and loaded while the unit is transformed.
Both of these bugs where discovered when working on the latest version of Rubble. The persistent timeout issue was solved by making a simple lua module:
--[[
Rubble Persistent Timeout DFHack Lua Pseudo Module
Copyright 2014 Milo Christiansen
This software is provided 'as-is', without any express or implied warranty. In
no event will the authors be held liable for any damages arising from the use of
this software.
Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject to
the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim
that you wrote the original software. If you use this software in a product, an
acknowledgment in the product documentation would be appreciated but is not
required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
]]
local _ENV = rubble.mkmodule("timeout")
data = data or {}
dfhack.onStateChange.rubble_libs_dfhack_timeout = function(code)
if code == SC_WORLD_UNLOADED then
-- save data to disk
print("rubble.timeout: Saving persistence data.")
local dat = io.open(rubble.savedir..'/raw/rubble_libs_dfhack_timeout.persist.lua', 'w')
dat:write("\n")
dat:write("rubble.timeout.data = {\n")
for k, v in pairs(data) do
if v ~= nil then
dat:write('\t["'..k..'"] = {')
dat:write(' delay = '..v.delay..',')
dat:write(' command = [[ '..v.command..' ]]')
dat:write(' },\n')
end
end
dat:write("}\n")
dat:close()
data = {}
end
if code == SC_WORLD_LOADED then
-- load data from disk
print("rubble.timeout: Loading persistence data.")
rubble.load_script(rubble.savedir.."/raw/rubble_libs_dfhack_timeout.persist.lua")
end
end
function add(id, delay, command)
data[id] = {delay = delay, command = command}
end
function del(id)
data[id] = nil
end
function tick()
for k, v in pairs(data) do
if v ~= nil then
v.delay = v.delay - 1
if v.delay <= 0 then
dfhack.run_command(v.command)
del(k)
end
end
end
dfhack.timeout(1, 'ticks', tick)
end
tick()
rubble.timeout = _ENV
And a command version:
--[[
Rubble Persistent Timeout DFHack Lua Command
Copyright 2014 Milo Christiansen
This software is provided 'as-is', without any express or implied warranty. In
no event will the authors be held liable for any damages arising from the use of
this software.
Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject to
the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim
that you wrote the original software. If you use this software in a product, an
acknowledgment in the product documentation would be appreciated but is not
required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
]]
local utils = require 'utils'
validArgs = validArgs or utils.invert({
'help',
'id',
'delete',
'delay',
'command',
})
local args = utils.processArgs({...}, validArgs)
if args.help then
print([[scripts/rubble_timeout
Create and manage persistent timeouts, eg timeouts that will survive a save/load.
Arguments:
-help
print this help message.
-id
The id to use for the delay (required!).
-delete
If specified removes an action instead of adding one.
-delay
How long to wait in ticks (defaults to 100).
-command
The command to run (as a string! defaults to `:lua print("rubble_timeout: Did we forget something?")`).
]])
return
end
if not args.id then
error "rubble_timeout: You MUST specify an id!"
end
if not args.delay or not tonumber(args.delay) then
args.delay = 100
end
if not args.command then
args.command = ':lua print("rubble_timeout: Did we forget something?")'
end
if not args.delete then
rubble.timeout.add(args.id, tonumber(args.delay), args.command)
else
rubble.timeout.del(args.id)
end
The module uses a custom Rubble system for loading "modules" from the raw directory, this is not easy to change as the persistence mechanic depends on it. You will either need to rewrite the system or you will need to load it with the system Rubble provides, namely put this in your init.lua:
-- Important Globals
dfhack.BASE_G.rubble = dfhack.BASE_G.rubble or {}
rubble.savedir = SAVE_PATH
function rubble.load_script(name)
env = {}
setmetatable(env, { __index = dfhack.BASE_G })
local f, perr = loadfile(name, 't', env)
if f then
return safecall(f)
end
dfhack.printerr(" Error: "..perr)
end
function rubble.mkmodule(name)
rubble[name] = rubble[name] or {}
setmetatable(rubble[name], { __index = dfhack.BASE_G })
return rubble[name]
end
-- Pseudo Modules and Scripts
local scrlist = dfhack.internal.getDir(SAVE_PATH.."/raw/dfhack/")
if scrlist then
table.sort(scrlist)
for i,name in ipairs(scrlist) do
if string.match(name,'%.mod.lua$') then
print(" Module: "..name)
rubble.load_script(SAVE_PATH.."/raw/dfhack/"..name)
end
end
for i,name in ipairs(scrlist) do
if string.match(name,'%.lua$') and not string.match(name,'%.mod.lua$') then
print(" Script: "..name)
rubble.load_script(SAVE_PATH.."/raw/dfhack/"..name)
end
end
end
And put the "module" in "raw/dfhack" with the .mod.lua extension.
The other issue didn't really cause any problems, but it is really weird.