Merge branch 'master' into digSmart

Conflicts:
	plugins/CMakeLists.txt
develop
expwnent 2013-03-30 12:46:10 -04:00
commit 0397912353
48 changed files with 8082 additions and 1230 deletions

6
.gitignore vendored

@ -58,5 +58,9 @@ build/CPack*Config.cmake
/cmakeall.bat
# vim swap files
# vim files
*.swp
.vimrc
# ctags file
tags

@ -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 "r3" CACHE STRING "Current release revision.")
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)

@ -67,6 +67,8 @@ program.
Mac OS X
========
If you are building on 10.6, please read the subsection below titled "Snow Leopard Changes" FIRST.
1. Download and unpack a copy of the latest DF
2. Install Xcode from Mac App Store
3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools.
@ -106,6 +108,18 @@ Mac OS X
make
make install
Snow Leopard Changes
====================
1. Add a step 6.2a (before Install XML::LibXSLT)::
In a separate Terminal window or tab, run:
``sudo ln -s /usr/include/libxml2/libxml /usr/include/libxml``
2. Add a step 7a (before building)::
In <dfhack directory>/library/LuaTypes.cpp, change line 467 to
``int len = strlen((char*)ptr);``
=======
Windows
=======

@ -402,10 +402,16 @@ ul.auto-toc {
<li><a class="reference internal" href="#plugins" id="id52">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id53">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id54">sort</a></li>
<li><a class="reference internal" href="#eventful" id="id55">Eventful</a><ul>
<li><a class="reference internal" href="#list-of-events" id="id56">List of events</a></li>
<li><a class="reference internal" href="#functions" id="id57">Functions</a></li>
<li><a class="reference internal" href="#examples" id="id58">Examples</a></li>
</ul>
</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>
<li><a class="reference internal" href="#scripts" id="id59">Scripts</a><ul>
<li><a class="reference internal" href="#save-init-script" id="id60">Save init script</a></li>
</ul>
</li>
</ul>
@ -3038,9 +3044,93 @@ set is the same as used by the command line.</p>
<p>Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.</p>
</div>
<div class="section" id="eventful">
<h2><a class="toc-backref" href="#id55">Eventful</a></h2>
<p>This plugin exports some events to lua thus allowing to run lua functions
on DF world events.</p>
<div class="section" id="list-of-events">
<h3><a class="toc-backref" href="#id56">List of events</a></h3>
<ol class="arabic">
<li><p class="first"><tt class="docutils literal">onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native)</tt></p>
<p>Auto activates if detects reactions starting with <tt class="docutils literal">LUA_HOOK_</tt>. Is called when reaction finishes.</p>
</li>
<li><p class="first"><tt class="docutils literal">onItemContaminateWound(item,unit,wound,number1,number2)</tt></p>
<p>Is called when item tries to contaminate wound (e.g. stuck in).</p>
</li>
<li><p class="first"><tt class="docutils literal">onProjItemCheckMovement(projectile)</tt></p>
<p>Is called when projectile moves.</p>
</li>
<li><p class="first"><tt class="docutils literal">onProjItemCheckImpact(projectile,somebool)</tt></p>
<p>Is called when projectile hits something.</p>
</li>
<li><p class="first"><tt class="docutils literal">onProjUnitCheckMovement(projectile)</tt></p>
<p>Is called when projectile moves.</p>
</li>
<li><p class="first"><tt class="docutils literal">onProjUnitCheckImpact(projectile,somebool)</tt></p>
<p>Is called when projectile hits something.</p>
</li>
<li><p class="first"><tt class="docutils literal">onWorkshopFillSidebarMenu(workshop,callnative)</tt></p>
<p>Is called when viewing a workshop in 'q' mode, to populate reactions, useful for custom viewscreens for shops.</p>
</li>
<li><p class="first"><tt class="docutils literal">postWorkshopFillSidebarMenu(workshop)</tt></p>
<p>Is called after calling (or not) native fillSidebarMenu(). Useful for job button
tweaking (e.g. adding custom reactions)</p>
</li>
</ol>
</div>
<div class="section" id="functions">
<h3><a class="toc-backref" href="#id57">Functions</a></h3>
<ol class="arabic">
<li><p class="first"><tt class="docutils literal">registerReaction(reaction_name,callback)</tt></p>
<p>Simplified way of using onReactionComplete; the callback is function (same params as event).</p>
</li>
<li><p class="first"><tt class="docutils literal">removeNative(shop_name)</tt></p>
<p>Removes native choice list from the building.</p>
</li>
<li><p class="first"><tt class="docutils literal">addReactionToShop(reaction_name,shop_name)</tt></p>
<p>Add a custom reaction to the building.</p>
</li>
</ol>
</div>
<div class="section" id="examples">
<h3><a class="toc-backref" href="#id58">Examples</a></h3>
<p>Spawn dragon breath on each item attempt to contaminate wound:</p>
<pre class="literal-block">
b=require &quot;plugins.eventful&quot;
b.onItemContaminateWound.one=function(item,unit,un_wound,x,y)
local flw=dfhack.maps.spawnFlow(unit.pos,6,0,0,50000)
end
</pre>
<p>Reaction complete example:</p>
<pre class="literal-block">
b=require &quot;plugins.eventful&quot;
b.onReactionComplete.one=function(reaction,unit,in_items,in_reag,out_items,call_native)
local pos=copyall(unit.pos)
-- spawn dragonbreath after 100 ticks
dfhack.timeout(100,&quot;ticks&quot;,function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end)
--do not call real item creation code
call_native.value=false
end
</pre>
<p>Grenade example:</p>
<pre class="literal-block">
b=require &quot;plugins.eventful&quot;
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
</pre>
<p>Integrated tannery:</p>
<pre class="literal-block">
b=require &quot;plugins.eventful&quot;
b.addReactionToShop(&quot;TAN_A_HIDE&quot;,&quot;LEATHERWORKS&quot;)
</pre>
</div>
</div>
</div>
<div class="section" id="scripts">
<h1><a class="toc-backref" href="#id55">Scripts</a></h1>
<h1><a class="toc-backref" href="#id59">Scripts</a></h1>
<p>Any files with the .lua extension placed into hack/scripts/*
are automatically used by the DFHack core as commands. The
matching command name consists of the name of the file sans
@ -3071,7 +3161,7 @@ The <tt class="docutils literal">name</tt> argument should be the name stem, as
</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>
<h2><a class="toc-backref" href="#id60">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>

@ -2958,6 +2958,96 @@ 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, useful for custom viewscreens for shops.
8. ``postWorkshopFillSidebarMenu(workshop)``
Is called after calling (or not) native fillSidebarMenu(). Useful 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)
-- spawn dragonbreath after 100 ticks
dfhack.timeout(100,"ticks",function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end)
--do not call real item creation code
call_native.value=false
end
Grenade 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

24
NEWS

@ -1,10 +1,15 @@
DFHack future
Is not yet known.
DFHack v0.34.11-r3
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.
- 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.
@ -28,6 +33,7 @@ DFHack future
- 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
- fix/cloth-stockpile: fixes bug 5739; needs to be run after savegame load every time.
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.
@ -35,6 +41,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.
@ -61,11 +68,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.
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.
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

File diff suppressed because it is too large Load Diff

@ -449,6 +449,26 @@ Options:
:bees: turn colonies into honey bee colonies
createitem
----------
Allows creating new items of arbitrary types and made of arbitrary materials.
Any items created are spawned at the feet of the selected unit.
Specify the item and material information as you would indicate them in custom reaction raws, with the following differences:
* Separate the item and material with a space rather than a colon
* If the item has no subtype, omit the :NONE
* If the item is REMAINS, FISH, FISH_RAW, VERMIN, PET, or EGG, specify a CREATURE:CASTE pair instead of a material token.
Corpses, body parts, and prepared meals cannot be created using this tool.
Examples:
``createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2``
Create 2 pairs of steel gauntlets.
``createitem WOOD PLANT_MAT:TOWER_CAP:WOOD``
Create tower-cap logs.
``createitem FISH FISH_SHAD:MALE 5``
Create a stack of 5 cleaned shad, ready to eat.
deramp (by zilpin)
------------------
Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation.
@ -1773,6 +1793,12 @@ Scripts in this subdirectory fix various bugs and issues, some of them obscure.
Diagnoses and fixes issues with nonexistant 'items occupying site', usually
caused by autodump bugs or other hacking mishaps.
* fix/cloth-stockpile
Fixes erratic behavior of cloth stockpiles by scanning material objects
in memory and patching up some invalid reference fields. Needs to be run
every time a save game is loaded; putting ``fix/cloth-stockpile enable``
in ``dfhack.init`` makes it run automatically.
gui/*
=====
@ -2032,6 +2058,17 @@ Exemples::
create-items bar CREATURE:CAT:SOAP
create-items bar adamantine
soundsense-season
=================
It is a well known issue that Soundsense cannot detect the correct
current season when a savegame is loaded and has to play random
season music until a season switch occurs.
This script registers a hook that prints the appropriate string
to gamelog.txt on every map load to fix this. For best results
call the script from ``dfhack.init``.
=======================
In-game interface tools
=======================
@ -2105,7 +2142,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
@ -2125,8 +2164,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:
@ -2182,8 +2222,25 @@ To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mo
.. image:: images/liquids.png
While active, use the suggested keys to switch the usual liquids parameters, and Enter
to select the target area and apply changes.
This script is a gui front-end to the liquids plugin and works similar to it,
allowing you to add or remove water & magma, and create obsidian walls & floors.
Note that there is **no undo support**, and that bugs in this plugin have been
known to create pathfinding problems and heat traps.
The ``b`` key changes how the affected area is selected. The default *Rectangle*
mode works by selecting two corners like any ordinary designation. The ``p``
key chooses between adding water, magma, obsidian walls & floors, or just
tweaking flags.
When painting liquids, it is possible to select the desired level with ``+-``,
and choose between setting it exactly, only increasing or only decreasing
with ``s``.
In addition, ``f`` allows disabling or enabling the flowing water computations
for an area, and ``r`` operates on the "permanent flow" property that makes
rivers power water wheels even when full and technically not flowing.
After setting up the desired operations using the described keys, use ``Enter`` to apply them.
gui/mechanisms
@ -2411,6 +2468,55 @@ 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/companion-order
=======================
A script to issue orders for companions. Select companions with lower case chars, issue orders with upper
case. Must be in look or talk mode to issue command on tile.
* move - orders selected companions to move to location. If companions are following they will move no more than 3 tiles from you.
* equip - try to equip items on the ground.
* pick-up - try to take items into hand (also wield)
* unequip - remove and drop equipment
* unwield - drop held items
* wait - temporarely remove from party
* follow - rejoin the party after "wait"
* leave - remove from party (can be rejoined by talking)
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-editor 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
=============
@ -2604,3 +2710,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 #
# #

@ -373,7 +373,8 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std::
if (possible.size() == 1)
{
completed = possible[0];
fprintf(stderr, "Autocompleted %s to %s\n", first.c_str(), completed.c_str());
//fprintf(stderr, "Autocompleted %s to %s\n", , );
con.printerr("%s is not recognized. Did you mean %s?\n", first.c_str(), completed.c_str());
return true;
}
@ -382,7 +383,8 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std::
std::string out;
for (size_t i = 0; i < possible.size(); i++)
out += " " + possible[i];
con.print("Possible completions:%s\n", out.c_str());
con.printerr("%s is not recognized. Possible completions:%s\n", first.c_str(), out.c_str());
return true;
}
return false;
@ -717,7 +719,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
else if (plug_mgr->eval_ruby && fileExists(filename + ".rb"))
res = runRubyScript(con, plug_mgr, first, parts);
else if (try_autocomplete(con, first, completed))
return runCommand(con, completed, parts);
return CR_NOT_IMPLEMENTED;// runCommand(con, completed, parts);
else
con.printerr("%s is not a recognized command.\n", first.c_str());
}

@ -212,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;
@ -229,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)

@ -312,12 +312,12 @@ void* Process::memAlloc(const int length)
return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
}
int Process::memDealloc(const void *ptr, const int length)
int Process::memDealloc(void *ptr, const int length)
{
return munmap(ptr, length);
}
int Process::memProtect(const void *ptr, const int length, const int prot)
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;

@ -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);

@ -247,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; }

@ -1,5 +1,7 @@
-- Simple binary patch with IDA dif file support.
local _ENV = mkmodule('binpatch')
local function load_patch(name)
local filename = name
if not string.match(filename, '[./\\]') then

@ -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

@ -249,31 +249,21 @@ df::building *Buildings::findAtTile(df::coord pos)
if (!occ || !occ->bits.building)
return NULL;
auto a = locationToBuilding.find(pos);
if ( a == locationToBuilding.end() ) {
cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <<pos.z << "." << endl;
exit(1);
}
int32_t id = (*a).second;
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, id);
if ( index == -1 ) {
cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <<pos.z << "." << endl;
exit(1);
}
df::building* building = df::global::world->buildings.all[index];
if (!building->isSettingOccupancy())
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->room.extents && building->isExtentShaped())
if (building && building->z == pos.z &&
building->isSettingOccupancy() &&
containsTile(building, pos, false))
{
auto etile = getExtentTile(building->room, pos);
if (!etile || !*etile)
return NULL;
}
return building;
}
}
/*
//old method: brute-force
// 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++)
{
@ -298,7 +288,6 @@ df::building *Buildings::findAtTile(df::coord pos)
}
return NULL;
*/
}
bool Buildings::findCivzonesAt(std::vector<df::building_civzonest*> *pvec, df::coord pos)
@ -1125,18 +1114,21 @@ void Buildings::clearBuildings(color_ostream& out) {
locationToBuilding.clear();
}
void Buildings::updateBuildings(color_ostream& out, void* ptr) {
//out.print("Updating buildings, %s %d\n", __FILE__, __LINE__);
void Buildings::updateBuildings(color_ostream& out, void* ptr)
{
int32_t id = (int32_t)ptr;
auto building = df::building::find(id);
if (building)
{
// Already cached -> weird, so bail out
if (corner1.count(id))
return;
// Civzones cannot be cached because they can
// overlap each other and normal buildings.
if (!building->isSettingOccupancy())
return;
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);
@ -1146,10 +1138,13 @@ void Buildings::updateBuildings(color_ostream& out, void* ptr) {
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);
if (containsTile(building, pt, false))
locationToBuilding[pt] = id;
}
}
} else {
}
else if (corner1.count(id))
{
//existing building: destroy it
df::coord p1 = corner1[id];
df::coord p2 = corner2[id];
@ -1157,7 +1152,10 @@ void Buildings::updateBuildings(color_ostream& out, void* ptr) {
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);
auto cur = locationToBuilding.find(pt);
if (cur != locationToBuilding.end() && cur->second == id)
locationToBuilding.erase(cur);
}
}

