Conflicts:
	plugins/devel/CMakeLists.txt
develop
Kelly Martin 2013-02-13 13:52:02 -06:00
commit 8d278a87db
60 changed files with 6883 additions and 950 deletions

3
.gitignore vendored

@ -57,3 +57,6 @@ dfhack/python/dist
build/CPack*Config.cmake
/cmakeall.bat
# vim swap files
*.swp

@ -58,14 +58,10 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac
endif()
# set up versioning.
set(DF_VERSION_MAJOR "0")
set(DF_VERSION_MINOR "34")
set(DF_VERSION_PATCH "11")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
set(DF_VERSION "0.34.11")
SET(DFHACK_RELEASE "r2" CACHE STRING "Current release revision.")
set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}")
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")
## where to install things (after the build is done, classic 'make install' or package structure)

@ -404,7 +404,10 @@ ul.auto-toc {
<li><a class="reference internal" href="#sort" id="id54">sort</a></li>
</ul>
</li>
<li><a class="reference internal" href="#scripts" id="id55">Scripts</a></li>
<li><a class="reference internal" href="#scripts" id="id55">Scripts</a><ul>
<li><a class="reference internal" href="#save-init-script" id="id56">Save init script</a></li>
</ul>
</li>
</ul>
</div>
<p>The current version of DFHack has extensive support for
@ -1034,6 +1037,9 @@ can be omitted.</p>
<li><p class="first"><tt class="docutils literal">dfhack.getHackPath()</tt></p>
<p>Returns the dfhack directory path, i.e. <tt class="docutils literal"><span class="pre">&quot;.../df/hack/&quot;</span></tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.getSavePath()</tt></p>
<p>Returns the path to the current save directory, or <em>nil</em> if no save loaded.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.getTickCount()</tt></p>
<p>Returns the tick count in ms, exactly as DF ui uses.</p>
</li>
@ -3064,6 +3070,25 @@ The <tt class="docutils literal">name</tt> argument should be the name stem, as
</li>
</ul>
<p>Note that this function lets errors propagate to the caller.</p>
<div class="section" id="save-init-script">
<h2><a class="toc-backref" href="#id56">Save init script</a></h2>
<p>If a save directory contains a file called <tt class="docutils literal">raw/init.lua</tt>, it is
automatically loaded and executed every time the save is loaded. It
can also define the following functions to be called by dfhack:</p>
<ul>
<li><p class="first"><tt class="docutils literal">function onStateChange(op) ... end</tt></p>
<p>Automatically called from the regular onStateChange event as long
as the save is still loaded. This avoids the need to install a hook
into the global <tt class="docutils literal">dfhack.onStateChange</tt> table, with associated
cleanup concerns.</p>
</li>
<li><p class="first"><tt class="docutils literal">function onUnload() ... end</tt></p>
<p>Called when the save containing the script is unloaded. This function
should clean up any global hooks installed by the script.</p>
</li>
</ul>
<p>Within the init script, the path to the save directory is available as <tt class="docutils literal">SAVE_PATH</tt>.</p>
</div>
</div>
</div>
</body>

@ -741,6 +741,10 @@ can be omitted.
Returns the dfhack directory path, i.e. ``".../df/hack/"``.
* ``dfhack.getSavePath()``
Returns the path to the current save directory, or *nil* if no save loaded.
* ``dfhack.getTickCount()``
Returns the tick count in ms, exactly as DF ui uses.
@ -2954,6 +2958,62 @@ sort
Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.
Eventful
========
This plugin exports some events to lua thus allowing to run lua functions
on DF world events.
List of events
--------------
1. onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native) - auto activates if detects reactions starting with ``LUA_HOOK_``. Is called when reaction finishes.
2. onItemContaminateWound(item,unit,wound,number1,number2) - Is called when item tries to contaminate wound (e.g. stuck in)
3. onProjItemCheckMovement(projectile) - is called when projectile moves
4. onProjItemCheckImpact(projectile,somebool) - is called when projectile hits something
5. onProjUnitCheckMovement(projectile) - is called when projectile moves
6. onProjUnitCheckImpact(projectile,somebool) - is called when projectile hits something
7. onWorkshopFillSidebarMenu(workshop,callnative) - is called when viewing a workshop in 'q' mode, to populate reactions, usefull for custom viewscreens for shops
8. postWorkshopFillSidebarMenu(workshop) - is called after calling (or not) native fillSidebarMenu(). Usefull for job button tweaking (e.g. adding custom reactions)
Functions
---------
1. registerReaction(reaction_name,callback) - simplified way of using onReactionComplete, the callback is function (same params as event)
2. removeNative(shop_name) - removes native choice list from the building
3. addReactionToShop(reaction_name,shop_name) - add a custom reaction to the building
Examples
--------
Spawn dragon breath on each item attempt to contaminate wound:
::
b=require "plugins.eventful"
b.onItemContaminateWound.one=function(item,unit,un_wound,x,y)
local flw=dfhack.maps.spawnFlow(unit.pos,6,0,0,50000)
end
Reaction complete example:
::
b=require "plugins.eventful"
b.onReactionComplete.one=function(reaction,unit,in_items,in_reag,out_items,call_native)
local pos=copyall(unit.pos)
dfhack.timeout(100,"ticks",function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end) -- spawn dragonbreath after 100 ticks
call_native.value=false --do not call real item creation code
end
Granade example:
::
b=require "plugins.eventful"
b.onProjItemCheckImpact.one=function(projectile)
-- you can check if projectile.item e.g. has correct material
dfhack.maps.spawnFlow(projectile.cur_pos,6,0,0,50000)
end
Integrated tannery:
::
b=require "plugins.eventful"
b.addReactionToShop("TAN_A_HIDE","LEATHERWORKS")
=======
Scripts
@ -2993,3 +3053,24 @@ from other scripts) in any context, via the same function the core uses:
The ``name`` argument should be the name stem, as would be used on the command line.
Note that this function lets errors propagate to the caller.
Save init script
================
If a save directory contains a file called ``raw/init.lua``, it is
automatically loaded and executed every time the save is loaded. It
can also define the following functions to be called by dfhack:
* ``function onStateChange(op) ... end``
Automatically called from the regular onStateChange event as long
as the save is still loaded. This avoids the need to install a hook
into the global ``dfhack.onStateChange`` table, with associated
cleanup concerns.
* ``function onUnload() ... end``
Called when the save containing the script is unloaded. This function
should clean up any global hooks installed by the script.
Within the init script, the path to the save directory is available as ``SAVE_PATH``.

15
NEWS

@ -3,6 +3,8 @@ DFHack future
Internals:
- support for displaying active keybindings properly.
- support for reusable widgets in lua screen library.
- Maps::canStepBetween: returns whether you can walk between two tiles in one step.
- EventManager: monitors various in game events centrally so that individual plugins don't have to monitor the same things redundantly.
Notable bugfixes:
- autobutcher can be re-enabled again after being stopped.
- stopped Dwarf Manipulator from unmasking vampires.
@ -13,6 +15,7 @@ DFHack future
- superdwarf: work in adventure mode too
- tweak stable-cursor: carries cursor location from/to Build menu.
- deathcause: allow selection from the unitlist screen
- slayrace: allow targetting undeads
New tweaks:
- tweak military-training: speed up melee squad training up to 10x (normally 3-5x).
New scripts:
@ -24,6 +27,7 @@ DFHack future
- lever: list and pull fort levers from the dfhack console.
- stripcaged: mark items inside cages for dumping, eg caged goblin weapons.
- soundsense-season: writes the correct season to gamelog.txt on world load.
- create-items: spawn items
New GUI scripts:
- gui/guide-path: displays the cached path for minecart Guide orders.
- gui/workshop-job: displays inputs of a workshop job and allows tweaking them.
@ -31,6 +35,7 @@ DFHack future
- gui/assign-rack: works together with a binary patch to fix weapon racks.
- gui/gm-editor: an universal editor for lots of dfhack things.
- gui/companion-order: a adventure mode command interface for your companions.
- gui/advfort: a way to do jobs with your adventurer (e.g. build fort).
New binary patches (for use with binpatch):
- armorstand-capacity: doubles the capacity of armor stands.
- custom-reagent-size: lets custom reactions use small amounts of inputs.
@ -57,6 +62,16 @@ DFHack future
saving you from having to trawl through long lists of materials each time you place one.
Dfusion plugin:
Reworked to make use of lua modules, now all the scripts can be used from other scripts.
New Eventful plugin:
A collection of lua events, that will allow new ways to interact with df world.
Auto syndrome plugin:
A way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws.
Infinite sky plugin:
Create new z-levels automatically or on request.
True transformation plugin:
A better way of doing permanent transformations that allows later transformations.
Work now plugin:
Makes the game assign jobs every time you pause.
DFHack v0.34.11-r2

@ -1850,13 +1850,16 @@ slayrace
========
Kills any unit of a given race.
With no argument, lists the available races.
With no argument, lists the available races and count eligible targets.
With the special argument ``him``, targets only the selected creature.
With the special argument ``undead``, targets all undeads on the map,
regardless of their race.
Any non-dead non-caged unit of the specified race gets its ``blood_count``
set to 0, which means immediate death at the next game tick. For creatures
such as vampires, also set animal.vanish_countdown to 2.
such as vampires, it also sets animal.vanish_countdown to 2.
An alternate mode is selected by adding a 2nd argument to the command,
``magma``. In this case, a column of 7/7 magma is generated on top of the
@ -1939,6 +1942,7 @@ deathcause
==========
Focus a body part ingame, and this script will display the cause of death of
the creature.
Also works when selecting units from the 'u'nitlist viewscreen.
lua
===
@ -2001,6 +2005,33 @@ alternatively pass cage IDs as arguments::
stripcaged weapons 25321 34228
create-items
============
Spawn arbitrary items under the cursor.
The first argument gives the item category, the second gives the material,
and the optionnal third gives the number of items to create (defaults to 20).
Currently supported item categories: ``boulder``, ``bar``, ``plant``, ``log``,
``web``.
Instead of material, using ``list`` makes the script list eligible materials.
The ``web`` item category will create an uncollected cobweb on the floor.
Note that the script does not enforce anything, and will let you create
boulders of toad blood and stuff like that.
However the ``list`` mode will only show 'normal' materials.
Exemples::
create-items boulders COAL_BITUMINOUS 12
create-items plant tail_pig
create-items log list
create-items web CREATURE:SPIDER_CAVE_GIANT:SILK
create-items bar CREATURE:CAT:SOAP
create-items bar adamantine
=======================
In-game interface tools
=======================
@ -2074,7 +2105,9 @@ directly to the main dwarf mode screen.
Search
======
The search plugin adds search to the Stocks, Trading, Stockpile and Unit List screens.
The search plugin adds search to the Stocks, Animals, Trading, Stockpile,
Noble (assignment candidates), Military (position candidates), Burrows
(unit list), Rooms, Announcements, Job List and Unit List screens.
.. image:: images/search.png
@ -2094,8 +2127,9 @@ Leaving any screen automatically clears the filter.
In the Trade screen, the actual trade will always only act on items that
are actually visible in the list; the same effect applies to the Trade
Value numbers displayed by the screen. Because of this, pressing the 't'
key while search is active clears the search instead of executing the trade.
Value numbers displayed by the screen. Because of this, the 't' key is
blocked while search is active, so you have to reset the filters first.
Pressing Alt-C will clear both search strings.
In the stockpile screen the option only appears if the cursor is in the
rightmost list:
@ -2380,6 +2414,40 @@ the intended user. In order to aid in the choice, it shows the number
of currently assigned racks for every valid squad.
gui/advfort
=============
This script allows to perform jobs in adventure mode. For more complete help
press '?' while script is running. It's most confortable to use this as a
keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:
* -a or --nodfassign - uses different method to assign items.
* -i or --inventory - checks inventory for possible items to use in the job.
* -c or --cheat - relaxes item requirements for buildings (e.g. walls from bones).
implies -a
* job - selects that job (e.g. Dig or FellTree)
gui/gm-editor
=============
There are three ways to open this editor:
* using gui/gm-editor command/keybinding - opens editor on what is selected
or viewed (e.g. unit/item description screen)
* using gui/gm-editor <lua command> - executes lua command and opens editor on
it's results (e.g. gui/gm-editor "df.global.world.items.all" shows all items)
* using gui/gm-edito dialog - shows an in game dialog to input lua command. Works
the same as version above.
This editor allows to change and modify almost anything in df. Press '?' for an
in-game help.
=============
Behavior Mods
=============
@ -2573,3 +2641,4 @@ be bought from caravans. :)
To be really useful this needs patches from bug 808, ``tweak fix-dimensions``
and ``tweak advmode-contained``.

@ -144,6 +144,9 @@ tweak military-training
# write the correct season to gamelog on world load
soundsense-season
# patch the material objects in memory to fix cloth stockpiles
fix/cloth-stockpile enable
#######################################################
# Apply binary patches at runtime #
# #

@ -111,6 +111,7 @@ include/modules/Burrows.h
include/modules/Constructions.h
include/modules/Units.h
include/modules/Engravings.h
include/modules/EventManager.h
include/modules/Gui.h
include/modules/Items.h
include/modules/Job.h
@ -132,6 +133,7 @@ modules/Burrows.cpp
modules/Constructions.cpp
modules/Units.cpp
modules/Engravings.cpp
modules/EventManager.cpp
modules/Gui.cpp
modules/Items.cpp
modules/Job.cpp

@ -44,6 +44,7 @@ using namespace std;
#include "VersionInfo.h"
#include "PluginManager.h"
#include "ModuleFactory.h"
#include "modules/EventManager.h"
#include "modules/Gui.h"
#include "modules/World.h"
#include "modules/Graphic.h"
@ -316,7 +317,7 @@ static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr,
rbcmd += "'" + args[i] + "', ";
rbcmd += "]\n";
rbcmd += "load './hack/scripts/" + name + ".rb'";
rbcmd += "catch(:script_finished) { load './hack/scripts/" + name + ".rb' }";
return plug_mgr->eval_ruby(out, rbcmd.c_str());
}
@ -947,6 +948,7 @@ bool Core::Init()
cerr << "Initializing Plugins.\n";
// create plugin manager
plug_mgr = new PluginManager(this);
plug_mgr->init(this);
IODATA *temp = new IODATA;
temp->core = this;
temp->plug_mgr = plug_mgr;
@ -1281,6 +1283,8 @@ static int buildings_timer = 0;
void Core::onUpdate(color_ostream &out)
{
EventManager::manageEvents(out);
// convert building reagents
if (buildings_do_onupdate && (++buildings_timer & 1))
buildings_onUpdate(out);
@ -1294,6 +1298,8 @@ void Core::onUpdate(color_ostream &out)
void Core::onStateChange(color_ostream &out, state_change_event event)
{
EventManager::onStateChange(out, event);
buildings_onStateChange(out, event);
plug_mgr->OnStateChange(out, event);

@ -1551,6 +1551,10 @@ void DFHack::Lua::Notification::bind(lua_State *state, const char *name)
void OpenDFHackApi(lua_State *state);
namespace DFHack { namespace Lua { namespace Core {
static void InitCoreContext();
}}}
lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
{
if (!state)
@ -1654,6 +1658,10 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_dup(state);
lua_rawseti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
// Init core-context specific stuff before loading dfhack.lua
if (IsCoreContext(state))
Lua::Core::InitCoreContext();
// load dfhack.lua
Require(out, state, "dfhack");
@ -1829,8 +1837,12 @@ void DFHack::Lua::Core::Init(color_ostream &out)
State = luaL_newstate();
// Calls InitCoreContext after checking IsCoreContext
Lua::Open(out, State);
}
static void Lua::Core::InitCoreContext()
{
lua_newtable(State);
lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);

@ -22,6 +22,7 @@ must not be misrepresented as being the original software.
distribution.
*/
#include "modules/EventManager.h"
#include "Internal.h"
#include "Core.h"
#include "MemAccess.h"
@ -211,9 +212,10 @@ bool Plugin::load(color_ostream &con)
}
const char ** plug_name =(const char ** ) LookupPlugin(plug, "name");
const char ** plug_version =(const char ** ) LookupPlugin(plug, "version");
if(!plug_name || !plug_version)
Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
if(!plug_name || !plug_version || !plug_self)
{
con.printerr("Plugin %s has no name or version.\n", filename.c_str());
con.printerr("Plugin %s has no name, version or self pointer.\n", filename.c_str());
ClosePlugin(plug);
RefAutolock lock(access);
state = PS_BROKEN;
@ -228,6 +230,7 @@ bool Plugin::load(color_ostream &con)
state = PS_BROKEN;
return false;
}
*plug_self = this;
RefAutolock lock(access);
plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
if(!plugin_init)
@ -270,6 +273,7 @@ bool Plugin::unload(color_ostream &con)
// if we are actually loaded
if(state == PS_LOADED)
{
EventManager::unregisterAll(this);
// notify the plugin about an attempt to shutdown
if (plugin_onstatechange &&
plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND)
@ -598,6 +602,22 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn)
}
PluginManager::PluginManager(Core * core)
{
cmdlist_mutex = new mutex();
eval_ruby = NULL;
}
PluginManager::~PluginManager()
{
for(size_t i = 0; i < all_plugins.size();i++)
{
delete all_plugins[i];
}
all_plugins.clear();
delete cmdlist_mutex;
}
void PluginManager::init(Core * core)
{
#ifdef LINUX_BUILD
string path = core->getHackPath() + "plugins/";
@ -606,8 +626,6 @@ PluginManager::PluginManager(Core * core)
string path = core->getHackPath() + "plugins\\";
const string searchstr = ".plug.dll";
#endif
cmdlist_mutex = new mutex();
eval_ruby = NULL;
vector <string> filez;
getdir(path, filez);
for(size_t i = 0; i < filez.size();i++)
@ -622,16 +640,6 @@ PluginManager::PluginManager(Core * core)
}
}
PluginManager::~PluginManager()
{
for(size_t i = 0; i < all_plugins.size();i++)
{
delete all_plugins[i];
}
all_plugins.clear();
delete cmdlist_mutex;
}
Plugin *PluginManager::getPluginByName (const std::string & name)
{
for(size_t i = 0; i < all_plugins.size(); i++)

@ -39,15 +39,54 @@ using namespace DFHack;
/*
* Code for accessing method pointers directly. Very compiler-specific.
*
* Pointers to methods in C++ are conceptually similar to pointers to
* functions, but with some complications. Specifically, the target of
* such pointer can be either:
*
* - An ordinary non-virtual method, in which case the pointer behaves
* not much differently from a simple function pointer.
* - A virtual method, in which case calling the pointer must emulate
* an ordinary call to that method, i.e. fetch the real code address
* from the vtable at the appropriate index.
*
* This means that pointers to virtual methods actually have to encode
* the relevant vtable index value in some way. Also, since these two
* types of pointers cannot be distinguished by data type and differ
* only in value, any sane compiler would ensure that any non-virtual
* method that can potentially be called via a pointer uses the same
* parameter passing rules as an equivalent virtual method, so that
* the same parameter passing code would work with both types of pointer.
*
* This means that with a few small low-level compiler-specific wrappers
* to access the data inside such pointers it is possible to:
*
* - Convert a non-virtual method pointer into a code address that
* can be directly put into a vtable.
* - Convert a pointer taken out of a vtable into a fake non-virtual
* method pointer that can be used to easily call the original
* vmethod body.
* - Extract a vtable index out of a virtual method pointer.
*
* Taken together, these features allow delegating all the difficult
* and fragile tasks like passing parameters and calculating the
* vtable index to the C++ compiler.
*/
#if defined(_MSC_VER)
// MSVC may use up to 3 different representations
// based on context, but adding the /vmg /vmm options
// forces it to stick to this one. It can accomodate
// multiple, but not virtual inheritance.
struct MSVC_MPTR {
void *method;
intptr_t this_shift;
};
// Debug builds sometimes use additional thunks that
// just jump to the real one, presumably to attach some
// additional debug info.
static uint32_t *follow_jmp(void *ptr)
{
uint8_t *p = (uint8_t*)ptr;
@ -56,10 +95,10 @@ static uint32_t *follow_jmp(void *ptr)
{
switch (*p)
{
case 0xE9:
case 0xE9: // jmp near rel32
p += 5 + *(int32_t*)(p+1);
break;
case 0xEB:
case 0xEB: // jmp short rel8
p += 2 + *(int8_t*)(p+1);
break;
default:
@ -120,8 +159,10 @@ void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
#elif defined(__GXX_ABI_VERSION)
// GCC seems to always use this structure - possibly unless
// virtual inheritance is involved, but that's irrelevant.
struct GCC_MPTR {
intptr_t method;
intptr_t method; // Code pointer or tagged vtable offset
intptr_t this_shift;
};
@ -254,6 +295,14 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v
{
if (vmethod_idx < 0 || interpose_method == NULL)
{
/*
* A failure here almost certainly means a problem in one
* of the pointer-to-method access wrappers above:
*
* - vmethod_idx comes from vmethod_pointer_to_idx_
* - interpose_method comes from method_pointer_to_addr_
*/
fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n",
vmethod_idx, unsigned(interpose_method));
fflush(stderr);

@ -205,6 +205,7 @@ namespace DFHack
friend class Plugin;
PluginManager(Core * core);
~PluginManager();
void init(Core* core);
void OnUpdate(color_ostream &out);
void OnStateChange(color_ostream &out, state_change_event event);
void registerCommands( Plugin * p );
@ -246,7 +247,8 @@ namespace DFHack
/// You have to have this in every plugin you write - just once. Ideally on top of the main file.
#define DFHACK_PLUGIN(plugin_name) \
DFhackDataExport const char * version = DFHACK_VERSION;\
DFhackDataExport const char * name = plugin_name;
DFhackDataExport const char * name = plugin_name;\
DFhackDataExport Plugin *plugin_self = NULL;
#define DFHACK_PLUGIN_LUA_COMMANDS \
DFhackCExport const DFHack::CommandReg plugin_lua_commands[] =

@ -28,6 +28,58 @@ distribution.
namespace DFHack
{
/* VMethod interpose API.
This API allows replacing an entry in the original vtable
with code defined by DFHack, while retaining ability to
call the original code. The API can be safely used from
plugins, and multiple hooks for the same vmethod are
automatically chained (subclass before superclass; at same
level highest priority called first; undefined order otherwise).
Usage:
struct my_hack : df::someclass {
typedef df::someclass interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) {
// If needed by the code, claim the suspend lock.
// DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK!
// CoreSuspendClaimer suspend;
...
INTERPOSE_NEXT(foo)(arg) // call the original
...
}
};
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
or
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority);
void init() {
if (!INTERPOSE_HOOK(my_hack, foo).apply())
error();
}
void shutdown() {
INTERPOSE_HOOK(my_hack, foo).remove();
}
Important caveat:
This will NOT intercept calls to the superclass vmethod
from overriding vmethod bodies in subclasses, i.e. whenever
DF code contains something like this, the call to "superclass::foo()"
doesn't actually use vtables, and thus will never trigger any hooks:
class superclass { virtual foo() { ... } };
class subclass : superclass { virtual foo() { ... superclass::foo(); ... } };
The only workaround is to implement and apply a second hook for subclass::foo,
and repeat that for any other subclasses and sub-subclasses that override this
vmethod.
*/
template<bool> struct StaticAssert;
template<> struct StaticAssert<true> {};
@ -81,43 +133,6 @@ namespace DFHack
return addr_to_method_pointer<P>(identity.get_vmethod_ptr(idx));
}
/* VMethod interpose API.
This API allows replacing an entry in the original vtable
with code defined by DFHack, while retaining ability to
call the original code. The API can be safely used from
plugins, and multiple hooks for the same vmethod are
automatically chained (subclass before superclass; at same
level highest priority called first; undefined order otherwise).
Usage:
struct my_hack : df::someclass {
typedef df::someclass interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) {
// If needed by the code, claim the suspend lock.
// DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK!
// CoreSuspendClaimer suspend;
...
INTERPOSE_NEXT(foo)(arg) // call the original
...
}
};
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
or
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority);
void init() {
if (!INTERPOSE_HOOK(my_hack, foo).apply())
error();
}
void shutdown() {
INTERPOSE_HOOK(my_hack, foo).remove();
}
*/
#define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \
typedef rtype (interpose_base::*interpose_ptr_##name)args; \
@ -142,18 +157,21 @@ namespace DFHack
friend class virtual_identity;
virtual_identity *host; // Class with the vtable
int vmethod_idx;
int vmethod_idx; // Index of the interposed method in the vtable
void *interpose_method; // Pointer to the code of the interposing method
void *chain_mptr; // Pointer to the chain field below
int priority;
void *chain_mptr; // Pointer to the chain field in the subclass below
int priority; // Higher priority hooks are called earlier
bool applied;
void *saved_chain; // Previous pointer to the code
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
bool applied; // True if this hook is currently applied
void *saved_chain; // Pointer to the code of the original vmethod or next hook
// inherited vtable members
// Chain of hooks within the same host
VMethodInterposeLinkBase *next, *prev;
// Subclasses that inherit this topmost hook directly
std::set<virtual_identity*> child_hosts;
// Hooks within subclasses that branch off this topmost hook
std::set<VMethodInterposeLinkBase*> child_next;
// (See the cpp file for a more detailed description of these links)
void set_chain(void *chain);
void on_host_delete(virtual_identity *host);
@ -172,6 +190,9 @@ namespace DFHack
template<class Base, class Ptr>
class VMethodInterposeLink : public VMethodInterposeLinkBase {
public:
// Exactly the same as the saved_chain field of superclass,
// but converted to the appropriate pointer-to-method type.
// Kept up to date via the chain_mptr pointer.
Ptr chain;
operator Ptr () { return chain; }

@ -25,7 +25,9 @@ distribution.
#pragma once
#include "Export.h"
#include "DataDefs.h"
#include "Types.h"
#include "df/building.h"
#include "df/building_type.h"
#include "df/civzone_type.h"
#include "df/furnace_type.h"
#include "df/workshop_type.h"
@ -181,5 +183,8 @@ DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector<df::job_i
*/
DFHACK_EXPORT bool deconstruct(df::building *bld);
void updateBuildings(color_ostream& out, void* ptr);
void clearBuildings(color_ostream& out);
}
}

@ -0,0 +1,60 @@
#pragma once
#ifndef EVENT_MANAGER_H_INCLUDED
#define EVENT_MANAGER_H_INCLUDED
#include "Core.h"
#include "Export.h"
#include "ColorText.h"
#include "PluginManager.h"
#include "Console.h"
namespace DFHack {
namespace EventManager {
namespace EventType {
enum EventType {
TICK,
JOB_INITIATED,
JOB_COMPLETED,
UNIT_DEATH,
ITEM_CREATED,
BUILDING,
CONSTRUCTION,
SYNDROME,
INVASION,
EVENT_MAX
};
}
struct EventHandler {
void (*eventHandler)(color_ostream&, void*); //called when the event happens
int32_t freq;
EventHandler(void (*eventHandlerIn)(color_ostream&, void*), int32_t freqIn): eventHandler(eventHandlerIn), freq(freqIn) {
}
bool operator==(EventHandler& handle) const {
return eventHandler == handle.eventHandler && freq == handle.freq;
}
bool operator!=(EventHandler& handle) const {
return !( *this == handle);
}
};
struct SyndromeData {
int32_t unitId;
int32_t syndromeIndex;
SyndromeData(int32_t unitId_in, int32_t syndromeIndex_in): unitId(unitId_in), syndromeIndex(syndromeIndex_in) {
}
};
DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin);
DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false);
DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin);
DFHACK_EXPORT void unregisterAll(Plugin* plugin);
void manageEvents(color_ostream& out);
void onStateChange(color_ostream& out, state_change_event event);
}
}
#endif

@ -49,7 +49,7 @@ namespace DFHack
{
namespace Job {
// Duplicate the job structure. It is not linked into any DF lists.
DFHACK_EXPORT df::job *cloneJobStruct(df::job *job);
DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepWorkerData=false);
// Delete a cloned structure.
DFHACK_EXPORT void deleteJobStruct(df::job *job);

@ -308,6 +308,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block,
extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which );
DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2);
DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2);
}
}
#endif

