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() endif()
# make sure all the necessary submodules have been set up # 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) 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)") 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() endif()
# set up versioning. # set up versioning.
@ -173,6 +174,8 @@ IF(BUILD_LIBRARY)
install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif() endif()
install(DIRECTORY dfhack-config DESTINATION .)
#build the plugins #build the plugins
IF(BUILD_PLUGINS) IF(BUILD_PLUGINS)
add_subdirectory (plugins) add_subdirectory (plugins)

@ -77,6 +77,7 @@ scamtank scamtank
Mason11987 Mason11987 Mason11987 Mason11987
James Logsdon jlogsdon James Logsdon jlogsdon
melkor217 melkor217 melkor217 melkor217
acwatkins acwatkins
======================= ==================== =========================== ======================= ==================== ===========================
And these are the cool people who made **Stonesense**. 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 will not be an issue in most cases.
This function also permits circular dependencies of scripts. 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 Save init script
================ ================

17
NEWS

@ -2,21 +2,34 @@ DFHack Future
Internals Internals
A method for caching screen output is now available to Lua (and C++) 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 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 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 New internal commands
kill-lua: Interrupt running Lua scripts
New plugins New plugins
New scripts 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 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 tweaks
New features
Fixes Fixes
steam-engine: fixed a crash on arena load
Plugins with vmethod hooks can now be reloaded on OS X Plugins with vmethod hooks can now be reloaded on OS X
Lua's os.system() now works on OS X Lua's os.system() now works on OS X
Fixed default arguments in Lua gametype detection functions 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 Misc Improvements
dwarfmonitor date format can be modified (see Readme)
"keybinding list" accepts a context "keybinding list" accepts a context
nyan: Can now be stopped with dfhack-run nyan: Can now be stopped with dfhack-run
quicksave: Restricted to fortress mode
Removed Removed
DFHack 0.40.24-r3 DFHack 0.40.24-r3

@ -1685,6 +1685,34 @@ Maintain 10-100 locally-made crafts of exceptional quality::
Fortress activity management 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 seedwatch
--------- ---------
Watches the numbers of seeds available and enables/disables seed and plant cooking. 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. * ``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 * ``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 petcapRemover
------------- -------------
This plugin allows you to remove or raise the pet population cap. In vanilla 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 script uses ``hack/patches/<df-version>/<name>.dif``, thus auto-selecting
the version appropriate for the currently loaded executable. 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 create-items
============ ============
Spawn arbitrary items under the cursor. 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 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 fortplan
======== ========
Usage: fortplan [filename] 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, script ``raw/scripts/more-item-descriptions.lua``. Both work as sparse lists,
so missing items simply go undescribed if not defined in the fallback. 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 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. # You can comment out the extension of a line.
# allow the fortress bookkeeper to queue jobs through the manager # allow the fortress bookkeeper to queue jobs through the manager
stockflow enable enable stockflow
########### ###########
# Scripts # # Scripts #

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