@ -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++)

@ -1 +1 @@
Subproject commit fbf671a7d5aacb41cb44059eb16a1ee9cad419be
Subproject commit 4d2afc3a0bcebdb17415dc2827b44fd35986a368

@ -11,5 +11,4 @@ else
export DYLD_FALLBACK_LIBRARY_PATH=${PWD}/hack:${PWD}/libs
export DYLD_FALLBACK_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs
fi
export DYLD_FORCE_FLAT_NAMESPACE=1
cd "${PWD}"; ./dwarfort.exe

@ -125,7 +125,7 @@ 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...
@ -137,6 +137,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(trueTransformation trueTransformation.cpp)
DFHACK_PLUGIN(infiniteSky infiniteSky.cpp)
DFHACK_PLUGIN(digSmart digSmart.cpp)
DFHACK_PLUGIN(createitem createitem.cpp)
endif()

@ -126,9 +126,8 @@ DFhackCExport command_result plugin_init(color_ostream& out, vector<PluginComman
));
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome");
EventManager::EventHandler handle(processJob, 5);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
return CR_OK;
}

@ -0,0 +1,257 @@
// Create arbitrary items
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "MiscUtils.h"
#include "modules/Maps.h"
#include "modules/Gui.h"
#include "modules/Items.h"
#include "modules/Materials.h"
#include "DataDefs.h"
#include "df/game_type.h"
#include "df/world.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/historical_entity.h"
#include "df/world_site.h"
#include "df/item.h"
#include "df/creature_raw.h"
#include "df/caste_raw.h"
#include "df/reaction_reagent.h"
#include "df/reaction_product_itemst.h"
using namespace std;
using namespace DFHack;
using df::global::world;
using df::global::ui;
using df::global::gametype;
DFHACK_PLUGIN("createitem");
command_result df_createitem (color_ostream &out, vector <string> & parameters);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector<PluginCommand> &commands)
{
commands.push_back(PluginCommand("createitem", "Create arbitrary item at the selected unit's feet.", df_createitem));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool glove2 = false)
{
vector<df::item *> out_items;
vector<df::reaction_reagent *> in_reag;
vector<df::item *> in_items;
bool is_gloves = (prod->item_type == df::item_type::GLOVES);
prod->produce(unit, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE,
df::historical_entity::find(unit->civ_id),
((*gametype == df::game_type::DWARF_MAIN) || (*gametype == df::game_type::DWARF_RECLAIM)) ? df::world_site::find(ui->site_id) : NULL);
if (!out_items.size())
return false;
for (size_t i = 0; i < out_items.size(); i++)
{
out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z);
if (is_gloves)
{
// if the reaction creates gloves without handedness, then create 2 sets (left and right)
if (out_items[i]->getGloveHandedness() > 0)
is_gloves = false;
else
out_items[i]->setGloveHandedness(glove2 ? 2 : 1);
}
}
if (is_gloves && !glove2)
return makeItem(prod, unit, true);
return true;
}
command_result df_createitem (color_ostream &out, vector <string> & parameters)
{
string item_str, material_str;
df::item_type item_type = df::item_type::NONE;
int16_t item_subtype = -1;
int16_t mat_type = -1;
int32_t mat_index = -1;
int count = 1;
if ((parameters.size() < 2) || (parameters.size() > 3))
{
out.print("Syntax: createitem <item> <material> [count]\n"
" <item> - Item token for what you wish to create, as specified in custom\n"
" reactions. If the item has no subtype, omit the :NONE.\n"
" <material> - The material you want the item to be made of, as specified\n"
" in custom reactions. For REMAINS, FISH, FISH_RAW, VERMIN,\n"
" PET, and EGG, replace this with a creature ID and caste.\n"
" [count] - How many of the item you wish to create.\n"
);
return CR_WRONG_USAGE;
}
item_str = parameters[0];
material_str = parameters[1];
if (parameters.size() == 3)
{
stringstream ss(parameters[2]);
ss >> count;
if (count < 1)
{
out.printerr("You cannot produce less than one item!\n");
return CR_FAILURE;
}
}
ItemTypeInfo item;
MaterialInfo material;
vector<string> tokens;
if (!item.find(item_str))
{
out.printerr("Unrecognized item type!\n");
return CR_FAILURE;
}
item_type = item.type;
item_subtype = item.subtype;
switch (item.type)
{
case df::item_type::INSTRUMENT:
case df::item_type::TOY:
case df::item_type::WEAPON:
case df::item_type::ARMOR:
case df::item_type::SHOES:
case df::item_type::SHIELD:
case df::item_type::HELM:
case df::item_type::GLOVES:
case df::item_type::AMMO:
case df::item_type::PANTS:
case df::item_type::SIEGEAMMO:
case df::item_type::TRAPCOMP:
case df::item_type::TOOL:
if (item_subtype == -1)
{
out.printerr("You must specify a subtype!\n");
return CR_FAILURE;
}
default:
if (!material.find(material_str))
{
out.printerr("Unrecognized material!\n");
return CR_FAILURE;
}
mat_type = material.type;
mat_index = material.index;
break;
case df::item_type::REMAINS:
case df::item_type::FISH:
case df::item_type::FISH_RAW:
case df::item_type::VERMIN:
case df::item_type::PET:
case df::item_type::EGG:
split_string(&tokens, material_str, ":");
if (tokens.size() != 2)
{
out.printerr("You must specify a creature ID and caste for this item type!\n");
return CR_FAILURE;
}
for (size_t i = 0; i < world->raws.creatures.all.size(); i++)
{
df::creature_raw *creature = world->raws.creatures.all[i];
if (creature->creature_id == tokens[0])
{
for (size_t j = 0; j < creature->caste.size(); j++)
{
df::caste_raw *caste = creature->caste[j];
if (creature->caste[j]->caste_id == tokens[1])
{
mat_type = i;
mat_index = j;
break;
}
}
if (mat_type == -1)
{
out.printerr("The creature you specified has no such caste!\n");
return CR_FAILURE;
}
}
}
if (mat_type == -1)
{
out.printerr("Unrecognized creature ID!\n");
return CR_FAILURE;
}
break;
case df::item_type::CORPSE:
case df::item_type::CORPSEPIECE:
case df::item_type::FOOD:
out.printerr("Cannot create that type of item!\n");
return CR_FAILURE;
break;
}
CoreSuspender suspend;
df::unit *unit = Gui::getSelectedUnit(out, true);
if (!unit)
{
out.printerr("No unit selected!\n");
return CR_FAILURE;
}
if (!Maps::IsValid())
{
out.printerr("Map is not available.\n");
return CR_FAILURE;
}
df::map_block *block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z);
if (block == NULL)
{
out.printerr("Unit does not reside in a valid map block, somehow?\n");
return CR_FAILURE;
}
df::reaction_product_itemst *prod = df::allocate<df::reaction_product_itemst>();
prod->item_type = item_type;
prod->item_subtype = item_subtype;
prod->mat_type = mat_type;
prod->mat_index = mat_index;
prod->probability = 100;
prod->count = count;
switch (item_type)
{
case df::item_type::BAR:
case df::item_type::POWDER_MISC:
case df::item_type::LIQUID_MISC:
case df::item_type::DRINK:
prod->product_dimension = 150;
break;
case df::item_type::THREAD:
prod->product_dimension = 15000;
break;
case df::item_type::CLOTH:
prod->product_dimension = 10000;
break;
default:
prod->product_dimension = 1;
break;
}
if (!makeItem(prod, unit))
{
out.printerr("Failed to create item!\n");
return CR_FAILURE;
}
return CR_OK;
}