@ -328,9 +328,11 @@ end
-- Command scripts
dfhack.internal.scripts = dfhack.internal.scripts or {}
local internal = dfhack.internal
local scripts = dfhack.internal.scripts
internal.scripts = internal.scripts or {}
local scripts = internal.scripts
local hack_path = dfhack.getHackPath()
function dfhack.run_script(name,...)
@ -349,5 +351,42 @@ function dfhack.run_script(name,...)
return f(...)
end
-- Per-save init file
function dfhack.getSavePath()
if dfhack.isWorldLoaded() then
return dfhack.getDFPath() .. '/data/save/' .. df.global.world.cur_savegame.save_dir
end
end
if dfhack.is_core_context then
dfhack.onStateChange.DFHACK_PER_SAVE = function(op)
if op == SC_WORLD_LOADED or op == SC_WORLD_UNLOADED then
if internal.save_init then
if internal.save_init.onUnload then
safecall(internal.save_init.onUnload)
end
internal.save_init = nil
end
local path = dfhack.getSavePath()
if path and op == SC_WORLD_LOADED then
local env = setmetatable({ SAVE_PATH = path }, { __index = base_env })
local f,perr = loadfile(path..'/raw/init.lua', 't', env)
if f == nil then
if not string.match(perr, 'No such file or directory') then
dfhack.printerr(perr)
end
elseif safecall(f) then
internal.save_init = env
end
end
elseif internal.save_init and internal.save_init.onStateChange then
safecall(internal.save_init.onStateChange, op)
end
end
end
-- Feed the table back to the require() mechanism.
return dfhack

@ -334,7 +334,22 @@ local trap_inputs = {
},
[df.trap_type.TrackStop] = { { flags2={ building_material=true, non_economic=true } } }
}
local siegeengine_input = {
[df.siegeengine_type.Catapult] = {
{
item_type=df.item_type.CATAPULTPARTS,
vector_id=df.job_item_vector_id.CATAPULTPARTS,
quantity=3
}
},
[df.siegeengine_type.Ballista] = {
{
item_type=df.item_type.BALLISTAPARTS,
vector_id=df.job_item_vector_id.BALLISTAPARTS,
quantity=3
}
},
}
--[[ Functions for lookup in tables. ]]
local function get_custom_inputs(custom)
@ -359,6 +374,8 @@ local function get_inputs_by_type(type,subtype,custom)
end
elseif type == df.building_type.Trap then
return trap_inputs[subtype]
elseif type == df.building_type.SiegeEngine then
return siegeengine_input[subtype]
else
return building_inputs[type]
end

@ -0,0 +1,591 @@
local _ENV = mkmodule('dfhack.workshops')
local utils = require 'utils'
input_filter_defaults = {
item_type = -1,
item_subtype = -1,
mat_type = -1,
mat_index = -1,
flags1 = {},
-- Instead of noting those that allow artifacts, mark those that forbid them.
-- Leaves actually enabling artifacts to the discretion of the API user,
-- which is the right thing because unlike the game UI these filters are
-- used in a way that does not give the user a chance to choose manually.
flags2 = { allow_artifact = true },
flags3 = {},
flags4 = 0,
flags5 = 0,
reaction_class = '',
has_material_reaction_product = '',
metal_ore = -1,
min_dimension = -1,
has_tool_use = -1,
quantity = 1
}
local fuel={item_type=df.item_type.BAR,mat_type=df.builtin_mats.COAL}
jobs_furnace={
[df.furnace_type.Smelter]={
{
name="Melt metal object",
items={fuel,{flags2={allow_melt_dump=true}}},--also maybe melt_designated
job_fields={job_type=df.job_type.MeltMetalObject}
}
},
[df.furnace_type.MagmaSmelter]={
{
name="Melt metal object",
items={{flags2={allow_melt_dump=true}}},--also maybe melt_designated
job_fields={job_type=df.job_type.MeltMetalObject}
}
},
--[[ [df.furnace_type.MetalsmithsForge]={
unpack(concat(furnaces,mechanism,anvil,crafts,coins,flask))
},
]]
--MetalsmithsForge,
--MagmaForge
--[[
forges:
weapons and ammo-> from raws...
armor -> raws
furniture -> builtins?
siege eq-> builtin (only balista head)
trap eq -> from raws+ mechanisms
other object-> anvil, crafts, goblets,toys,instruments,nestbox... (raws?) flask, coins,stud with iron
metal clothing-> raws???
]]
[df.furnace_type.GlassFurnace]={
{
name="collect sand",
items={},
job_fields={job_type=df.job_type.CollectSand}
},
--glass crafts x3
},
[df.furnace_type.WoodFurnace]={
defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD},
{
name="make charcoal",
items={{}},
job_fields={job_type=df.job_type.MakeCharcoal}
},
{
name="make ash",
items={{}},
job_fields={job_type=df.job_type.MakeAsh}
}
},
[df.furnace_type.Kiln]={
{
name="collect clay",
items={},
job_fields={job_type=df.job_type.CollectClay}
}
},
}
jobs_workshop={
[df.workshop_type.Jewelers]={
{
name="cut gems",
items={{item_type=df.item_type.ROUGH,flags1={unrotten=true}}},
job_fields={job_type=df.job_type.CutGems}
},
{
name="encrust finished goods with gems",
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,finished_goods=true}}},
job_fields={job_type=df.job_type.EncrustWithGems}
},
{
name="encrust ammo with gems",
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,ammo=true}}},
job_fields={job_type=df.job_type.EncrustWithGems}
},
{
name="encrust furniture with gems",
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,furniture=true}}},
job_fields={job_type=df.job_type.EncrustWithGems}
},
},
[df.workshop_type.Fishery]={
{
name="prepare raw fish",
items={{item_type=df.item_type.FISH_RAW,flags1={unrotten=true}}},
job_fields={job_type=df.job_type.PrepareRawFish}
},
{
name="extract from raw fish",
items={{flags1={unrotten=true,extract_bearing_fish=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}},
job_fields={job_type=df.job_type.ExtractFromRawFish}
},
{
name="catch live fish",
items={},
job_fields={job_type=df.job_type.CatchLiveFish}
}, -- no items?
},
[df.workshop_type.Still]={
{
name="brew drink",
items={{flags1={distillable=true},vector_id=22},{flags1={empty=true},flags3={food_storage=true}}},
job_fields={job_type=df.job_type.BrewDrink}
},
{
name="extract from plants",
items={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}},
job_fields={job_type=df.job_type.ExtractFromPlants}
},
--mead from raws?
},
[df.workshop_type.Masons]={
defaults={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,flags3={hard=true}},--flags2={non_economic=true},
{
name="construct armor stand",
items={{}},
job_fields={job_type=df.job_type.ConstructArmorStand}
},
{
name="construct blocks",
items={{}},
job_fields={job_type=df.job_type.ConstructBlocks}
},
{
name="construct throne",
items={{}},
job_fields={job_type=df.job_type.ConstructThrone}
},
{
name="construct coffin",
items={{}},
job_fields={job_type=df.job_type.ConstructCoffin}
},
{
name="construct door",
items={{}},
job_fields={job_type=df.job_type.ConstructDoor}
},
{
name="construct floodgate",
items={{}},
job_fields={job_type=df.job_type.ConstructFloodgate}
},
{
name="construct hatch cover",
items={{}},
job_fields={job_type=df.job_type.ConstructHatchCover}
},
{
name="construct grate",
items={{}},
job_fields={job_type=df.job_type.ConstructGrate}
},
{
name="construct cabinet",
items={{}},
job_fields={job_type=df.job_type.ConstructCabinet}
},
{
name="construct chest",
items={{}},
job_fields={job_type=df.job_type.ConstructChest}
},
{
name="construct statue",
items={{}},
job_fields={job_type=df.job_type.ConstructStatue}
},
{
name="construct slab",
items={{}},
job_fields={job_type=df.job_type.ConstructSlab}
},
{
name="construct table",
items={{}},
job_fields={job_type=df.job_type.ConstructTable}
},
{
name="construct weapon rack",
items={{}},
job_fields={job_type=df.job_type.ConstructWeaponRack}
},
{
name="construct quern",
items={{}},
job_fields={job_type=df.job_type.ConstructQuern}
},
{
name="construct millstone",
items={{}},
job_fields={job_type=df.job_type.ConstructMillstone}
},
},
[df.workshop_type.Carpenters]={
--training weapons, wooden shields
defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD},
{
name="make barrel",
items={{}},
job_fields={job_type=df.job_type.MakeBarrel}
},
{
name="make bucket",
items={{}},
job_fields={job_type=df.job_type.MakeBucket}
},
{
name="make animal trap",
items={{}},
job_fields={job_type=df.job_type.MakeAnimalTrap}
},
{
name="make cage",
items={{}},
job_fields={job_type=df.job_type.MakeCage}
},
{
name="construct bed",
items={{}},
job_fields={job_type=df.job_type.ConstructBed}
},
{
name="construct bin",
items={{}},
job_fields={job_type=df.job_type.ConstructBin}
},
{
name="construct armor stand",
items={{}},
job_fields={job_type=df.job_type.ConstructArmorStand}
},
{
name="construct blocks",
items={{}},
job_fields={job_type=df.job_type.ConstructBlocks}
},
{
name="construct throne",
items={{}},
job_fields={job_type=df.job_type.ConstructThrone}
},
{
name="construct coffin",
items={{}},
job_fields={job_type=df.job_type.ConstructCoffin}
},
{
name="construct door",
items={{}},
job_fields={job_type=df.job_type.ConstructDoor}
},
{
name="construct floodgate",
items={{}},
job_fields={job_type=df.job_type.ConstructFloodgate}
},
{
name="construct hatch cover",
items={{}},
job_fields={job_type=df.job_type.ConstructHatchCover}
},
{
name="construct grate",
items={{}},
job_fields={job_type=df.job_type.ConstructGrate}
},
{
name="construct cabinet",
items={{}},
job_fields={job_type=df.job_type.ConstructCabinet}
},
{
name="construct chest",
items={{}},
job_fields={job_type=df.job_type.ConstructChest}
},
{
name="construct statue",
items={{}},
job_fields={job_type=df.job_type.ConstructStatue}
},
{
name="construct table",
items={{}},
job_fields={job_type=df.job_type.ConstructTable}
},
{
name="construct weapon rack",
items={{}},
job_fields={job_type=df.job_type.ConstructWeaponRack}
},
{
name="construct splint",
items={{}},
job_fields={job_type=df.job_type.ConstructSplint}
},
{
name="construct crutch",
items={{}},
job_fields={job_type=df.job_type.ConstructCrutch}
},
},
[df.workshop_type.Kitchen]={
--mat_type=2,3,4
defaults={flags1={unrotten=true,cookable=true}},
{
name="prepare easy meal",
items={{flags1={solid=true}},{}},
job_fields={job_type=df.job_type.PrepareMeal,mat_type=2}
},
{
name="prepare fine meal",
items={{flags1={solid=true}},{},{}},
job_fields={job_type=df.job_type.PrepareMeal,mat_type=3}
},
{
name="prepare lavish meal",
items={{flags1={solid=true}},{},{},{}},
job_fields={job_type=df.job_type.PrepareMeal,mat_type=4}
},
},
[df.workshop_type.Butchers]={
{
name="butcher an animal",
items={{flags1={butcherable=true,unrotten=true,nearby=true}}},
job_fields={job_type=df.job_type.ButcherAnimal}
},
{
name="extract from land animal",
items={{flags1={extract_bearing_vermin=true,unrotten=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}},
job_fields={job_type=df.job_type.ExtractFromLandAnimal}
},
{
name="catch live land animal",
items={},
job_fields={job_type=df.job_type.CatchLiveLandAnimal}
},
},
[df.workshop_type.Mechanics]={
{
name="construct mechanisms",
items={{item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,
flags3={hard=true}}},
job_fields={job_type=df.job_type.ConstructMechanisms}
},
{
name="construct traction bench",
items={{item_type=df.item_type.TABLE},{item_type=df.item_type.MECHANISM},{item_type=df.item_type.CHAIN}},
job_fields={job_type=df.job_type.ConstructTractionBench}
},
},
[df.workshop_type.Loom]={
{
name="weave plant thread cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={plant=true}}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="weave silk thread cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={silk=true}}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="weave yarn cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={yarn=true}}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="weave inorganic cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},mat_type=0}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="collect webs",
items={{item_type=df.item_type.THREAD,quantity=10,min_dimension=10,flags1={undisturbed=true}}},
job_fields={job_type=df.job_type.CollectWebs}
},
},
[df.workshop_type.Leatherworks]={
defaults={item_type=SKIN_TANNED},
{
name="construct leather bag",
items={{}},
job_fields={job_type=df.job_type.ConstructChest}
},
{
name="construct waterskin",
items={{}},
job_fields={job_type=df.job_type.MakeFlask}
},
{
name="construct backpack",
items={{}},
job_fields={job_type=df.job_type.MakeBackpack}
},
{
name="construct quiver",
items={{}},
job_fields={job_type=df.job_type.MakeQuiver}
},
{
name="sew leather image",
items={{item_type=-1,flags1={empty=true},flags2={sewn_imageless=true}},{}},
job_fields={job_type=df.job_type.SewImage}
},
},
[df.workshop_type.Dyers]={
{
name="dye thread",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={dyeable=true}},
{flags1={unrotten=true},flags2={dye=true}}},
job_fields={job_type=df.job_type.DyeThread}
},
{
name="dye cloth",
items={{item_type=df.item_type.CLOTH,quantity=10000,min_dimension=10000,flags2={dyeable=true}},
{flags1={unrotten=true},flags2={dye=true}}},
job_fields={job_type=df.job_type.DyeThread}
},
},
[df.workshop_type.Siege]={
{
name="construct balista parts",
items={{item_type=df.item_type.WOOD}},
job_fields={job_type=df.job_type.ConstructBallistaParts}
},
{
name="construct catapult parts",
items={{item_type=df.item_type.WOOD}},
job_fields={job_type=df.job_type.ConstructCatapultParts}
},
{
name="assemble balista arrow",
items={{item_type=df.item_type.WOOD}},
job_fields={job_type=df.job_type.AssembleSiegeAmmo}
},
{
name="assemble tipped balista arrow",
items={{item_type=df.item_type.WOOD},{item_type=df.item_type.BALLISTAARROWHEAD}},
job_fields={job_type=df.job_type.AssembleSiegeAmmo}
},
},
}
local function matchIds(bid1,wid1,cid1,bid2,wid2,cid2)
if bid1~=-1 and bid2~=-1 and bid1~=bid2 then
return false
end
if wid1~=-1 and wid2~=-1 and wid1~=wid2 then
return false
end
if cid1~=-1 and cid2~=-1 and cid1~=cid2 then
return false
end
return true
end
local function scanRawsReaction(buildingId,workshopId,customId)
local ret={}
for idx,reaction in ipairs(df.global.world.raws.reactions) do
for k,v in pairs(reaction.building.type) do
if matchIds(buildingId,workshopId,customId,v,reaction.building.subtype[k],reaction.building.custom[k]) then
table.insert(ret,reaction)
end
end
end
return ret
end
local function reagentToJobItem(reagent,react_id,reagentId)
local ret_item
ret_item=utils.clone_with_default(reagent, input_filter_defaults)
ret_item.reaction_id=react_id
ret_item.reagent_index=reagentId
return ret_item
end
local function addReactionJobs(ret,bid,wid,cid)
local reactions=scanRawsReaction(bid,wid or -1,cid or -1)
for idx,react in pairs(reactions) do
local job={name=react.name,
items={},job_fields={job_type=df.job_type.CustomReaction,reaction_name=react.code}
}
for reagentId,reagent in pairs(react.reagents) do
table.insert(job.items,reagentToJobItem(reagent,idx,reagentId))
end
if react.flags.FUEL then
table.insert(job.items,fuel)
end
table.insert(ret,job)
end
end
local function scanRawsOres()
local ret={}
for idx,ore in ipairs(df.global.world.raws.inorganics) do
if #ore.metal_ore.mat_index~=0 then
ret[idx]=ore
end
end
return ret
end
local function addSmeltJobs(ret,use_fuel)
local ores=scanRawsOres()
for idx,ore in pairs(ores) do
print("adding:",ore.material.state_name.Solid)
printall(ore)
local job={name="smelt "..ore.material.state_name.Solid,job_fields={job_type=df.job_type.SmeltOre,mat_type=df.builtin_mats.INORGANIC,mat_index=idx},items={
{item_type=df.item_type.BOULDER,mat_type=df.builtin_mats.INORGANIC,mat_index=idx,vector_id=df.job_item_vector_id.BOULDER}}}
if use_fuel then
table.insert(job.items,fuel)
end
table.insert(ret,job)
end
return ret
end
function getJobs(buildingId,workshopId,customId)
local ret={}
local c_jobs
if buildingId==df.building_type.Workshop then
c_jobs=jobs_workshop[workshopId]
elseif buildingId==df.building_type.Furnace then
c_jobs=jobs_furnace[workshopId]
if workshopId == df.furnace_type.Smelter or workshopId == df.furnace_type.MagmaSmelter then
c_jobs=utils.clone(c_jobs,true)
addSmeltJobs(c_jobs,workshopId == df.furnace_type.Smelter)
end
else
return nil
end
if c_jobs==nil then
c_jobs={}
else
c_jobs=utils.clone(c_jobs,true)
end
addReactionJobs(c_jobs,buildingId,workshopId,customId)
for jobId,contents in pairs(c_jobs) do
if jobId~="defaults" then
local entry={}
entry.name=contents.name
local lclDefaults=utils.clone(input_filter_defaults,true)
if c_jobs.defaults ~=nil then
utils.assign(lclDefaults,c_jobs.defaults)
end
entry.items={}
for k,item in pairs(contents.items) do
entry.items[k]=utils.clone(lclDefaults,true)
utils.assign(entry.items[k],item)
end
if contents.job_fields~=nil then
entry.job_fields={}
utils.assign(entry.job_fields,contents.job_fields)
end
ret[jobId]=entry
end
end
--get jobs, add in from raws
return ret
end
return _ENV

