Merge branch 'master' of git://github.com/peterix/dfhack

develop
Warmist 2013-02-15 20:23:39 +02:00
commit b0528d283b
36 changed files with 6018 additions and 500 deletions

3
.gitignore vendored

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

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

10
NEWS

@ -3,6 +3,8 @@ DFHack future
Internals: Internals:
- support for displaying active keybindings properly. - support for displaying active keybindings properly.
- support for reusable widgets in lua screen library. - support for reusable widgets in lua screen library.
- Maps::canStepBetween: returns whether you can walk between two tiles in one step.
- EventManager: monitors various in game events centrally so that individual plugins don't have to monitor the same things redundantly.
Notable bugfixes: Notable bugfixes:
- autobutcher can be re-enabled again after being stopped. - autobutcher can be re-enabled again after being stopped.
- stopped Dwarf Manipulator from unmasking vampires. - stopped Dwarf Manipulator from unmasking vampires.
@ -62,6 +64,14 @@ DFHack future
Reworked to make use of lua modules, now all the scripts can be used from other scripts. Reworked to make use of lua modules, now all the scripts can be used from other scripts.
New Eventful plugin: New Eventful plugin:
A collection of lua events, that will allow new ways to interact with df world. 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 DFHack v0.34.11-r2

@ -3,13 +3,13 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.9.1: http://docutils.sourceforge.net/" /> <meta name="generator" content="Docutils 0.10: http://docutils.sourceforge.net/" />
<title>DFHack Readme</title> <title>DFHack Readme</title>
<style type="text/css"> <style type="text/css">
/* /*
:Author: David Goodger (goodger@python.org) :Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7434 2012-05-11 21:06:27Z milde $ :Id: $Id: html4css1.css 7514 2012-09-14 14:27:12Z milde $
:Copyright: This stylesheet has been placed in the public domain. :Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils. Default cascading style sheet for the HTML output of Docutils.
@ -77,7 +77,7 @@ div.tip p.admonition-title {
div.attention p.admonition-title, div.caution p.admonition-title, div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title { div.warning p.admonition-title, .code .error {
color: red ; color: red ;
font-weight: bold ; font-weight: bold ;
font-family: sans-serif } font-family: sans-serif }
@ -253,13 +253,14 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ; margin-left: 2em ;
margin-right: 2em } margin-right: 2em }
pre.code .ln { /* line numbers */ pre.code .ln { color: grey; } /* line numbers */
color: grey; pre.code, code { background-color: #eeeeee }
} pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
.code { pre.code .literal.string, code .literal.string { color: #0C5404 }
background-color: #eeeeee pre.code .name.builtin, code .name.builtin { color: #352B84 }
} pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier { span.classifier {
font-family: sans-serif ; font-family: sans-serif ;
@ -508,39 +509,42 @@ access DF memory and allow for easier development of new tools.</p>
<li><a class="reference internal" href="#embark" id="id131">embark</a></li> <li><a class="reference internal" href="#embark" id="id131">embark</a></li>
<li><a class="reference internal" href="#lever" id="id132">lever</a></li> <li><a class="reference internal" href="#lever" id="id132">lever</a></li>
<li><a class="reference internal" href="#stripcaged" id="id133">stripcaged</a></li> <li><a class="reference internal" href="#stripcaged" id="id133">stripcaged</a></li>
<li><a class="reference internal" href="#create-items" id="id134">create-items</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#in-game-interface-tools" id="id134">In-game interface tools</a><ul> <li><a class="reference internal" href="#in-game-interface-tools" id="id135">In-game interface tools</a><ul>
<li><a class="reference internal" href="#dwarf-manipulator" id="id135">Dwarf Manipulator</a></li> <li><a class="reference internal" href="#dwarf-manipulator" id="id136">Dwarf Manipulator</a></li>
<li><a class="reference internal" href="#search" id="id136">Search</a></li> <li><a class="reference internal" href="#search" id="id137">Search</a></li>
<li><a class="reference internal" href="#automaterial" id="id137">AutoMaterial</a></li> <li><a class="reference internal" href="#automaterial" id="id138">AutoMaterial</a></li>
<li><a class="reference internal" href="#gui-liquids" id="id138">gui/liquids</a></li> <li><a class="reference internal" href="#gui-liquids" id="id139">gui/liquids</a></li>
<li><a class="reference internal" href="#gui-mechanisms" id="id139">gui/mechanisms</a></li> <li><a class="reference internal" href="#gui-mechanisms" id="id140">gui/mechanisms</a></li>
<li><a class="reference internal" href="#gui-rename" id="id140">gui/rename</a></li> <li><a class="reference internal" href="#gui-rename" id="id141">gui/rename</a></li>
<li><a class="reference internal" href="#gui-room-list" id="id141">gui/room-list</a></li> <li><a class="reference internal" href="#gui-room-list" id="id142">gui/room-list</a></li>
<li><a class="reference internal" href="#gui-choose-weapons" id="id142">gui/choose-weapons</a></li> <li><a class="reference internal" href="#gui-choose-weapons" id="id143">gui/choose-weapons</a></li>
<li><a class="reference internal" href="#gui-guide-path" id="id143">gui/guide-path</a></li> <li><a class="reference internal" href="#gui-guide-path" id="id144">gui/guide-path</a></li>
<li><a class="reference internal" href="#gui-workshop-job" id="id144">gui/workshop-job</a></li> <li><a class="reference internal" href="#gui-workshop-job" id="id145">gui/workshop-job</a></li>
<li><a class="reference internal" href="#gui-workflow" id="id145">gui/workflow</a></li> <li><a class="reference internal" href="#gui-workflow" id="id146">gui/workflow</a></li>
<li><a class="reference internal" href="#gui-assign-rack" id="id146">gui/assign-rack</a></li> <li><a class="reference internal" href="#gui-assign-rack" id="id147">gui/assign-rack</a></li>
<li><a class="reference internal" href="#gui-advfort" id="id148">gui/advfort</a></li>
<li><a class="reference internal" href="#gui-gm-editor" id="id149">gui/gm-editor</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#behavior-mods" id="id147">Behavior Mods</a><ul> <li><a class="reference internal" href="#behavior-mods" id="id150">Behavior Mods</a><ul>
<li><a class="reference internal" href="#siege-engine" id="id148">Siege Engine</a><ul> <li><a class="reference internal" href="#siege-engine" id="id151">Siege Engine</a><ul>
<li><a class="reference internal" href="#rationale" id="id149">Rationale</a></li> <li><a class="reference internal" href="#rationale" id="id152">Rationale</a></li>
<li><a class="reference internal" href="#configuration-ui" id="id150">Configuration UI</a></li> <li><a class="reference internal" href="#configuration-ui" id="id153">Configuration UI</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#power-meter" id="id151">Power Meter</a></li> <li><a class="reference internal" href="#power-meter" id="id154">Power Meter</a></li>
<li><a class="reference internal" href="#steam-engine" id="id152">Steam Engine</a><ul> <li><a class="reference internal" href="#steam-engine" id="id155">Steam Engine</a><ul>
<li><a class="reference internal" href="#id1" id="id153">Rationale</a></li> <li><a class="reference internal" href="#id1" id="id156">Rationale</a></li>
<li><a class="reference internal" href="#construction" id="id154">Construction</a></li> <li><a class="reference internal" href="#construction" id="id157">Construction</a></li>
<li><a class="reference internal" href="#operation" id="id155">Operation</a></li> <li><a class="reference internal" href="#operation" id="id158">Operation</a></li>
<li><a class="reference internal" href="#explosions" id="id156">Explosions</a></li> <li><a class="reference internal" href="#explosions" id="id159">Explosions</a></li>
<li><a class="reference internal" href="#save-files" id="id157">Save files</a></li> <li><a class="reference internal" href="#save-files" id="id160">Save files</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#add-spatter" id="id158">Add Spatter</a></li> <li><a class="reference internal" href="#add-spatter" id="id161">Add Spatter</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -2688,11 +2692,13 @@ quickly after you unpause.</p>
<div class="section" id="slayrace"> <div class="section" id="slayrace">
<h2><a class="toc-backref" href="#id124">slayrace</a></h2> <h2><a class="toc-backref" href="#id124">slayrace</a></h2>
<p>Kills any unit of a given race.</p> <p>Kills any unit of a given race.</p>
<p>With no argument, lists the available races.</p> <p>With no argument, lists the available races and count eligible targets.</p>
<p>With the special argument <tt class="docutils literal">him</tt>, targets only the selected creature.</p> <p>With the special argument <tt class="docutils literal">him</tt>, targets only the selected creature.</p>
<p>With the special argument <tt class="docutils literal">undead</tt>, targets all undeads on the map,
regardless of their race.</p>
<p>Any non-dead non-caged unit of the specified race gets its <tt class="docutils literal">blood_count</tt> <p>Any non-dead non-caged unit of the specified race gets its <tt class="docutils literal">blood_count</tt>
set to 0, which means immediate death at the next game tick. For creatures set to 0, which means immediate death at the next game tick. For creatures
such as vampires, also set animal.vanish_countdown to 2.</p> such as vampires, it also sets animal.vanish_countdown to 2.</p>
<p>An alternate mode is selected by adding a 2nd argument to the command, <p>An alternate mode is selected by adding a 2nd argument to the command,
<tt class="docutils literal">magma</tt>. In this case, a column of 7/7 magma is generated on top of the <tt class="docutils literal">magma</tt>. In this case, a column of 7/7 magma is generated on top of the
targets until they die (Warning: do not call on magma-safe creatures. Also, targets until they die (Warning: do not call on magma-safe creatures. Also,
@ -2761,7 +2767,8 @@ superdwarf add
<div class="section" id="deathcause"> <div class="section" id="deathcause">
<h2><a class="toc-backref" href="#id129">deathcause</a></h2> <h2><a class="toc-backref" href="#id129">deathcause</a></h2>
<p>Focus a body part ingame, and this script will display the cause of death of <p>Focus a body part ingame, and this script will display the cause of death of
the creature.</p> the creature.
Also works when selecting units from the 'u'nitlist viewscreen.</p>
</div> </div>
<div class="section" id="lua"> <div class="section" id="lua">
<h2><a class="toc-backref" href="#id130">lua</a></h2> <h2><a class="toc-backref" href="#id130">lua</a></h2>
@ -2818,9 +2825,31 @@ alternatively pass cage IDs as arguments:</p>
stripcaged weapons 25321 34228 stripcaged weapons 25321 34228
</pre> </pre>
</div> </div>
<div class="section" id="create-items">
<h2><a class="toc-backref" href="#id134">create-items</a></h2>
<p>Spawn arbitrary items under the cursor.</p>
<p>The first argument gives the item category, the second gives the material,
and the optionnal third gives the number of items to create (defaults to 20).</p>
<p>Currently supported item categories: <tt class="docutils literal">boulder</tt>, <tt class="docutils literal">bar</tt>, <tt class="docutils literal">plant</tt>, <tt class="docutils literal">log</tt>,
<tt class="docutils literal">web</tt>.</p>
<p>Instead of material, using <tt class="docutils literal">list</tt> makes the script list eligible materials.</p>
<p>The <tt class="docutils literal">web</tt> item category will create an uncollected cobweb on the floor.</p>
<p>Note that the script does not enforce anything, and will let you create
boulders of toad blood and stuff like that.
However the <tt class="docutils literal">list</tt> mode will only show 'normal' materials.</p>
<p>Exemples:</p>
<pre class="literal-block">
create-items boulders COAL_BITUMINOUS 12
create-items plant tail_pig
create-items log list
create-items web CREATURE:SPIDER_CAVE_GIANT:SILK
create-items bar CREATURE:CAT:SOAP
create-items bar adamantine
</pre>
</div>
</div> </div>
<div class="section" id="in-game-interface-tools"> <div class="section" id="in-game-interface-tools">
<h1><a class="toc-backref" href="#id134">In-game interface tools</a></h1> <h1><a class="toc-backref" href="#id135">In-game interface tools</a></h1>
<p>These tools work by displaying dialogs or overlays in the game window, and <p>These tools work by displaying dialogs or overlays in the game window, and
are mostly implemented by lua scripts.</p> are mostly implemented by lua scripts.</p>
<div class="note"> <div class="note">
@ -2833,7 +2862,7 @@ existing DF screens, they deliberately use red instead of green for the key.</p>
guideline because it arguably just fixes small usability bugs in the game UI.</p> guideline because it arguably just fixes small usability bugs in the game UI.</p>
</div> </div>
<div class="section" id="dwarf-manipulator"> <div class="section" id="dwarf-manipulator">
<h2><a class="toc-backref" href="#id135">Dwarf Manipulator</a></h2> <h2><a class="toc-backref" href="#id136">Dwarf Manipulator</a></h2>
<p>Implemented by the manipulator plugin. To activate, open the unit screen and <p>Implemented by the manipulator plugin. To activate, open the unit screen and
press 'l'.</p> press 'l'.</p>
<img alt="images/manipulator.png" src="images/manipulator.png" /> <img alt="images/manipulator.png" src="images/manipulator.png" />
@ -2872,8 +2901,10 @@ cursor onto that cell instead of toggling it.</li>
directly to the main dwarf mode screen.</p> directly to the main dwarf mode screen.</p>
</div> </div>
<div class="section" id="search"> <div class="section" id="search">
<h2><a class="toc-backref" href="#id136">Search</a></h2> <h2><a class="toc-backref" href="#id137">Search</a></h2>
<p>The search plugin adds search to the Stocks, Trading, Stockpile and Unit List screens.</p> <p>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.</p>
<img alt="images/search.png" src="images/search.png" /> <img alt="images/search.png" src="images/search.png" />
<p>Searching works the same way as the search option in &quot;Move to Depot&quot; does. <p>Searching works the same way as the search option in &quot;Move to Depot&quot; does.
You will see the Search option displayed on screen with a hotkey (usually 's'). You will see the Search option displayed on screen with a hotkey (usually 's').
@ -2888,8 +2919,9 @@ filter).</p>
<p>Leaving any screen automatically clears the filter.</p> <p>Leaving any screen automatically clears the filter.</p>
<p>In the Trade screen, the actual trade will always only act on items that <p>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 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' Value numbers displayed by the screen. Because of this, the 't' key is
key while search is active clears the search instead of executing the trade.</p> blocked while search is active, so you have to reset the filters first.
Pressing Alt-C will clear both search strings.</p>
<p>In the stockpile screen the option only appears if the cursor is in the <p>In the stockpile screen the option only appears if the cursor is in the
rightmost list:</p> rightmost list:</p>
<img alt="images/search-stockpile.png" src="images/search-stockpile.png" /> <img alt="images/search-stockpile.png" src="images/search-stockpile.png" />
@ -2899,7 +2931,7 @@ only fat or tallow by forbidding fats, then searching for fat/tallow, and
using Permit Fats again while the list is filtered.</p> using Permit Fats again while the list is filtered.</p>
</div> </div>
<div class="section" id="automaterial"> <div class="section" id="automaterial">
<h2><a class="toc-backref" href="#id137">AutoMaterial</a></h2> <h2><a class="toc-backref" href="#id138">AutoMaterial</a></h2>
<p>The automaterial plugin makes building constructions (walls, floors, fortifications, <p>The automaterial plugin makes building constructions (walls, floors, fortifications,
etc) a little bit easier by saving you from having to trawl through long lists of etc) a little bit easier by saving you from having to trawl through long lists of
materials each time you place one.</p> materials each time you place one.</p>
@ -2926,14 +2958,14 @@ materials, it returns you back to this screen. If you use this along with severa
enabled materials, you should be able to place complex constructions more conveniently.</p> enabled materials, you should be able to place complex constructions more conveniently.</p>
</div> </div>
<div class="section" id="gui-liquids"> <div class="section" id="gui-liquids">
<h2><a class="toc-backref" href="#id138">gui/liquids</a></h2> <h2><a class="toc-backref" href="#id139">gui/liquids</a></h2>
<p>To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.</p> <p>To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.</p>
<img alt="images/liquids.png" src="images/liquids.png" /> <img alt="images/liquids.png" src="images/liquids.png" />
<p>While active, use the suggested keys to switch the usual liquids parameters, and Enter <p>While active, use the suggested keys to switch the usual liquids parameters, and Enter
to select the target area and apply changes.</p> to select the target area and apply changes.</p>
</div> </div>
<div class="section" id="gui-mechanisms"> <div class="section" id="gui-mechanisms">
<h2><a class="toc-backref" href="#id139">gui/mechanisms</a></h2> <h2><a class="toc-backref" href="#id140">gui/mechanisms</a></h2>
<p>To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.</p> <p>To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.</p>
<img alt="images/mechanisms.png" src="images/mechanisms.png" /> <img alt="images/mechanisms.png" src="images/mechanisms.png" />
<p>Lists mechanisms connected to the building, and their links. Navigating the list centers <p>Lists mechanisms connected to the building, and their links. Navigating the list centers
@ -2943,7 +2975,7 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter
re-entering the mechanisms ui.</p> re-entering the mechanisms ui.</p>
</div> </div>
<div class="section" id="gui-rename"> <div class="section" id="gui-rename">
<h2><a class="toc-backref" href="#id140">gui/rename</a></h2> <h2><a class="toc-backref" href="#id141">gui/rename</a></h2>
<p>Backed by the rename plugin, this script allows entering the desired name <p>Backed by the rename plugin, this script allows entering the desired name
via a simple dialog in the game ui.</p> via a simple dialog in the game ui.</p>
<ul> <ul>
@ -2966,7 +2998,7 @@ their species string.</p>
unit profession change to Ctrl-Shift-T.</p> unit profession change to Ctrl-Shift-T.</p>
</div> </div>
<div class="section" id="gui-room-list"> <div class="section" id="gui-room-list">
<h2><a class="toc-backref" href="#id141">gui/room-list</a></h2> <h2><a class="toc-backref" href="#id142">gui/room-list</a></h2>
<p>To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, <p>To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode,
either immediately or after opening the assign owner page.</p> either immediately or after opening the assign owner page.</p>
<img alt="images/room-list.png" src="images/room-list.png" /> <img alt="images/room-list.png" src="images/room-list.png" />
@ -2974,7 +3006,7 @@ either immediately or after opening the assign owner page.</p>
list, and allows unassigning them.</p> list, and allows unassigning them.</p>
</div> </div>
<div class="section" id="gui-choose-weapons"> <div class="section" id="gui-choose-weapons">
<h2><a class="toc-backref" href="#id142">gui/choose-weapons</a></h2> <h2><a class="toc-backref" href="#id143">gui/choose-weapons</a></h2>
<p>Bind to a key (the example config uses Ctrl-W), and activate in the Equip-&gt;View/Customize <p>Bind to a key (the example config uses Ctrl-W), and activate in the Equip-&gt;View/Customize
page of the military screen.</p> page of the military screen.</p>
<p>Depending on the cursor location, it rewrites all 'individual choice weapon' entries <p>Depending on the cursor location, it rewrites all 'individual choice weapon' entries
@ -2985,7 +3017,7 @@ only that entry, and does it even if it is not 'individual choice'.</p>
and may lead to inappropriate weapons being selected.</p> and may lead to inappropriate weapons being selected.</p>
</div> </div>
<div class="section" id="gui-guide-path"> <div class="section" id="gui-guide-path">
<h2><a class="toc-backref" href="#id143">gui/guide-path</a></h2> <h2><a class="toc-backref" href="#id144">gui/guide-path</a></h2>
<p>Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with <p>Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with
the cursor over a Guide order.</p> the cursor over a Guide order.</p>
<img alt="images/guide-path.png" src="images/guide-path.png" /> <img alt="images/guide-path.png" src="images/guide-path.png" />
@ -2993,7 +3025,7 @@ the cursor over a Guide order.</p>
computes it when the order is executed for the first time.</p> computes it when the order is executed for the first time.</p>
</div> </div>
<div class="section" id="gui-workshop-job"> <div class="section" id="gui-workshop-job">
<h2><a class="toc-backref" href="#id144">gui/workshop-job</a></h2> <h2><a class="toc-backref" href="#id145">gui/workshop-job</a></h2>
<p>Bind to a key (the example config uses Alt-A), and activate with a job selected in <p>Bind to a key (the example config uses Alt-A), and activate with a job selected in
a workshop in the 'q' mode.</p> a workshop in the 'q' mode.</p>
<img alt="images/workshop-job.png" src="images/workshop-job.png" /> <img alt="images/workshop-job.png" src="images/workshop-job.png" />
@ -3029,7 +3061,7 @@ and then try to change the input item type, now it won't let you select <em>plan
you have to unset the material first.</p> you have to unset the material first.</p>
</div> </div>
<div class="section" id="gui-workflow"> <div class="section" id="gui-workflow">
<h2><a class="toc-backref" href="#id145">gui/workflow</a></h2> <h2><a class="toc-backref" href="#id146">gui/workflow</a></h2>
<p>Bind to a key (the example config uses Alt-W), and activate with a job selected <p>Bind to a key (the example config uses Alt-W), and activate with a job selected
in a workshop in the 'q' mode.</p> in a workshop in the 'q' mode.</p>
<img alt="images/workflow.png" src="images/workflow.png" /> <img alt="images/workflow.png" src="images/workflow.png" />
@ -3076,7 +3108,7 @@ the current stock value. The bright green dashed line is the target
limit (maximum) and the dark green line is that minus the gap (minimum).</p> limit (maximum) and the dark green line is that minus the gap (minimum).</p>
</div> </div>
<div class="section" id="gui-assign-rack"> <div class="section" id="gui-assign-rack">
<h2><a class="toc-backref" href="#id146">gui/assign-rack</a></h2> <h2><a class="toc-backref" href="#id147">gui/assign-rack</a></h2>
<p>Bind to a key (the example config uses P), and activate when viewing a weapon <p>Bind to a key (the example config uses P), and activate when viewing a weapon
rack in the 'q' mode.</p> rack in the 'q' mode.</p>
<img alt="images/assign-rack.png" src="images/assign-rack.png" /> <img alt="images/assign-rack.png" src="images/assign-rack.png" />
@ -3099,9 +3131,36 @@ are assigned to the barracks/armory containing the selected stand as
the intended user. In order to aid in the choice, it shows the number the intended user. In order to aid in the choice, it shows the number
of currently assigned racks for every valid squad.</p> of currently assigned racks for every valid squad.</p>
</div> </div>
<div class="section" id="gui-advfort">
<h2><a class="toc-backref" href="#id148">gui/advfort</a></h2>
<p>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:</p>
<ul class="simple">
<li>-a or --nodfassign - uses different method to assign items.</li>
<li>-i or --inventory - checks inventory for possible items to use in the job.</li>
<li>-c or --cheat - relaxes item requirements for buildings (e.g. walls from bones).
implies -a</li>
<li>job - selects that job (e.g. Dig or FellTree)</li>
</ul>
</div>
<div class="section" id="gui-gm-editor">
<h2><a class="toc-backref" href="#id149">gui/gm-editor</a></h2>
<p>There are three ways to open this editor:</p>
<ul class="simple">
<li>using gui/gm-editor command/keybinding - opens editor on what is selected
or viewed (e.g. unit/item description screen)</li>
<li>using gui/gm-editor &lt;lua command&gt; - executes lua command and opens editor on
it's results (e.g. gui/gm-editor &quot;df.global.world.items.all&quot; shows all items)</li>
<li>using gui/gm-edito dialog - shows an in game dialog to input lua command. Works
the same as version above.</li>
</ul>
<p>This editor allows to change and modify almost anything in df. Press '?' for an
in-game help.</p>
</div>
</div> </div>
<div class="section" id="behavior-mods"> <div class="section" id="behavior-mods">
<h1><a class="toc-backref" href="#id147">Behavior Mods</a></h1> <h1><a class="toc-backref" href="#id150">Behavior Mods</a></h1>
<p>These plugins, when activated via configuration UI or by detecting certain <p>These plugins, when activated via configuration UI or by detecting certain
structures in RAWs, modify the game engine behavior concerning the target structures in RAWs, modify the game engine behavior concerning the target
objects to add features not otherwise present.</p> objects to add features not otherwise present.</p>
@ -3112,20 +3171,20 @@ technical challenge, and do not represent any long-term plans to produce more
similar modifications of the game.</p> similar modifications of the game.</p>
</div> </div>
<div class="section" id="siege-engine"> <div class="section" id="siege-engine">
<h2><a class="toc-backref" href="#id148">Siege Engine</a></h2> <h2><a class="toc-backref" href="#id151">Siege Engine</a></h2>
<p>The siege-engine plugin enables siege engines to be linked to stockpiles, and <p>The siege-engine plugin enables siege engines to be linked to stockpiles, and
aimed at an arbitrary rectangular area across Z levels, instead of the original aimed at an arbitrary rectangular area across Z levels, instead of the original
four directions. Also, catapults can be ordered to load arbitrary objects, not four directions. Also, catapults can be ordered to load arbitrary objects, not
just stones.</p> just stones.</p>
<div class="section" id="rationale"> <div class="section" id="rationale">
<h3><a class="toc-backref" href="#id149">Rationale</a></h3> <h3><a class="toc-backref" href="#id152">Rationale</a></h3>
<p>Siege engines are a very interesting feature, but sadly almost useless in the current state <p>Siege engines are a very interesting feature, but sadly almost useless in the current state
because they haven't been updated since 2D and can only aim in four directions. This is an because they haven't been updated since 2D and can only aim in four directions. This is an
attempt to bring them more up to date until Toady has time to work on it. Actual improvements, attempt to bring them more up to date until Toady has time to work on it. Actual improvements,
e.g. like making siegers bring their own, are something only Toady can do.</p> e.g. like making siegers bring their own, are something only Toady can do.</p>
</div> </div>
<div class="section" id="configuration-ui"> <div class="section" id="configuration-ui">
<h3><a class="toc-backref" href="#id150">Configuration UI</a></h3> <h3><a class="toc-backref" href="#id153">Configuration UI</a></h3>
<p>The configuration front-end to the plugin is implemented by the gui/siege-engine <p>The configuration front-end to the plugin is implemented by the gui/siege-engine
script. Bind it to a key (the example config uses Alt-A) and activate after selecting script. Bind it to a key (the example config uses Alt-A) and activate after selecting
a siege engine in 'q' mode.</p> a siege engine in 'q' mode.</p>
@ -3148,7 +3207,7 @@ menu.</p>
</div> </div>
</div> </div>
<div class="section" id="power-meter"> <div class="section" id="power-meter">
<h2><a class="toc-backref" href="#id151">Power Meter</a></h2> <h2><a class="toc-backref" href="#id154">Power Meter</a></h2>
<p>The power-meter plugin implements a modified pressure plate that detects power being <p>The power-meter plugin implements a modified pressure plate that detects power being
supplied to gear boxes built in the four adjacent N/S/W/E tiles.</p> supplied to gear boxes built in the four adjacent N/S/W/E tiles.</p>
<p>The configuration front-end is implemented by the gui/power-meter script. Bind it to a <p>The configuration front-end is implemented by the gui/power-meter script. Bind it to a
@ -3159,11 +3218,11 @@ in the build menu.</p>
configuration page, but configures parameters relevant to the modded power meter building.</p> configuration page, but configures parameters relevant to the modded power meter building.</p>
</div> </div>
<div class="section" id="steam-engine"> <div class="section" id="steam-engine">
<h2><a class="toc-backref" href="#id152">Steam Engine</a></h2> <h2><a class="toc-backref" href="#id155">Steam Engine</a></h2>
<p>The steam-engine plugin detects custom workshops with STEAM_ENGINE in <p>The steam-engine plugin detects custom workshops with STEAM_ENGINE in
their token, and turns them into real steam engines.</p> their token, and turns them into real steam engines.</p>
<div class="section" id="id1"> <div class="section" id="id1">
<h3><a class="toc-backref" href="#id153">Rationale</a></h3> <h3><a class="toc-backref" href="#id156">Rationale</a></h3>
<p>The vanilla game contains only water wheels and windmills as sources of <p>The vanilla game contains only water wheels and windmills as sources of
power, but windmills give relatively little power, and water wheels require power, but windmills give relatively little power, and water wheels require
flowing water, which must either be a real river and thus immovable and flowing water, which must either be a real river and thus immovable and
@ -3174,7 +3233,7 @@ it can be done just by combining existing features of the game engine
in a new way with some glue code and a bit of custom logic.</p> in a new way with some glue code and a bit of custom logic.</p>
</div> </div>
<div class="section" id="construction"> <div class="section" id="construction">
<h3><a class="toc-backref" href="#id154">Construction</a></h3> <h3><a class="toc-backref" href="#id157">Construction</a></h3>
<p>The workshop needs water as its input, which it takes via a <p>The workshop needs water as its input, which it takes via a
passable floor tile below it, like usual magma workshops do. passable floor tile below it, like usual magma workshops do.
The magma version also needs magma.</p> The magma version also needs magma.</p>
@ -3198,7 +3257,7 @@ short axles that can be built later than both of the engines.</p>
</div> </div>
</div> </div>
<div class="section" id="operation"> <div class="section" id="operation">
<h3><a class="toc-backref" href="#id155">Operation</a></h3> <h3><a class="toc-backref" href="#id158">Operation</a></h3>
<p>In order to operate the engine, queue the Stoke Boiler job (optionally <p>In order to operate the engine, queue the Stoke Boiler job (optionally
on repeat). A furnace operator will come, possibly bringing a bar of fuel, on repeat). A furnace operator will come, possibly bringing a bar of fuel,
and perform it. As a result, a &quot;boiling water&quot; item will appear and perform it. As a result, a &quot;boiling water&quot; item will appear
@ -3229,7 +3288,7 @@ decrease it by further 4%, and also decrease the whole steam
use rate by 10%.</p> use rate by 10%.</p>
</div> </div>
<div class="section" id="explosions"> <div class="section" id="explosions">
<h3><a class="toc-backref" href="#id156">Explosions</a></h3> <h3><a class="toc-backref" href="#id159">Explosions</a></h3>
<p>The engine must be constructed using barrel, pipe and piston <p>The engine must be constructed using barrel, pipe and piston
from fire-safe, or in the magma version magma-safe metals.</p> from fire-safe, or in the magma version magma-safe metals.</p>
<p>During operation weak parts get gradually worn out, and <p>During operation weak parts get gradually worn out, and
@ -3238,7 +3297,7 @@ toppled during operation by a building destroyer, or a
tantruming dwarf.</p> tantruming dwarf.</p>
</div> </div>
<div class="section" id="save-files"> <div class="section" id="save-files">
<h3><a class="toc-backref" href="#id157">Save files</a></h3> <h3><a class="toc-backref" href="#id160">Save files</a></h3>
<p>It should be safe to load and view engine-using fortresses <p>It should be safe to load and view engine-using fortresses
from a DF version without DFHack installed, except that in such from a DF version without DFHack installed, except that in such
case the engines won't work. However actually making modifications case the engines won't work. However actually making modifications
@ -3249,7 +3308,7 @@ being generated.</p>
</div> </div>
</div> </div>
<div class="section" id="add-spatter"> <div class="section" id="add-spatter">
<h2><a class="toc-backref" href="#id158">Add Spatter</a></h2> <h2><a class="toc-backref" href="#id161">Add Spatter</a></h2>
<p>This plugin makes reactions with names starting with <tt class="docutils literal">SPATTER_ADD_</tt> <p>This plugin makes reactions with names starting with <tt class="docutils literal">SPATTER_ADD_</tt>
produce contaminants on the items instead of improvements. The produced produce contaminants on the items instead of improvements. The produced
contaminants are immune to being washed away by water or destroyed by contaminants are immune to being washed away by water or destroyed by

@ -2105,7 +2105,9 @@ directly to the main dwarf mode screen.
Search 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 .. image:: images/search.png
@ -2125,8 +2127,9 @@ Leaving any screen automatically clears the filter.
In the Trade screen, the actual trade will always only act on items that 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 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' Value numbers displayed by the screen. Because of this, the 't' key is
key while search is active clears the search instead of executing the trade. 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 In the stockpile screen the option only appears if the cursor is in the
rightmost list: rightmost list:

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

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

@ -44,6 +44,7 @@ using namespace std;
#include "VersionInfo.h" #include "VersionInfo.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "ModuleFactory.h" #include "ModuleFactory.h"
#include "modules/EventManager.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Graphic.h" #include "modules/Graphic.h"
@ -947,6 +948,7 @@ bool Core::Init()
cerr << "Initializing Plugins.\n"; cerr << "Initializing Plugins.\n";
// create plugin manager // create plugin manager
plug_mgr = new PluginManager(this); plug_mgr = new PluginManager(this);
plug_mgr->init(this);
IODATA *temp = new IODATA; IODATA *temp = new IODATA;
temp->core = this; temp->core = this;
temp->plug_mgr = plug_mgr; temp->plug_mgr = plug_mgr;
@ -1281,6 +1283,8 @@ static int buildings_timer = 0;
void Core::onUpdate(color_ostream &out) void Core::onUpdate(color_ostream &out)
{ {
EventManager::manageEvents(out);
// convert building reagents // convert building reagents
if (buildings_do_onupdate && (++buildings_timer & 1)) if (buildings_do_onupdate && (++buildings_timer & 1))
buildings_onUpdate(out); buildings_onUpdate(out);
@ -1294,6 +1298,8 @@ void Core::onUpdate(color_ostream &out)
void Core::onStateChange(color_ostream &out, state_change_event event) void Core::onStateChange(color_ostream &out, state_change_event event)
{ {
EventManager::onStateChange(out, event);
buildings_onStateChange(out, event); buildings_onStateChange(out, event);
plug_mgr->OnStateChange(out, event); plug_mgr->OnStateChange(out, event);

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

@ -39,15 +39,54 @@ using namespace DFHack;
/* /*
* Code for accessing method pointers directly. Very compiler-specific. * 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) #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 { struct MSVC_MPTR {
void *method; void *method;
intptr_t this_shift; 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) static uint32_t *follow_jmp(void *ptr)
{ {
uint8_t *p = (uint8_t*)ptr; uint8_t *p = (uint8_t*)ptr;
@ -56,10 +95,10 @@ static uint32_t *follow_jmp(void *ptr)
{ {
switch (*p) switch (*p)
{ {
case 0xE9: case 0xE9: // jmp near rel32
p += 5 + *(int32_t*)(p+1); p += 5 + *(int32_t*)(p+1);
break; break;
case 0xEB: case 0xEB: // jmp short rel8
p += 2 + *(int8_t*)(p+1); p += 2 + *(int8_t*)(p+1);
break; break;
default: default:
@ -120,8 +159,10 @@ void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
#elif defined(__GXX_ABI_VERSION) #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 { struct GCC_MPTR {
intptr_t method; intptr_t method; // Code pointer or tagged vtable offset
intptr_t this_shift; intptr_t this_shift;
}; };
@ -254,6 +295,14 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v
{ {
if (vmethod_idx < 0 || interpose_method == NULL) 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", fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n",
vmethod_idx, unsigned(interpose_method)); vmethod_idx, unsigned(interpose_method));
fflush(stderr); fflush(stderr);

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

@ -28,6 +28,58 @@ distribution.
namespace DFHack 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<bool> struct StaticAssert;
template<> struct StaticAssert<true> {}; template<> struct StaticAssert<true> {};
@ -81,43 +133,6 @@ namespace DFHack
return addr_to_method_pointer<P>(identity.get_vmethod_ptr(idx)); 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) \ #define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \
typedef rtype (interpose_base::*interpose_ptr_##name)args; \ typedef rtype (interpose_base::*interpose_ptr_##name)args; \
@ -142,18 +157,21 @@ namespace DFHack
friend class virtual_identity; friend class virtual_identity;
virtual_identity *host; // Class with the vtable 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 *interpose_method; // Pointer to the code of the interposing method
void *chain_mptr; // Pointer to the chain field below void *chain_mptr; // Pointer to the chain field in the subclass below
int priority; int priority; // Higher priority hooks are called earlier
bool applied; bool applied; // True if this hook is currently applied
void *saved_chain; // Previous pointer to the code void *saved_chain; // Pointer to the code of the original vmethod or next hook
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
// 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; std::set<virtual_identity*> child_hosts;
// Hooks within subclasses that branch off this topmost hook
std::set<VMethodInterposeLinkBase*> child_next; std::set<VMethodInterposeLinkBase*> child_next;
// (See the cpp file for a more detailed description of these links)
void set_chain(void *chain); void set_chain(void *chain);
void on_host_delete(virtual_identity *host); void on_host_delete(virtual_identity *host);
@ -172,6 +190,9 @@ namespace DFHack
template<class Base, class Ptr> template<class Base, class Ptr>
class VMethodInterposeLink : public VMethodInterposeLinkBase { class VMethodInterposeLink : public VMethodInterposeLinkBase {
public: 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; Ptr chain;
operator Ptr () { return chain; } operator Ptr () { return chain; }

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

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

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

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

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

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

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

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

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

@ -1 +1 @@
Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc Subproject commit c7e2c28febd6dca06ff7e9951090982fbbee12b5

@ -131,7 +131,11 @@ if (BUILD_SUPPORTED)
# not yet. busy with other crud again... # not yet. busy with other crud again...
#DFHACK_PLUGIN(versionosd versionosd.cpp) #DFHACK_PLUGIN(versionosd versionosd.cpp)
DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(misery misery.cpp)
DFHACK_PLUGIN(workNow workNow.cpp)
#DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread)
DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp)
DFHACK_PLUGIN(trueTransformation trueTransformation.cpp)
DFHACK_PLUGIN(infiniteSky infiniteSky.cpp)
endif() endif()

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

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

File diff suppressed because it is too large Load Diff

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

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

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

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

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd Subproject commit 37f6e626b054571b72535e2ac0ee3957e07432f1

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

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

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