Merge branch 'develop' of https://github.com/DFHack/dfhack into describe-items

develop
Peridexis Errant 2015-05-26 17:28:29 +10:00
commit 3bd258816f
39 changed files with 911 additions and 170 deletions

@ -53,8 +53,9 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
endif()
# make sure all the necessary submodules have been set up
if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/clsocket/CMakeLists.txt)
message(FATAL_ERROR "Required submodules could not be found! First run 'git submodule init' and 'git submodule update' from the root DFHack directory. (See the section 'Getting the Code' in Compile.html)")
if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/clsocket/CMakeLists.txt
OR NOT EXISTS ${dfhack_SOURCE_DIR}/depends/jsonxx/CMakeLists.txt)
message(FATAL_ERROR "One or more required submodules could not be found! Run 'git submodule update --init' from the root DFHack directory. (See the section 'Getting the Code' in Compile.rst or Compile.html)")
endif()
# set up versioning.
@ -173,6 +174,8 @@ IF(BUILD_LIBRARY)
install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif()
install(DIRECTORY dfhack-config DESTINATION .)
#build the plugins
IF(BUILD_PLUGINS)
add_subdirectory (plugins)

@ -77,6 +77,7 @@ scamtank scamtank
Mason11987 Mason11987
James Logsdon jlogsdon
melkor217 melkor217
acwatkins acwatkins
======================= ==================== ===========================
And these are the cool people who made **Stonesense**.

@ -3457,6 +3457,30 @@ Note that this function lets errors propagate to the caller.
This will not be an issue in most cases.
This function also permits circular dependencies of scripts.
* ``dfhack.reqscript(name)`` or ``reqscript(name)``
Nearly identical to script_environment() but requires scripts being loaded to
include a line similar to::
--@ module = true
This is intended to only allow scripts that take appropriate action when used
as a module to be loaded.
Enabling and disabling scripts
==============================
Scripts can choose to recognize the built-in ``enable`` and ``disable`` commands
by including the following line anywhere in their file::
--@ enable = true
When the ``enable`` and ``disable`` commands are invoked, a ``dfhack_flags``
table will be passed to the script with the following fields set:
* ``enable``: Always true if the script is being enabled *or* disabled
* ``enable_state``: True if the script is being enabled, false otherwise
Save init script
================

17
NEWS

@ -2,21 +2,34 @@ DFHack Future
Internals
A method for caching screen output is now available to Lua (and C++)
Developer plugins can be ignored on startup by setting the DFHACK_NO_DEV_PLUGINS environment variable
The console on Linux and OS X now recognizes keyboard input between prompts
Lua
Scripts can be enabled with the built-in enable/disable commands
A new function, reqscript(), is available as a safer alternative to script_environment()
New internal commands
kill-lua: Interrupt running Lua scripts
New plugins
New scripts
burial: sets all unowned coffins to allow burial ("-pets" to allow pets too)
fix-ster: changes fertility/sterility of animals or dwarves
view-item-info: adds information and customisable descriptions to item viewscreens
warn-starving: check for starving, thirsty, or very drowsy units and pause with warning if any are found
New tweaks
New features
Fixes
steam-engine: fixed a crash on arena load
Plugins with vmethod hooks can now be reloaded on OS X
Lua's os.system() now works on OS X
Fixed default arguments in Lua gametype detection functions
gui/hack-wish now properly assigns quality to items.
gui/gm-editor handles lua tables properly
steam-engine: fixed a crash on arena load
tweak fps-min fixed
workflow: Fixed some issues with stuck jobs
- Note: Existing stuck jobs must be cancelled and re-added
Misc Improvements
dwarfmonitor date format can be modified (see Readme)
"keybinding list" accepts a context
nyan: Can now be stopped with dfhack-run
quicksave: Restricted to fortress mode
Removed
DFHack 0.40.24-r3

@ -1685,6 +1685,34 @@ Maintain 10-100 locally-made crafts of exceptional quality::
Fortress activity management
============================
dwarfmonitor
------------
Records dwarf activity to measure fort efficiency.
Options:
``dwarfmonitor enable <mode>``:
Start monitoring ``mode``. ``mode`` can be "work", "misery", or "all".
``dwarfmonitor disable <mode>``:
Stop monitoring ``mode`` (see above)
``dwarfmonitor stats``:
Show statistics summary
``dwarfmonitor prefs``:
Show dwarf preferences summary
``dwarfmonitor reload``:
Reload configuration file (``dfhack-config/dwarfmonitor.json``)
Configuration options:
``date_format``:
Date format
Example configuration::
{
"date_format": "y-m-d"
}
seedwatch
---------
Watches the numbers of seeds available and enables/disables seed and plant cooking.
@ -2067,6 +2095,12 @@ Tools:
* ``sand``: Displays an indicator when sand is present in the currently-selected area, similar to the default clay/stone indicators.
* ``sticky``: Maintains the selected local area while navigating the world map
kill-lua
--------
Interrupts any currently-running Lua scripts. By default, scripts can only be
interrupted every 256 instructions. Use ``kill-lua force`` to interrupt
the next instruction.
petcapRemover
-------------
This plugin allows you to remove or raise the pet population cap. In vanilla
@ -2219,6 +2253,11 @@ If the name of the patch has no extension or directory separators, the
script uses ``hack/patches/<df-version>/<name>.dif``, thus auto-selecting
the version appropriate for the currently loaded executable.
burial
======
Sets all unowned coffins to allow burial. ``burial -pets`` also allows burial
of pets.
create-items
============
Spawn arbitrary items under the cursor.
@ -2325,6 +2364,18 @@ To purify all elves on the map with fire (may have side-effects)::
exterminate elve magma
fix-ster
========
Utilizes the orientation tag to either fix infertile creatures or inflict
infertility on creatures that you do not want to breed. Usage::
fix-ster [fert|ster] [all|animals|only:<creature>]
``fert`` or ``ster`` is a required argument; whether to make the target fertile
or sterile. Optional arguments specify the target: no argument for the
selected unit, ``all`` for all units on the map, ``animals`` for all non-dwarf
creatures, or ``only:<creature>`` to only process matching creatures.
fortplan
========
Usage: fortplan [filename]
@ -2584,6 +2635,14 @@ of items. Individual descriptions can be added or overridden by a similar
script ``raw/scripts/more-item-descriptions.lua``. Both work as sparse lists,
so missing items simply go undescribed if not defined in the fallback.
warn-starving
=============
If any (live) units are starving, very thirsty, or very drowsy, the game will
be paused and a warning shown and logged to the console. Use with the
``repeat`` command for regular checks.
Use ``warn-starving all`` to display a list of all problematic units.
========
modtools
========

@ -1 +1 @@
Subproject commit 209c47649c1bba1693872a0d9c90011e2330d711
Subproject commit 5f80ee324b8f82f9a0d093fbf0485f3b276139ee

@ -0,0 +1,3 @@
{
"date_format": "y-m-d"
}

@ -218,7 +218,7 @@ enable \
# You can comment out the extension of a line.
# allow the fortress bookkeeper to queue jobs through the manager
stockflow enable
enable stockflow
###########
# Scripts #

@ -397,7 +397,7 @@ namespace DFHack
// We want read to return every single byte, without timeout.
raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0;// 1 byte, no timer
// put terminal in raw mode after flushing
if (tcsetattr(STDIN_FILENO,TCSAFLUSH,&raw) < 0)
if (tcsetattr(STDIN_FILENO,TCSADRAIN,&raw) < 0)
return -1;
rawmode = 1;
return 0;