@ -0,0 +1,285 @@
-- Stock dialog for selecting buildings
local _ENV = mkmodule('gui.buildings')
local gui = require('gui')
local widgets = require('gui.widgets')
local dlg = require('gui.dialogs')
local utils = require('utils')
ARROW = string.char(26)
WORKSHOP_ABSTRACT={
[df.building_type.Civzone]=true,[df.building_type.Stockpile]=true,
}
WORKSHOP_SPECIAL={
[df.building_type.Workshop]=true,[df.building_type.Furnace]=true,[df.building_type.Trap]=true,
[df.building_type.Construction]=true,[df.building_type.SiegeEngine]=true
}
BuildingDialog = defclass(BuildingDialog, gui.FramedScreen)
BuildingDialog.focus_path = 'BuildingDialog'
BuildingDialog.ATTRS{
prompt = 'Type or select a building from this list',
frame_style = gui.GREY_LINE_FRAME,
frame_inset = 1,
frame_title = 'Select Building',
-- new attrs
none_caption = 'none',
hide_none = false,
use_abstract = true,
use_workshops = true,
use_tool_workshop=true,
use_furnace = true,
use_construction = true,
use_siege = true,
use_trap = true,
use_custom = true,
building_filter = DEFAULT_NIL,
on_select = DEFAULT_NIL,
on_cancel = DEFAULT_NIL,
on_close = DEFAULT_NIL,
}
function BuildingDialog:init(info)
self:addviews{
widgets.Label{
text = {
self.prompt, '\n\n',
'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN }
},
text_pen = COLOR_WHITE,
frame = { l = 0, t = 0 },
},
widgets.Label{
view_id = 'back',
visible = false,
text = { { key = 'LEAVESCREEN', text = ': Back' } },
frame = { r = 0, b = 0 },
auto_width = true,
},
widgets.FilteredList{
view_id = 'list',
not_found_label = 'No matching buildings',
frame = { l = 0, r = 0, t = 4, b = 2 },
icon_width = 2,
on_submit = self:callback('onSubmitItem'),
},
widgets.Label{
text = { {
key = 'SELECT', text = ': Select',
disabled = function() return not self.subviews.list:canSubmit() end
} },
frame = { l = 0, b = 0 },
}
}
self:initBuiltinMode()
end
function BuildingDialog:getWantedFrameSize(rect)
return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
end
function BuildingDialog:onDestroy()
if self.on_close then
self.on_close()
end
end
function BuildingDialog:initBuiltinMode()
local choices = {}
if not self.hide_none then
table.insert(choices, { text = self.none_caption, type_id = -1, subtype_id = -1, custom_id=-1})
end
if self.use_workshops then
table.insert(choices, {
icon = ARROW, text = 'workshop', key = 'CUSTOM_SHIFT_W',
cb = self:callback('initWorkshopMode')
})
end
if self.use_furnace then
table.insert(choices, {
icon = ARROW, text = 'furnaces', key = 'CUSTOM_SHIFT_F',
cb = self:callback('initFurnaceMode')
})
end
if self.use_trap then
table.insert(choices, {
icon = ARROW, text = 'traps', key = 'CUSTOM_SHIFT_T',
cb = self:callback('initTrapMode')
})
end
if self.use_construction then
table.insert(choices, {
icon = ARROW, text = 'constructions', key = 'CUSTOM_SHIFT_C',
cb = self:callback('initConstructionMode')
})
end
if self.use_siege then
table.insert(choices, {
icon = ARROW, text = 'siege engine', key = 'CUSTOM_SHIFT_S',
cb = self:callback('initSiegeMode')
})
end
if self.use_custom then
table.insert(choices, {
icon = ARROW, text = 'custom workshop', key = 'CUSTOM_SHIFT_U',
cb = self:callback('initCustomMode')
})
end
for i=0,df.building_type._last_item do
if (not WORKSHOP_ABSTRACT[i] or self.use_abstract)and not WORKSHOP_SPECIAL[i] then
self:addBuilding(choices, df.building_type[i], i, -1,-1,nil)
end
end
self:pushContext('Any building', choices)
end
function BuildingDialog:initWorkshopMode()
local choices = {}
for i=0,df.workshop_type._last_item do
if i~=df.workshop_type.Custom and (i~=df.workshop_type.Tool or self.use_tool_workshop) then
self:addBuilding(choices, df.workshop_type[i], df.building_type.Workshop, i,-1,nil)
end
end
self:pushContext('Workshops', choices)
end
function BuildingDialog:initTrapMode()
local choices = {}
for i=0,df.trap_type._last_item do
self:addBuilding(choices, df.trap_type[i], df.building_type.Trap, i,-1,nil)
end
self:pushContext('Traps', choices)
end
function BuildingDialog:initConstructionMode()
local choices = {}
for i=0,df.construction_type._last_item do
self:addBuilding(choices, df.construction_type[i], df.building_type.Construction, i,-1,nil)
end
self:pushContext('Constructions', choices)
end
function BuildingDialog:initFurnaceMode()
local choices = {}
for i=0,df.furnace_type._last_item do
self:addBuilding(choices, df.furnace_type[i], df.building_type.Furnace, i,-1,nil)
end
self:pushContext('Furnaces', choices)
end
function BuildingDialog:initSiegeMode()
local choices = {}
for i=0,df.siegeengine_type._last_item do
self:addBuilding(choices, df.siegeengine_type[i], df.building_type.SiegeEngine, i,-1,nil)
end
self:pushContext('Siege weapons', choices)
end
function BuildingDialog:initCustomMode()
local choices = {}
local raws=df.global.world.raws.buildings.all
for k,v in pairs(raws) do
self:addBuilding(choices, v.name, df.building_type.Workshop,df.workshop_type.Custom,v.id,v)
end
self:pushContext('Custom workshops', choices)
end
function BuildingDialog:addBuilding(choices, name,type_id, subtype_id, custom_id, parent)
-- Check the filter
if self.building_filter and not self.building_filter(name,type_id,subtype_id,custom_id, parent) then
return
end
table.insert(choices, {
text = name:lower(),
customshop = parent,
type_id = type_id, subtype_id = subtype_id, custom_id=custom_id
})
end
function BuildingDialog:pushContext(name, choices)
if not self.back_stack then
self.back_stack = {}
self.subviews.back.visible = false
else
table.insert(self.back_stack, {
context_str = self.context_str,
all_choices = self.subviews.list:getChoices(),
edit_text = self.subviews.list:getFilter(),
selected = self.subviews.list:getSelected(),
})
self.subviews.back.visible = true
end
self.context_str = name
self.subviews.list:setChoices(choices, 1)
end
function BuildingDialog:onGoBack()
local save = table.remove(self.back_stack)
self.subviews.back.visible = (#self.back_stack > 0)
self.context_str = save.context_str
self.subviews.list:setChoices(save.all_choices)
self.subviews.list:setFilter(save.edit_text, save.selected)
end
function BuildingDialog:submitBuilding(type_id,subtype_id,custom_id,choice,index)
self:dismiss()
if self.on_select then
self.on_select(type_id,subtype_id,custom_id,choice,index)
end
end
function BuildingDialog:onSubmitItem(idx, item)
if item.cb then
item:cb(idx)
else
self:submitBuilding(item.type_id, item.subtype_id,item.custom_id,item,idx)
end
end
function BuildingDialog:onInput(keys)
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
self:onGoBack()
else
self:dismiss()
if self.on_cancel then
self.on_cancel()
end
end
else
self:inputToSubviews(keys)
end
end
function showBuildingPrompt(title, prompt, on_select, on_cancel, build_filter)
BuildingDialog{
frame_title = title,
prompt = prompt,
building_filter = build_filter,
on_select = on_select,
on_cancel = on_cancel,
}:show()
end
return _ENV

@ -25,11 +25,16 @@ distribution.
#include "Internal.h"
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
#include <map>
using namespace std;
#include "ColorText.h"
#include "VersionInfo.h"
#include "MemAccess.h"
#include "Types.h"
@ -77,6 +82,14 @@ using df::global::building_next_id;
using df::global::process_jobs;
using df::building_def;
struct CoordHash {
size_t operator()(const df::coord pos) const {
return pos.x*65537 + pos.y*17 + pos.z;
}
};
static unordered_map<df::coord, int32_t, CoordHash> locationToBuilding;
static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile)
{
if (!extent.extents)
@ -236,6 +249,21 @@ df::building *Buildings::findAtTile(df::coord pos)
if (!occ || !occ->bits.building)
return NULL;
// Try cache lookup in case it works:
auto cached = locationToBuilding.find(pos);
if (cached != locationToBuilding.end())
{
auto building = df::building::find(cached->second);
if (building && building->z == pos.z &&
building->isSettingOccupancy() &&
containsTile(building, pos, false))
{
return building;
}
}
// The authentic method, i.e. how the game generally does this:
auto &vec = df::building::get_vector();
for (size_t i = 0; i < vec.size(); i++)
{
@ -1077,3 +1105,52 @@ bool Buildings::deconstruct(df::building *bld)
return true;
}
static unordered_map<int32_t, df::coord> corner1;
static unordered_map<int32_t, df::coord> corner2;
void Buildings::clearBuildings(color_ostream& out) {
corner1.clear();
corner2.clear();
locationToBuilding.clear();
}
void Buildings::updateBuildings(color_ostream& out, void* ptr) {
//out.print("Updating buildings, %s %d\n", __FILE__, __LINE__);
int32_t id = (int32_t)ptr;
if ( corner1.find(id) == corner1.end() ) {
//new building: mark stuff
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, id);
if ( index == -1 ) {
out.print("%s, line %d: Couldn't find new building id=%d.\n", __FILE__, __LINE__, id);
exit(1);
}
df::building* building = df::global::world->buildings.all[index];
df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z);
df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z);
corner1[id] = p1;
corner2[id] = p2;
for ( int32_t x = p1.x; x <= p2.x; x++ ) {
for ( int32_t y = p1.y; y <= p2.y; y++ ) {
df::coord pt(x,y,building->z);
locationToBuilding[pt] = id;
}
}
} else {
//existing building: destroy it
df::coord p1 = corner1[id];
df::coord p2 = corner2[id];
for ( int32_t x = p1.x; x <= p2.x; x++ ) {
for ( int32_t y = p1.y; y <= p2.y; y++ ) {
df::coord pt(x,y,p1.z);
locationToBuilding.erase(pt);
}
}
corner1.erase(id);
corner2.erase(id);
}
}

@ -0,0 +1,503 @@
#include "Core.h"
#include "Console.h"
#include "modules/Buildings.h"
#include "modules/Constructions.h"
#include "modules/EventManager.h"
#include "modules/Job.h"
#include "modules/World.h"
#include "df/building.h"
#include "df/construction.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/job.h"
#include "df/job_list_link.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/unit_syndrome.h"
#include "df/world.h"
#include <map>
#include <unordered_map>
#include <unordered_set>
using namespace std;
using namespace DFHack;
using namespace EventManager;
/*
* TODO:
* error checking
* consider a typedef instead of a struct for EventHandler
**/
//map<uint32_t, vector<DFHack::EventManager::EventHandler> > tickQueue;
multimap<uint32_t, EventHandler> tickQueue;
//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever
multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
uint32_t eventLastTick[EventType::EVENT_MAX];
const uint32_t ticksPerYear = 403200;
void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) {
handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler));
}
void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
if ( !Core::getInstance().isWorldLoaded() ) {
tick = 0;
if ( absolute ) {
Core::getInstance().getConsole().print("Warning: absolute flag will not be honored.\n");
}
}
if ( absolute ) {
tick = 0;
}
tickQueue.insert(pair<uint32_t, EventHandler>(tick+(uint32_t)when, handler));
handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,handler));
return;
}
void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) {
for ( multimap<Plugin*, EventHandler>::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) {
if ( (*i).first != plugin )
break;
EventHandler handle = (*i).second;
if ( handle == handler ) {
handlers[e].erase(i);
break;
}
}
return;
}
void DFHack::EventManager::unregisterAll(Plugin* plugin) {
for ( auto i = handlers[EventType::TICK].find(plugin); i != handlers[EventType::TICK].end(); i++ ) {
if ( (*i).first != plugin )
break;
//shenanigans to avoid concurrent modification
EventHandler getRidOf = (*i).second;
bool didSomething;
do {
didSomething = false;
for ( auto j = tickQueue.begin(); j != tickQueue.end(); j++ ) {
EventHandler candidate = (*j).second;
if ( getRidOf != candidate )
continue;
tickQueue.erase(j);
didSomething = true;
break;
}
} while(didSomething);
}
for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) {
handlers[a].erase(plugin);
}
return;
}
static void manageTickEvent(color_ostream& out);
static void manageJobInitiatedEvent(color_ostream& out);
static void manageJobCompletedEvent(color_ostream& out);
static void manageUnitDeathEvent(color_ostream& out);
static void manageItemCreationEvent(color_ostream& out);
static void manageBuildingEvent(color_ostream& out);
static void manageConstructionEvent(color_ostream& out);
static void manageSyndromeEvent(color_ostream& out);
static void manageInvasionEvent(color_ostream& out);
//tick event
static uint32_t lastTick = 0;
//job initiated
static int32_t lastJobId = -1;
//job completed
static unordered_map<int32_t, df::job*> prevJobs;
//unit death
static unordered_set<int32_t> livingUnits;
//item creation
static int32_t nextItem;
//building
static int32_t nextBuilding;
static unordered_set<int32_t> buildings;
//construction
static unordered_set<df::construction*> constructions;
static bool gameLoaded;
//invasion
static int32_t nextInvasion;
void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) {
static bool doOnce = false;
if ( !doOnce ) {
//TODO: put this somewhere else
doOnce = true;
EventHandler buildingHandler(Buildings::updateBuildings, 100);
DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL);
//out.print("Registered listeners.\n %d", __LINE__);
}
if ( event == DFHack::SC_WORLD_UNLOADED ) {
lastTick = 0;
lastJobId = -1;
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
}
prevJobs.clear();
tickQueue.clear();
livingUnits.clear();
nextItem = -1;
nextBuilding = -1;
buildings.clear();
constructions.clear();
Buildings::clearBuildings(out);
gameLoaded = false;
nextInvasion = -1;
} else if ( event == DFHack::SC_WORLD_LOADED ) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
multimap<uint32_t,EventHandler> newTickQueue;
for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ ) {
newTickQueue.insert(pair<uint32_t,EventHandler>(tick + (*i).first, (*i).second));
}
tickQueue.clear();
tickQueue.insert(newTickQueue.begin(), newTickQueue.end());
nextItem = 0;
nextBuilding = 0;
lastTick = 0;
nextInvasion = df::global::ui->invasions.next_id;
gameLoaded = true;
}
}
void DFHack::EventManager::manageEvents(color_ostream& out) {
if ( !gameLoaded ) {
return;
}
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
if ( tick <= lastTick )
return;
lastTick = tick;
int32_t eventFrequency[EventType::EVENT_MAX];
for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) {
int32_t min = 1000000000;
for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) {
EventHandler bob = (*b).second;
if ( bob.freq < min )
min = bob.freq;
}
eventFrequency[a] = min;
}
manageTickEvent(out);
if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) {
manageJobInitiatedEvent(out);
eventLastTick[EventType::JOB_INITIATED] = tick;
}
if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) {
manageJobCompletedEvent(out);
eventLastTick[EventType::JOB_COMPLETED] = tick;
}
if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) {
manageUnitDeathEvent(out);
eventLastTick[EventType::UNIT_DEATH] = tick;
}
if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) {
manageItemCreationEvent(out);
eventLastTick[EventType::ITEM_CREATED] = tick;
}
if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) {
manageBuildingEvent(out);
eventLastTick[EventType::BUILDING] = tick;
}
if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) {
manageConstructionEvent(out);
eventLastTick[EventType::CONSTRUCTION] = tick;
}
if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) {
manageSyndromeEvent(out);
eventLastTick[EventType::SYNDROME] = tick;
}
if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) {
manageInvasionEvent(out);
eventLastTick[EventType::INVASION] = tick;
}
return;
}
static void manageTickEvent(color_ostream& out) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
while ( !tickQueue.empty() ) {
if ( tick < (*tickQueue.begin()).first )
break;
EventHandler handle = (*tickQueue.begin()).second;
tickQueue.erase(tickQueue.begin());
handle.eventHandler(out, (void*)tick);
}
}
static void manageJobInitiatedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_INITIATED].empty() )
return;
if ( lastJobId == -1 ) {
lastJobId = *df::global::job_next_id - 1;
return;
}
if ( lastJobId+1 == *df::global::job_next_id ) {
return; //no new jobs
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_INITIATED].begin(), handlers[EventType::JOB_INITIATED].end());
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
if ( link->item->id <= lastJobId )
continue;
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
(*i).second.eventHandler(out, (void*)link->item);
}
}
lastJobId = *df::global::job_next_id - 1;
}
static void manageJobCompletedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_COMPLETED].empty() ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
map<int32_t, df::job*> nowJobs;
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
nowJobs[link->item->id] = link->item;
}
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
if ( nowJobs.find((*i).first) != nowJobs.end() )
continue;
//recently finished or cancelled job!
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
(*j).second.eventHandler(out, (void*)(*i).second);
}
}
//erase old jobs, copy over possibly altered jobs
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
}
prevJobs.clear();
//create new jobs
for ( auto j = nowJobs.begin(); j != nowJobs.end(); j++ ) {
/*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first);
if ( i != prevJobs.end() ) {
continue;
}*/
df::job* newJob = Job::cloneJobStruct((*j).second, true);
prevJobs[newJob->id] = newJob;
}
/*//get rid of old pointers to deallocated jobs
for ( size_t a = 0; a < toDelete.size(); a++ ) {
prevJobs.erase(a);
}*/
}
static void manageUnitDeathEvent(color_ostream& out) {
if ( handlers[EventType::UNIT_DEATH].empty() ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[a];
if ( unit->counters.death_id == -1 ) {
livingUnits.insert(unit->id);
continue;
}
//dead: if dead since last check, trigger events
if ( livingUnits.find(unit->id) == livingUnits.end() )
continue;
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
(*i).second.eventHandler(out, (void*)unit->id);
}
livingUnits.erase(unit->id);
}
}
static void manageItemCreationEvent(color_ostream& out) {
if ( handlers[EventType::ITEM_CREATED].empty() ) {
return;
}
if ( nextItem >= *df::global::item_next_id ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end());
size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false);
for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) {
df::item* item = df::global::world->items.all[a];
//invaders
if ( item->flags.bits.foreign )
continue;
//traders who bring back your items?
if ( item->flags.bits.trader )
continue;
//migrants
if ( item->flags.bits.owned )
continue;
//spider webs don't count
if ( item->flags.bits.spider_web )
continue;
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
(*i).second.eventHandler(out, (void*)item->id);
}
}
nextItem = *df::global::item_next_id;
}
static void manageBuildingEvent(color_ostream& out) {
/*
* TODO: could be faster
* consider looking at jobs: building creation / destruction
**/
if ( handlers[EventType::BUILDING].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) {
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a);
if ( index == -1 ) {
//out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a);
//the tricky thing is that when the game first starts, it's ok to skip buildings, but otherwise, if you skip buildings, something is probably wrong. TODO: make this smarter
continue;
}
buildings.insert(a);
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler bob = (*b).second;
bob.eventHandler(out, (void*)a);
}
}
nextBuilding = *df::global::building_next_id;
//now alert people about destroyed buildings
unordered_set<int32_t> toDelete;
for ( auto a = buildings.begin(); a != buildings.end(); a++ ) {
int32_t id = *a;
int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id);
if ( index != -1 )
continue;
toDelete.insert(id);
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler bob = (*b).second;
bob.eventHandler(out, (void*)id);
}
}
for ( auto a = toDelete.begin(); a != toDelete.end(); a++ ) {
int32_t id = *a;
buildings.erase(id);
}
//out.print("Sent building event.\n %d", __LINE__);
}
static void manageConstructionEvent(color_ostream& out) {
if ( handlers[EventType::CONSTRUCTION].empty() )
return;
unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end());
multimap<Plugin*,EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end());
for ( auto a = constructions.begin(); a != constructions.end(); a++ ) {
df::construction* construction = *a;
if ( constructionsNow.find(construction) != constructionsNow.end() )
continue;
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler handle = (*b).second;
handle.eventHandler(out, (void*)construction);
}
}
for ( auto a = constructionsNow.begin(); a != constructionsNow.end(); a++ ) {
df::construction* construction = *a;
if ( constructions.find(construction) != constructions.end() )
continue;
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler handle = (*b).second;
handle.eventHandler(out, (void*)construction);
}
}
constructions.clear();
constructions.insert(constructionsNow.begin(), constructionsNow.end());
}
static void manageSyndromeEvent(color_ostream& out) {
if ( handlers[EventType::SYNDROME].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) {
df::unit* unit = *a;
if ( unit->flags1.bits.dead )
continue;
for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) {
df::unit_syndrome* syndrome = unit->syndromes.active[b];
uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time;
if ( startTime <= eventLastTick[EventType::SYNDROME] )
continue;
SyndromeData data(unit->id, b);
for ( auto c = copy.begin(); c != copy.end(); c++ ) {
EventHandler handle = (*c).second;
handle.eventHandler(out, (void*)&data);
}
}
}
}
static void manageInvasionEvent(color_ostream& out) {
if ( handlers[EventType::INVASION].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end());
if ( df::global::ui->invasions.next_id <= nextInvasion )
return;
nextInvasion = df::global::ui->invasions.next_id;
for ( auto a = copy.begin(); a != copy.end(); a++ ) {
EventHandler handle = (*a).second;
handle.eventHandler(out, (void*)nextInvasion);
}
}