@ -288,6 +288,10 @@ namespace {
const string *pcmd; const string *pcmd;
vector<string> *pargs; vector<string> *pargs;
}; };
struct ScriptEnableState {
const string *pcmd;
bool pstate;
};
} }
static bool init_run_script(color_ostream &out, lua_State *state, void *info) 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; 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) 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()) 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++) 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) if(!plug)
{
std::string lua = findScript(this->p->getPath(), part + ".lua");
if (lua.size())
{
res = enableLuaScript(con, part, enable);
}
else
{ {
res = CR_NOT_FOUND; 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()) else if (!plug->can_set_enabled())
{ {
res = CR_NOT_IMPLEMENTED; 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 else
{ {
res = plug->set_enabled(con, enable); res = plug->set_enabled(con, enable);
if (res != CR_OK || plug->is_enabled() != 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" " cls - Clear the console.\n"
" fpause - Force DF to pause.\n" " fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n" " die - Force DF to close immediately\n"
" kill-lua - Stop an active Lua script\n"
" keybinding - Modify bindings of commands to keys\n" " keybinding - Modify bindings of commands to keys\n"
" script FILENAME - Run the commands specified in a file.\n" " script FILENAME - Run the commands specified in a file.\n"
" sc-script - Automatically run specified scripts on state change events\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); _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") else if(first == "script")
{ {
if(parts.size() == 1) if(parts.size() == 1)

@ -24,6 +24,7 @@ distribution.
#include "Internal.h" #include "Internal.h"
#include <csignal>
#include <string> #include <string>
#include <vector> #include <vector>
#include <map> #include <map>
@ -354,6 +355,35 @@ static int dfhack_lineedit(lua_State *S)
* Exception handling * 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 int DFHACK_EXCEPTION_META_TOKEN = 0;
static void error_tostring(lua_State *L, bool keep_old = false) 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) if (!state)
state = luaL_newstate(); state = luaL_newstate();
interrupt_init(state);
luaL_openlibs(state); luaL_openlibs(state);
AttachDFGlobals(state); AttachDFGlobals(state);

@ -230,7 +230,6 @@ bool Plugin::load(color_ostream &con)
plugin_check_symbol("plugin_self") plugin_check_symbol("plugin_self")
plugin_check_symbol("plugin_init") plugin_check_symbol("plugin_init")
plugin_check_symbol("plugin_globals") plugin_check_symbol("plugin_globals")
plugin_check_symbol("plugin_dev")
const char ** plug_name =(const char ** ) LookupPlugin(plug, "name"); const char ** plug_name =(const char ** ) LookupPlugin(plug, "name");
const char ** plug_version =(const char ** ) LookupPlugin(plug, "version"); const char ** plug_version =(const char ** ) LookupPlugin(plug, "version");
Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self"); Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
@ -243,7 +242,7 @@ bool Plugin::load(color_ostream &con)
return false; return false;
} }
bool *plug_dev = (bool*)LookupPlugin(plug, "plugin_dev"); 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); con.print("Skipping dev plugin: %s\n", *plug_name);
plugin_abort_load; 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_enable = (command_result (*)(color_ostream &,bool)) LookupPlugin(plug, "plugin_enable");
plugin_is_enabled = (bool*) LookupPlugin(plug, "plugin_is_enabled"); plugin_is_enabled = (bool*) LookupPlugin(plug, "plugin_is_enabled");
plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby"); 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); index_lua(plug);
this->name = *plug_name; this->name = *plug_name;
plugin_lib = plug; plugin_lib = plug;
@ -531,6 +531,16 @@ Plugin::plugin_state Plugin::getState() const
return state; 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) void Plugin::index_lua(DFLibrary *lib)
{ {
if (auto cmdlist = (CommandReg*)LookupPlugin(lib, "plugin_lua_commands")) 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); 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) PluginManager::PluginManager(Core * core)
{ {
cmdlist_mutex = new mutex(); cmdlist_mutex = new mutex();
@ -766,6 +789,16 @@ Plugin *PluginManager::getPluginByCommand(const std::string &command)
return NULL; 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... // FIXME: handle name collisions...
command_result PluginManager::InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters) 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*), bool (*init)(color_ostream&, lua_State*, void*),
void *arg); 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 * Push utility functions
*/ */

@ -50,6 +50,7 @@ namespace df
namespace DFHack namespace DFHack
{ {
class Core; class Core;
class PluginExports;
class PluginManager; class PluginManager;
class virtual_identity; class virtual_identity;
class RPCService; class RPCService;
@ -165,6 +166,7 @@ namespace DFHack
command_result invoke(color_ostream &out, const std::string & command, std::vector <std::string> & parameters); 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 ); bool can_invoke_hotkey(const std::string & command, df::viewscreen *top );
plugin_state getState () const; plugin_state getState () const;
PluginExports *getExports();
RPCService *rpc_connect(color_ostream &out); RPCService *rpc_connect(color_ostream &out);
@ -227,7 +229,16 @@ namespace DFHack
command_result (*plugin_enable)(color_ostream &, bool); command_result (*plugin_enable)(color_ostream &, bool);
RPCService* (*plugin_rpcconnect)(color_ostream &); RPCService* (*plugin_rpcconnect)(color_ostream &);
command_result (*plugin_eval_ruby)(color_ostream &, const char*); 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 class DFHACK_EXPORT PluginManager
{ {
// PRIVATE METHODS // PRIVATE METHODS
@ -244,6 +255,7 @@ namespace DFHack
public: public:
Plugin *getPluginByName (const std::string & name); Plugin *getPluginByName (const std::string & name);
Plugin *getPluginByCommand (const std::string &command); 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); command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters);
bool CanInvokeHotkey(const std::string &command, df::viewscreen *top); bool CanInvokeHotkey(const std::string &command, df::viewscreen *top);
Plugin* operator[] (std::size_t index) Plugin* operator[] (std::size_t index)
@ -293,6 +305,17 @@ namespace DFHack
DFhackDataExport bool plugin_is_enabled = false; \ DFhackDataExport bool plugin_is_enabled = false; \
bool &varname = plugin_is_enabled; 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 \ #define DFHACK_PLUGIN_LUA_COMMANDS \
DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = DFhackCExport const DFHack::CommandReg plugin_lua_commands[] =
#define DFHACK_PLUGIN_LUA_FUNCTIONS \ #define DFHACK_PLUGIN_LUA_FUNCTIONS \

@ -386,15 +386,33 @@ end
local internal = dfhack.internal local internal = dfhack.internal
internal.scripts = internal.scripts or {} Script = defclass(Script)
internal.scriptPath = internal.scriptPath or {} function Script:init(path)
internal.scriptMtime = internal.scriptMtime or {} self.path = path
internal.scriptRun = internal.scriptRun or {} 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 scripts = internal.scripts
local scriptPath = internal.scriptPath
local scriptMtime = internal.scriptMtime
local scriptRun = internal.scriptRun
local hack_path = dfhack.getHackPath() local hack_path = dfhack.getHackPath()
@ -419,28 +437,69 @@ function dfhack.findScript(name)
return nil return nil
end 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,...) function dfhack.run_script(name,...)
return dfhack.run_script_with_env(nil,name,...) return dfhack.run_script_with_env(nil, name, nil, ...)
end 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) 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 return env
end 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) local file = dfhack.findScript(name)
if not file then if not file then
error('Could not find script ' .. name) error('Could not find script ' .. name)
end end
if scriptPath[name] and scriptPath[name] ~= file then
--new file path: must have loaded a different save or unloaded if scripts[file] == nil then
scriptPath[name] = file scripts[file] = Script(file)
scriptMtime[file] = dfhack.filesystem.mtime(file) end
--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 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 end
local env = scripts[file] local env = scripts[file].env
if env == nil then if env == nil then
env = {} env = {}
setmetatable(env, { __index = base_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 for x,y in pairs(envVars or {}) do
env[x] = y env[x] = y
end end
env.dfhack_flags = flags
env.moduleMode = flags.module
local f local f
local perr local perr
local time = dfhack.filesystem.mtime(file) local time = dfhack.filesystem.mtime(file)
if time == scriptMtime[file] and scriptRun[file] then if time == scripts[file].mtime and scripts[file].run then
f = scriptRun[file] f = scripts[file].run
else else
--reload --reload
f, perr = loadfile(file, 't', env) f, perr = loadfile(file, 't', env)
@ -460,10 +521,10 @@ function dfhack.run_script_with_env(envVars,name,...)
error(perr) error(perr)
end end
-- avoid updating mtime if the script failed to load -- avoid updating mtime if the script failed to load
scriptMtime[file] = time scripts[file].mtime = time
end end
scripts[file] = env scripts[file].env = env
scriptRun[file] = f scripts[file].run = f
return f(...), env return f(...), env
end end

@ -396,6 +396,11 @@ bool MaterialInfo::matches(const df::job_material_category &cat)
using namespace df::enums::material_flags; using namespace df::enums::material_flags;
TEST(plant, STRUCTURAL_PLANT_MAT); 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(wood, WOOD);
TEST(cloth, THREAD_PLANT); TEST(cloth, THREAD_PLANT);
TEST(silk, SILK); TEST(silk, SILK);

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

@ -112,7 +112,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(digFlood digFlood.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp)
add_subdirectory(diggingInvaders) add_subdirectory(diggingInvaders)
DFHACK_PLUGIN(drybuckets drybuckets.cpp) 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(embark-tools embark-tools.cpp)
DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp)

@ -109,6 +109,9 @@ enum dwarf_state {
// Busy with a useful task // Busy with a useful task
BUSY, BUSY,
// Busy with a useful task that requires a tool
EXCLUSIVE,
// In the military, can't work // In the military, can't work
MILITARY, MILITARY,
@ -119,11 +122,12 @@ enum dwarf_state {
OTHER OTHER
}; };
const int NUM_STATE = 5; const int NUM_STATE = 6;
static const char *state_names[] = { static const char *state_names[] = {
"IDLE", "IDLE",
"BUSY", "BUSY",
"EXCLUSIVE",
"MILITARY", "MILITARY",
"CHILD", "CHILD",
"OTHER", "OTHER",
@ -133,13 +137,13 @@ static const dwarf_state dwarf_states[] = {
BUSY /* CarveFortification */, BUSY /* CarveFortification */,
BUSY /* DetailWall */, BUSY /* DetailWall */,
BUSY /* DetailFloor */, BUSY /* DetailFloor */,
BUSY /* Dig */, EXCLUSIVE /* Dig */,
BUSY /* CarveUpwardStaircase */, EXCLUSIVE /* CarveUpwardStaircase */,
BUSY /* CarveDownwardStaircase */, EXCLUSIVE /* CarveDownwardStaircase */,
BUSY /* CarveUpDownStaircase */, EXCLUSIVE /* CarveUpDownStaircase */,
BUSY /* CarveRamp */, EXCLUSIVE /* CarveRamp */,
BUSY /* DigChannel */, EXCLUSIVE /* DigChannel */,
BUSY /* FellTree */, EXCLUSIVE /* FellTree */,
BUSY /* GatherPlants */, BUSY /* GatherPlants */,
BUSY /* RemoveConstruction */, BUSY /* RemoveConstruction */,
BUSY /* CollectWebs */, BUSY /* CollectWebs */,
@ -154,7 +158,7 @@ static const dwarf_state dwarf_states[] = {
OTHER /* Sleep */, OTHER /* Sleep */,
BUSY /* CollectSand */, BUSY /* CollectSand */,
BUSY /* Fish */, BUSY /* Fish */,
BUSY /* Hunt */, EXCLUSIVE /* Hunt */,
OTHER /* HuntVermin */, OTHER /* HuntVermin */,
BUSY /* Kidnap */, BUSY /* Kidnap */,
BUSY /* BeatCriminal */, BUSY /* BeatCriminal */,
@ -183,7 +187,7 @@ static const dwarf_state dwarf_states[] = {
OTHER /* GoShopping2 */, OTHER /* GoShopping2 */,
BUSY /* Clean */, BUSY /* Clean */,
OTHER /* Rest */, OTHER /* Rest */,
BUSY /* PickupEquipment */, EXCLUSIVE /* PickupEquipment */,
BUSY /* DumpItem */, BUSY /* DumpItem */,
OTHER /* StrangeMoodCrafter */, OTHER /* StrangeMoodCrafter */,
OTHER /* StrangeMoodJeweller */, OTHER /* StrangeMoodJeweller */,
@ -393,8 +397,15 @@ struct labor_default
int active_dwarfs; int active_dwarfs;
}; };
// The percentage of the dwarves assigned as haulers at any one time.
static int hauler_pct = 33; 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 std::vector<struct labor_info> labor_infos;
static const struct labor_default default_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 medical; // this dwarf has medical responsibility
bool trader; // this dwarf has trade responsibility bool trader; // this dwarf has trade responsibility
bool diplomacy; // this dwarf meets with diplomats 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) static bool isOptionEnabled(unsigned flag)
@ -738,7 +748,13 @@ struct laborinfo_sorter
{ {
bool operator() (int i,int j) bool operator() (int i,int j)
{ {
if (labor_infos[i].mode() != labor_infos[j].mode())
return 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_dwarf = 0;
int best_value = -10000; int best_value = -10000;
int best_skill = 0;
std::vector<int> values(n_dwarfs); std::vector<int> values(n_dwarfs);
std::vector<int> candidates; std::vector<int> candidates;
@ -813,6 +830,9 @@ static void assign_labor(unit_labor::unit_labor labor,
dwarf_skill[dwarf] = skill_level; dwarf_skill[dwarf] = skill_level;
dwarf_skillxp[dwarf] = skill_experience; dwarf_skillxp[dwarf] = skill_experience;
if (best_skill < skill_level)
best_skill = skill_level;
value += skill_level * 100; value += skill_level * 100;
value += skill_experience / 20; value += skill_experience / 20;
if (skill_level > 0 || skill_experience > 0) if (skill_level > 0 || skill_experience > 0)
@ -832,6 +852,9 @@ static void assign_labor(unit_labor::unit_labor labor,
value += 350; value += 350;
} }
if (dwarf_info[dwarf].has_exclusive_labor)
value -= 500;
// bias by happiness // bias by happiness
//value += dwarfs[dwarf]->status.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) if (unit_labor::FISH == labor && !has_fishery)
min_dwarfs = max_dwarfs = 0; min_dwarfs = max_dwarfs = 0;
bool want_idle_dwarf = true; // If there are enough idle dwarves to choose from, enter an aggressive assignment
if (state_count[IDLE] < 2) // mode. "Enough" idle dwarves is defined as 2 or 10% of the total number of dwarves,
want_idle_dwarf = false; // 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 * 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: * 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 * - We are in aggressive mode and have not yet assigned an idle dwarf
* - The dwarf has nonzero skill associated with the labor * - The dwarf is good at this skill
* - The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. * - The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled.
* We stop assigning dwarfs when we reach the maximum allowed. * 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 * 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]; int dwarf = candidates[i];
if (dwarf_info[dwarf].trader && trader_requested)
continue;
if (dwarf_info[dwarf].diplomacy)
continue;
assert(dwarf >= 0); assert(dwarf >= 0);
assert(dwarf < n_dwarfs); assert(dwarf < n_dwarfs);
bool preferred_dwarf = false; bool preferred_dwarf = false;
if (want_idle_dwarf && dwarf_info[dwarf].state == IDLE) if (dwarf_skillxp[dwarf] > 0 && dwarf_skill[dwarf] >= best_skill / 2)
preferred_dwarf = true;
if (dwarf_skill[dwarf] > 0)
preferred_dwarf = true; 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; preferred_dwarf = true;
if (dwarf_info[dwarf].medical && labor == df::unit_labor::DIAGNOSE) if (dwarf_info[dwarf].medical && labor == df::unit_labor::DIAGNOSE)
preferred_dwarf = true; 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; continue;
if (!dwarfs[dwarf]->status.labors[labor]) if (!dwarfs[dwarf]->status.labors[labor])
@ -952,11 +987,11 @@ static void assign_labor(unit_labor::unit_labor labor,
if (print_debug) 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)" : ""); 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++; labor_infos[labor].active_dwarfs++;
if (dwarf_info[dwarf].state == IDLE) 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++) for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{ {
dwarf_info[dwarf].single_labor = -1;
if (dwarfs[dwarf]->status.souls.size() <= 0) if (dwarfs[dwarf]->status.souls.size() <= 0)
continue; continue;
@ -1214,7 +1247,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
laborinfo_sorter lasorter; laborinfo_sorter lasorter;
std::sort(labors.begin(), labors.end(), 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) for (auto lp = labors.begin(); lp != labors.end(); ++lp)
{ {
auto labor = *lp; auto labor = *lp;
@ -1224,12 +1259,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) 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 (dwarfs[dwarf]->status.labors[labor])
{ {
if (labor_infos[labor].is_exclusive) 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 // 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. // 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) if (num_haulers < 1)
num_haulers = 1; num_haulers = 1;
@ -1274,7 +1303,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
continue; 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); hauler_ids.push_back(dwarf);
} }
dwarfinfo_sorter sorter(dwarf_info); dwarfinfo_sorter sorter(dwarf_info);
@ -1305,7 +1334,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarfs[dwarf]->status.labors[labor] = true; dwarfs[dwarf]->status.labors[labor] = true;
dwarf_info[dwarf].assigned_jobs++; 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++; labor_infos[labor].active_dwarfs++;
if (print_debug) 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]); con.print("%c",buf[j+i]);
else else
con.print("."); con.print(".");
//con.print("\n"); con.print("\n");
} }
con.print("\n"); con.print("\n");
} }

@ -41,6 +41,8 @@
#include "df/descriptor_shape.h" #include "df/descriptor_shape.h"
#include "df/descriptor_color.h" #include "df/descriptor_color.h"
#include "jsonxx.h"
using std::deque; using std::deque;
DFHACK_PLUGIN("dwarfmonitor"); 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_jobs = false;
static bool monitor_misery = true; static bool monitor_misery = true;
static bool monitor_date = true; static bool monitor_date = true;
@ -1720,9 +1727,29 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest
ostringstream date_str; ostringstream date_str;
auto month = World::ReadCurrentMonth() + 1; auto month = World::ReadCurrentMonth() + 1;
auto day = World::ReadCurrentDay(); auto day = World::ReadCurrentDay();
date_str << "Date:" << World::ReadCurrentYear() << "-" << date_str << "Date:";
((month < 10) ? "0" : "") << month << "-" << for (size_t i = 0; i < dwarfmonitor_config.date_format.size(); i++)
((day < 10) ? "0" : "") << day; {
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()); 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; 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) static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & parameters)
{ {
bool show_help = false; bool show_help = false;
@ -1869,6 +1909,10 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & par
if(Maps::IsValid()) if(Maps::IsValid())
Screen::show(new ViewscreenPreferences()); Screen::show(new ViewscreenPreferences());
} }
else if (cmd == 'r' || cmd == 'R')
{
load_config(out);
}
else else
{ {
show_help = true; 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) DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands)
{ {
load_config(out);
activity_labels[JOB_IDLE] = "Idle"; activity_labels[JOB_IDLE] = "Idle";
activity_labels[JOB_MILITARY] = "Military Duty"; activity_labels[JOB_MILITARY] = "Military Duty";
activity_labels[JOB_LEISURE] = "Leisure"; activity_labels[JOB_LEISURE] = "Leisure";
@ -1914,6 +1959,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
" Show statistics summary\n" " Show statistics summary\n"
"dwarfmonitor prefs\n" "dwarfmonitor prefs\n"
" Show dwarf preferences summary\n\n" " Show dwarf preferences summary\n\n"
"dwarfmonitor reload\n"
" Reload configuration file (hack/config/dwarfmonitor.json)\n"
)); ));
return CR_OK; return CR_OK;