@ -288,6 +288,10 @@ namespace {
const string *pcmd;
vector<string> *pargs;
};
struct ScriptEnableState {
const string *pcmd;
bool pstate;
};
}
static bool init_run_script(color_ostream &out, lua_State *state, void *info)
@ -315,6 +319,30 @@ static command_result runLuaScript(color_ostream &out, std::string name, vector<
return ok ? CR_OK : CR_FAILURE;
}
static bool init_enable_script(color_ostream &out, lua_State *state, void *info)
{
auto args = (ScriptEnableState*)info;
if (!lua_checkstack(state, 4))
return false;
Lua::PushDFHack(state);
lua_getfield(state, -1, "enable_script");
lua_remove(state, -2);
lua_pushstring(state, args->pcmd->c_str());
lua_pushboolean(state, args->pstate);
return true;
}
static command_result enableLuaScript(color_ostream &out, std::string name, bool state)
{
ScriptEnableState data;
data.pcmd = &name;
data.pstate = state;
bool ok = Lua::RunCoreQueryLoop(out, Lua::Core::State, init_enable_script, &data);
return ok ? CR_OK : CR_FAILURE;
}
static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, std::string name, vector<string> &args)
{
if (!plug_mgr->ruby || !plug_mgr->ruby->is_enabled())
@ -633,24 +661,43 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
for (size_t i = 0; i < parts.size(); i++)
{
Plugin * plug = plug_mgr->getPluginByName(parts[i]);
std::string part = parts[i];
if (part.find('\\') != std::string::npos)
{
con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", part.c_str());
for (size_t j = 0; j < part.size(); j++)
{
if (part[j] == '\\')
part[j] = '/';
}
}
Plugin * plug = plug_mgr->getPluginByName(part);
if(!plug)
{
std::string lua = findScript(this->p->getPath(), part + ".lua");
if (lua.size())
{
res = enableLuaScript(con, part, enable);
}
else
{
res = CR_NOT_FOUND;
con.printerr("No such plugin: %s\n", parts[i].c_str());
con.printerr("No such plugin or Lua script: %s\n", part.c_str());
}
}
else if (!plug->can_set_enabled())
{
res = CR_NOT_IMPLEMENTED;
con.printerr("Cannot %s plugin: %s\n", first.c_str(), parts[i].c_str());
con.printerr("Cannot %s plugin: %s\n", first.c_str(), part.c_str());
}
else
{
res = plug->set_enabled(con, enable);
if (res != CR_OK || plug->is_enabled() != enable)
con.printerr("Could not %s plugin: %s\n", first.c_str(), parts[i].c_str());
con.printerr("Could not %s plugin: %s\n", first.c_str(), part.c_str());
}
}
@ -706,6 +753,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
" cls - Clear the console.\n"
" fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n"
" kill-lua - Stop an active Lua script\n"
" keybinding - Modify bindings of commands to keys\n"
" script FILENAME - Run the commands specified in a file.\n"
" sc-script - Automatically run specified scripts on state change events\n"
@ -823,6 +871,17 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
{
_exit(666);
}
else if(first == "kill-lua")
{
bool force = false;
for (auto it = parts.begin(); it != parts.end(); ++it)
{
if (*it == "force")
force = true;
}
if (!Lua::Interrupt(force))
con.printerr("Failed to register hook - use 'kill-lua force' to force\n");
}
else if(first == "script")
{
if(parts.size() == 1)

@ -24,6 +24,7 @@ distribution.
#include "Internal.h"
#include <csignal>
#include <string>
#include <vector>
#include <map>
@ -354,6 +355,35 @@ static int dfhack_lineedit(lua_State *S)
* Exception handling
*/
volatile std::sig_atomic_t lstop = 0;
static void interrupt_hook (lua_State *L, lua_Debug *ar);
static void interrupt_init (lua_State *L)
{
lua_sethook(L, interrupt_hook, LUA_MASKCOUNT, 256);
}
static void interrupt_hook (lua_State *L, lua_Debug *ar)
{
if (lstop)
{
lstop = 0;
interrupt_init(L); // Restore default settings if necessary
luaL_error(L, "interrupted!");
}
}
bool DFHack::Lua::Interrupt (bool force)
{
lua_State *L = Lua::Core::State;
if (L->hook != interrupt_hook && !force)
return false;
if (force)
lua_sethook(L, interrupt_hook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT, 1);
lstop = 1;
return true;
}
static int DFHACK_EXCEPTION_META_TOKEN = 0;
static void error_tostring(lua_State *L, bool keep_old = false)
@ -1561,6 +1591,8 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
if (!state)
state = luaL_newstate();
interrupt_init(state);
luaL_openlibs(state);
AttachDFGlobals(state);

@ -230,7 +230,6 @@ bool Plugin::load(color_ostream &con)
plugin_check_symbol("plugin_self")
plugin_check_symbol("plugin_init")
plugin_check_symbol("plugin_globals")
plugin_check_symbol("plugin_dev")
const char ** plug_name =(const char ** ) LookupPlugin(plug, "name");
const char ** plug_version =(const char ** ) LookupPlugin(plug, "version");
Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
@ -243,7 +242,7 @@ bool Plugin::load(color_ostream &con)
return false;
}
bool *plug_dev = (bool*)LookupPlugin(plug, "plugin_dev");
if (*plug_dev && getenv("DFHACK_NO_DEV_PLUGINS"))
if (plug_dev && *plug_dev && getenv("DFHACK_NO_DEV_PLUGINS"))
{
con.print("Skipping dev plugin: %s\n", *plug_name);
plugin_abort_load;
@ -277,6 +276,7 @@ bool Plugin::load(color_ostream &con)
plugin_enable = (command_result (*)(color_ostream &,bool)) LookupPlugin(plug, "plugin_enable");
plugin_is_enabled = (bool*) LookupPlugin(plug, "plugin_is_enabled");
plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby");
plugin_get_exports = (PluginExports* (*)(void)) LookupPlugin(plug, "plugin_get_exports");
index_lua(plug);
this->name = *plug_name;
plugin_lib = plug;
@ -531,6 +531,16 @@ Plugin::plugin_state Plugin::getState() const
return state;
}
PluginExports *Plugin::getExports()
{
if (!plugin_get_exports)
return NULL;
PluginExports *exports = plugin_get_exports();
if (!exports->bind(plugin_lib))
return NULL;
return exports;
};
void Plugin::index_lua(DFLibrary *lib)
{
if (auto cmdlist = (CommandReg*)LookupPlugin(lib, "plugin_lua_commands"))
@ -703,6 +713,19 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn)
lua_pushcclosure(state, lua_fun_wrapper, 4);
}
bool PluginExports::bind(DFLibrary *lib)
{
for (auto it = bindings.begin(); it != bindings.end(); ++it)
{
std::string name = it->first;
void** dest = it->second;
*dest = LookupPlugin(lib, name.c_str());
if (!*dest)
return false;
}
return true;
}
PluginManager::PluginManager(Core * core)
{
cmdlist_mutex = new mutex();
@ -766,6 +789,16 @@ Plugin *PluginManager::getPluginByCommand(const std::string &command)
return NULL;
}
void *PluginManager::getPluginExports(const std::string &name)
{
Plugin *plug = getPluginByName(name);
if (!plug)
return NULL;
if (plug->getState() != Plugin::plugin_state::PS_LOADED)
return NULL;
return plug->getExports();
}
// FIXME: handle name collisions...
command_result PluginManager::InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters)
{

@ -263,6 +263,17 @@ namespace DFHack {namespace Lua {
bool (*init)(color_ostream&, lua_State*, void*),
void *arg);
/**
* Attempt to interrupt the currently-executing lua function by raising a lua error
* from a lua debug hook, similar to how SIGINT is handled in the lua interpreter (depends/lua/src/lua.c).
* The flag set here will only be checked every 256 instructions by default.
* Returns false if another debug hook is set and 'force' is false.
*
* force: Overwrite any existing debug hooks and interrupt the next instruction
*/
DFHACK_EXPORT bool Interrupt (bool force=false);
/**
* Push utility functions
*/

@ -50,6 +50,7 @@ namespace df
namespace DFHack
{
class Core;
class PluginExports;
class PluginManager;
class virtual_identity;
class RPCService;
@ -165,6 +166,7 @@ namespace DFHack
command_result invoke(color_ostream &out, const std::string & command, std::vector <std::string> & parameters);
bool can_invoke_hotkey(const std::string & command, df::viewscreen *top );
plugin_state getState () const;
PluginExports *getExports();
RPCService *rpc_connect(color_ostream &out);
@ -227,7 +229,16 @@ namespace DFHack
command_result (*plugin_enable)(color_ostream &, bool);
RPCService* (*plugin_rpcconnect)(color_ostream &);
command_result (*plugin_eval_ruby)(color_ostream &, const char*);
PluginExports* (*plugin_get_exports)(void);
};
class DFHACK_EXPORT PluginExports {
protected:
friend class Plugin;
std::map<std::string, void**> bindings;
bool bind(DFLibrary* lib);
};
#define PLUGIN_EXPORT_BIND(sym) bindings.insert(std::pair<std::string, void**>(#sym, (void**)&this->sym))
#define PLUGIN_EXPORT_BINDN(sym, name) bindings.insert(std::pair<std::string, void**>(name, (void**)&this->sym))
class DFHACK_EXPORT PluginManager
{
// PRIVATE METHODS
@ -244,6 +255,7 @@ namespace DFHack
public:
Plugin *getPluginByName (const std::string & name);
Plugin *getPluginByCommand (const std::string &command);
void *getPluginExports(const std::string &name);
command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters);
bool CanInvokeHotkey(const std::string &command, df::viewscreen *top);
Plugin* operator[] (std::size_t index)
@ -293,6 +305,17 @@ namespace DFHack
DFhackDataExport bool plugin_is_enabled = false; \
bool &varname = plugin_is_enabled;
#define DFHACK_PLUGIN_EXPORTS(clsname) \
DFhackCExport PluginExports* plugin_get_exports() \
{ \
static clsname* instance = NULL; \
if (!instance) \
instance = new clsname; \
return (PluginExports*)instance; \
}
#define GET_PLUGIN_EXPORTS(plugname, clsname) \
(clsname*)DFHack::Core::getInstance().getPluginManager()->getPluginExports(plugname)
#define DFHACK_PLUGIN_LUA_COMMANDS \
DFhackCExport const DFHack::CommandReg plugin_lua_commands[] =
#define DFHACK_PLUGIN_LUA_FUNCTIONS \

@ -386,15 +386,33 @@ end
local internal = dfhack.internal
internal.scripts = internal.scripts or {}
internal.scriptPath = internal.scriptPath or {}
internal.scriptMtime = internal.scriptMtime or {}
internal.scriptRun = internal.scriptRun or {}
Script = defclass(Script)
function Script:init(path)
self.path = path
self.mtime = dfhack.filesystem.mtime(path)
self._flags = {}
end
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()
for line in contents:gmatch('%-%-@([^\n]+)') do
local chunk = load(line, self.path, 't', self._flags)
if chunk then
chunk()
else
dfhack.printerr('Parse error: ' .. line)
end
end
end
return self._flags
end
internal.scripts = internal.scripts or {}
local scripts = internal.scripts
local scriptPath = internal.scriptPath
local scriptMtime = internal.scriptMtime
local scriptRun = internal.scriptRun
local hack_path = dfhack.getHackPath()
@ -419,28 +437,69 @@ function dfhack.findScript(name)
return nil
end
local valid_script_flags = {
enable = {required = true, error = 'Does not recognize enable/disable commands'},
enable_state = {required = false},
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},
}
function dfhack.run_script(name,...)
return dfhack.run_script_with_env(nil,name,...)
return dfhack.run_script_with_env(nil, name, nil, ...)
end
function dfhack.enable_script(name, state)
local res, err = dfhack.pcall(dfhack.run_script_with_env, nil, name, {enable=true, enable_state=state})
if not res then
dfhack.printerr(err.message)
qerror(('Cannot %s Lua script: %s'):format(state and 'enable' or 'disable', name))
end
end
function dfhack.reqscript(name)
_, env = dfhack.run_script_with_env(nil, name, {module=true})
return env
end
reqscript = dfhack.reqscript
function dfhack.script_environment(name)
_, env = dfhack.run_script_with_env({moduleMode=true}, name)
_, env = dfhack.run_script_with_env(nil, name, {module=true, module_strict=false})
return env
end
function dfhack.run_script_with_env(envVars,name,...)
function dfhack.run_script_with_env(envVars, name, flags, ...)
if type(flags) ~= 'table' then flags = {} end
local file = dfhack.findScript(name)
if not file then
error('Could not find script ' .. name)
end
if scriptPath[name] and scriptPath[name] ~= file then
--new file path: must have loaded a different save or unloaded
scriptPath[name] = file
scriptMtime[file] = dfhack.filesystem.mtime(file)
--it is the responsibility of the script to clear its own data on unload so it's safe for us to not delete it here
if scripts[file] == nil then
scripts[file] = Script(file)
end
local script_flags = scripts[file]:get_flags()
for flag, value in pairs(flags) do
if value then
local v = valid_script_flags[flag]
if not v then
error('Invalid flag: ' .. flag)
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
end
end
end
local env = scripts[file]
local env = scripts[file].env
if env == nil then
env = {}
setmetatable(env, { __index = base_env })
@ -448,11 +507,13 @@ function dfhack.run_script_with_env(envVars,name,...)
for x,y in pairs(envVars or {}) do
env[x] = y
end
env.dfhack_flags = flags
env.moduleMode = flags.module
local f
local perr
local time = dfhack.filesystem.mtime(file)
if time == scriptMtime[file] and scriptRun[file] then
f = scriptRun[file]
if time == scripts[file].mtime and scripts[file].run then
f = scripts[file].run
else
--reload
f, perr = loadfile(file, 't', env)
@ -460,10 +521,10 @@ function dfhack.run_script_with_env(envVars,name,...)
error(perr)
end
-- avoid updating mtime if the script failed to load
scriptMtime[file] = time
scripts[file].mtime = time
end
scripts[file] = env
scriptRun[file] = f
scripts[file].env = env
scripts[file].run = f
return f(...), env
end

@ -396,6 +396,11 @@ bool MaterialInfo::matches(const df::job_material_category &cat)
using namespace df::enums::material_flags;
TEST(plant, STRUCTURAL_PLANT_MAT);
TEST(plant, SEED_MAT);
TEST(plant, THREAD_PLANT);
TEST(plant, ALCOHOL_PLANT);
TEST(plant, POWDER_MISC_PLANT);
TEST(plant, LIQUID_MISC_PLANT);
TEST(wood, WOOD);
TEST(cloth, THREAD_PLANT);
TEST(silk, SILK);

@ -1 +1 @@
Subproject commit c42e4d124ae9c527289999051c6a5460d2a014a7
Subproject commit 8cd1994027d023f32e7eb6e424232fc30d37fb90

@ -112,7 +112,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(digFlood digFlood.cpp)
add_subdirectory(diggingInvaders)
DFHACK_PLUGIN(drybuckets drybuckets.cpp)
DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp)
DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES jsonxx)
DFHACK_PLUGIN(embark-tools embark-tools.cpp)
DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(fastdwarf fastdwarf.cpp)