@ -55,7 +55,7 @@ using namespace std;
using namespace DFHack;
using namespace df::enums;
df::job *DFHack::Job::cloneJobStruct(df::job *job)
df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepWorkerData)
{
CHECK_NULL_POINTER(job);
@ -76,7 +76,7 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job)
{
df::general_ref *ref = pnew->general_refs[i];
if (virtual_cast<df::general_ref_unit>(ref))
if (!keepWorkerData && virtual_cast<df::general_ref_unit_workerst>(ref))
vector_erase_at(pnew->general_refs, i);
else
pnew->general_refs[i] = ref->clone();

@ -30,10 +30,12 @@ distribution.
#include <map>
#include <set>
#include <cstdlib>
#include <iostream>
using namespace std;
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "ColorText.h"
#include "Error.h"
#include "VersionInfo.h"
#include "MemAccess.h"
@ -60,6 +62,7 @@ using namespace std;
#include "df/region_map_entry.h"
#include "df/flow_info.h"
#include "df/plant.h"
#include "df/building_type.h"
using namespace DFHack;
using namespace df::enums;
@ -536,6 +539,129 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2)
return tile1 && tile1 == tile2;
}
bool Maps::canStepBetween(df::coord pos1, df::coord pos2)
{
color_ostream& out = Core::getInstance().getConsole();
int32_t dx = pos2.x-pos1.x;
int32_t dy = pos2.y-pos1.y;
int32_t dz = pos2.z-pos1.z;
if ( dx*dx > 1 || dy*dy > 1 || dz*dz > 1 )
return false;
if ( pos2.z < pos1.z ) {
df::coord temp = pos1;
pos1 = pos2;
pos2 = temp;
}
df::map_block* block1 = getTileBlock(pos1);
df::map_block* block2 = getTileBlock(pos2);
if ( !block1 || !block2 )
return false;
if ( !index_tile<uint16_t>(block1->walkable,pos1) || !index_tile<uint16_t>(block2->walkable,pos2) ) {
return false;
}
if ( dz == 0 )
return true;
df::tiletype* type1 = Maps::getTileType(pos1);
df::tiletype* type2 = Maps::getTileType(pos2);
df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type1);
df::tiletype_shape shape2 = ENUM_ATTR(tiletype,shape,*type2);
if ( dx == 0 && dy == 0 ) {
//check for forbidden hatches and floors and such
df::enums::tile_building_occ::tile_building_occ upOcc = index_tile<df::tile_occupancy>(block2->occupancy,pos2).bits.building;
if ( upOcc == df::enums::tile_building_occ::Impassable || upOcc == df::enums::tile_building_occ::Obstacle || upOcc == df::enums::tile_building_occ::Floored )
return false;
if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == shape1 )
return true;
if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == tiletype_shape::STAIR_DOWN )
return true;
if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_UPDOWN )
return true;
if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_DOWN )
return true;
if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP_TOP ) {
//it depends
//there has to be a wall next to the ramp
bool foundWall = false;
for ( int32_t x = -1; x <= 1; x++ ) {
for ( int32_t y = -1; y <= 1; y++ ) {
if ( x == 0 && y == 0 )
continue;
df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z));
df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type);
if ( shape1 == tiletype_shape::WALL ) {
foundWall = true;
x = 2;
break;
}
}
}
if ( !foundWall )
return false; //unusable ramp
//there has to be an unforbidden hatch above the ramp
if ( index_tile<df::tile_occupancy>(block2->occupancy,pos2).bits.building != df::enums::tile_building_occ::Dynamic )
return false;
//note that forbidden hatches have Floored occupancy. unforbidden ones have dynamic occupancy
df::building* building = Buildings::findAtTile(pos2);
if ( building == NULL ) {
out << __FILE__ << ", line " << __LINE__ << ": couldn't find hatch.\n";
return false;
}
if ( building->getType() != df::enums::building_type::Hatch ) {
return false;
}
return true;
}
return false;
}
//diagonal up: has to be a ramp
if ( shape1 == tiletype_shape::RAMP /*&& shape2 == tiletype_shape::RAMP*/ ) {
df::coord up = df::coord(pos1.x,pos1.y,pos1.z+1);
bool foundWall = false;
for ( int32_t x = -1; x <= 1; x++ ) {
for ( int32_t y = -1; y <= 1; y++ ) {
if ( x == 0 && y == 0 )
continue;
df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z));
df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type);
if ( shape1 == tiletype_shape::WALL ) {
foundWall = true;
x = 2;
break;
}
}
}
if ( !foundWall )
return false; //unusable ramp
df::tiletype* typeUp = Maps::getTileType(up);
df::tiletype_shape shapeUp = ENUM_ATTR(tiletype,shape,*typeUp);
if ( shapeUp != tiletype_shape::RAMP_TOP )
return false;
df::map_block* blockUp = getTileBlock(up);
if ( !blockUp )
return false;
df::enums::tile_building_occ::tile_building_occ occupancy = index_tile<df::tile_occupancy>(blockUp->occupancy,up).bits.building;
if ( occupancy == df::enums::tile_building_occ::Obstacle || occupancy == df::enums::tile_building_occ::Floored || occupancy == df::enums::tile_building_occ::Impassable )
return false;
return true;
}
return false;
}
#define COPY(a,b) memcpy(&a,&b,sizeof(a))
MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)

@ -766,7 +766,7 @@ bool Materials::ReadCreatureTypesEx (void)
for(size_t k = 0; k < sizecolormod;k++)
{
// color mod [0] -> color list
auto & indexes = colorings[k]->color_indexes;
auto & indexes = colorings[k]->pattern_index;
size_t sizecolorlist = indexes.size();
caste.ColorModifier[k].colorlist.resize(sizecolorlist);
for(size_t l = 0; l < sizecolorlist; l++)

@ -785,10 +785,10 @@ bool DFHack::Units::isSane(df::unit *unit)
if (unit->flags1.bits.dead ||
unit->flags3.bits.ghostly ||
isOpposedToLife(unit) ||
unit->unknown8.unk2)
unit->enemy.undead)
return false;
if (unit->unknown8.normal_race == unit->unknown8.were_race && isCrazed(unit))
if (unit->enemy.normal_race == unit->enemy.were_race && isCrazed(unit))
return false;
switch (unit->mood)
@ -839,7 +839,7 @@ bool DFHack::Units::isDwarf(df::unit *unit)
CHECK_NULL_POINTER(unit);
return unit->race == ui->race_id ||
unit->unknown8.normal_race == ui->race_id;
unit->enemy.normal_race == ui->race_id;
}
double DFHack::Units::getAge(df::unit *unit, bool true_age)
@ -1225,12 +1225,12 @@ int Units::computeMovementSpeed(df::unit *unit)
// Stance
if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2)
if (!unit->flags1.bits.on_ground && unit->status2.limbs_stand_max > 2)
{
// WTF
int as = unit->status2.able_stand;
int as = unit->status2.limbs_stand_max;
int x = (as-1) - (as>>1);
int y = as - unit->status2.able_stand_impair;
int y = as - unit->status2.limbs_stand_count;
if (unit->flags3.bits.on_crutch) y--;
y = y * 500 / x;
if (y > 0) speed += y;

@ -125,13 +125,17 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(steam-engine steam-engine.cpp)
DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(reactionhooks reactionhooks.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(add-spatter add-spatter.cpp)
DFHACK_PLUGIN(fix-armory fix-armory.cpp)
# not yet. busy with other crud again...
#DFHACK_PLUGIN(versionosd versionosd.cpp)
DFHACK_PLUGIN(misery misery.cpp)
DFHACK_PLUGIN(workNow workNow.cpp)
#DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread)
DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp)
DFHACK_PLUGIN(trueTransformation trueTransformation.cpp)
DFHACK_PLUGIN(infiniteSky infiniteSky.cpp)
endif()

@ -0,0 +1,476 @@
#include "PluginManager.h"
#include "Export.h"
#include "DataDefs.h"
#include "Core.h"
#include "modules/EventManager.h"
#include "modules/Job.h"
#include "modules/Maps.h"
#include "df/building.h"
#include "df/caste_raw.h"
#include "df/creature_raw.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/item_boulderst.h"
#include "df/job.h"
#include "df/job_type.h"
#include "df/reaction.h"
#include "df/reaction_product.h"
#include "df/reaction_product_type.h"
#include "df/reaction_product_itemst.h"
#include "df/syndrome.h"
#include "df/unit_syndrome.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/general_ref.h"
#include "df/general_ref_building_holderst.h"
#include "df/general_ref_type.h"
#include "df/general_ref_unit_workerst.h"
#include <string>
#include <vector>
#include <unordered_set>
#include <unordered_map>
using namespace std;
using namespace DFHack;
/*
Example usage:
//////////////////////////////////////////////
//In file inorganic_duck.txt
inorganic_stone_duck
[OBJECT:INORGANIC]
[INORGANIC:DUCK_ROCK]
[USE_MATERIAL_TEMPLATE:STONE_TEMPLATE]
[STATE_NAME_ADJ:ALL_SOLID:drakium][DISPLAY_COLOR:0:7:0][TILE:'.']
[IS_STONE]
[SOLID_DENSITY:1][MELTING_POINT:25000]
[BOILING_POINT:9999] //This is the critical line: boiling point must be <= 10000
[SYNDROME]
[SYN_NAME:Chronic Duck Syndrome]
[CE_BODY_TRANSFORMATION:PROB:100:START:0]
[CE:CREATURE:BIRD_DUCK:MALE] //even though we don't have SYN_INHALED, the plugin will add it
///////////////////////////////////////////////
//In file building_duck.txt
building_duck
[OBJECT:BUILDING]
[BUILDING_WORKSHOP:DUCK_WORKSHOP]
[NAME:Duck Workshop]
[NAME_COLOR:7:0:1]
[DIM:1:1]
[WORK_LOCATION:1:1]
[BLOCK:1:0:0:0]
[TILE:0:1:236]
[COLOR:0:1:0:0:1]
[TILE:1:1:' ']
[COLOR:1:1:0:0:0]
[TILE:2:1:8]
[COLOR:2:1:0:0:1]
[TILE:3:1:8]
[COLOR:3:2:0:4:1]
[BUILD_ITEM:1:NONE:NONE:NONE:NONE]
[BUILDMAT]
[WORTHLESS_STONE_ONLY]
[CAN_USE_ARTIFACT]
///////////////////////////////////////////////
//In file reaction_duck.txt
reaction_duck
[OBJECT:REACTION]
[REACTION:DUCKIFICATION]
[NAME:become a duck]
[BUILDING:DUCK_WORKSHOP:NONE]
[PRODUCT:100:100:STONE:NO_SUBTYPE:STONE:DUCK_ROCK]
//////////////////////////////////////////////
//Add the following lines to your entity in entity_default.txt (or wherever it is)
[PERMITTED_BUILDING:DUCK_WORKSHOP]
[PERMITTED_REACTION:DUCKIFICATION]
//////////////////////////////////////////////
Next, start a new fort in a new world, build a duck workshop, then have someone become a duck.
*/
bool enabled = true;
DFHACK_PLUGIN("autoSyndrome");
command_result autoSyndrome(color_ostream& out, vector<string>& parameters);
void processJob(color_ostream& out, void* jobPtr);
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome);
DFhackCExport command_result plugin_init(color_ostream& out, vector<PluginCommand> &commands) {
commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false,
"autoSyndrome:\n"
" autoSyndrome 0 //disable\n"
" autoSyndrome 1 //enable\n"
" autoSyndrome disable //disable\n"
" autoSyndrome enable //enable\n"
"\n"
"autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n"
"\n"
"Requirements:\n"
" 1) The job must be a custom reaction.\n"
" 2) The job must produce a stone of some inorganic material.\n"
" 3) The stone must have a boiling temperature less than or equal to 9000.\n"
"\n"
"When these conditions are met, the unit that completed the job will immediately become afflicted with all applicable syndromes associated with the inorganic material of the stone, or stones. It should correctly check for whether the creature or caste is affected or immune, and it should also correctly account for affected and immune creature classes.\n"
"Multiple syndromes per stone, or multiple boiling rocks produced with the same reaction should work fine.\n"
));
EventManager::EventHandler handle(processJob, 5);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream& out) {
return CR_OK;
}
/*DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event e) {
return CR_OK;
}*/
command_result autoSyndrome(color_ostream& out, vector<string>& parameters) {
if ( parameters.size() > 1 )
return CR_WRONG_USAGE;
bool wasEnabled = enabled;
if ( parameters.size() == 1 ) {
if ( parameters[0] == "enable" ) {
enabled = true;
} else if ( parameters[0] == "disable" ) {
enabled = false;
} else {
int32_t a = atoi(parameters[0].c_str());
if ( a < 0 || a > 1 )
return CR_WRONG_USAGE;
enabled = (bool)a;
}
}
out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled");
if ( enabled == wasEnabled )
return CR_OK;
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome");
if ( enabled ) {
EventManager::EventHandler handle(processJob, 5);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me);
} else {
EventManager::unregisterAll(me);
}
return CR_OK;
}
bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df::unit* unit) {
df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race];
df::caste_raw* caste = creature->caste[unit->caste];
std::string& creature_name = creature->creature_id;
std::string& creature_caste = caste->caste_id;
//check that the syndrome applies to that guy
/*
* If there is no affected class or affected creature, then anybody who isn't immune is fair game.
*
* Otherwise, it works like this:
* add all the affected class creatures
* add all the affected creatures
* remove all the immune class creatures
* remove all the immune creatures
* you're affected if and only if you're in the remaining list after all of that
**/
bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature.size() == 0;
for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) {
if ( applies )
break;
for ( size_t d = 0; d < caste->creature_class.size(); d++ ) {
if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) {
applies = true;
break;
}
}
}
for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) {
if ( !applies )
break;
for ( size_t d = 0; d < caste->creature_class.size(); d++ ) {
if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) {
applies = false;
return false;
}
}
}
if ( syndrome->syn_affected_creature.size() != syndrome->syn_affected_caste.size() ) {
out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__);
return false;
}
for ( size_t c = 0; c < syndrome->syn_affected_creature.size(); c++ ) {
if ( creature_name != *syndrome->syn_affected_creature[c] )
continue;
if ( *syndrome->syn_affected_caste[c] == "ALL" ||
*syndrome->syn_affected_caste[c] == creature_caste ) {
applies = true;
break;
}
}
for ( size_t c = 0; c < syndrome->syn_immune_creature.size(); c++ ) {
if ( creature_name != *syndrome->syn_immune_creature[c] )
continue;
if ( *syndrome->syn_immune_caste[c] == "ALL" ||
*syndrome->syn_immune_caste[c] == creature_caste ) {
applies = false;
return false;
}
}
if ( !applies ) {
return false;
}
if ( giveSyndrome(out, workerId, syndrome) < 0 )
return false;
return true;
}
void processJob(color_ostream& out, void* jobPtr) {
df::job* job = (df::job*)jobPtr;
if ( job == NULL ) {
out.print("Error %s line %d: null job.\n", __FILE__, __LINE__);
return;
}
if ( job->completion_timer > 0 )
return;
if ( job->job_type != df::job_type::CustomReaction )
return;
df::reaction* reaction = NULL;
for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) {
df::reaction* candidate = df::global::world->raws.reactions[a];
if ( candidate->code != job->reaction_name )
continue;
reaction = candidate;
break;
}
if ( reaction == NULL ) {
out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() );
return;
}
int32_t workerId = -1;
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
continue;
if ( workerId != -1 ) {
out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__);
}
workerId = ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id;
if (workerId == -1) {
out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__);
continue;
}
}
int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId);
if ( workerIndex < 0 ) {
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId);
return;
}
df::unit* worker = df::global::world->units.all[workerIndex];
//find the building that made it
int32_t buildingId = -1;
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER )
continue;
if ( buildingId != -1 ) {
out.print("%s, line %d: Found two buildings for the same job.\n", __FILE__, __LINE__);
}
buildingId = ((df::general_ref_building_holderst*)job->general_refs[a])->building_id;
if (buildingId == -1) {
out.print("%s, line %d: invalid building.\n", __FILE__, __LINE__);
continue;
}
}
df::building* building;
{
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, buildingId);
if ( index == -1 ) {
out.print("%s, line %d: error: couldn't find building %d.\n", __FILE__, __LINE__, buildingId);
return;
}
building = df::global::world->buildings.all[index];
}
//find all of the products it makes. Look for a stone with a low boiling point.
bool appliedSomething = false;
for ( size_t a = 0; a < reaction->products.size(); a++ ) {
df::reaction_product_type type = reaction->products[a]->getType();
//out.print("type = %d\n", (int32_t)type);
if ( type != df::enums::reaction_product_type::item )
continue;
df::reaction_product_itemst* bob = (df::reaction_product_itemst*)reaction->products[a];
//out.print("item_type = %d\n", (int32_t)bob->item_type);
if ( bob->item_type != df::enums::item_type::BOULDER )
continue;
//for now don't worry about subtype
//must be a boiling rock syndrome
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index];
if ( inorganic->material.heat.boiling_point > 9000 ) {
continue;
}
for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) {
//add each syndrome to the guy who did the job
df::syndrome* syndrome = inorganic->material.syndrome[b];
bool workerOnly = false;
bool allowMultipleTargets = false;
bool foundCommand = false;
bool destroyRock = true;
string commandStr;
vector<string> args;
for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) {
std::string* clazz = syndrome->syn_class[c];
if ( foundCommand ) {
if ( commandStr == "" ) {
if ( *clazz == "\\WORKER_ONLY" ) {
workerOnly = true;
} else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) {
allowMultipleTargets = true;
} else if ( *clazz == "\\PRESERVE_ROCK" ) {
destroyRock = false;
}
else {
commandStr = *clazz;
}
} else {
stringstream bob;
if ( *clazz == "\\LOCATION" ) {
bob << job->pos.x;
args.push_back(bob.str());
bob.str("");
bob.clear();
bob << job->pos.y;
args.push_back(bob.str());
bob.str("");
bob.clear();
bob << job->pos.z;
args.push_back(bob.str());
bob.str("");
bob.clear();
} else if ( *clazz == "\\WORKER_ID" ) {
bob << workerId;
args.push_back(bob.str());
} else if ( *clazz == "\\REACTION_INDEX" ) {
bob << reaction->index;
args.push_back(bob.str());
} else {
args.push_back(*clazz);
}
}
} else if ( *clazz == "\\COMMAND" ) {
foundCommand = true;
}
}
if ( commandStr != "" ) {
Core::getInstance().runCommand(out, commandStr, args);
}
if ( destroyRock ) {
//find the rock and kill it before it can boil and cause problems and ugliness
for ( size_t c = 0; c < df::global::world->items.all.size(); c++ ) {
df::item* item = df::global::world->items.all[c];
if ( item->pos.z != building->z )
continue;
if ( item->pos.x < building->x1 || item->pos.x > building->x2 )
continue;
if ( item->pos.y < building->y1 || item->pos.y > building->y2 )
continue;
if ( item->getType() != df::enums::item_type::BOULDER )
continue;
//make sure it's the right type of boulder
df::item_boulderst* boulder = (df::item_boulderst*)item;
if ( boulder->mat_index != bob->mat_index )
continue;
boulder->flags.bits.garbage_collect = true;
boulder->flags.bits.forbid = true;
boulder->flags.bits.hidden = true;
}
}
//only one syndrome per reaction will be applied, unless multiples are allowed.
if ( appliedSomething && !allowMultipleTargets )
continue;
if ( maybeApply(out, syndrome, workerId, worker) ) {
appliedSomething = true;
}
if ( workerOnly )
continue;
//now try applying it to everybody inside the building
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[a];
if ( unit == worker )
continue;
if ( unit->pos.z != building->z )
continue;
if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 )
continue;
if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 )
continue;
if ( maybeApply(out, syndrome, unit->id, unit) ) {
appliedSomething = true;
if ( !allowMultipleTargets )
break;
}
}
}
}
return;
}
/*
* Heavily based on https://gist.github.com/4061959/
**/
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome) {
int32_t index = df::unit::binsearch_index(df::global::world->units.all, workerId);
if ( index < 0 ) {
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId);
return -1;
}
df::unit* unit = df::global::world->units.all[index];
df::unit_syndrome* unitSyndrome = new df::unit_syndrome();
unitSyndrome->type = syndrome->id;
unitSyndrome->year = 0;
unitSyndrome->year_time = 0;
unitSyndrome->ticks = 1;
unitSyndrome->unk1 = 1;
unitSyndrome->flags = 0; //typecast
for ( size_t a = 0; a < syndrome->ce.size(); a++ ) {
df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms();
symptom->unk1 = 0;
symptom->unk2 = 0;
symptom->ticks = 1;
symptom->flags = 2; //TODO: ???
unitSyndrome->symptoms.push_back(symptom);
}
unit->syndromes.active.push_back(unitSyndrome);
return 0;
}