@ -203,12 +203,20 @@ for job,flag in pairs(plant_products) do
local itag = 'idx_'..string.lower(flag) local itag = 'idx_'..string.lower(flag)
job_outputs[job] = function(callback, job) job_outputs[job] = function(callback, job)
local mat_type, mat_index = -1, -1 local mat_type, mat_index = -1, -1
local seed_type, seed_index = -1, -1
local mat = dfhack.matinfo.decode(job.job_items[0]) local mat = dfhack.matinfo.decode(job.job_items[0])
if mat and mat.plant and mat.plant.flags[flag] then if mat and mat.plant and mat.plant.flags[flag] then
mat_type = mat.plant.material_defs[ttag] mat_type = mat.plant.material_defs[ttag]
mat_index = mat.plant.material_defs[itag] mat_index = mat.plant.material_defs[itag]
seed_type = mat.plant.material_defs['type_seed']
seed_index = mat.plant.material_defs['idx_seed']
end 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
end end

@ -249,6 +249,11 @@ public:
static search_generic<S, T> *lock; static search_generic<S, T> *lock;
bool in_entry_mode()
{
return entry_mode;
}
protected: protected:
virtual string get_element_description(T element) const = 0; virtual string get_element_description(T element) const = 0;
virtual void render() const = 0; virtual void render() const = 0;
@ -270,11 +275,6 @@ protected:
} }
bool in_entry_mode()
{
return entry_mode;
}
void start_entry_mode() void start_entry_mode()
{ {
entry_mode = true; entry_mode = true;
@ -693,6 +693,13 @@ struct generic_search_hook : T
if (ok) if (ok)
module.render(); 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; 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) \ #define IMPLEMENT_HOOKS_WITH_ID(screen, module, id, prio) \
typedef generic_search_hook<screen, module, id> module##_hook; \ 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, 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) \ #define IMPLEMENT_HOOKS(screen, module) \
typedef generic_search_hook<screen, module> module##_hook; \ typedef generic_search_hook<screen, module> module##_hook; \
template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \ 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) \ #define IMPLEMENT_HOOKS_PRIO(screen, module, prio) \
typedef generic_search_hook<screen, module> module##_hook; \ typedef generic_search_hook<screen, module> module##_hook; \
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, feed, prio); \ 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 // END: Generic Search functionality
@ -1026,8 +1036,8 @@ private:
}; };
typedef generic_search_hook<df::viewscreen_unitlistst, unitlist_search> unitlist_search_hook; typedef generic_search_hook<df::viewscreen_unitlistst, unitlist_search> unitlist_search_hook;
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); IMPLEMENT_HOOKS_PRIO(df::viewscreen_unitlistst, unitlist_search, 100);
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100);
// //
// END: Unit screen search // END: Unit screen search
// //
@ -1761,7 +1771,8 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{ {
#define HOOK_ACTION(hook) \ #define HOOK_ACTION(hook) \
!INTERPOSE_HOOK(hook, feed).apply(enable) || \ !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) if (SEARCH_HOOKS 0)
return CR_FAILURE; return CR_FAILURE;
@ -1795,7 +1806,8 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{ {
#define HOOK_ACTION(hook) \ #define HOOK_ACTION(hook) \
INTERPOSE_HOOK(hook, feed).remove(); \ INTERPOSE_HOOK(hook, feed).remove(); \
INTERPOSE_HOOK(hook, render).remove(); INTERPOSE_HOOK(hook, render).remove(); \
INTERPOSE_HOOK(hook, key_conflict).remove();
SEARCH_HOOKS 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) 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( commands.push_back(PluginCommand(
"tweak", "Various tweaks for minor bugs.", tweak, false, "tweak", "Various tweaks for minor bugs.", tweak, false,
" tweak clear-missing\n" " tweak clear-missing\n"
@ -665,12 +666,16 @@ static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vect
if (vector_get(parameters, 1) == "disable") if (vector_get(parameters, 1) == "disable")
{ {
hook.remove(); 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 else
{ {
if (hook.apply()) 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 else
out.printerr("Could not activate tweak %s (%s)\n", parameters[0].c_str(), hook.name()); 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"); bool state = (vector_get(parameters, 1) != "disable");
recognized = true; recognized = true;
tweak_onupdate_hookst hook = it->second; tweak_onupdate_hookst &hook = it->second;
hook.enabled = state; 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) if (!recognized)

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

@ -1,4 +1,5 @@
-- Adds emotions to creatures. -- Adds emotions to creatures.
--@ module = true
local utils=require('utils') 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({ validArgs = validArgs or utils.invert({
'r', 'r',
'help', 'help',
'unit' 'unit',
'keep_corpse'
}) })
local args = utils.processArgs({...}, validArgs) local args = utils.processArgs({...}, validArgs)
@ -20,6 +21,8 @@ if args.help then
print(' heal the unit with the given id') print(' heal the unit with the given id')
print(' full-heal -r -unit [unitId]') 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(' 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(' full-heal')
print(' heal the currently selected unit') print(' heal the currently selected unit')
print(' full-heal -r') print(' full-heal -r')
@ -49,6 +52,15 @@ if unit then
unit.flags1.dead = false unit.flags1.dead = false
unit.flags2.killed = false unit.flags2.killed = false
unit.flags3.ghostly = 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 --unit.unk_100 = 3
end end

@ -215,11 +215,11 @@ function GmEditorUi:editSelected(index,choice)
elseif trg_type == 'boolean' then elseif trg_type == 'boolean' then
trg.target[trg_key] = not trg.target[trg_key] trg.target[trg_key] = not trg.target[trg_key]
self:updateTarget(true) self:updateTarget(true)
elseif trg_type=='userdata' then elseif trg_type == 'userdata' or trg_type == 'table' then
self:pushTarget(trg.target[trg_key]) self:pushTarget(trg.target[trg_key])
else else
print("Unknow type:"..trg_type) print("Unknown type:"..trg_type)
print("Subtype:"..tostring(trg.target[trg_key]._kind)) pcall(function() print("Subtype:"..tostring(trg.target[trg_key]._kind)) end)
end end
end end
end end

@ -4,7 +4,7 @@
-- edited by expwnent -- edited by expwnent
function getGenderString(gender) local function getGenderString(gender)
local genderStr local genderStr
if gender==0 then if gender==0 then
genderStr=string.char(12) genderStr=string.char(12)
@ -16,7 +16,7 @@ function getGenderString(gender)
return string.char(40)..genderStr..string.char(41) return string.char(40)..genderStr..string.char(41)
end end
function getCreatureList() local function getCreatureList()
local crList={} local crList={}
for k,cr in ipairs(df.global.world.raws.creatures.alphabetic) do for k,cr in ipairs(df.global.world.raws.creatures.alphabetic) do
for kk,ca in ipairs(cr.caste) do for kk,ca in ipairs(cr.caste) do
@ -28,7 +28,47 @@ function getCreatureList()
return crList return crList
end 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={ local itemTypes={
SEEDS=function(mat,parent,typ,idx) SEEDS=function(mat,parent,typ,idx)
return mat.flags.SEED_MAT return mat.flags.SEED_MAT
@ -70,54 +110,17 @@ function getMatFilter(itemtype)
return itemTypes[df.item_type[itemtype]] or getRestrictiveMatFilter(itemtype) return itemTypes[df.item_type[itemtype]] or getRestrictiveMatFilter(itemtype)
end end
function getRestrictiveMatFilter(itemType) local function createItem(mat,itemType,quality,creator,description)
if not args.restrictive then return nil end local item=df.item.find(dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator))
local itemTypes={ if pcall(function() print(item.quality) end) then
WEAPON=function(mat,parent,typ,idx) item.quality=quality-1
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 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 if df.item_type[itemType[1]]=='SLAB' then
item.description=description item.description=description
end end
end end
function qualityTable() local function qualityTable()
return {{'None'}, return {{'None'},
{'-Well-crafted-'}, {'-Well-crafted-'},
{'+Finely-crafted+'}, {'+Finely-crafted+'},
@ -129,7 +132,7 @@ end
local script=require('gui.script') local script=require('gui.script')
function showItemPrompt(text,item_filter,hide_none) local function showItemPrompt(text,item_filter,hide_none)
require('gui.materials').ItemTypeDialog{ require('gui.materials').ItemTypeDialog{
prompt=text, prompt=text,
item_filter=item_filter, item_filter=item_filter,
@ -142,7 +145,7 @@ function showItemPrompt(text,item_filter,hide_none)
return script.wait() return script.wait()
end 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{ require('gui.materials').MaterialDialog{
frame_title = title, frame_title = title,
prompt = prompt, prompt = prompt,
@ -158,12 +161,12 @@ function showMaterialPrompt(title, prompt, filter, inorganic, creature, plant) -
return script.wait() return script.wait()
end 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} typesThatUseCreatures={REMAINS=true,FISH=true,FISH_RAW=true,VERMIN=true,PET=true,EGG=true,CORPSE=true,CORPSEPIECE=true}
return typesThatUseCreatures[df.item_type[itemtype]] return typesThatUseCreatures[df.item_type[itemtype]]
end 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] return df.global.world.raws.creatures.list_creature[caste.index],df.global.world.raws.creatures.list_caste[caste.index]
end end
@ -198,11 +201,15 @@ function hackWish(unit)
dfhack.items.createItem(itemtype, itemsubtype, mattype, matindex, unit) dfhack.items.createItem(itemtype, itemsubtype, mattype, matindex, unit)
end end
end end
return true
end end
return false
else else
if mattype and itemtype then if mattype and itemtype then
createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description) createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description)
return true
end end
return false
end end
end) end)
end end

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

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

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

@ -4,6 +4,10 @@ if not dfhack.isMapLoaded() then
qerror("World and map aren't loaded.") qerror("World and map aren't loaded.")
end 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 ui_main = df.global.ui.main
local flags4 = df.global.d_init.flags4 local flags4 = df.global.d_init.flags4

@ -36,7 +36,8 @@ if args.showunitid or args.showpos then
end end
else else
local unit = args.unit and df.unit.find(args.unit) or dfhack.gui.getSelectedUnit(true) 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) teleport(unit,pos)
end 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