@ -109,6 +109,9 @@ enum dwarf_state {
// Busy with a useful task
BUSY,
// Busy with a useful task that requires a tool
EXCLUSIVE,
// In the military, can't work
MILITARY,
@ -119,11 +122,12 @@ enum dwarf_state {
OTHER
};
const int NUM_STATE = 5;
const int NUM_STATE = 6;
static const char *state_names[] = {
"IDLE",
"BUSY",
"EXCLUSIVE",
"MILITARY",
"CHILD",
"OTHER",
@ -133,13 +137,13 @@ static const dwarf_state dwarf_states[] = {
BUSY /* CarveFortification */,
BUSY /* DetailWall */,
BUSY /* DetailFloor */,
BUSY /* Dig */,
BUSY /* CarveUpwardStaircase */,
BUSY /* CarveDownwardStaircase */,
BUSY /* CarveUpDownStaircase */,
BUSY /* CarveRamp */,
BUSY /* DigChannel */,
BUSY /* FellTree */,
EXCLUSIVE /* Dig */,
EXCLUSIVE /* CarveUpwardStaircase */,
EXCLUSIVE /* CarveDownwardStaircase */,
EXCLUSIVE /* CarveUpDownStaircase */,
EXCLUSIVE /* CarveRamp */,
EXCLUSIVE /* DigChannel */,
EXCLUSIVE /* FellTree */,
BUSY /* GatherPlants */,
BUSY /* RemoveConstruction */,
BUSY /* CollectWebs */,
@ -154,7 +158,7 @@ static const dwarf_state dwarf_states[] = {
OTHER /* Sleep */,
BUSY /* CollectSand */,
BUSY /* Fish */,
BUSY /* Hunt */,
EXCLUSIVE /* Hunt */,
OTHER /* HuntVermin */,
BUSY /* Kidnap */,
BUSY /* BeatCriminal */,
@ -183,7 +187,7 @@ static const dwarf_state dwarf_states[] = {
OTHER /* GoShopping2 */,
BUSY /* Clean */,
OTHER /* Rest */,
BUSY /* PickupEquipment */,
EXCLUSIVE /* PickupEquipment */,
BUSY /* DumpItem */,
OTHER /* StrangeMoodCrafter */,
OTHER /* StrangeMoodJeweller */,
@ -393,8 +397,15 @@ struct labor_default
int active_dwarfs;
};
// The percentage of the dwarves assigned as haulers at any one time.
static int hauler_pct = 33;
// The maximum percentage of dwarves who will be allowed to be idle.
// Decreasing this will encourage autolabor to keep dwarves busy,
// at the expense of making it harder for dwarves to specialize in
// specific skills.
static int idler_pct = 10;
static std::vector<struct labor_info> labor_infos;
static const struct labor_default default_labor_infos[] = {
@ -521,7 +532,6 @@ struct dwarf_info_t
bool medical; // this dwarf has medical responsibility
bool trader; // this dwarf has trade responsibility
bool diplomacy; // this dwarf meets with diplomats
int single_labor; // this dwarf will be exclusively assigned to one labor (-1/NONE for none)
};
static bool isOptionEnabled(unsigned flag)
@ -738,7 +748,13 @@ struct laborinfo_sorter
{
bool operator() (int i,int j)
{
if (labor_infos[i].mode() != labor_infos[j].mode())
return labor_infos[i].mode() < labor_infos[j].mode();
if (labor_infos[i].is_exclusive != labor_infos[j].is_exclusive)
return labor_infos[i].is_exclusive;
if (labor_infos[i].maximum_dwarfs() != labor_infos[j].maximum_dwarfs())
return labor_infos[i].maximum_dwarfs() < labor_infos[j].maximum_dwarfs();
return false;
};
};
@ -769,6 +785,7 @@ static void assign_labor(unit_labor::unit_labor labor,
int best_dwarf = 0;
int best_value = -10000;
int best_skill = 0;
std::vector<int> values(n_dwarfs);
std::vector<int> candidates;
@ -813,6 +830,9 @@ static void assign_labor(unit_labor::unit_labor labor,
dwarf_skill[dwarf] = skill_level;
dwarf_skillxp[dwarf] = skill_experience;
if (best_skill < skill_level)
best_skill = skill_level;
value += skill_level * 100;
value += skill_experience / 20;
if (skill_level > 0 || skill_experience > 0)
@ -832,6 +852,9 @@ static void assign_labor(unit_labor::unit_labor labor,
value += 350;
}
if (dwarf_info[dwarf].has_exclusive_labor)
value -= 500;
// bias by happiness
//value += dwarfs[dwarf]->status.happiness;
@ -897,15 +920,28 @@ static void assign_labor(unit_labor::unit_labor labor,
if (unit_labor::FISH == labor && !has_fishery)
min_dwarfs = max_dwarfs = 0;
bool want_idle_dwarf = true;
if (state_count[IDLE] < 2)
want_idle_dwarf = false;
// If there are enough idle dwarves to choose from, enter an aggressive assignment
// mode. "Enough" idle dwarves is defined as 2 or 10% of the total number of dwarves,
// whichever is higher.
//
// In aggressive mode, we will always pick at least one idle dwarf for each skill,
// in order to try to get the idle dwarves to start doing something. We also pick
// any dwarf more preferable to the idle dwarf, since we'd rather have a more
// preferable dwarf do a new job if one becomes available (probably because that
// dwarf just finished a job).
//
// In non-aggressive mode, only dwarves that are good at a labor will be assigned
// to it. Dwarves good at nothing, or nothing that needs doing, will tend to get
// assigned to hauling by the hauler code. If there are no hauling jobs to do,
// they will sit around idle and when enough build up they will trigger aggressive
// mode again.
bool aggressive_mode = state_count[IDLE] >= 2 && state_count[IDLE] >= n_dwarfs * idler_pct / 100;
/*
* Assign dwarfs to this labor. We assign at least the minimum number of dwarfs, in
* order of preference, and then assign additional dwarfs that meet any of these conditions:
* - The dwarf is idle and there are no idle dwarves assigned to this labor
* - The dwarf has nonzero skill associated with the labor
* - We are in aggressive mode and have not yet assigned an idle dwarf
* - The dwarf is good at this skill
* - The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled.
* We stop assigning dwarfs when we reach the maximum allowed.
* Note that only idle and busy dwarfs count towards the number of dwarfs. "Other" dwarfs
@ -917,24 +953,23 @@ static void assign_labor(unit_labor::unit_labor labor,
{
int dwarf = candidates[i];
if (dwarf_info[dwarf].trader && trader_requested)
continue;
if (dwarf_info[dwarf].diplomacy)
continue;
assert(dwarf >= 0);
assert(dwarf < n_dwarfs);
bool preferred_dwarf = false;
if (want_idle_dwarf && dwarf_info[dwarf].state == IDLE)
preferred_dwarf = true;
if (dwarf_skill[dwarf] > 0)
if (dwarf_skillxp[dwarf] > 0 && dwarf_skill[dwarf] >= best_skill / 2)
preferred_dwarf = true;
if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive)
if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive && dwarf_info[dwarf].state == EXCLUSIVE)
preferred_dwarf = true;
if (dwarf_info[dwarf].medical && labor == df::unit_labor::DIAGNOSE)
preferred_dwarf = true;
if (dwarf_info[dwarf].trader && trader_requested)
continue;
if (dwarf_info[dwarf].diplomacy)
continue;
if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf)
if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf && !aggressive_mode)
continue;
if (!dwarfs[dwarf]->status.labors[labor])
@ -952,11 +987,11 @@ static void assign_labor(unit_labor::unit_labor labor,
if (print_debug)
out.print("Dwarf %i \"%s\" assigned %s: value %i %s %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), values[dwarf], dwarf_info[dwarf].trader ? "(trader)" : "", dwarf_info[dwarf].diplomacy ? "(diplomacy)" : "");
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY)
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE)
labor_infos[labor].active_dwarfs++;
if (dwarf_info[dwarf].state == IDLE)
want_idle_dwarf = false;
aggressive_mode = false;
}
}
@ -1049,8 +1084,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{
dwarf_info[dwarf].single_labor = -1;
if (dwarfs[dwarf]->status.souls.size() <= 0)
continue;
@ -1214,7 +1247,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
laborinfo_sorter lasorter;
std::sort(labors.begin(), labors.end(), lasorter);
// Handle DISABLED skills (just bookkeeping)
// Handle DISABLED skills (just bookkeeping).
// Note that autolabor should *NEVER* enable or disable a skill that has been marked as DISABLED, for any reason.
// The user has told us that they want manage this skill manually, and we must respect that.
for (auto lp = labors.begin(); lp != labors.end(); ++lp)
{
auto labor = *lp;
@ -1224,12 +1259,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{
if ((dwarf_info[dwarf].trader && trader_requested) ||
dwarf_info[dwarf].diplomacy)
{
dwarfs[dwarf]->status.labors[labor] = false;
}
if (dwarfs[dwarf]->status.labors[labor])
{
if (labor_infos[labor].is_exclusive)
@ -1252,7 +1281,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// Set about 1/3 of the dwarfs as haulers. The haulers have all HAULER labors enabled. Having a lot of haulers helps
// make sure that hauling jobs are handled quickly rather than building up.
int num_haulers = state_count[IDLE] + state_count[BUSY] * hauler_pct / 100;
int num_haulers = state_count[IDLE] + (state_count[BUSY] + state_count[EXCLUSIVE]) * hauler_pct / 100;
if (num_haulers < 1)
num_haulers = 1;
@ -1274,7 +1303,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
continue;
}
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY)
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE)
hauler_ids.push_back(dwarf);
}
dwarfinfo_sorter sorter(dwarf_info);
@ -1305,7 +1334,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarfs[dwarf]->status.labors[labor] = true;
dwarf_info[dwarf].assigned_jobs++;
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY)
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY || dwarf_info[dwarf].state == EXCLUSIVE)
labor_infos[labor].active_dwarfs++;
if (print_debug)

@ -90,7 +90,7 @@ void outputHex(uint8_t *buf,uint8_t *lbuf,size_t len,size_t start,color_ostream
con.print("%c",buf[j+i]);
else
con.print(".");
//con.print("\n");
con.print("\n");
}
con.print("\n");
}

@ -41,6 +41,8 @@
#include "df/descriptor_shape.h"
#include "df/descriptor_color.h"
#include "jsonxx.h"
using std::deque;
DFHACK_PLUGIN("dwarfmonitor");
@ -67,6 +69,11 @@ struct less_second {
}
};
struct dwarfmonitor_configst {
std::string date_format;
};
static dwarfmonitor_configst dwarfmonitor_config;
static bool monitor_jobs = false;
static bool monitor_misery = true;
static bool monitor_date = true;
@ -1720,9 +1727,29 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest
ostringstream date_str;
auto month = World::ReadCurrentMonth() + 1;
auto day = World::ReadCurrentDay();
date_str << "Date:" << World::ReadCurrentYear() << "-" <<
((month < 10) ? "0" : "") << month << "-" <<
((day < 10) ? "0" : "") << day;
date_str << "Date:";
for (size_t i = 0; i < dwarfmonitor_config.date_format.size(); i++)
{
char c = dwarfmonitor_config.date_format[i];
switch (c)
{
case 'Y':
case 'y':
date_str << World::ReadCurrentYear();
break;
case 'M':
case 'm':
date_str << ((month < 10) ? "0" : "") << month;
break;
case 'D':
case 'd':
date_str << ((day < 10) ? "0" : "") << day;
break;
default:
date_str << c;
break;
}
}
OutputString(COLOR_GREY, x, y, date_str.str());
@ -1819,6 +1846,19 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
return CR_OK;
}
static bool load_config (color_ostream &out)
{
jsonxx::Object o;
std::ifstream infile("dfhack-config/dwarfmonitor.json");
if (infile.good())
{
std::string contents((std::istreambuf_iterator<char>(infile)), (std::istreambuf_iterator<char>()));
if (!o.parse(contents))
out.printerr("dwarfmonitor: invalid JSON\n");
}
dwarfmonitor_config.date_format = o.get<jsonxx::String>("date_format", "y-m-d");
}
static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & parameters)
{
bool show_help = false;
@ -1869,6 +1909,10 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & par
if(Maps::IsValid())
Screen::show(new ViewscreenPreferences());
}
else if (cmd == 'r' || cmd == 'R')
{
load_config(out);
}
else
{
show_help = true;
@ -1883,6 +1927,7 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & par
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands)
{
load_config(out);
activity_labels[JOB_IDLE] = "Idle";
activity_labels[JOB_MILITARY] = "Military Duty";
activity_labels[JOB_LEISURE] = "Leisure";
@ -1914,6 +1959,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
" Show statistics summary\n"
"dwarfmonitor prefs\n"
" Show dwarf preferences summary\n\n"
"dwarfmonitor reload\n"
" Reload configuration file (hack/config/dwarfmonitor.json)\n"
));
return CR_OK;

@ -203,12 +203,20 @@ for job,flag in pairs(plant_products) do
local itag = 'idx_'..string.lower(flag)
job_outputs[job] = function(callback, job)
local mat_type, mat_index = -1, -1
local seed_type, seed_index = -1, -1
local mat = dfhack.matinfo.decode(job.job_items[0])
if mat and mat.plant and mat.plant.flags[flag] then
mat_type = mat.plant.material_defs[ttag]
mat_index = mat.plant.material_defs[itag]
seed_type = mat.plant.material_defs['type_seed']
seed_index = mat.plant.material_defs['idx_seed']
end
default_output(callback, job, mat_type, mat_index)
local mat_mask = { }
if flag ~= 'LEAVES' then
mat_mask.plant = true
end
default_output(callback, job, mat_type, mat_index, mat_mask)
callback{ item_type = df.item_type.SEEDS, mat_type = seed_type, mat_index = seed_index }
end
end

@ -249,6 +249,11 @@ public:
static search_generic<S, T> *lock;
bool in_entry_mode()
{
return entry_mode;
}
protected:
virtual string get_element_description(T element) const = 0;
virtual void render() const = 0;
@ -270,11 +275,6 @@ protected:
}
bool in_entry_mode()
{
return entry_mode;
}
void start_entry_mode()
{
entry_mode = true;
@ -693,6 +693,13 @@ struct generic_search_hook : T
if (ok)
module.render();
}
DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key))
{
if (module.in_entry_mode() && (key == interface_key::MOVIES || key == interface_key::HELP))
return true;
return INTERPOSE_NEXT(key_conflict)(key);
}
};
template <class T, class V, int D> V generic_search_hook<T, V, D> ::module;
@ -702,17 +709,20 @@ template <class T, class V, int D> V generic_search_hook<T, V, D> ::module;
#define IMPLEMENT_HOOKS_WITH_ID(screen, module, id, prio) \
typedef generic_search_hook<screen, module, id> module##_hook; \
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, feed, prio); \
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, render, prio)
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, render, prio); \
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, key_conflict, prio)
#define IMPLEMENT_HOOKS(screen, module) \
typedef generic_search_hook<screen, module> module##_hook; \
template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \
template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render)
template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render); \
template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, key_conflict)
#define IMPLEMENT_HOOKS_PRIO(screen, module, prio) \
typedef generic_search_hook<screen, module> module##_hook; \
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, feed, prio); \
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, render, prio)
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, render, prio); \
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, key_conflict, prio)
//
// END: Generic Search functionality
@ -1026,8 +1036,8 @@ private:
};
typedef generic_search_hook<df::viewscreen_unitlistst, unitlist_search> unitlist_search_hook;
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100);
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100);
IMPLEMENT_HOOKS_PRIO(df::viewscreen_unitlistst, unitlist_search, 100);
//
// END: Unit screen search
//
@ -1761,7 +1771,8 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{
#define HOOK_ACTION(hook) \
!INTERPOSE_HOOK(hook, feed).apply(enable) || \
!INTERPOSE_HOOK(hook, render).apply(enable) ||
!INTERPOSE_HOOK(hook, render).apply(enable) || \
!INTERPOSE_HOOK(hook, key_conflict).apply(enable) ||
if (SEARCH_HOOKS 0)
return CR_FAILURE;
@ -1795,7 +1806,8 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
#define HOOK_ACTION(hook) \
INTERPOSE_HOOK(hook, feed).remove(); \
INTERPOSE_HOOK(hook, render).remove();
INTERPOSE_HOOK(hook, render).remove(); \
INTERPOSE_HOOK(hook, key_conflict).remove();
SEARCH_HOOKS

@ -1 +1 @@
Subproject commit 0778976b127159e2dd378b2fd807c42179888154
Subproject commit 600d171bb6eaf44f1a66a379b2f43000d84fab04

@ -130,6 +130,7 @@ static std::multimap<std::string, tweak_onupdate_hookst> tweak_onupdate_hooks;
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
is_enabled = true; // Allow plugin_onupdate to work (subcommands are enabled individually)
commands.push_back(PluginCommand(
"tweak", "Various tweaks for minor bugs.", tweak, false,
" tweak clear-missing\n"
@ -665,12 +666,16 @@ static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vect
if (vector_get(parameters, 1) == "disable")
{
hook.remove();
out.print("Disabled tweak %s (%s)\n", parameters[0].c_str(), hook.name());
fprintf(stderr, "Disabled tweak %s (%s)\n", parameters[0].c_str(), hook.name());
fflush(stderr);
}
else
{
if (hook.apply())
out.print("Enabled tweak %s (%s)\n", parameters[0].c_str(), hook.name());
{
fprintf(stderr, "Enabled tweak %s (%s)\n", parameters[0].c_str(), hook.name());
fflush(stderr);
}
else
out.printerr("Could not activate tweak %s (%s)\n", parameters[0].c_str(), hook.name());
}
@ -694,9 +699,10 @@ static command_result enable_tweak(string tweak, color_ostream &out, vector <str
{
bool state = (vector_get(parameters, 1) != "disable");
recognized = true;
tweak_onupdate_hookst hook = it->second;
tweak_onupdate_hookst &hook = it->second;
hook.enabled = state;
out.print("%s tweak %s (%s)\n", state ? "Enabled" : "Disabled", cmd.c_str(), hook.name.c_str());
fprintf(stderr, "%s tweak %s (%s)\n", state ? "Enabled" : "Disabled", cmd.c_str(), hook.name.c_str());
fflush(stderr);
}
}
if (!recognized)

@ -254,7 +254,7 @@ public:
if (!resume_time)
want_resumed = false;
else if (world->frame_counter >= resume_time)
actual_job->flags.bits.suspend = false;
set_resumed(true);
}
}
@ -262,7 +262,7 @@ public:
{
actual_job = job;
job->flags.bits.repeat = true;
job->flags.bits.suspend = true;
set_resumed(false);
resume_delay = std::min(DAY_TICKS*MONTH_DAYS, 5*resume_delay/3);
resume_time = world->frame_counter + resume_delay;
@ -272,16 +272,23 @@ public:
{
if (resume)
{
if (world->frame_counter >= resume_time)
if (world->frame_counter >= resume_time && actual_job->flags.bits.suspend)
{
actual_job->unk_v4020_1 = -1;
actual_job->flags.bits.suspend = false;
}
}
else
{
resume_time = 0;
if (isActuallyResumed())
resume_delay = DAY_TICKS;
if (!actual_job->flags.bits.suspend)
{
actual_job->flags.bits.suspend = true;
actual_job->unk_v4020_1 = -1;
}
}
want_resumed = resume;

@ -1,4 +1,5 @@
-- Adds emotions to creatures.
--@ module = true
local utils=require('utils')

@ -0,0 +1,19 @@
-- allows burial in unowned coffins
-- by Putnam https://gist.github.com/Putnam3145/e7031588f4d9b24b9dda
local utils=require('utils')
validArgs = validArgs or utils.invert({
'pets'
})
local args = utils.processArgs({...}, validArgs)
for k,v in ipairs(df.global.world.buildings.other.COFFIN) do
if v.owner_id==-1 then
v.burial_mode.allow_burial=true
if not args.pets then
v.burial_mode.no_pets=true
end
end
end

@ -0,0 +1,130 @@
-- makes creatures [in]fertile, by modifying orientation
-- usage: fix-ster [fert|ster] [all|animals|only:<creature>]
-- by Tacomagic (minor fixes by PeridexisErrant)
--[[
This script utilizes the orientation tag to either fix infertile creatures
or inflict infertility on creatures that you do not want to breed
Required arg:
fert: sets the flag to assure creature(s) are fertile
ster: sets the flag to assure creature(s) are sterile
Optional args:
<no arg> the script will only process the currently selected creature
all: process all creatures currently on the map
animals: processes everything but dwarves on the map
only:<creature>: processes all of the creatures matching the specified creature on the map
]]--
function getunit()
local unit
unit=dfhack.gui.getSelectedUnit()
if unit==nil then
print("No unit under cursor.")
return
end
return unit
end
function changeorient(unit,ori)
--Sets the fertility flag based on gender.
if unit.sex == 0 then
unit.status.current_soul.orientation_flags.marry_male=ori
else
unit.status.current_soul.orientation_flags.marry_female=ori
end
return
end
function changelots(creatures,ori)
local v
local unit
local c = 0
--loops through indexes in creatures table and changes orientation flags
for _,v in ipairs(creatures) do
unit = df.global.world.units.active[v]
changeorient(unit,ori)
c = c + 1
end
print("Changed "..c.. " creatures.")
return
end
function process_args(arg)
local n,v
local ori
local creatures = {}
local crenum
--Checks for any arguments at all.
if arg==nil then
print("No arguments. Usage is: fixster <fert|ster> [all|animals|only:<creature>]")
return
end
if string.lower(arg[1])=="ster" then
ori = false
elseif string.lower(arg[1])=="fert" then
ori = true
else
print("Unrecognised first argument. Aborting.")
return
end
--Checks for the existence of the second argument. If it's missing, uses selected unit (if any)
if arg[2]==nil then
unit = getunit()
if unit == nil then return end
changeorient(unit,ori)
print('Changed selected creature.')
--ALL arg processing
elseif string.lower(arg[2])=="all" then
--Create table of all current unit indexes
for n,v in ipairs(df.global.world.units.active) do
table.insert(creatures,n)
end
changelots(creatures,ori)
--ANIMALS arg processing
elseif string.lower(arg[2])=="animals" then
--Create a table of all creature indexes except dwarves on the current map
for n,v in ipairs(df.global.world.units.active) do
if v.race~=df.global.ui.race_id then
table.insert(creatures,n)
end
end
changelots(creatures,ori)
-- ONLY:<creature> arg processing
elseif string.lower(string.sub(arg[2],1,4))=="only" then
creidx = string.upper(string.sub(arg[2],6))
print(string.upper(string.sub(arg[2],6)))
--Search raws for creature
for k,v in pairs(df.global.world.raws.creatures.all) do
if v.creature_id==creidx then
crenum = k
end
end
--If no match, abort
if crenum == nil then
print("Creature not found. Check spelling.")
return
end
--create a table of all the matching creature indexes on the map for processing
for n,v in ipairs(df.global.world.units.active) do
if v.race == crenum then
table.insert(creatures,n)
end
end
changelots(creatures,ori)
else
print("Unrecognised optional argument. Aborting")
return
end
return
end
local arg = table.pack(...)
process_args(arg)

@ -9,7 +9,8 @@ local utils=require('utils')
validArgs = validArgs or utils.invert({
'r',
'help',
'unit'
'unit',
'keep_corpse'
})
local args = utils.processArgs({...}, validArgs)
@ -20,6 +21,8 @@ if args.help then
print(' heal the unit with the given id')
print(' full-heal -r -unit [unitId]')
print(' heal the unit with the given id and bring them back from death if they are dead')
print(' full-heal -r -keep_corpse -unit [unitId]')
print(' heal the unit with the given id and bring them back from death if they are dead, without removing their corpse')
print(' full-heal')
print(' heal the currently selected unit')
print(' full-heal -r')
@ -49,6 +52,15 @@ if unit then
unit.flags1.dead = false
unit.flags2.killed = false
unit.flags3.ghostly = false
if not args.keep_corpse then
for _,corpse in ipairs(df.global.world.items.other.CORPSE) do
if corpse.unit_id==unit.id then
corpse.flags.garbage_collect=true
corpse.flags.forbid=true
corpse.flags.hidden=true
end
end
end
--unit.unk_100 = 3
end

@ -212,14 +212,14 @@ function GmEditorUi:editSelected(index,choice)
dialog.showInputPrompt(tostring(trg_key),"Enter new value:",COLOR_WHITE,
tostring(trg.target[trg_key]),self:callback("commitEdit",trg_key))
elseif trg_type=='boolean' then
trg.target[trg_key]= not trg.target[trg_key]
elseif trg_type == 'boolean' then
trg.target[trg_key] = not trg.target[trg_key]
self:updateTarget(true)
elseif trg_type=='userdata' then
elseif trg_type == 'userdata' or trg_type == 'table' then
self:pushTarget(trg.target[trg_key])
else
print("Unknow type:"..trg_type)
print("Subtype:"..tostring(trg.target[trg_key]._kind))
print("Unknown type:"..trg_type)
pcall(function() print("Subtype:"..tostring(trg.target[trg_key]._kind)) end)
end
end
end

@ -4,7 +4,7 @@
-- edited by expwnent
function getGenderString(gender)
local function getGenderString(gender)
local genderStr
if gender==0 then
genderStr=string.char(12)
@ -16,7 +16,7 @@ function getGenderString(gender)
return string.char(40)..genderStr..string.char(41)
end
function getCreatureList()
local function getCreatureList()
local crList={}
for k,cr in ipairs(df.global.world.raws.creatures.alphabetic) do
for kk,ca in ipairs(cr.caste) do
@ -28,7 +28,47 @@ function getCreatureList()
return crList
end
function getMatFilter(itemtype)
local function getRestrictiveMatFilter(itemType)
if not args.restrictive then return nil end
local itemTypes={
WEAPON=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_WEAPON or mat.flags.ITEMS_WEAPON_RANGED)
end,
AMMO=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_AMMO)
end,
ARMOR=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_ARMOR)
end,
INSTRUMENT=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_HARD)
end,
AMULET=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_SOFT or mat.flags.ITEMS_HARD)
end,
ROCK=function(mat,parent,typ,idx)
return (mat.flags.IS_STONE)
end,
BOULDER=ROCK,
BAR=function(mat,parent,typ,idx)
return (mat.flags.IS_METAL or mat.flags.SOAP or mat.id==COAL)
end
}
for k,v in ipairs({'GOBLET','FLASK','TOY','RING','CROWN','SCEPTER','FIGURINE','TOOL'}) do
itemTypes[v]=itemTypes.INSTRUMENT
end
for k,v in ipairs({'SHOES','SHIELD','HELM','GLOVES'}) do
itemTypes[v]=itemTypes.ARMOR
end
for k,v in ipairs({'EARRING','BRACELET'}) do
itemTypes[v]=itemTypes.AMULET
end
itemTypes.BOULDER=itemTypes.ROCK
return itemTypes[df.item_type[itemType]]
end
local function getMatFilter(itemtype)
local itemTypes={
SEEDS=function(mat,parent,typ,idx)
return mat.flags.SEED_MAT
@ -70,54 +110,17 @@ function getMatFilter(itemtype)
return itemTypes[df.item_type[itemtype]] or getRestrictiveMatFilter(itemtype)
end
function getRestrictiveMatFilter(itemType)
if not args.restrictive then return nil end
local itemTypes={
WEAPON=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_WEAPON or mat.flags.ITEMS_WEAPON_RANGED)
end,
AMMO=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_AMMO)
end,
ARMOR=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_ARMOR)
end,
INSTRUMENT=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_HARD)
end,
AMULET=function(mat,parent,typ,idx)
return (mat.flags.ITEMS_SOFT or mat.flags.ITEMS_HARD)
end,
ROCK=function(mat,parent,typ,idx)
return (mat.flags.IS_STONE)
end,
BOULDER=ROCK,
BAR=function(mat,parent,typ,idx)
return (mat.flags.IS_METAL or mat.flags.SOAP or mat.id==COAL)
end
}
for k,v in ipairs({'GOBLET','FLASK','TOY','RING','CROWN','SCEPTER','FIGURINE','TOOL'}) do
itemTypes[v]=itemTypes.INSTRUMENT
local function createItem(mat,itemType,quality,creator,description)
local item=df.item.find(dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator))
if pcall(function() print(item.quality) end) then
item.quality=quality-1
end
for k,v in ipairs({'SHOES','SHIELD','HELM','GLOVES'}) do
itemTypes[v]=itemTypes.ARMOR
end
for k,v in ipairs({'EARRING','BRACELET'}) do
itemTypes[v]=itemTypes.AMULET
end
itemTypes.BOULDER=itemTypes.ROCK
return itemTypes[df.item_type[itemType]]
end
function createItem(mat,itemType,quality,creator,description)
dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator)
if df.item_type[itemType[1]]=='SLAB' then
item.description=description
end
end
function qualityTable()
local function qualityTable()
return {{'None'},
{'-Well-crafted-'},
{'+Finely-crafted+'},
@ -129,7 +132,7 @@ end
local script=require('gui.script')
function showItemPrompt(text,item_filter,hide_none)
local function showItemPrompt(text,item_filter,hide_none)
require('gui.materials').ItemTypeDialog{
prompt=text,
item_filter=item_filter,
@ -142,7 +145,7 @@ function showItemPrompt(text,item_filter,hide_none)
return script.wait()
end
function showMaterialPrompt(title, prompt, filter, inorganic, creature, plant) --the one included with DFHack doesn't have a filter or the inorganic, creature, plant things available
local function showMaterialPrompt(title, prompt, filter, inorganic, creature, plant) --the one included with DFHack doesn't have a filter or the inorganic, creature, plant things available
require('gui.materials').MaterialDialog{
frame_title = title,
prompt = prompt,
@ -158,12 +161,12 @@ function showMaterialPrompt(title, prompt, filter, inorganic, creature, plant) -
return script.wait()
end
function usesCreature(itemtype)
local function usesCreature(itemtype)
typesThatUseCreatures={REMAINS=true,FISH=true,FISH_RAW=true,VERMIN=true,PET=true,EGG=true,CORPSE=true,CORPSEPIECE=true}
return typesThatUseCreatures[df.item_type[itemtype]]
end
function getCreatureRaceAndCaste(caste)
local function getCreatureRaceAndCaste(caste)
return df.global.world.raws.creatures.list_creature[caste.index],df.global.world.raws.creatures.list_caste[caste.index]
end
@ -198,11 +201,15 @@ function hackWish(unit)
dfhack.items.createItem(itemtype, itemsubtype, mattype, matindex, unit)
end
end
return true
end
return false
else
if mattype and itemtype then
createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description)
return true
end
return false
end
end)
end

@ -1,6 +1,7 @@
-- scripts/modtools/reaction-product-trigger.lua
-- author expwnent
-- trigger commands just before and after custom reactions produce items
--@ module = true
local eventful = require 'plugins.eventful'
local utils = require 'utils'

@ -1,6 +1,7 @@
-- scripts/modtools/reaction-trigger.lua
-- author expwnent
-- replaces autoSyndrome: trigger commands when custom reactions are completed
--@ module = true
local eventful = require 'plugins.eventful'
local syndromeUtil = require 'syndrome-util'

@ -109,7 +109,7 @@ for _,syn in ipairs(df.global.world.raws.syndromes.all) do
end
if not syndrome then
error ('Could not find syndrome named ' .. syndrome)
error ('Could not find syndrome named ' .. args.syndrome)
end
onInfection[syndrome] = onInfection[syndrome] or {}

@ -4,6 +4,10 @@ if not dfhack.isMapLoaded() then
qerror("World and map aren't loaded.")
end
if not dfhack.world.isFortressMode() then
qerror('This script can only be used in fortress mode')
end
local ui_main = df.global.ui.main
local flags4 = df.global.d_init.flags4

@ -36,7 +36,8 @@ if args.showunitid or args.showpos then
end
else
local unit = args.unit and df.unit.find(args.unit) or dfhack.gui.getSelectedUnit(true)
local pos = not(not args.x or not args.y or not args.z) and {x=args.x,y=args.y,z=args.z} or df.global.cursor
local pos = not(not args.x or not args.y or not args.z) and {x=args.x,y=args.y,z=args.z} or {x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z}
if not unit then qerror('A unit needs to be selected or specified. Use teleport -showunitid to get a unit\'s ID.') end
if not pos.x or pos.x==-30000 then qerror('A position needs to be highlighted or specified. Use teleport -showpos to get a position\'s exact xyz values.') end
teleport(unit,pos)
end

@ -0,0 +1,129 @@
-- Pauses the game with a warning if a creature is starving, dehydrated, or very drowsy.
-- By Meneth32, PeridexisErrant, Lethosor
--@ module = true
starvingUnits = starvingUnits or {}
dehydratedUnits = dehydratedUnits or {}
sleepyUnits = sleepyUnits or {}
function clear()
starvingUnits = {}
dehydratedUnits = {}
sleepyUnits = {}
end
local gui = require 'gui'
local utils = require 'utils'
local units = df.global.world.units.active
local args = utils.invert({...})
if args.all or args.clear then
clear()
end
warning = defclass(warning, gui.FramedScreen)
warning.ATTRS = {
frame_style = gui.GREY_LINE_FRAME,
frame_title = 'Warning',
frame_width = 20,
frame_height = 18,
frame_inset = 1,
focus_path = 'warn-starving',
}
function warning:init(args)
self.start = 1
self.messages = args.messages
self.frame_height = math.min(18, #self.messages)
self.max_start = #self.messages - self.frame_height + 1
for _, msg in pairs(self.messages) do
self.frame_width = math.max(self.frame_width, #msg + 2)
end
self.frame_width = math.min(df.global.gps.dimx - 2, self.frame_width)
end
function warning:onRenderBody(p)
for i = self.start, math.min(self.start + self.frame_height - 1, #self.messages) do
p:string(self.messages[i]):newline()
end
if #self.messages > self.frame_height then
if self.start > 1 then
p:seek(self.frame_width - 1, 0):string(string.char(24), COLOR_LIGHTCYAN) -- up
end
if self.start < self.max_start then
p:seek(self.frame_width - 1, self.frame_height - 1):string(string.char(25), COLOR_LIGHTCYAN) -- down
end
end
end
function warning:onInput(keys)
if keys.LEAVESCREEN or keys.SELECT then
self:dismiss()
elseif keys.CURSOR_UP or keys.STANDARDSCROLL_UP then
self.start = math.max(1, self.start - 1)
elseif keys.CURSOR_DOWN or keys.STANDARDSCROLL_DOWN then
self.start = math.min(self.start + 1, self.max_start)
end
end
local function findRaceCaste(unit)
local rraw = df.creature_raw.find(unit.race)
return rraw, safe_index(rraw, 'caste', unit.caste)
end
local function getSexString(sex)
local sexStr = ""
if sex==0 then sexStr=string.char(12)
elseif sex==1 then sexStr=string.char(11)
end
return string.char(40)..sexStr..string.char(41)
end
local function nameOrSpeciesAndNumber(unit)
if unit.name.has_name then
return dfhack.TranslateName(dfhack.units.getVisibleName(unit))..' '..getSexString(unit.sex),true
else
return 'Unit #'..unit.id..' ('..df.creature_raw.find(unit.race).caste[unit.caste].caste_name[0]..' '..getSexString(unit.sex)..')',false
end
end
local function checkVariable(var, limit, description, map, unit)
local rraw = findRaceCaste(unit)
local species = rraw.name[0]
local profname = dfhack.units.getProfessionName(unit)
if #profname == 0 then profname = nil end
local name = nameOrSpeciesAndNumber(unit)
if var > limit then
if not map[unit.id] then
map[unit.id] = true
return name .. ", " .. (profname or species) .. " is " .. description .. "!"
end
else
map[unit.id] = false
end
return nil
end
function doCheck()
local messages = {}
for i=#units-1, 0, -1 do
local unit = units[i]
local rraw = findRaceCaste(unit)
if rraw and not unit.flags1.dead and not dfhack.units.isOpposedToLife(unit) then
table.insert(messages, checkVariable(unit.counters2.hunger_timer, 75000, 'starving', starvingUnits, unit))
table.insert(messages, checkVariable(unit.counters2.thirst_timer, 50000, 'dehydrated', dehydratedUnits, unit))
table.insert(messages, checkVariable(unit.counters2.sleepiness_timer, 150000, 'very drowsy', sleepyUnits, unit))
end
end
if #messages > 0 then
dfhack.color(COLOR_LIGHTMAGENTA)
for _, msg in pairs(messages) do
print(dfhack.df2console(msg))
end
dfhack.color()
df.global.pause_state = true
warning{messages=messages}:show()
end
end
if not moduleMode then doCheck() end