@ -19,6 +19,9 @@ DFHACK_PLUGIN(rprobe rprobe.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp)
DFHACK_PLUGIN(vshook vshook.cpp)
DFHACK_PLUGIN(autolabor2 autolabor2.cpp)
DFHACK_PLUGIN(eventExample eventExample.cpp)
DFHACK_PLUGIN(printArgs printArgs.cpp)
IF(UNIX)
DFHACK_PLUGIN(ref-index ref-index.cpp)
ENDIF()
DFHACK_PLUGIN(stepBetween stepBetween.cpp)

@ -0,0 +1,108 @@
#include "Console.h"
#include "Core.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h"
#include "DataDefs.h"
#include "df/item.h"
#include "df/world.h"
#include <vector>
using namespace DFHack;
using namespace std;
DFHACK_PLUGIN("eventExample");
void jobInitiated(color_ostream& out, void* job);
void jobCompleted(color_ostream& out, void* job);
void timePassed(color_ostream& out, void* ptr);
void unitDeath(color_ostream& out, void* ptr);
void itemCreate(color_ostream& out, void* ptr);
void building(color_ostream& out, void* ptr);
void construction(color_ostream& out, void* ptr);
void syndrome(color_ostream& out, void* ptr);
void invasion(color_ostream& out, void* ptr);
command_result eventExample(color_ostream& out, vector<string>& parameters);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
commands.push_back(PluginCommand("eventExample", "Sets up a few event triggers.",eventExample));
return CR_OK;
}
command_result eventExample(color_ostream& out, vector<string>& parameters) {
EventManager::EventHandler initiateHandler(jobInitiated, 10);
EventManager::EventHandler completeHandler(jobCompleted, 5);
EventManager::EventHandler timeHandler(timePassed, 1);
EventManager::EventHandler deathHandler(unitDeath, 500);
EventManager::EventHandler itemHandler(itemCreate, 1000);
EventManager::EventHandler buildingHandler(building, 500);
EventManager::EventHandler constructionHandler(construction, 100);
EventManager::EventHandler syndromeHandler(syndrome, 1);
EventManager::EventHandler invasionHandler(invasion, 1000);
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample");
EventManager::unregisterAll(me);
EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me);
EventManager::registerTick(timeHandler, 1, me);
EventManager::registerTick(timeHandler, 2, me);
EventManager::registerTick(timeHandler, 4, me);
EventManager::registerTick(timeHandler, 8, me);
EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, me);
EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, me);
EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, me);
EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, me);
EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, me);
EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, me);
out.print("Events registered.\n");
return CR_OK;
}
void jobInitiated(color_ostream& out, void* job) {
out.print("Job initiated! 0x%X\n", job);
}
void jobCompleted(color_ostream& out, void* job) {
out.print("Job completed! 0x%X\n", job);
}
void timePassed(color_ostream& out, void* ptr) {
out.print("Time: %d\n", (int32_t)(ptr));
}
void unitDeath(color_ostream& out, void* ptr) {
out.print("Death: %d\n", (int32_t)(ptr));
}
void itemCreate(color_ostream& out, void* ptr) {
int32_t item_index = df::item::binsearch_index(df::global::world->items.all, (int32_t)ptr);
if ( item_index == -1 ) {
out.print("%s, %d: Error.\n", __FILE__, __LINE__);
}
df::item* item = df::global::world->items.all[item_index];
df::item_type type = item->getType();
df::coord pos = item->pos;
out.print("Item created: %d, %s, at (%d,%d,%d)\n", (int32_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z);
}
void building(color_ostream& out, void* ptr) {
out.print("Building created/destroyed: %d\n", (int32_t)ptr);
}
void construction(color_ostream& out, void* ptr) {
out.print("Construction created/destroyed: 0x%X\n", ptr);
}
void syndrome(color_ostream& out, void* ptr) {
EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr;
out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex);
}
void invasion(color_ostream& out, void* ptr) {
out.print("New invasion! %d\n", (int32_t)ptr);
}

@ -0,0 +1,32 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include <iostream>
using namespace DFHack;
using namespace std;
command_result printArgs (color_ostream &out, std::vector <std::string> & parameters);
DFHACK_PLUGIN("printArgs");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"printArgs", "Print the arguments given.",
printArgs, false
));
return CR_OK;
}
command_result printArgs (color_ostream &out, std::vector <std::string> & parameters)
{
for ( size_t a = 0; a < parameters.size(); a++ ) {
out << "Argument " << (a+1) << ": \"" << parameters[a] << "\"" << endl;
}
return CR_OK;
}

@ -0,0 +1,87 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
// DF data structure definition headers
#include "DataDefs.h"
#include "df/world.h"
#include "modules/Gui.h"
#include "modules/Maps.h"
using namespace DFHack;
using namespace df::enums;
command_result stepBetween (color_ostream &out, std::vector <std::string> & parameters);
// A plugin must be able to return its name and version.
// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case
DFHACK_PLUGIN("stepBetween");
// Mandatory init function. If you have some global state, create it here.
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
// Fill the command list with your commands.
commands.push_back(PluginCommand(
"stepBetween", "Do nothing, look pretty.",
stepBetween, false, /* true means that the command can't be used from non-interactive user interface */
// Extended help string. Used by CR_WRONG_USAGE and the help command:
" This command does nothing at all.\n"
));
return CR_OK;
}
// This is called right before the plugin library is removed from memory.
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
// You *MUST* kill all threads you created before this returns.
// If everything fails, just return CR_FAILURE. Your plugin will be
// in a zombie state, but things won't crash.
return CR_OK;
}
// Called to notify the plugin about important state changes.
// Invoked with DF suspended, and always before the matching plugin_onupdate.
// More event codes may be added in the future.
/*
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_GAME_LOADED:
// initialize from the world just loaded
break;
case SC_GAME_UNLOADED:
// cleanup
break;
default:
break;
}
return CR_OK;
}
*/
// Whatever you put here will be done in each game step. Don't abuse it.
// It's optional, so you can just comment it out like this if you don't need it.
/*
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
// whetever. You don't need to suspend DF execution here.
return CR_OK;
}
*/
df::coord prev;
// A command! It sits around and looks pretty. And it's nice and friendly.
command_result stepBetween (color_ostream &out, std::vector <std::string> & parameters)
{
df::coord bob = Gui::getCursorPos();
out.print("(%d,%d,%d), (%d,%d,%d): canStepBetween = %d, canWalkBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canWalkBetween(prev,bob));
prev = bob;
return CR_OK;
}

@ -0,0 +1,327 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <string.h>
#include <VTableInterpose.h>
#include "df/building_workshopst.h"
#include "df/unit.h"
#include "df/item.h"
#include "df/item_actual.h"
#include "df/unit_wound.h"
#include "df/world.h"
#include "df/reaction.h"
#include "df/reaction_reagent_itemst.h"
#include "df/reaction_product_itemst.h"
#include "df/proj_itemst.h"
#include "df/proj_unitst.h"
#include "MiscUtils.h"
#include "LuaTools.h"
using std::vector;
using std::string;
using std::stack;
using namespace DFHack;
using namespace df::enums;
using df::global::gps;
using df::global::world;
using df::global::ui;
typedef df::reaction_product_itemst item_product;
DFHACK_PLUGIN("eventful");
struct ReagentSource {
int idx;
df::reaction_reagent *reagent;
ReagentSource() : idx(-1), reagent(NULL) {}
};
struct MaterialSource : ReagentSource {
bool product;
std::string product_name;
int mat_type, mat_index;
MaterialSource() : product(false), mat_type(-1), mat_index(-1) {}
};
struct ProductInfo {
df::reaction *react;
item_product *product;
MaterialSource material;
bool isValid() {
return (material.mat_type >= 0 || material.reagent);
}
};
struct ReactionInfo {
df::reaction *react;
std::vector<ProductInfo> products;
};
static std::map<std::string, ReactionInfo> reactions;
static std::map<df::reaction_product*, ProductInfo*> products;
static ReactionInfo *find_reaction(const std::string &name)
{
auto it = reactions.find(name);
return (it != reactions.end()) ? &it->second : NULL;
}
static bool is_lua_hook(const std::string &name)
{
return name.size() > 9 && memcmp(name.data(), "LUA_HOOK_", 9) == 0;
}
/*
* Hooks
*/
static void handle_fillsidebar(color_ostream &out,df::building_workshopst*,bool *call_native){};
static void handle_postfillsidebar(color_ostream &out,df::building_workshopst*){};
static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector<df::item*> *in_items,std::vector<df::reaction_reagent*> *in_reag
, std::vector<df::item*> *out_items,bool *call_native){};
static void handle_contaminate_wound(color_ostream &out,df::item_actual*,df::unit* unit, df::unit_wound* wound, uint8_t a1, int16_t a2){};
static void handle_projitem_ci(color_ostream &out,df::proj_itemst*,bool){};
static void handle_projitem_cm(color_ostream &out,df::proj_itemst*){};
static void handle_projunit_ci(color_ostream &out,df::proj_unitst*,bool){};
static void handle_projunit_cm(color_ostream &out,df::proj_unitst*){};
DEFINE_LUA_EVENT_2(onWorkshopFillSidebarMenu, handle_fillsidebar, df::building_workshopst*,bool* );
DEFINE_LUA_EVENT_1(postWorkshopFillSidebarMenu, handle_postfillsidebar, df::building_workshopst*);
DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector<df::item*> *,std::vector<df::reaction_reagent*> *,std::vector<df::item*> *,bool *);
DEFINE_LUA_EVENT_5(onItemContaminateWound, handle_contaminate_wound, df::item_actual*,df::unit* , df::unit_wound* , uint8_t , int16_t );
//projectiles
DEFINE_LUA_EVENT_2(onProjItemCheckImpact, handle_projitem_ci, df::proj_itemst*,bool );
DEFINE_LUA_EVENT_1(onProjItemCheckMovement, handle_projitem_cm, df::proj_itemst*);
DEFINE_LUA_EVENT_2(onProjUnitCheckImpact, handle_projunit_ci, df::proj_unitst*,bool );
DEFINE_LUA_EVENT_1(onProjUnitCheckMovement, handle_projunit_cm, df::proj_unitst* );
DFHACK_PLUGIN_LUA_EVENTS {
DFHACK_LUA_EVENT(onWorkshopFillSidebarMenu),
DFHACK_LUA_EVENT(postWorkshopFillSidebarMenu),
DFHACK_LUA_EVENT(onReactionComplete),
DFHACK_LUA_EVENT(onItemContaminateWound),
DFHACK_LUA_EVENT(onProjItemCheckImpact),
DFHACK_LUA_EVENT(onProjItemCheckMovement),
DFHACK_LUA_EVENT(onProjUnitCheckImpact),
DFHACK_LUA_EVENT(onProjUnitCheckMovement),
DFHACK_LUA_END
};
struct workshop_hook : df::building_workshopst{
typedef df::building_workshopst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void,fillSidebarMenu,())
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
bool call_native=true;
onWorkshopFillSidebarMenu(out,this,&call_native);
if(call_native)
INTERPOSE_NEXT(fillSidebarMenu)();
postWorkshopFillSidebarMenu(out,this);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, fillSidebarMenu);
struct product_hook : item_product {
typedef item_product interpose_base;
DEFINE_VMETHOD_INTERPOSE(
void, produce,
(df::unit *unit, std::vector<df::item*> *out_items,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *in_items,
int32_t quantity, df::job_skill skill,
df::historical_entity *entity, df::world_site *site)
) {
if (auto product = products[this])
{
df::reaction* this_reaction=product->react;
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
bool call_native=true;
onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native);
if(!call_native)
return;
}
INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce);
struct item_hooks :df::item_actual {
typedef df::item_actual interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, contaminateWound,(df::unit* unit, df::unit_wound* wound, uint8_t a1, int16_t a2))
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onItemContaminateWound(out,this,unit,wound,a1,a2);
INTERPOSE_NEXT(contaminateWound)(unit,wound,a1,a2);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(item_hooks, contaminateWound);
struct proj_item_hook: df::proj_itemst{
typedef df::proj_itemst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool,checkImpact,(bool mode))
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onProjItemCheckImpact(out,this,mode);
return INTERPOSE_NEXT(checkImpact)(mode); //returns destroy item or not?
}
DEFINE_VMETHOD_INTERPOSE(bool,checkMovement,())
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onProjItemCheckMovement(out,this);
return INTERPOSE_NEXT(checkMovement)();
}
};
IMPLEMENT_VMETHOD_INTERPOSE(proj_item_hook,checkImpact);
IMPLEMENT_VMETHOD_INTERPOSE(proj_item_hook,checkMovement);
struct proj_unit_hook: df::proj_unitst{
typedef df::proj_unitst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool,checkImpact,(bool mode))
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onProjUnitCheckImpact(out,this,mode);
return INTERPOSE_NEXT(checkImpact)(mode); //returns destroy item or not?
}
DEFINE_VMETHOD_INTERPOSE(bool,checkMovement,())
{
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onProjUnitCheckMovement(out,this);
return INTERPOSE_NEXT(checkMovement)();
}
};
IMPLEMENT_VMETHOD_INTERPOSE(proj_unit_hook,checkImpact);
IMPLEMENT_VMETHOD_INTERPOSE(proj_unit_hook,checkMovement);
/*
* Scan raws for matching reactions.
*/
static void parse_product(
color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod
) {
info.react = react;
info.product = prod;
info.material.mat_type = prod->mat_type;
info.material.mat_index = prod->mat_index;
}
static bool find_reactions(color_ostream &out)
{
reactions.clear();
auto &rlist = world->raws.reactions;
for (size_t i = 0; i < rlist.size(); i++)
{
if (!is_lua_hook(rlist[i]->code))
continue;
reactions[rlist[i]->code].react = rlist[i];
}
for (auto it = reactions.begin(); it != reactions.end(); ++it)
{
auto &prod = it->second.react->products;
auto &out_prod = it->second.products;
for (size_t i = 0; i < prod.size(); i++)
{
auto itprod = strict_virtual_cast<item_product>(prod[i]);
if (!itprod) continue;
out_prod.push_back(ProductInfo());
parse_product(out, out_prod.back(), it->second.react, itprod);
}
for (size_t i = 0; i < prod.size(); i++)
{
if (out_prod[i].isValid())
products[out_prod[i].product] = &out_prod[i];
}
}
return !products.empty();
}
static void enable_hooks(bool enable)
{
INTERPOSE_HOOK(workshop_hook,fillSidebarMenu).apply(enable);
INTERPOSE_HOOK(item_hooks,contaminateWound).apply(enable);
INTERPOSE_HOOK(proj_unit_hook,checkImpact).apply(enable);
INTERPOSE_HOOK(proj_unit_hook,checkMovement).apply(enable);
INTERPOSE_HOOK(proj_item_hook,checkImpact).apply(enable);
INTERPOSE_HOOK(proj_item_hook,checkMovement).apply(enable);
}
static void world_specific_hooks(color_ostream &out,bool enable)
{
if(enable && find_reactions(out))
{
out.print("Detected reaction hooks - enabling plugin.\n");
INTERPOSE_HOOK(product_hook, produce).apply(true);
}
else
{
INTERPOSE_HOOK(product_hook, produce).apply(false);
reactions.clear();
products.clear();
}
}
void disable_all_hooks(color_ostream &out)
{
world_specific_hooks(out,false);
enable_hooks(false);
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_WORLD_LOADED:
world_specific_hooks(out,true);
break;
case SC_WORLD_UNLOADED:
world_specific_hooks(out,false);
break;
default:
break;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (Core::getInstance().isWorldLoaded())
plugin_onstatechange(out, SC_WORLD_LOADED);
enable_hooks(true);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
disable_all_hooks(out);
return CR_OK;
}