@ -18,6 +18,7 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp)
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)

File diff suppressed because it is too large Load Diff

@ -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 true;//due to mat_type being -1 = any
}
};
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;
}

@ -149,6 +149,8 @@ void doInfiniteSky(color_ostream& out, int32_t howMany) {
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;
}
}

@ -178,6 +178,7 @@ end
function BinaryPlugin:move_to_df()
local _,addr=df.sizeof(self.data)
markAsExecutable(addr)
return addr
end
function BinaryPlugin:print_data()
local out=""
@ -220,7 +221,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

@ -9,9 +9,18 @@ FriendshipRainbow.name="FriendshipRainbow"
-- os independant... I think...
FriendshipRainbow.ATTRS{filename="hack/lua/plugins/dfusion/friendship.o",name="FriendshipRainbow",race_data=DEFAULT_NIL}
FriendshipRainbow.class_status="valid, not installed"
function FriendshipRainbow:findall_needles(codesg,needle) -- todo move to memscan.lua
local cidx,caddr=codesg.uint8_t:find(needle)
local ret={}
while cidx~=nil do
table.insert(ret,{cidx,caddr})
cidx,caddr=codesg.uint8_t:find(needle,cidx+1)
end
return ret
end
function FriendshipRainbow:find_one(codesg,needle,crace)
dfu.concatTables(needle,dfu.dwordToTable(crace))
return codesg.uint8_t:findall(needle)
return self:findall_needles(codesg,needle)
end
function FriendshipRainbow:find_all()
local code=ms.get_code_segment()