@ -0,0 +1,182 @@
#include "Core.h"
#include "Console.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/World.h"
#include "df/construction.h"
#include "df/game_mode.h"
#include "df/map_block.h"
#include "df/map_block_column.h"
#include "df/world.h"
#include "df/z_level_flags.h"
#include <cstring>
#include <string>
#include <vector>
using namespace std;
using namespace DFHack;
using namespace df::enums;
command_result infiniteSky (color_ostream &out, std::vector <std::string> & parameters);
DFHACK_PLUGIN("infiniteSky");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"infiniteSky",
"Creates new sky levels on request, or as you construct up.",
infiniteSky, false,
"Usage:\n"
" infiniteSky\n"
" creates one more z-level\n"
" infiniteSky [n]\n"
" creates n more z-level(s)\n"
" infiniteSky enable\n"
" enables monitoring of constructions\n"
" infiniteSky disable\n"
" disable monitoring of constructions\n"
"\n"
"If construction monitoring is enabled, then the plugin will automatically create new sky z-levels as you construct upward.\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
/*
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_GAME_LOADED:
// initialize from the world just loaded
break;
case SC_GAME_UNLOADED:
// cleanup
break;
default:
break;
}
return CR_OK;
}
*/
static size_t constructionSize = 0;
static bool enabled = false;
void doInfiniteSky(color_ostream& out, int32_t howMany);
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
if ( !enabled )
return CR_OK;
if ( !Core::getInstance().isMapLoaded() )
return CR_OK;
{
t_gamemodes mode;
if ( !World::ReadGameMode(mode) )
return CR_FAILURE;
if ( mode.g_mode != df::enums::game_mode::DWARF )
return CR_OK;
}
if ( df::global::world->constructions.size() == constructionSize )
return CR_OK;
int32_t zNow = df::global::world->map.z_count_block;
for ( size_t a = constructionSize; a < df::global::world->constructions.size(); a++ ) {
df::construction* construct = df::global::world->constructions[a];
if ( construct->pos.z+2 < zNow )
continue;
doInfiniteSky(out, 1);
zNow = df::global::world->map.z_count_block;
///break;
}
constructionSize = df::global::world->constructions.size();
return CR_OK;
}
void doInfiniteSky(color_ostream& out, int32_t howMany) {
df::world* world = df::global::world;
CoreSuspender suspend;
int32_t x_count_block = world->map.x_count_block;
int32_t y_count_block = world->map.y_count_block;
for ( int32_t count = 0; count < howMany; count++ ) {
//change the size of the pointer stuff
int32_t z_count_block = world->map.z_count_block;
df::map_block**** block_index = world->map.block_index;
for ( int32_t a = 0; a < x_count_block; a++ ) {
for ( int32_t b = 0; b < y_count_block; b++ ) {
df::map_block** blockColumn = new df::map_block*[z_count_block+1];
memcpy(blockColumn, block_index[a][b], z_count_block*sizeof(df::map_block*));
blockColumn[z_count_block] = NULL;
delete[] block_index[a][b];
block_index[a][b] = blockColumn;
//deal with map_block_column stuff even though it'd probably be fine
df::map_block_column* column = world->map.column_index[a][b];
if ( !column ) {
out.print("%s, line %d: column is null (%d, %d).\n", __FILE__, __LINE__, a, b);
continue;
}
df::map_block_column::T_unmined_glyphs* glyphs = new df::map_block_column::T_unmined_glyphs;
glyphs->x[0] = 0;
glyphs->x[1] = 1;
glyphs->x[2] = 2;
glyphs->x[3] = 3;
glyphs->y[0] = 0;
glyphs->y[1] = 0;
glyphs->y[2] = 0;
glyphs->y[3] = 0;
glyphs->tile[0] = 'e';
glyphs->tile[1] = 'x';
glyphs->tile[2] = 'p';
glyphs->tile[3] = '^';
column->unmined_glyphs.push_back(glyphs);
}
}
df::z_level_flags* flags = new df::z_level_flags[z_count_block+1];
memcpy(flags, world->map.z_level_flags, z_count_block*sizeof(df::z_level_flags));
flags[z_count_block].whole = 0;
flags[z_count_block].bits.update = 1;
world->map.z_count_block++;
world->map.z_count++;
delete[] world->map.z_level_flags;
world->map.z_level_flags = flags;
}
}
command_result infiniteSky (color_ostream &out, std::vector <std::string> & parameters)
{
if ( parameters.size() > 1 )
return CR_WRONG_USAGE;
if ( parameters.size() == 0 ) {
out.print("Construction monitoring is %s.\n", enabled ? "enabled" : "disabled");
return CR_OK;
}
if (parameters[0] == "enable") {
enabled = true;
out.print("Construction monitoring enabled.\n");
return CR_OK;
}
if (parameters[0] == "disable") {
enabled = false;
out.print("Construction monitoring disabled.\n");
constructionSize = 0;
return CR_OK;
}
int32_t howMany = 0;
howMany = atoi(parameters[0].c_str());
out.print("InfiniteSky: creating %d new z-level%s of sky.\n", howMany, howMany == 1 ? "" : "s" );
doInfiniteSky(out, howMany);
return CR_OK;
}

@ -220,7 +220,7 @@ function SimpleMenu:display()
local ans
repeat
local r
r=io.stdin:read()
r=dfhack.lineedit()
if r==nil then return end
if r=='q' then return end
ans=tonumber(r)

@ -1,5 +1,6 @@
local _ENV = mkmodule('plugins.dfusion.adv_tools')
local dfu=require("plugins.dfusion")
local tools=require("plugins.dfusion.tools")
menu=dfu.SimpleMenu()
function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess
if swap_soul==nil then
@ -9,7 +10,7 @@ function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess
if adv.flags1.dead==false then
qerror("You are not dead (yet)!")
end
local hist_fig=getNemesis(adv).figure
local hist_fig=dfhack.units.getNemesis(adv).figure
if hist_fig==nil then
qerror("No historical figure for adventurer...")
end
@ -18,9 +19,9 @@ function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess
for i=#events-1,0,-1 do -- reverse search because almost always it will be last entry
if df.history_event_hist_figure_diedst:is_instance(events[i]) then
--print("is instance:"..i)
if events[i].victim==hist_fig.id then
if events[i].victim_hf==hist_fig.id then
--print("Is same id:"..i)
trg_hist_fig=events[i].slayer
trg_hist_fig=events[i].slayer_hf
if trg_hist_fig then
trg_hist_fig=df.historical_figure.find(trg_hist_fig)
end
@ -38,7 +39,7 @@ function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess
end
local trg_unit_final=df.unit.find(trg_unit)
tools.change_adv(trg_unit_final)
change_adv(trg_unit_final)
if swap_soul then --actually add a soul...
t_soul=adv.status.current_soul
adv.status.current_soul=df.NULL
@ -53,7 +54,7 @@ function change_adv(unit,nemesis)
nemesis=true --default value is nemesis switch too.
end
if unit==nil then
unit=getCreatureAtPointer()
unit=dfhack.gui.getSelectedUnit()--getCreatureAtPointer()
end
if unit==nil then
error("Invalid unit!")
@ -72,8 +73,8 @@ function change_adv(unit,nemesis)
other[unit_indx]=other[0]
other[0]=unit
if nemesis then --basicly copied from advtools plugin...
local nem=getNemesis(unit)
local other_nem=getNemesis(other[unit_indx])
local nem=dfhack.units.getNemesis(unit)
local other_nem=dfhack.units.getNemesis(other[unit_indx])
if other_nem then
other_nem.flags[0]=false
other_nem.flags[1]=true
@ -113,4 +114,59 @@ function log_pos()
f:close()
end
menu:add("Log adventurers position",log_pos)
function addSite(x,y,rgn_max_x,rgn_min_x,rgn_max_y,rgn_min_y,civ_id,name,sitetype)
if x==nil or y==nil then
x=(df.global.world.map.region_x+1)/16
y=(df.global.world.map.region_y+1)/16
end
if name==nil then
name=dfhack.lineedit("Site name:")or "Hacked site"
end
if sitetype==nil then
sitetype=tonumber(dfhack.lineedit("Site type (numeric):")) or 7
end
rgn_max_x=rgn_max_x or (df.global.world.map.region_x+1)%16
rgn_max_y=rgn_max_y or (df.global.world.map.region_y+1)%16
rgn_min_y=rgn_min_y or rgn_max_y
rgn_min_x=rgn_min_x or rgn_max_x
print("Region:",rgn_max_x,rgn_min_x,rgn_max_y,rgn_min_y)
--[=[
<angavrilov> global = pos*16 + rgn
<angavrilov> BUT
<angavrilov> for cities global is usually 17x17, i.e. max size
<angavrilov> while rgn designates a small bit in the middle
<angavrilov> for stuff like forts that formula holds exactly
]=]--
local wd=df.global.world.world_data
local nsite=df.world_site:new()
nsite.name.first_name=name
nsite.name.has_name=true
nsite.pos:assign{x=x,y=y}
nsite.rgn_max_x=rgn_max_x
nsite.rgn_min_x=rgn_min_x
nsite.rgn_min_y=rgn_min_y
nsite.rgn_max_y=rgn_max_y
nsite.global_max_x=nsite.pos.x*16+nsite.rgn_max_x
nsite.global_min_x=nsite.pos.x*16+nsite.rgn_min_x
nsite.global_max_y=nsite.pos.y*16+nsite.rgn_max_y
nsite.global_min_y=nsite.pos.y*16+nsite.rgn_min_y
nsite.id=wd.next_site_id
nsite.civ_id=civ_id or -1
nsite.cur_owner_id=civ_id or -1
nsite.type=sitetype --lair = 7
nsite.flags:resize(23)
--nsite.flags[4]=true
--nsite.flags[5]=true
--nsite.flags[6]=true
nsite.index=#wd.sites+1
wd.sites:insert("#",nsite)
wd.next_site_id=wd.next_site_id+1
--might not be needed...
--[[local unk130=df.world_site_unk130:new()
unk130.index=#wd.site_unk130+1
wd.site_unk130:insert("#",unk130)
--wd.next_site_unk136_id=wd.next_site_unk136_id+1--]]
return nsite
end
menu:add("Create site at current location",addSite)
return _ENV

@ -26,7 +26,7 @@ function setrace(name)
if name == nil then
print("Type new race's token name in full caps (q to quit):")
repeat
local entry=io.stdin:read()
local entry=dfhack.lineedit()
if entry=="q" then
return
end
@ -48,7 +48,7 @@ function GiveSentience(names)
ids={}
print("Type race's token name in full caps to give sentience to:")
repeat
id=io.stdin:read()
id=dfhack.lineedit()
id=RaceTable[entry]
if id~=nil then
table.insert(ids,id)
@ -189,10 +189,10 @@ function healunit(unit)
unit.body.wounds:resize(0) -- memory leak here :/
unit.body.blood_count=unit.body.blood_max
--set flags for standing and grasping...
unit.status2.able_stand=4
unit.status2.able_stand_impair=4
unit.status2.able_grasp=4
unit.status2.able_grasp_impair=4
unit.status2.limbs_stand_max=4
unit.status2.limbs_stand_count=4
unit.status2.limbs_grasp_max=4
unit.status2.limbs_grasp_count=4
--should also set temperatures, and flags for breath etc...
unit.flags1.dead=false
unit.flags2.calculated_bodyparts=false

@ -0,0 +1,112 @@
local _ENV = mkmodule('plugins.eventful')
--[[
--]]
local function getShopName(btype,bsubtype,bcustom)
local typenames_shop={[df.workshop_type.Carpenters]="CARPENTERS",[df.workshop_type.Farmers]="FARMERS",
[df.workshop_type.Masons]="MASONS",[df.workshop_type.Craftsdwarfs]="CRAFTSDWARFS",
[df.workshop_type.Jewelers]="JEWELERS",[df.workshop_type.MetalsmithsForge]="METALSMITHSFORGE",
[df.workshop_type.MagmaForge]="MAGMAFORGE",[df.workshop_type.Bowyers]="BOWYERS",
[df.workshop_type.Mechanics]="MECHANICS",[df.workshop_type.Siege]="SIEGE",
[df.workshop_type.Butchers]="BUTCHERS",[df.workshop_type.Leatherworks]="LEATHERWORKS",
[df.workshop_type.Tanners]="TANNERS",[df.workshop_type.Clothiers]="CLOTHIERS",
[df.workshop_type.Fishery]="FISHERY",[df.workshop_type.Still]="STILL",
[df.workshop_type.Loom]="LOOM",[df.workshop_type.Quern]="QUERN",
[df.workshop_type.Kennels]="KENNELS",[df.workshop_type.Ashery]="ASHERY",
[df.workshop_type.Kitchen]="KITCHEN",[df.workshop_type.Dyers]="DYERS",
[df.workshop_type.Tool]="TOOL",[df.workshop_type.Millstone]="MILLSTONE",
}
local typenames_furnace={[df.furnace_type.WoodFurnace]="WOOD_FURNACE",[df.furnace_type.Smelter]="SMELTER",
[df.furnace_type.GlassFurnace]="GLASS_FURNACE",[df.furnace_type.MagmaSmelter]="MAGMA_SMELTER",
[df.furnace_type.MagmaGlassFurnace]="MAGMA_GLASS_FURNACE",[df.furnace_type.MagmaKiln]="MAGMA_KILN",
[df.furnace_type.Kiln]="KILN"}
if btype==df.building_type.Workshop then
if typenames_shop[bsubtype]~=nil then
return typenames_shop[bsubtype]
else
return nil --todo add custom (not very useful)
end
elseif btype==df.building_type.Furnace then
if typenames_furnace[bsubtype]~=nil then
return typenames_furnace[bsubtype]
else
return nil --todo add custom (not very useful)
end
end
end
_registeredStuff={}
local function unregall(state)
if state==SC_WORLD_UNLOADED then
onReactionComplete._library=nil
postWorkshopFillSidebarMenu._library=nil
dfhack.onStateChange.eventful= nil
_registeredStuff={}
end
end
local function onReact(reaction,unit,input_items,input_reagents,output_items,call_native)
if _registeredStuff.reactionCallbacks and _registeredStuff.reactionCallbacks[reaction.code] then
_registeredStuff.reactionCallbacks[reaction.code](reaction,unit,input_items,input_reagents,output_items,call_native)
end
end
local function onPostSidebar(workshop)
local shop_id=getShopName(workshop:getType(),workshop:getSubtype(),workshop:getCustomType())
if shop_id then
if _registeredStuff.shopNonNative and _registeredStuff.shopNonNative[shop_id] then
if _registeredStuff.shopNonNative[shop_id].all then
--[[for _,button in ipairs(df.global.ui_sidebar_menus.workshop_job.choices_all) do
button.is_hidden=true
end]]
df.global.ui_sidebar_menus.workshop_job.choices_visible:resize(0)
else
--todo by name
end
end
if _registeredStuff.reactionToShop and _registeredStuff.reactionToShop[shop_id] then
for _,reaction_name in ipairs(_registeredStuff.reactionToShop[shop_id]) do
local new_button=df.interface_button_building_new_jobst:new()
--new_button.hotkey_id=--todo get hotkey
new_button.is_hidden=false
new_button.building=workshop
new_button.job_type=df.job_type.CustomReaction --could be used for other stuff too i guess...
new_button.reaction_name=reaction_name
new_button.is_custom=true
local wjob=df.global.ui_sidebar_menus.workshop_job
wjob.choices_all:insert("#",new_button)
wjob.choices_visible:insert("#",new_button)
end
end
end
end
function registerReaction(reaction_name,callback)
_registeredStuff.reactionCallbacks=_registeredStuff.reactionCallbacks or {}
_registeredStuff.reactionCallbacks[reaction_name]=callback
onReactionComplete._library=onReact
dfhack.onStateChange.eventful=unregall
end
function removeNative(shop_name,name)
_registeredStuff.shopNonNative=_registeredStuff.shopNonNative or {}
local shops=_registeredStuff.shopNonNative
shops[shop_name]=shops[shop_name] or {}
if name~=nil then
table.insert(shops[shop_name],name)
else
shops[shop_name].all=true
end
postWorkshopFillSidebarMenu._library=onPostSidebar
dfhack.onStateChange.eventful=unregall
end
function addReactionToShop(reaction_name,shop_name)
_registeredStuff.reactionToShop=_registeredStuff.reactionToShop or {}
local shops=_registeredStuff.reactionToShop
shops[shop_name]=shops[shop_name] or {}
table.insert(shops[shop_name],reaction_name)
postWorkshopFillSidebarMenu._library=onPostSidebar
dfhack.onStateChange.eventful=unregall
end
return _ENV

@ -1,13 +0,0 @@
local _ENV = mkmodule('plugins.reactionhooks')
--[[
Native events:
* onReactionComplete(burrow)
--]]
rawset_default(_ENV, dfhack.reactionhooks)
return _ENV

@ -1,322 +0,0 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <modules/Gui.h>
#include <modules/Screen.h>
#include <modules/Maps.h>
#include <modules/Job.h>
#include <modules/Items.h>
#include <modules/Units.h>
#include <TileTypes.h>
#include <vector>
#include <cstdio>
#include <stack>
#include <string>
#include <cmath>
#include <string.h>
#include <VTableInterpose.h>
#include "df/item_liquid_miscst.h"
#include "df/item_constructed.h"
#include "df/builtin_mats.h"
#include "df/world.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/job_item_ref.h"
#include "df/ui.h"
#include "df/report.h"
#include "df/reaction.h"
#include "df/reaction_reagent_itemst.h"
#include "df/reaction_product_itemst.h"
#include "df/matter_state.h"
#include "df/contaminant.h"
#include "MiscUtils.h"
#include "LuaTools.h"
using std::vector;
using std::string;
using std::stack;
using namespace DFHack;
using namespace df::enums;
using df::global::gps;
using df::global::world;
using df::global::ui;
typedef df::reaction_product_itemst item_product;
DFHACK_PLUGIN("reactionhooks");
struct ReagentSource {
int idx;
df::reaction_reagent *reagent;
ReagentSource() : idx(-1), reagent(NULL) {}
};
struct MaterialSource : ReagentSource {
bool product;
std::string product_name;
int mat_type, mat_index;
MaterialSource() : product(false), mat_type(-1), mat_index(-1) {}
};
struct ProductInfo {
df::reaction *react;
item_product *product;
MaterialSource material;
bool isValid() {
return (material.mat_type >= 0 || material.reagent);
}
};
struct ReactionInfo {
df::reaction *react;
std::vector<ProductInfo> products;
};
static std::map<std::string, ReactionInfo> reactions;
static std::map<df::reaction_product*, ProductInfo*> products;
static ReactionInfo *find_reaction(const std::string &name)
{
auto it = reactions.find(name);
return (it != reactions.end()) ? &it->second : NULL;
}
static bool is_lua_hook(const std::string &name)
{
return name.size() > 9 && memcmp(name.data(), "LUA_HOOK_", 9) == 0;
}
static void find_material(int *type, int *index, df::item *input, MaterialSource &mat)
{
if (input && mat.reagent)
{
MaterialInfo info(input);
if (mat.product)
{
if (!info.findProduct(info, mat.product_name))
{
color_ostream_proxy out(Core::getInstance().getConsole());
out.printerr("Cannot find product '%s'\n", mat.product_name.c_str());
}
}
*type = info.type;
*index = info.index;
}
else
{
*type = mat.mat_type;
*index = mat.mat_index;
}
}
/*
* Hooks
*/
typedef std::map<int, std::vector<df::item*> > item_table;
static void index_items(item_table &table, df::job *job, ReactionInfo *info)
{
for (int i = job->items.size()-1; i >= 0; i--)
{
auto iref = job->items[i];
if (iref->job_item_idx < 0) continue;
auto iitem = job->job_items[iref->job_item_idx];
if (iitem->contains.empty())
{
table[iitem->reagent_index].push_back(iref->item);
}
else
{
std::vector<df::item*> contents;
Items::getContainedItems(iref->item, &contents);
for (int j = contents.size()-1; j >= 0; j--)
{
for (int k = iitem->contains.size()-1; k >= 0; k--)
{
int ridx = iitem->contains[k];
auto reag = info->react->reagents[ridx];
if (reag->matchesChild(contents[j], info->react, iitem->reaction_id))
table[ridx].push_back(contents[j]);
}
}
}
}
}
df::item* find_item(ReagentSource &info, item_table &table)
{
if (!info.reagent)
return NULL;
if (table[info.idx].empty())
return NULL;
return table[info.idx].back();
}
df::item* find_item(
ReagentSource &info,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *in_items
) {
if (!info.reagent)
return NULL;
for (int i = in_items->size(); i >= 0; i--)
if ((*in_reag)[i] == info.reagent)
return (*in_items)[i];
return NULL;
}
static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector<df::item*> *in_items,std::vector<df::reaction_reagent*> *in_reag
, std::vector<df::item*> *out_items,bool *call_native){};
DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector<df::item*> *,std::vector<df::reaction_reagent*> *,std::vector<df::item*> *,bool *);
DFHACK_PLUGIN_LUA_EVENTS {
DFHACK_LUA_EVENT(onReactionComplete),
DFHACK_LUA_END
};
struct product_hook : item_product {
typedef item_product interpose_base;
DEFINE_VMETHOD_INTERPOSE(
void, produce,
(df::unit *unit, std::vector<df::item*> *out_items,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *in_items,
int32_t quantity, df::job_skill skill,
df::historical_entity *entity, df::world_site *site)
) {
if (auto product = products[this])
{
df::reaction* this_reaction=product->react;
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
bool call_native=true;
onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native);
if(!call_native)
return;
}
INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce);
/*
* Scan raws for matching reactions.
*/
static void parse_product(
color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod
) {
info.react = react;
info.product = prod;
info.material.mat_type = prod->mat_type;
info.material.mat_index = prod->mat_index;
}
static bool find_reactions(color_ostream &out)
{
reactions.clear();
auto &rlist = world->raws.reactions;
for (size_t i = 0; i < rlist.size(); i++)
{
if (!is_lua_hook(rlist[i]->code))
continue;
reactions[rlist[i]->code].react = rlist[i];
}
for (auto it = reactions.begin(); it != reactions.end(); ++it)
{
auto &prod = it->second.react->products;
auto &out_prod = it->second.products;
for (size_t i = 0; i < prod.size(); i++)
{
auto itprod = strict_virtual_cast<item_product>(prod[i]);
if (!itprod) continue;
out_prod.push_back(ProductInfo());
parse_product(out, out_prod.back(), it->second.react, itprod);
}
for (size_t i = 0; i < prod.size(); i++)
{
if (out_prod[i].isValid())
products[out_prod[i].product] = &out_prod[i];
}
}
return !products.empty();
}
static void enable_hooks(bool enable)
{
INTERPOSE_HOOK(product_hook, produce).apply(enable);
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_WORLD_LOADED:
if (find_reactions(out))
{
out.print("Detected reaction hooks - enabling plugin.\n");
enable_hooks(true);
}
else
enable_hooks(false);
break;
case SC_WORLD_UNLOADED:
enable_hooks(false);
reactions.clear();
products.clear();
break;
default:
break;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (Core::getInstance().isWorldLoaded())
plugin_onstatechange(out, SC_WORLD_LOADED);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
enable_hooks(false);
return CR_OK;
}

@ -48,6 +48,7 @@ If you create such a script, e.g. 'test.rb', that will add a new dfhack console
command 'test'.
The script can access the console command arguments through the global variable
'$script_args', which is an array of ruby Strings.
To exit early from a script, use 'throw :script_finished'
The help string displayed in dfhack 'ls' command is the first line of the
script, if it is a comment (ie starts with '# ').

@ -39,7 +39,7 @@ static color_ostream *r_console; // color_ostream given as argument, if NU
static const char *r_command;
static tthread::thread *r_thread;
static int onupdate_active;
static int onupdate_minyear, onupdate_minyeartick;
static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1;
static color_ostream_proxy *console_proxy;
@ -180,10 +180,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (!onupdate_active)
return CR_OK;
if (*df::global::cur_year < onupdate_minyear)
if (df::global::cur_year && (*df::global::cur_year < onupdate_minyear))
return CR_OK;
if (*df::global::cur_year == onupdate_minyear &&
*df::global::cur_year_tick < onupdate_minyeartick)
if (df::global::cur_year_tick && onupdate_minyeartick >= 0 &&
(*df::global::cur_year == onupdate_minyear &&
*df::global::cur_year_tick < onupdate_minyeartick))
return CR_OK;
if (df::global::cur_year_tick_advmode && onupdate_minyeartickadv >= 0 &&
(*df::global::cur_year == onupdate_minyear &&
*df::global::cur_year_tick_advmode < onupdate_minyeartickadv))
return CR_OK;
return plugin_eval_ruby(out, "DFHack.onupdate");
@ -481,6 +486,17 @@ static VALUE rb_dfonupdate_minyeartick_set(VALUE self, VALUE val)
return Qtrue;
}
static VALUE rb_dfonupdate_minyeartickadv(VALUE self)
{
return rb_uint2inum(onupdate_minyeartickadv);
}
static VALUE rb_dfonupdate_minyeartickadv_set(VALUE self, VALUE val)
{
onupdate_minyeartickadv = rb_num2ulong(val);
return Qtrue;
}
static VALUE rb_dfprint_str(VALUE self, VALUE s)
{
if (r_console)
@ -531,6 +547,23 @@ static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr)
return rb_uint2inum(*(uint32_t*)rb_num2ulong(objptr));
}
// run a dfhack command, as if typed from the dfhack console
static VALUE rb_dfhack_run(VALUE self, VALUE cmd)
{
if (!r_console) // XXX
return Qnil;
std::string s;
int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0));
s.assign(rb_string_value_ptr(&cmd), strlen);
// allow the target command to suspend
// FIXME
//CoreSuspendClaimer suspend(true);
Core::getInstance().runCommand(*r_console, s);
return Qtrue;
}
@ -663,6 +696,49 @@ static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw)
return ret ? Qtrue : Qfalse;
}
// allocate memory pages
static VALUE rb_dfmemory_pagealloc(VALUE self, VALUE len)
{
void *ret = Core::getInstance().p->memAlloc(rb_num2ulong(len));
return (ret == (void*)-1) ? Qnil : rb_uint2inum((uint32_t)ret);
}
// free memory from pagealloc
static VALUE rb_dfmemory_pagedealloc(VALUE self, VALUE ptr, VALUE len)
{
int ret = Core::getInstance().p->memDealloc((void*)rb_num2ulong(ptr), rb_num2ulong(len));
return ret ? Qfalse : Qtrue;
}
// change memory page permissions
// ptr must be page-aligned
// prot is a String, eg 'rwx', 'r', 'x'
static VALUE rb_dfmemory_pageprotect(VALUE self, VALUE ptr, VALUE len, VALUE prot_str)
{
int ret, prot=0;
char *prot_p = rb_string_value_ptr(&prot_str);
if (*prot_p == 'r') {
prot |= Process::MemProt::READ;
++prot_p;
}
if (*prot_p == 'w') {
prot |= Process::MemProt::WRITE;
++prot_p;
}
if (*prot_p == 'x') {
prot |= Process::MemProt::EXEC;
++prot_p;
}
Core::printerr("pageprot %x %x %x\n", rb_num2ulong(ptr), rb_num2ulong(len), prot);
ret = Core::getInstance().p->memProtect((void*)rb_num2ulong(ptr), rb_num2ulong(len), prot);
return ret ? Qfalse : Qtrue;
}
// stl::string
static VALUE rb_dfmemory_stlstring_new(VALUE self)
@ -963,14 +1039,20 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "onupdate_minyear=", RUBY_METHOD_FUNC(rb_dfonupdate_minyear_set), 1);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick), 0);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick_set), 1);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartickadv", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartickadv), 0);
rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartickadv=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartickadv_set), 1);
rb_define_singleton_method(rb_cDFHack, "get_global_address", RUBY_METHOD_FUNC(rb_dfget_global_address), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1);
rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
//rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1);
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1);
rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1);
rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);
rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1);
rb_define_singleton_method(rb_cDFHack, "pagealloc", RUBY_METHOD_FUNC(rb_dfmemory_pagealloc), 1);
rb_define_singleton_method(rb_cDFHack, "pagedealloc", RUBY_METHOD_FUNC(rb_dfmemory_pagedealloc), 2);
rb_define_singleton_method(rb_cDFHack, "pageprotect", RUBY_METHOD_FUNC(rb_dfmemory_pageprotect), 3);
rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8);
rb_define_singleton_method(rb_cDFHack, "version", RUBY_METHOD_FUNC(rb_dfversion), 0);

@ -64,6 +64,7 @@ module DFHack
# register a callback to be called every gframe or more
# ex: DFHack.onupdate_register('fastdwarf') { DFHack.world.units[0].counters.job_counter = 0 }
# if ticklimit is given, do not call unless this much game ticks have passed. Handles advmode time stretching.
def onupdate_register(descr, ticklimit=nil, initialtickdelay=0, &b)
raise ArgumentError, 'need a description as 1st arg' unless descr.kind_of?(::String)
@onupdate_list ||= []
@ -82,7 +83,7 @@ module DFHack
@onupdate_list.delete b
if @onupdate_list.empty?
DFHack.onupdate_active = false
DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = 0
DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = DFHack.onupdate_minyeartickadv = -1
end
end
@ -94,20 +95,32 @@ module DFHack
end
TICKS_PER_YEAR = 1200*28*12
# this method is called by dfhack every 'onupdate' if onupdate_active is true
# this method is called by ruby.cpp if df.onupdate_active is true
def onupdate
@onupdate_list ||= []
ticks_per_year = TICKS_PER_YEAR
ticks_per_year *= 72 if gametype == :ADVENTURE_MAIN or gametype == :ADVENTURE_ARENA
y = cur_year
ytmax = TICKS_PER_YEAR
if df.gamemode == :ADVENTURE and df.respond_to?(:cur_year_tick_advmode)
yt = cur_year_tick_advmode
ytmax *= 144
else
yt = cur_year_tick
end
@onupdate_list.each { |o|
o.check_run(cur_year, cur_year_tick, ticks_per_year)
o.check_run(y, yt, ytmax)
}
if onext = @onupdate_list.sort.first
DFHack.onupdate_minyear = onext.minyear
DFHack.onupdate_minyeartick = onext.minyeartick
if ytmax > TICKS_PER_YEAR
DFHack.onupdate_minyeartick = -1
DFHack.onupdate_minyeartickadv = onext.minyeartick
else
DFHack.onupdate_minyeartick = onext.minyeartick
DFHack.onupdate_minyeartickadv = -1
end
end
end

@ -76,7 +76,7 @@ module DFHack
u.mood == :Berserk or
unit_testflagcurse(u, :CRAZED) or
unit_testflagcurse(u, :OPPOSED_TO_LIFE) or
u.unknown8.unk2 or
u.enemy.undead or
u.flags3.ghostly or
u.flags1.marauder or u.flags1.active_invader or u.flags1.invader_origin or
u.flags1.forest or
@ -103,16 +103,158 @@ module DFHack
# some other stuff with ui.race_id ? (jobs only?)
end
# merchant: df.ui.caravans.find { |cv| cv.entity == u.civ_id }
# diplomat: df.ui.dip_meeting_info.find { |m| m.diplomat_id == u.hist_figure_id or m.diplomat_id2 == u.hist_figure_id }
def unit_nemesis(u)
if ref = u.general_refs.find { |r| r.kind_of?(DFHack::GeneralRefIsNemesisst) }
ref.nemesis_tg
end
end
# return the subcategory for :Others (from vs_unitlist)
def unit_other_category(u)
# comment is actual code returned by the df function
return :Berserk if u.mood == :Berserk # 5
return :Berserk if unit_testflagcurse(u, :CRAZED) # 14
return :Undead if unit_testflagcurse(u, :OPPOSED_TO_LIFE) # 1
return :Undead if u.flags3.ghostly # 15
if df.gamemode == :ADVENTURE
return :Hostile if u.civ_id == -1 # 2
if u.animal.population.region_x == -1
return :Wild if u.flags2.roaming_wilderness_population_source_not_a_map_feature # 0
else
return :Hostile if u.flags2.important_historical_figure and n = unit_nemesis(u) and n.flags[:ACTIVE_ADVENTURER] # 2
end
return :Hostile if u.flags2.resident # 3
return :Hostile # 4
end
return :Invader if u.flags1.active_invader or u.flags1.invader_origin # 6
return :Friendly if u.flags1.forest or u.flags1.merchant or u.flags1.diplomat # 8
return :Hostile if u.flags1.tame # 7
if u.civ_id != -1
return :Unsure if u.civ_id != df.ui.civ_id or u.flags1.resident or u.flags1.visitor or u.flags1.visitor_uninvited # 10
return :Hostile # 7
elsif u.animal.population.region_x == -1
return :Friendly if u.flags2.visitor # 8
return :Uninvited if u.flags2.visitor_uninvited # 12
return :Underworld if r = u.race_tg and r.underground_layer_min == 5 # 9
return :Resident if u.flags2.resident # 13
return :Friendly # 8
else
return :Friendly if u.flags2.visitor # 8
return :Underworld if r = u.race_tg and r.underground_layer_min == 5 # 9
return :Wild if u.animal.population.feature_idx == -1 and u.animal.population.cave_id == -1 # 0
return :Wild # 11
end
end
def unit_iscitizen(u)
unit_category(u) == :Citizens
end
def unit_hostiles
world.units.active.find_all { |u|
unit_ishostile(u)
}
end
# returns if an unit is openly hostile
# does not include ghosts / wildlife
def unit_ishostile(u)
unit_category(u) == :Others and
# TODO
true
# return true if u.flags3.ghostly and not u.flags1.dead
return unless unit_category(u) == :Others
case unit_other_category(u)
when :Berserk, :Undead, :Hostile, :Invader, :Underworld
# XXX :Resident, :Uninvited?
true
when :Unsure
# from df code, with removed duplicate checks already in other_category
return true if u.enemy.undead or u.flags3.ghostly or u.flags1.marauder
return false if u.flags1.forest or u.flags1.merchant or u.flags1.diplomat or u.flags2.visitor
return true if u.flags1.tame or u.flags2.underworld
if histfig = u.hist_figure_tg
group = df.ui.group_tg
case unit_checkdiplomacy_hf_ent(histfig, group)
when 4, 5
true
end
elsif diplo = u.civ_tg.unknown1b.diplomacy.binsearch(df.ui.group_id, :group_id)
diplo.relation != 1 and diplo.relation != 5
else
u.animal.population.region_x != -1 or u.flags2.resident or u.flags2.visitor_uninvited
end
end
end
def unit_checkdiplomacy_hf_ent(histfig, group)
var_3d = var_3e = var_45 = var_46 = var_47 = var_48 = var_49 = nil
var_3d = 1 if group.type == :Outcast or group.type == :NomadicGroup or
(group.type == :Civilization and group.entity_raw.flags[:LOCAL_BANDITRY])
histfig.entity_links.each { |link|
if link.entity_id == group.id
case link.getType
when :MEMBER, :MERCENARY, :SLAVE, :PRISONER, :POSITION, :HERO
var_47 = 1
when :FORMER_MEMBER, :FORMER_MERCENARY, :FORMER_SLAVE, :FORMER_PRISONER
var_48 = 1
when :ENEMY
var_49 = 1
when :CRIMINAL
var_45 = 1
end
else
case link.getType
when :MEMBER, :MERCENARY, :SLAVE
if link_entity = link.entity_tg
diplo = group.unknown1b.diplomacy.binsearch(link.entity_id, :group_id)
case diplo.relation
when 0, 3, 4
var_48 = 1
when 1, 5
var_46 = 1
end
var_3e = 1 if link_entity.type == :Outcast or link_entity.type == :NomadicGroup or
(link_entity.type == :Civilization and link_entity.entity_raw.flags[:LOCAL_BANDITRY])
end
end
end
}
if var_49
4
elsif var_46
5
elsif !var_47 and group.resources.ethic[:KILL_NEUTRAL] == 16
4
elsif df.gamemode == :ADVENTURE and !var_47 and (var_3e or !var_3d)
4
elsif var_45
3
elsif var_47
2
elsif var_48
1
else
0
end
end
# list workers (citizen, not crazy / child / inmood / noble)
def unit_workers
world.units.active.find_all { |u|
@ -122,6 +264,7 @@ module DFHack
def unit_isworker(u)
unit_iscitizen(u) and
u.race == df.ui.race_id and
u.mood == :None and
u.profession != :CHILD and
u.profession != :BABY and
@ -150,8 +293,8 @@ module DFHack
def unit_entitypositions(unit)
list = []
return list if not hf = unit.hist_figure_tg
hf.entity_links.each { |el|
return list if not histfig = unit.hist_figure_tg
histfig.entity_links.each { |el|
next if el._rtti_classname != :histfig_entity_link_positionst
next if not ent = el.entity_tg
next if not pa = ent.positions.assignments.binsearch(el.assignment_id)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,87 @@
#include "Core.h"
#include "Console.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h"
#include "df/caste_raw.h"
#include "df/creature_raw.h"
#include "df/syndrome.h"
#include "df/unit.h"
#include "df/unit_syndrome.h"
#include "df/world.h"
#include <cstdlib>
using namespace DFHack;
using namespace std;
DFHACK_PLUGIN("trueTransformation");
void syndromeHandler(color_ostream& out, void* ptr);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
EventManager::EventHandler syndrome(syndromeHandler, 1);
EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, plugin_self);
return CR_OK;
}
void syndromeHandler(color_ostream& out, void* ptr) {
EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr;
//out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex);
int32_t index = df::unit::binsearch_index(df::global::world->units.active, data->unitId);
if ( index < 0 ) {
out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__);
return;
}
df::unit* unit = df::global::world->units.active[index];
df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex];
df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type];
bool foundIt = false;
int32_t raceId = -1;
df::creature_raw* creatureRaw = NULL;
int32_t casteId = -1;
for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) {
if ( *syndrome->syn_class[a] == "\\PERMANENT" ) {
foundIt = true;
}
if ( foundIt && raceId == -1 ) {
//find the race with the name
string& name = *syndrome->syn_class[a];
for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) {
df::creature_raw* creature = df::global::world->raws.creatures.all[b];
if ( creature->creature_id != name )
continue;
raceId = b;
creatureRaw = creature;
break;
}
continue;
}
if ( foundIt && raceId != -1 ) {
string& name = *syndrome->syn_class[a];
for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) {
df::caste_raw* caste = creatureRaw->caste[b];
if ( caste->caste_id != name )
continue;
casteId = b;
break;
}
break;
}
}
if ( !foundIt || raceId == -1 || casteId == -1 )
return;
unit->enemy.normal_race = raceId;
unit->enemy.normal_caste = casteId;
//that's it!
}

@ -694,7 +694,7 @@ static int adjust_unit_divisor(int value) {
static bool can_spar(df::unit *unit) {
return unit->counters2.exhaustion <= 2000 && // actually 4000, but leave a gap
(unit->status2.able_grasp_impair > 0 || unit->status2.able_grasp == 0) &&
(unit->status2.limbs_grasp_count > 0 || unit->status2.limbs_grasp_max == 0) &&
(!unit->health || (unit->health->flags.whole&0x7FF) == 0) &&
(!unit->job.current_job || unit->job.current_job != job_type::Rest);
}

@ -0,0 +1,70 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "DataDefs.h"
#include "modules/World.h"
#include "df/global_objects.h"
#include <vector>
using namespace std;
using namespace DFHack;
DFHACK_PLUGIN("workNow");
static bool active = false;
DFhackCExport command_result workNow(color_ostream& out, vector<string>& parameters);
DFhackCExport command_result plugin_init(color_ostream& out, std::vector<PluginCommand> &commands) {
commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs every time you pause", workNow, false, "When workNow is active, every time the game pauses, DF will make dwarves perform any appropriate available jobs. This includes when you one step through the game using the pause menu.\n"
"workNow 1\n"
" activate workNow\n"
"workNow 0\n"
" deactivate workNow\n"));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) {
active = false;
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) {
if ( !active )
return CR_OK;
if ( e == DFHack::SC_WORLD_UNLOADED ) {
active = false;
return CR_OK;
}
if ( e != DFHack::SC_PAUSED )
return CR_OK;
*df::global::process_jobs = true;
*df::global::process_dig = true;
return CR_OK;
}
DFhackCExport command_result workNow(color_ostream& out, vector<string>& parameters) {
if ( parameters.size() == 0 ) {
out.print("workNow status = %s\n", active ? "active" : "inactive");
return CR_OK;
}
if ( parameters.size() > 1 ) {
return CR_WRONG_USAGE;
}
int32_t a = atoi(parameters[0].c_str());
if (a < 0 || a > 1)
return CR_WRONG_USAGE;
active = (bool)a;
return CR_OK;
}

@ -0,0 +1,161 @@
# create arbitrary items under cursor
category = $script_args[0] || 'help'
mat_raw = $script_args[1] || 'list'
count = $script_args[2]
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs']) || 'help'
if category == 'help'
puts <<EOS
Create items under the cursor.
Usage:
create [category] [raws token] [number]
Item categories:
bars, boulders, plants, logs, web
Raw token:
either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
(the missing part is autocompleted depending on the item category)
use 'list' to show all possibilities
Exemples:
create boulders hematite 30
create bars CREATURE_MAT:CAT:SOAP 10
create web cave_giant
create plants list
EOS
throw :script_finished
elsif mat_raw == 'list'
# allowed with no cursor
elsif df.cursor.x == -30000
puts "Please place the game cursor somewhere"
throw :script_finished
elsif !(maptile = df.map_tile_at(df.cursor))
puts "Error: unallocated map block !"
throw :script_finished
elsif !maptile.shape_passablehigh
puts "Error: impassible tile !"
throw :script_finished
end
def match_list(tok, list)
if tok != 'list'
tok = df.match_rawname(tok, list)
if not tok
puts "Invalid raws token, use one in:"
tok = 'list'
end
end
if tok == 'list'
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
throw :script_finished
end
tok
end
case category
when 'bars'
# create metal bar, eg createbar INORGANIC:IRON
cls = DFHack::ItemBarst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
customize = lambda { |item|
item.dimension = 150
item.subtype = -1
}
when 'boulders'
cls = DFHack::ItemBoulderst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_STONE]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
when 'plants'
cls = DFHack::ItemPlantst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
puts mat_raw
end
when 'logs'
cls = DFHack::ItemWoodst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'WOOD' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
puts mat_raw
end
when 'webs'
cls = DFHack::ItemThreadst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.creatures.all.find_all { |cre|
cre.material.find { |mat| mat.id == 'SILK' }
}.map { |cre| cre.creature_id }
mat_raw = match_list(mat_raw, list)
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
puts mat_raw
end
count ||= 1
customize = lambda { |item|
item.flags.spider_web = true
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
}
end
mat = df.decode_mat mat_raw
count ||= 20
count.to_i.times {
item = cls.cpp_new
item.id = df.item_next_id
item.stack_size = 1
item.mat_type = mat.mat_type
item.mat_index = mat.mat_index
customize[item] if customize
df.item_next_id += 1
item.categorize(true)
df.world.items.all << item
item.pos = df.cursor
item.flags.on_ground = true
df.map_tile_at.mapblock.items << item.id
df.map_tile_at.occupancy.item = true
}
# move game view, so that the ui menu updates
df.curview.feed_keys(:CURSOR_UP_Z)
df.curview.feed_keys(:CURSOR_DOWN_Z)