@ -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)

@ -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;
}

@ -843,7 +843,7 @@ sub render_item_number {
} elsif ($subtype eq 'uint8_t') {
push @lines_rb, 'number 8, false';
} elsif ($subtype eq 'int8_t') {
push @lines_rb, 'number 8, false';
push @lines_rb, 'number 8, true';
} elsif ($subtype eq 'bool') {
push @lines_rb, 'number 8, true';
$initvalue ||= 'nil';

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd
Subproject commit 83e2b8a3754e2feb0d5bf106d2b43ff71ff63935

@ -26,8 +26,7 @@ void syndromeHandler(color_ostream& out, void* ptr);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
EventManager::EventHandler syndrome(syndromeHandler, 1);
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("trueTransformation");
EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, me);
EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, plugin_self);
return CR_OK;
}
@ -36,12 +35,12 @@ 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 ) {
df::unit* unit = df::unit::find(data->unitId);
if (!unit) {
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];

@ -251,9 +251,15 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest
// Force update of ui state
set<df::interface_key> tmp;
if (last_cursor.z < 2)
tmp.insert(interface_key::CURSOR_UP_Z);
else
tmp.insert(interface_key::CURSOR_DOWN_Z);
INTERPOSE_NEXT(feed)(&tmp);
tmp.clear();
if (last_cursor.z < 2)
tmp.insert(interface_key::CURSOR_DOWN_Z);
else
tmp.insert(interface_key::CURSOR_UP_Z);
INTERPOSE_NEXT(feed)(&tmp);
}
@ -809,14 +815,18 @@ struct military_training_ct_hook : df::activity_event_combat_trainingst {
spar++;
}
#if 0
color_ostream_proxy out(Core::getInstance().getConsole());
#endif
// If the xp gap is low, sometimes replace with sparring
if ((maxv - minv) < 64*15 && spar == units.size() &&
random_int(45) >= 30 + (maxv-minv)/64)
{
#if 0
out.print("Replacing %s demonstration (xp %d-%d, gap %d) with sparring.\n",
ENUM_KEY_STR(job_skill, sd->skill).c_str(), minv, maxv, maxv-minv);
#endif
if (auto spar = df::allocate<df::activity_event_sparringst>())
{
@ -838,18 +848,22 @@ struct military_training_ct_hook : df::activity_event_combat_trainingst {
// If the teacher has less xp than somebody else, switch
if (best >= 0 && maxv > cur_xp)
{
#if 0
out.print("Replacing %s teacher %d (%d xp) with %d (%d xp); xp gap %d.\n",
ENUM_KEY_STR(job_skill, sd->skill).c_str(),
sd->unit_id, cur_xp, units[best], maxv, maxv-minv);
#endif
sd->hist_figure_id = sd->participants.histfigs[best];
sd->unit_id = units[best];
}
else
{
#if 0
out.print("Not changing %s demonstration (xp %d-%d, gap %d).\n",
ENUM_KEY_STR(job_skill, sd->skill).c_str(),
minv, maxv, maxv-minv);
#endif
}
}
}

@ -3,39 +3,51 @@
#include "Export.h"
#include "PluginManager.h"
#include "DataDefs.h"
#include "modules/EventManager.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;
static int mode = 0;
DFhackCExport command_result workNow(color_ostream& out, vector<string>& parameters);
void jobCompletedHandler(color_ostream& out, void* ptr);
EventManager::EventHandler handler(jobCompletedHandler,1);
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"
commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs whever they finish one, or 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. When workNow is in mode 2, it will make dwarves look for jobs every time a job completes (or is cancelled).\n"
"workNow\n"
" print workNow status\n"
"workNow 0\n"
" deactivate workNow\n"));
" deactivate workNow\n"
"workNow 1\n"
" activate workNow (look for jobs on pause, and only then)\n"
"workNow 2\n"
" make dwarves look for jobs whenever a job completes\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) {
active = false;
mode = 0;
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) {
if ( !active )
if ( !mode )
return CR_OK;
if ( e == DFHack::SC_WORLD_UNLOADED ) {
active = false;
mode = 0;
return CR_OK;
}
if ( e != DFHack::SC_PAUSED )
@ -47,11 +59,9 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
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");
out.print("workNow status = %d\n", mode);
return CR_OK;
}
if ( parameters.size() > 1 ) {
@ -59,12 +69,25 @@ DFhackCExport command_result workNow(color_ostream& out, vector<string>& paramet
}
int32_t a = atoi(parameters[0].c_str());
if (a < 0 || a > 1)
if (a < 0 || a > 2)
return CR_WRONG_USAGE;
active = (bool)a;
if ( a == 2 && mode != 2 ) {
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handler, plugin_self);
} else if ( mode == 2 && a != 2 ) {
EventManager::unregister(EventManager::EventType::JOB_COMPLETED, handler, plugin_self);
}
mode = a;
out.print("workNow status = %d\n", mode);
return CR_OK;
}
void jobCompletedHandler(color_ostream& out, void* ptr) {
if ( mode < 2 )
return;
*df::global::process_jobs = true;
*df::global::process_dig = true;
}

@ -102,9 +102,9 @@ class AutoFarm
farms_u = []
df.world.buildings.other[:FARM_PLOT].each { |f|
if (f.flags.exists)
outside = df.map_designation_at(f.centerx,f.centery,f.z).outside
farms_s.push(f) if outside
farms_u.push(f) unless outside
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
farms_s.push(f) unless underground
farms_u.push(f) if underground
end
}

@ -0,0 +1,88 @@
-- Fixes cloth/thread stockpiles by correcting material object data.
local raws = df.global.world.raws
-- Cache references to vectors in lua tables for a speed-up
local organic_types = {}
for i,v in ipairs(raws.mat_table.organic_types) do
organic_types[i] = v
end
local organic_indexes = {}
for i,v in ipairs(raws.mat_table.organic_indexes) do
organic_indexes[i] = v
end
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

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 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
if trg.target and trg.target._kind and trg.target._kind=="container" then
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,132 +137,124 @@ 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)
function GmEditorUi:set(key,input)
local trg=self:currentTarget()
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
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
current_item=current_item+1
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
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
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.CUSTOM_ALT_E then
--self:specialEditor()
elseif keys.CUSTOM_ALT_I then --insert
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.CUSTOM_ALT_D then --delete
self:deleteSelected()
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
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
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)
@ -212,17 +262,24 @@ function GmEditorUi:pushTarget(target_to_push)
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