@ -10,6 +10,20 @@ def display_death_event(e)
puts str.chomp(',') + '.'
end
def display_death_unit(u)
death_info = u.counters.death_tg
killer = death_info.killer_tg if death_info
str = "The #{u.race_tg.name[0]}"
str << " #{u.name}" if u.name.has_name
str << " died"
str << " in year #{death_info.event_year}" if death_info
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
puts str.chomp(',') + '.'
end
item = df.item_find(:selected)
unit = df.unit_find(:selected)
@ -27,8 +41,11 @@ if not hf
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
elsif hf == -1
# TODO try to retrieve info from the unit (u = item.unit_tg)
puts "Not a historical figure, cannot death find info"
if unit ||= item.unit_tg
display_death_unit(unit)
else
puts "Not a historical figure, cannot death find info"
end
else
histfig = df.world.history.figures.binsearch(hf)

@ -0,0 +1,80 @@
-- Fixes cloth/thread stockpiles by correcting material object data.
local raws = df.global.world.raws
local organic_types = raws.mat_table.organic_types
local organic_indexes = raws.mat_table.organic_indexes
local function verify(category,idx,vtype,vidx)
if idx == -1 then
-- Purely for reporting reasons
return true
end
local tvec = organic_types[category]
if idx < 0 or idx >= #tvec or tvec[idx] ~= vtype then
return false
end
return organic_indexes[category][idx] == vidx
end
local patched_cnt = 0
local mat_cnt = 0
function patch_material(mat,mat_type,mat_index)
local idxarr = mat.food_mat_index
-- These refer to fish/eggs, i.e. castes and not materials
idxarr[1] = -1
idxarr[2] = -1
idxarr[3] = -1
for i = 0,#idxarr-1 do
if not verify(i,idxarr[i],mat_type,mat_index) then
idxarr[i] = -1
patched_cnt = patched_cnt+1
end
end
mat_cnt = mat_cnt + 1
end
function patch_materials()
patched_cnt = 0
mat_cnt = 0
print('Fixing cloth stockpile handling (bug 5739)...')
for i,v in ipairs(raws.inorganics) do
patch_material(v.material, 0, i)
end
for i,v in ipairs(raws.creatures.all) do
for j,m in ipairs(v.material) do
patch_material(m, 19+j, i)
end
end
for i,v in ipairs(raws.plants.all) do
for j,m in ipairs(v.material) do
patch_material(m, 419+j, i)
end
end
print('Patched '..patched_cnt..' bad references in '..mat_cnt..' materials.')
end
local args = {...}
if args[1] == 'enable' then
dfhack.onStateChange[_ENV] = function(sc)
if sc == SC_WORLD_LOADED then
patch_materials()
end
end
if dfhack.isWorldLoaded() then
patch_materials()
end
elseif args[1] == 'disable' then
dfhack.onStateChange[_ENV] = nil
else
patch_materials()
end

@ -38,9 +38,9 @@ def fixunit(unit)
end
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed)
if fixed and unit.unknown8.enemy_status_slot != -1
i = unit.unknown8.enemy_status_slot
unit.unknown8.enemy_status_slot = -1
if fixed and unit.enemy.enemy_status_slot != -1
i = unit.enemy.enemy_status_slot
unit.enemy.enemy_status_slot = -1
cache = df.world.enemy_status_cache
cache.slot_used[i] = false
cache.rel_map[i].map! { -1 }

File diff suppressed because it is too large Load Diff

@ -1,7 +1,17 @@
-- Interface powered item editor.
local gui = require 'gui'
local dialog = require 'gui.dialogs'
local widgets =require 'gui.widgets'
local args={...}
local keybindings={
offset={key="CUSTOM_ALT_O",desc="Show current items offset"},
find={key="CUSTOM_F",desc="Find a value by entering a predicate"},
lua_set={key="CUSTOM_ALT_S",desc="Set by using a lua function"},
insert={key="CUSTOM_ALT_I",desc="Insert a new value to the vector"},
delete={key="CUSTOM_ALT_D",desc="Delete selected entry"},
help={key="HELP",desc="Show this help"},
}
function getTargetFromScreens()
local my_trg
if dfhack.gui.getCurFocus() == 'item' then
@ -28,46 +38,94 @@ function getTargetFromScreens()
return my_trg
end
local MODE_BROWSE=0
local MODE_EDIT=1
GmEditorUi = defclass(GmEditorUi, gui.FramedScreen)
GmEditorUi.ATTRS={
frame_style = gui.GREY_LINE_FRAME,
frame_title = "GameMaster's editor",
}
function GmEditorUi:onHelp()
self.subviews.pages:setSelected(2)
end
function burning_red(input) -- todo does not work! bug angavrilov that so that he would add this, very important!!
local col=COLOR_LIGHTRED
return {text=input,pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}
end
function Disclaimer(tlb)
local dsc={"Association Of ",{text="Psychic ",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}},
"Dwarves (AOPD) is not responsible for all the damage",NEWLINE,"that this tool can (and will) cause to you and your loved dwarves",NEWLINE,"and/or saves.Please use with caution.",NEWLINE,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}}
if tlb then
for _,v in ipairs(dsc) do
table.insert(tlb,v)
end
end
return dsc
end
function GmEditorUi:init(args)
self.stack={}
self.item_count=0
self.mode=MODE_BROWSE
self.keys={}
self:pushTarget(args.target)
local helptext={{text="Help"},NEWLINE,NEWLINE}
for k,v in pairs(keybindings) do
table.insert(helptext,{text=v.desc,key=v.key,key_sep=':'})
table.insert(helptext,NEWLINE)
end
table.insert(helptext,NEWLINE)
Disclaimer(helptext)
local helpPage=widgets.Panel{
subviews={widgets.Label{text=helptext,frame = {l=1,t=1,yalign=0}}}}
local mainList=widgets.List{view_id="list_main",choices={},frame = {l=1,t=3,yalign=0},on_submit=self:callback("editSelected"),
text_pen=dfhack.pen.parse{fg=COLOR_DARKGRAY,bg=0},cursor_pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}
local mainPage=widgets.Panel{
subviews={
mainList,
widgets.Label{text={{text="<no item>",id="name"},{gap=1,text="Help",key="HELP",key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}},
--widgets.Label{text="BLAH2"}
}
,view_id='page_main'}
return self
local pages=widgets.Pages{subviews={mainPage,helpPage},view_id="pages"}
self:addviews{
pages
}
self:pushTarget(args.target)
end
function GmEditorUi:find(test)
local trg=self:currentTarget()
if test== nil then
dialog.showInputPrompt("Test function","Input function that tests(k,v as argument):",COLOR_WHITE,"",dfhack.curry(self.find,self))
return
end
local e,what=load("return function(k,v) return "..test.." end")
if e==nil then
dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_RED)
end
if trg.target and trg.target._kind and trg.target._kind=="container" then
if test== nil then
dialog.showInputPrompt("Test function","Input function that tests(k,v as argument):",COLOR_WHITE,"",dfhack.curry(self.find,self))
return
end
local e,what=load("return function(k,v) return "..test.." end")
if e==nil then
dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_RED)
end
for k,v in pairs(trg.target) do
if e()(k,v)==true then
self:pushTarget(v)
return
end
end
else
local i=1
for k,v in pairs(trg.target) do
if e()(k,v)==true then
self.subviews.list_main:setSelected(i)
return
end
i=i+1
end
end
end
function GmEditorUi:insertNew(typename)
local tp=typename
if typename== nil then
dialog.showInputPrompt("Class type","Input class type:",COLOR_WHITE,"",dfhack.curry(self.insertNew,self))
dialog.showInputPrompt("Class type","Input class type:",COLOR_WHITE,"",self:callback("insertNew"))
return
end
local ntype=df[tp]
@ -79,150 +137,149 @@ function GmEditorUi:insertNew(typename)
local trg=self:currentTarget()
if trg.target and trg.target._kind and trg.target._kind=="container" then
local thing=ntype:new()
dfhack.call_with_finalizer(1,false,df.delete,thing,trg.target.insert,trg.target,'#',thing)
dfhack.call_with_finalizer(1,false,df.delete,thing,function (tscreen,target,to_insert)
target:insert("#",to_insert); tscreen:updateTarget(true,true);end,self,trg.target,thing)
end
end
function GmEditorUi:deleteSelected()
function GmEditorUi:deleteSelected(key)
local trg=self:currentTarget()
if trg.target and trg.target._kind and trg.target._kind=="container" then
trg.target:erase(trg.keys[trg.selected])
trg.target:erase(key)
self:updateTarget(true,true)
end
end
function GmEditorUi:getSelectedKey()
return self:currentTarget().keys[self.subviews.list_main:getSelected()]
end
function GmEditorUi:currentTarget()
return self.stack[#self.stack]
end
function GmEditorUi:changeSelected(delta)
local trg=self:currentTarget()
if trg.item_count <= 1 then return end
trg.selected = 1 + (trg.selected + delta - 1) % trg.item_count
end
function GmEditorUi:editSelected()
function GmEditorUi:editSelected(index,choice)
local trg=self:currentTarget()
local trg_key=trg.keys[index]
if trg.target and trg.target._kind and trg.target._kind=="bitfield" then
trg.target[trg.keys[trg.selected]]= not trg.target[trg.keys[trg.selected]]
trg.target[trg_key]= not trg.target[trg_key]
self:updateTarget(true)
else
--print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "")
local trg_type=type(trg.target[trg.keys[trg.selected]])
local trg_type=type(trg.target[trg_key])
if trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected
self.mode=MODE_EDIT
self.input=tostring(trg.target[trg.keys[trg.selected]])
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.keys[trg.selected]]= not trg.target[trg.keys[trg.selected]]
trg.target[trg_key]= not trg.target[trg_key]
self:updateTarget(true)
elseif trg_type=='userdata' then
self:pushTarget(trg.target[trg.keys[trg.selected]])
--local screen = mkinstance(gui.FramedScreen,GmEditorUi):init(trg.target[trg.keys[trg.selected]]) -- does not work
--screen:show()
self:pushTarget(trg.target[trg_key])
else
print("Unknow type:"..trg_type)
print("Subtype:"..tostring(trg.target[trg.keys[trg.selected]]._kind))
print("Subtype:"..tostring(trg.target[trg_key]._kind))
end
end
end
function GmEditorUi:cancelEdit()
self.mode=MODE_BROWSE
self.input=""
end
function GmEditorUi:commitEdit()
function GmEditorUi:commitEdit(key,value)
local trg=self:currentTarget()
self.mode=MODE_BROWSE
if type(trg.target[trg.keys[trg.selected]])=='number' then
trg.target[trg.keys[trg.selected]]=tonumber(self.input)
elseif type(trg.target[trg.keys[trg.selected]])=='string' then
trg.target[trg.keys[trg.selected]]=self.input
if type(trg.target[key])=='number' then
trg.target[key]=tonumber(value)
elseif type(trg.target[key])=='string' then
trg.target[key]=value
end
self:updateTarget(true)
end
function GmEditorUi:onRenderBody( dc)
local trg=self:currentTarget()
dc:seek(2,1):string(tostring(trg.target), COLOR_RED)
local offset=2
local page_offset=0
local current_item=1
local t_col
local width,height=self:getWindowSize()
local window_height=height-offset-2
local cursor_window=math.floor(trg.selected / window_height)
if cursor_window>0 then
page_offset=cursor_window*window_height-1
end
for k,v in pairs(trg.target) do
if current_item==trg.selected then
t_col=COLOR_LIGHTGREEN
else
t_col=COLOR_GRAY
end
if current_item-page_offset > 0 then
local y_pos=current_item-page_offset+offset
dc:seek(2,y_pos):string(tostring(k),t_col)
if self.mode==MODE_EDIT and current_item==trg.selected then
dc:seek(20,y_pos):string(self.input..'_',COLOR_GREEN)
else
dc:seek(20,y_pos):string(tostring(v),t_col)
end
if y_pos+3>height then
break
end
end
current_item=current_item+1
function GmEditorUi:set(key,input)
local trg=self:currentTarget()
if input== nil then
dialog.showInputPrompt("Set to what?","Lua code to set to (v cur target):",COLOR_WHITE,"",self:callback("set",key))
return
end
local e,what=load("return function(v) return "..input.." end")
if e==nil then
dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_RED)
return
end
trg.target[key]=e()(trg)
self:updateTarget(true)
end
function GmEditorUi:onInput(keys)
if self.mode==MODE_BROWSE then
if keys.LEAVESCREEN then
function GmEditorUi:onInput(keys)
if keys.LEAVESCREEN then
if self.subviews.pages:getSelected()==2 then
self.subviews.pages:setSelected(1)
else
self:popTarget()
elseif keys.CURSOR_UP then
self:changeSelected(-1)
elseif keys.CURSOR_DOWN then
self:changeSelected(1)
elseif keys.CURSOR_UP_FAST then
self:changeSelected(-10)
elseif keys.CURSOR_DOWN_FAST then
self:changeSelected(10)
elseif keys.SELECT then
self:editSelected()
elseif keys.CUSTOM_ALT_F then
self:find()
elseif keys.CUSTOM_ALT_E then
--self:specialEditor()
elseif keys.CUSTOM_ALT_I then --insert
self:insertNew()
elseif keys.CUSTOM_ALT_D then --delete
self:deleteSelected()
end
elseif self.mode==MODE_EDIT then
if keys.LEAVESCREEN then
self:cancelEdit()
elseif keys.SELECT then
self:commitEdit()
elseif keys._STRING then
if keys._STRING==0 then
self.input=string.sub(self.input,1,-2)
else
self.input=self.input.. string.char(keys._STRING)
end
elseif keys[keybindings.offset.key] then
local trg=self:currentTarget()
local _,stoff=df.sizeof(trg.target)
local size,off=df.sizeof(trg.target:_field(self:getSelectedKey()))
dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d\nRelative hex=%x dec=%d",size,off,size,off,off-stoff,off-stoff),COLOR_WHITE)
--elseif keys.CUSTOM_ALT_F then --filter?
elseif keys[keybindings.find.key] then
self:find()
elseif keys[keybindings.lua_set.key] then
self:set(self:getSelectedKey())
--elseif keys.CUSTOM_I then
-- self:insertSimple()
elseif keys[keybindings.insert.key] then --insert
self:insertNew()
elseif keys[keybindings.delete.key] then --delete
self:deleteSelected(self:getSelectedKey())
end
self.super.onInput(self,keys)
end
function GmEditorUi:updateTarget(preserve_pos,reindex)
local trg=self:currentTarget()
if reindex then
trg.keys={}
for k,v in pairs(trg.target) do
table.insert(trg.keys,k)
end
end
self.subviews.lbl_current_item:itemById('name').text=tostring(trg.target)
local t={}
for k,v in pairs(trg.keys) do
table.insert(t,{text={{text=string.format("%-25s",tostring(v))},{gap=1,text=tostring(trg.target[v]),}}})
end
local last_pos
if preserve_pos then
last_pos=self.subviews.list_main:getSelected()
end
self.subviews.list_main:setChoices(t)
if last_pos then
self.subviews.list_main:setSelected(last_pos)
else
self.subviews.list_main:setSelected(trg.selected)
end
end
function GmEditorUi:pushTarget(target_to_push)
local new_tbl={}
new_tbl.target=target_to_push
new_tbl.keys={}
new_tbl.selected=1
if self:currentTarget()~=nil then
self:currentTarget().selected=self.subviews.list_main:getSelected()
end
for k,v in pairs(target_to_push) do
table.insert(new_tbl.keys,k)
end
new_tbl.item_count=#new_tbl.keys
table.insert(self.stack,new_tbl)
self:updateTarget()
end
function GmEditorUi:popTarget()
table.remove(self.stack) --removes last element
if #self.stack==0 then
self:dismiss()
return
end
self:updateTarget()
end
function show_editor(trg)
local screen = GmEditorUi{target=trg}

@ -0,0 +1,31 @@
--set target unit as king/queen
local unit=dfhack.gui.getSelectedUnit()
if not unit then qerror("No unit selected") end
local newfig=dfhack.units.getNemesis(unit).figure
local my_entity=df.historical_entity.find(df.global.ui.civ_id)
local monarch_id
for k,v in pairs(my_entity.positions.own) do
if v.code=="MONARCH" then
monarch_id=v.id
break
end
end
if not monarch_id then qerror("No monarch found!") end
local old_id
for pos_id,v in pairs(my_entity.positions.assignments) do
if v.position_id==monarch_id then
old_id=v.histfig
v.histfig=newfig.id
local oldfig=df.historical_figure.find(old_id)
for k,v in pairs(oldfig.entity_links) do
if df.histfig_entity_link_positionst:is_instance(v) and v.assignment_id==pos_id and v.entity_id==df.global.ui.civ_id then
oldfig.entity_links:erase(k)
break
end
end
newfig.entity_links:insert("#",{new=df.histfig_entity_link_positionst,entity_id=df.global.ui.civ_id,
link_strength=100,assignment_id=pos_id,start_year=df.global.cur_year})
break
end
end

@ -1,14 +1,17 @@
# slay all creatures of a given race
# race = name of the race to eradicate, use 'him' to target only the selected creature
# use 'undead' to target all undeads
race = $script_args[0]
# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death
magma = ($script_args[1] == 'magma')
checkunit = lambda { |u|
u.body.blood_count != 0 and
(u.body.blood_count != 0 or u.body.blood_max == 0) and
not u.flags1.dead and
not u.flags1.caged and
not u.flags1.caged and not u.flags1.chained and
#not u.flags1.hidden_in_ambush and
not df.map_designation_at(u).hidden
}
@ -26,7 +29,8 @@ slayit = lambda { |u|
df.onupdate_unregister(ouh)
else
x, y, z = u.pos.x, u.pos.y, u.pos.z
z += 1 while tile = df.map_tile_at(x, y, z+1) and tile.shape_passableflow
z += 1 while tile = df.map_tile_at(x, y, z+1) and
tile.shape_passableflow and tile.shape_passablelow
df.map_tile_at(x, y, z).spawn_magma(7)
end
}
@ -36,17 +40,41 @@ slayit = lambda { |u|
all_races = Hash.new(0)
df.world.units.active.map { |u|
all_races[u.race_tg.creature_id] += 1 if checkunit[u]
if checkunit[u]
if (u.enemy.undead or
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
u.curse.rem_tags1.OPPOSED_TO_LIFE))
all_races['Undead'] += 1
else
all_races[u.race_tg.creature_id] += 1
end
end
}
if !race
case race
when nil
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
elsif race == 'him'
when 'him'
if him = df.unit_find
slayit[him]
else
puts "Choose target"
puts "Select a target ingame"
end
when /^undead/i
count = 0
df.world.units.active.each { |u|
if (u.enemy.undead or
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
u.curse.rem_tags1.OPPOSED_TO_LIFE)) and
checkunit[u]
slayit[u]
count += 1
end
}
puts "slain #{count} undeads"
else
raw_race = df.match_rawname(race, all_races.keys)
raise 'invalid race' if not raw_race
@ -60,6 +88,6 @@ else
count += 1
end
}
puts "slain #{count} #{raw_race}"
end

@ -138,7 +138,10 @@ if here_only
if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) }
end
puts 'Please select a cage' if list.empty?
if list.empty?
puts 'Please select a cage'
throw :script_finished
end
elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
list = []
@ -153,7 +156,10 @@ elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
list << it
end
}
puts 'Please use a valid cage id' if list.empty?
if list.empty?
puts 'Please use a valid cage id'
throw :script_finished
end
else
list = df.world.items.other[:ANY_CAGE_OR_TRAP]
@ -162,18 +168,16 @@ end
# act
case $script_args[0]
when 'items'
cage_dump_items(list) if not list.empty?
when 'armor'
cage_dump_armor(list) if not list.empty?
when 'weapons'
cage_dump_weapons(list) if not list.empty?
when /^it/i
cage_dump_items(list)
when /^arm/i
cage_dump_armor(list)
when /^wea/i
cage_dump_weapons(list)
when 'all'
cage_dump_all(list) if not list.empty?
cage_dump_all(list)
when 'list'
cage_dump_list(list) if not list.empty?
cage_dump_list(list)
else
puts <<EOS
Marks items inside all cages for dumping.

@ -8,12 +8,7 @@ when 'add'
if u = df.unit_find
$superdwarf_ids |= [u.id]
if df.gamemode == :ADVENTURE and not df.respond_to?(:cur_year_tick_advmode)
onupdate_delay = nil
else
onupdate_delay = 1
end
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', onupdate_delay) {
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) {
if $superdwarf_ids.empty?
df.onupdate_unregister($superdwarf_onupdate)
$superdwarf_onupdate = nil