Merge remote-tracking branch 'origin/develop' into mousequery-z-level-fix

Conflicts:
	NEWS
develop
lethosor 2014-11-08 14:36:20 -05:00
commit 04ceb64bad
52 changed files with 1982 additions and 1176 deletions

@ -58,7 +58,7 @@ 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 "0.40.13") set(DF_VERSION "0.40.14")
SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.") SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.")
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")

32
NEWS

@ -1,12 +1,42 @@
DFHack future DFHack Future
Fixes: Fixes:
- mousequery: Fixed behavior when selecting a tile on the lowest z-level - mousequery: Fixed behavior when selecting a tile on the lowest z-level
Internals:
- EventManager: deals with frame_counter getting reset properly now.
- modtools/item-trigger: fixed equip/unequip bug and corrected minor documentation error
DFHack 0.40.14-r1
Internals:
- The DFHack console can now be disabled by setting the DFHACK_DISABLE_CONSOLE
environment variable: "DFHACK_DISABLE_CONSOLE=1 ./dfhack"
Fixes:
- Stopped duplicate load/unload events when unloading a world
- Stopped "-e" from being echoed when DFHack quits on Linux
- automelt now uses a faster method to locate items
- autotrade: "Mark all" no longer double-marks bin contents
- drainaquifer.rb: replaced with a faster less buggy drain-aquifer.lua
- embark-tools no longer conflicts with keys on the notes screen
- fastdwarf: Fixed problems with combat/attacks
- forum-dwarves should work now
- manipulator now uses a stable sort, allowing sorting by multiple categories
- rendermax updated to work with 0.40
New plugins:
- trackstop: Shows track stop friction and dump direction in its 'q' menu
New tweaks:
- farm-plot-select: Adds "Select all" and "Deselect all" options to farm plot menus
- import-priority-category: Allows changing the priority of all goods in a
category when discussing an import agreement with the liaison
- manager-quantity: Removes the limit of 30 jobs per manager order
- civ-view-agreement: Fixes overlapping text on the "view agreement" screen
- nestbox-color: Fixes the color of built nestboxes
DFHack 0.40.13-r1 DFHack 0.40.13-r1
Internals: Internals:
- unified spatter structs - unified spatter structs
- added ruby df.print_color(color, string) method for dfhack console
Fixes: Fixes:
- no more -e after terminating - no more -e after terminating
- fixed superdwarf
DFHack 0.40.12-r1 DFHack 0.40.12-r1
Fixes: Fixes:

@ -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.8.1: http://docutils.sourceforge.net/" /> <meta name="generator" content="Docutils 0.11: 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 7056 2011-06-17 10:50:48Z milde $ :Id: $Id: html4css1.css 7614 2013-02-21 15:55:51Z 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 }
@ -249,10 +249,19 @@ pre.address {
margin-top: 0 ; margin-top: 0 ;
font: inherit } font: inherit }
pre.literal-block, pre.doctest-block, pre.math { 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 { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
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 ;
font-style: oblique } font-style: oblique }
@ -304,6 +313,21 @@ table.docutils th.field-name, table.docinfo th.docinfo-name {
white-space: nowrap ; white-space: nowrap ;
padding-left: 0 } padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% } font-size: 100% }
@ -501,7 +525,7 @@ access DF memory and allow for easier development of new tools.</p>
<li><a class="reference internal" href="#binpatch" id="id132">binpatch</a></li> <li><a class="reference internal" href="#binpatch" id="id132">binpatch</a></li>
<li><a class="reference internal" href="#create-items" id="id133">create-items</a></li> <li><a class="reference internal" href="#create-items" id="id133">create-items</a></li>
<li><a class="reference internal" href="#digfort" id="id134">digfort</a></li> <li><a class="reference internal" href="#digfort" id="id134">digfort</a></li>
<li><a class="reference internal" href="#drainaquifer" id="id135">drainaquifer</a></li> <li><a class="reference internal" href="#drain-aquifer" id="id135">drain-aquifer</a></li>
<li><a class="reference internal" href="#deathcause" id="id136">deathcause</a></li> <li><a class="reference internal" href="#deathcause" id="id136">deathcause</a></li>
<li><a class="reference internal" href="#dfstatus" id="id137">dfstatus</a></li> <li><a class="reference internal" href="#dfstatus" id="id137">dfstatus</a></li>
<li><a class="reference internal" href="#embark" id="id138">embark</a></li> <li><a class="reference internal" href="#embark" id="id138">embark</a></li>
@ -520,48 +544,50 @@ access DF memory and allow for easier development of new tools.</p>
<li><a class="reference internal" href="#source" id="id151">source</a></li> <li><a class="reference internal" href="#source" id="id151">source</a></li>
<li><a class="reference internal" href="#superdwarf" id="id152">superdwarf</a></li> <li><a class="reference internal" href="#superdwarf" id="id152">superdwarf</a></li>
<li><a class="reference internal" href="#stripcaged" id="id153">stripcaged</a></li> <li><a class="reference internal" href="#stripcaged" id="id153">stripcaged</a></li>
<li><a class="reference internal" href="#undump-buildings" id="id154">undump-buildings</a></li> <li><a class="reference internal" href="#teleport" id="id154">teleport</a></li>
<li><a class="reference internal" href="#undump-buildings" id="id155">undump-buildings</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#modtools" id="id155">modtools</a></li> <li><a class="reference internal" href="#modtools" id="id156">modtools</a></li>
<li><a class="reference internal" href="#in-game-interface-tools" id="id156">In-game interface tools</a><ul> <li><a class="reference internal" href="#in-game-interface-tools" id="id157">In-game interface tools</a><ul>
<li><a class="reference internal" href="#dwarf-manipulator" id="id157">Dwarf Manipulator</a></li> <li><a class="reference internal" href="#dwarf-manipulator" id="id158">Dwarf Manipulator</a></li>
<li><a class="reference internal" href="#search" id="id158">Search</a></li> <li><a class="reference internal" href="#search" id="id159">Search</a></li>
<li><a class="reference internal" href="#automaterial" id="id159">AutoMaterial</a></li> <li><a class="reference internal" href="#automaterial" id="id160">AutoMaterial</a></li>
<li><a class="reference internal" href="#stockpile-automation" id="idautomelt">Stockpile Automation</a></li> <li><a class="reference internal" href="#stockpile-automation" id="id161">Stockpile Automation</a></li>
<li><a class="reference internal" href="#gui-advfort" id="id160">gui/advfort</a></li> <li><a class="reference internal" href="#track-stop-menu" id="id162">Track Stop Menu</a></li>
<li><a class="reference internal" href="#gui-assign-rack" id="id161">gui/assign-rack</a></li> <li><a class="reference internal" href="#gui-advfort" id="id163">gui/advfort</a></li>
<li><a class="reference internal" href="#gui-choose-weapons" id="id162">gui/choose-weapons</a></li> <li><a class="reference internal" href="#gui-assign-rack" id="id164">gui/assign-rack</a></li>
<li><a class="reference internal" href="#gui-clone-uniform" id="id163">gui/clone-uniform</a></li> <li><a class="reference internal" href="#gui-choose-weapons" id="id165">gui/choose-weapons</a></li>
<li><a class="reference internal" href="#gui-companion-order" id="id164">gui/companion-order</a></li> <li><a class="reference internal" href="#gui-clone-uniform" id="id166">gui/clone-uniform</a></li>
<li><a class="reference internal" href="#gui-gm-editor" id="id165">gui/gm-editor</a></li> <li><a class="reference internal" href="#gui-companion-order" id="id167">gui/companion-order</a></li>
<li><a class="reference internal" href="#hotkeys" id="idhotkeys">Hotkeys</a></li> <li><a class="reference internal" href="#gui-gm-editor" id="id168">gui/gm-editor</a></li>
<li><a class="reference internal" href="#gui-liquids" id="id166">gui/liquids</a></li> <li><a class="reference internal" href="#hotkeys" id="id169">Hotkeys</a></li>
<li><a class="reference internal" href="#gui-mechanisms" id="id167">gui/mechanisms</a></li> <li><a class="reference internal" href="#gui-liquids" id="id170">gui/liquids</a></li>
<li><a class="reference internal" href="#gui-mod-manager" id="id168">gui/mod-manager</a></li> <li><a class="reference internal" href="#gui-mechanisms" id="id171">gui/mechanisms</a></li>
<li><a class="reference internal" href="#gui-rename" id="id169">gui/rename</a></li> <li><a class="reference internal" href="#gui-mod-manager" id="id172">gui/mod-manager</a></li>
<li><a class="reference internal" href="#gui-room-list" id="id170">gui/room-list</a></li> <li><a class="reference internal" href="#gui-rename" id="id173">gui/rename</a></li>
<li><a class="reference internal" href="#gui-guide-path" id="id171">gui/guide-path</a></li> <li><a class="reference internal" href="#gui-room-list" id="id174">gui/room-list</a></li>
<li><a class="reference internal" href="#gui-workflow" id="id172">gui/workflow</a></li> <li><a class="reference internal" href="#gui-guide-path" id="id175">gui/guide-path</a></li>
<li><a class="reference internal" href="#gui-workshop-job" id="id173">gui/workshop-job</a></li> <li><a class="reference internal" href="#gui-workflow" id="id176">gui/workflow</a></li>
<li><a class="reference internal" href="#gui-workshop-job" id="id177">gui/workshop-job</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#behavior-mods" id="id174">Behavior Mods</a><ul> <li><a class="reference internal" href="#behavior-mods" id="id178">Behavior Mods</a><ul>
<li><a class="reference internal" href="#siege-engine" id="id175">Siege Engine</a><ul> <li><a class="reference internal" href="#siege-engine" id="id179">Siege Engine</a><ul>
<li><a class="reference internal" href="#rationale" id="id176">Rationale</a></li> <li><a class="reference internal" href="#rationale" id="id180">Rationale</a></li>
<li><a class="reference internal" href="#configuration-ui" id="id177">Configuration UI</a></li> <li><a class="reference internal" href="#configuration-ui" id="id181">Configuration UI</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#power-meter" id="id178">Power Meter</a></li> <li><a class="reference internal" href="#power-meter" id="id182">Power Meter</a></li>
<li><a class="reference internal" href="#steam-engine" id="id179">Steam Engine</a><ul> <li><a class="reference internal" href="#steam-engine" id="id183">Steam Engine</a><ul>
<li><a class="reference internal" href="#id1" id="id180">Rationale</a></li> <li><a class="reference internal" href="#id1" id="id184">Rationale</a></li>
<li><a class="reference internal" href="#construction" id="id181">Construction</a></li> <li><a class="reference internal" href="#construction" id="id185">Construction</a></li>
<li><a class="reference internal" href="#operation" id="id182">Operation</a></li> <li><a class="reference internal" href="#operation" id="id186">Operation</a></li>
<li><a class="reference internal" href="#explosions" id="id183">Explosions</a></li> <li><a class="reference internal" href="#explosions" id="id187">Explosions</a></li>
<li><a class="reference internal" href="#save-files" id="id184">Save files</a></li> <li><a class="reference internal" href="#save-files" id="id188">Save files</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#add-spatter" id="id185">Add Spatter</a></li> <li><a class="reference internal" href="#add-spatter" id="id189">Add Spatter</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -1930,14 +1956,6 @@ they are scuttled.</td>
<tr class="field"><th class="field-name">stable-cursor:</th><td class="field-body"><p class="first">Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.</p> <tr class="field"><th class="field-name">stable-cursor:</th><td class="field-body"><p class="first">Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.</p>
</td> </td>
</tr> </tr>
<tr class="field"><th class="field-name">patrol-duty:</th><td class="field-body"><p class="first">Makes Train orders not count as patrol duty to stop unhappy thoughts.
Does NOT fix the problem when soldiers go off-duty (i.e. civilian).</p>
</td>
</tr>
<tr class="field"><th class="field-name">stable-temp:</th><td class="field-body"><p class="first">Fixes performance bug 6012 by squashing jitter in temperature updates.
In very item-heavy forts with big stockpiles this can improve FPS by 50-100%</p>
</td>
</tr>
<tr class="field"><th class="field-name">fast-heat:</th><td class="field-body"><p class="first">Further improves temperature update performance by ensuring that 1 degree <tr class="field"><th class="field-name">fast-heat:</th><td class="field-body"><p class="first">Further improves temperature update performance by ensuring that 1 degree
of item temperature is crossed in no more than specified number of frames of item temperature is crossed in no more than specified number of frames
when updating from the environment temperature. This reduces the time it when updating from the environment temperature. This reduces the time it
@ -1975,7 +1993,24 @@ With this tweak, items made from cloth and leather will gain a level of wear eve
<tr class="field"><td>&nbsp;</td><td class="field-body"><p class="first">Prevents adamantine clothing from wearing out while being worn (bug 6481).</p> <tr class="field"><td>&nbsp;</td><td class="field-body"><p class="first">Prevents adamantine clothing from wearing out while being worn (bug 6481).</p>
</td> </td>
</tr> </tr>
<tr class="field"><th class="field-name">confirm-embark:</th><td class="field-body"><p class="first last">Adds a prompt before embarking (on the &quot;prepare carefully&quot; screen).</p> <tr class="field"><th class="field-name" colspan="2">farm-plot-select:</th></tr>
<tr class="field"><td>&nbsp;</td><td class="field-body"><p class="first">Adds &quot;Select all&quot; and &quot;Deselect all&quot; options to farm plot menus</p>
</td>
</tr>
<tr class="field"><th class="field-name" colspan="2">import-priority-category:</th></tr>
<tr class="field"><td>&nbsp;</td><td class="field-body"><p class="first">Allows changing the priority of all goods in a
category when discussing an import agreement with the liaison</p>
</td>
</tr>
<tr class="field"><th class="field-name" colspan="2">manager-quantity:</th></tr>
<tr class="field"><td>&nbsp;</td><td class="field-body"><p class="first">Removes the limit of 30 jobs per manager order</p>
</td>
</tr>
<tr class="field"><th class="field-name" colspan="2">civ-view-agreement:</th></tr>
<tr class="field"><td>&nbsp;</td><td class="field-body"><p class="first">Fixes overlapping text on the &quot;view agreement&quot; screen</p>
</td>
</tr>
<tr class="field"><th class="field-name">nestbox-color:</th><td class="field-body"><p class="first last">Fixes the color of built nestboxes</p>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -2107,7 +2142,7 @@ with visualizers.</p>
<col class="field-name" /> <col class="field-name" />
<col class="field-body" /> <col class="field-body" />
<tbody valign="top"> <tbody valign="top">
<tr class="field"><th class="field-name">maps:</th><td class="field-body">Exports all fifteen detailed maps</td> <tr class="field"><th class="field-name">maps:</th><td class="field-body">Exports all seventeen detailed maps</td>
</tr> </tr>
<tr class="field"><th class="field-name">all:</th><td class="field-body">first exports the world/gen info, then the XML, then all detailed maps</td> <tr class="field"><th class="field-name">all:</th><td class="field-body">first exports the world/gen info, then the XML, then all detailed maps</td>
</tr> </tr>
@ -2310,9 +2345,38 @@ workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
<h2><a class="toc-backref" href="#id109">Fortress activity management</a></h2> <h2><a class="toc-backref" href="#id109">Fortress activity management</a></h2>
<div class="section" id="seedwatch"> <div class="section" id="seedwatch">
<h3><a class="toc-backref" href="#id110">seedwatch</a></h3> <h3><a class="toc-backref" href="#id110">seedwatch</a></h3>
<p>Tool for turning cooking of seeds and plants on/off depending on how much you <p>Watches the numbers of seeds available and enables/disables seed and plant cooking.</p>
have of them.</p> <p>Each plant type can be assigned a limit. If their number falls below that limit,
<p>See 'seedwatch help' for detailed description.</p> the plants and seeds of that type will be excluded from cookery.
If the number rises above the limit + 20, then cooking will be allowed.</p>
<p>The plugin needs a fortress to be loaded and will deactivate automatically otherwise.
You have to reactivate with 'seedwatch start' after you load the game.</p>
<p>Options:</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
<tbody valign="top">
<tr class="field"><th class="field-name">all:</th><td class="field-body">Adds all plants from the abbreviation list to the watch list.</td>
</tr>
<tr class="field"><th class="field-name">start:</th><td class="field-body">Start watching.</td>
</tr>
<tr class="field"><th class="field-name">stop:</th><td class="field-body">Stop watching.</td>
</tr>
<tr class="field"><th class="field-name">info:</th><td class="field-body">Display whether seedwatch is watching, and the watch list.</td>
</tr>
<tr class="field"><th class="field-name">clear:</th><td class="field-body">Clears the watch list.</td>
</tr>
</tbody>
</table>
<p>Examples:</p>
<dl class="docutils">
<dt><tt class="docutils literal">seedwatch MUSHROOM_HELMET_PLUMP 30</tt></dt>
<dd>add <tt class="docutils literal">MUSHROOM_HELMET_PLUMP</tt> to the watch list, limit = 30</dd>
<dt><tt class="docutils literal">seedwatch MUSHROOM_HELMET_PLUMP</tt></dt>
<dd>removes <tt class="docutils literal">MUSHROOM_HELMET_PLUMP</tt> from the watch list.</dd>
<dt><tt class="docutils literal">seedwatch all 30</tt></dt>
<dd>adds all plants from the abbreviation list to the watch list, the limit being 30.</dd>
</dl>
</div> </div>
<div class="section" id="zone"> <div class="section" id="zone">
<h3><a class="toc-backref" href="#id111">zone</a></h3> <h3><a class="toc-backref" href="#id111">zone</a></h3>
@ -2873,8 +2937,8 @@ as an offset for the pattern: instead of starting at the cursor, it will start
<p>The script takes the plan filename, starting from the root df folder (where <p>The script takes the plan filename, starting from the root df folder (where
Dwarf Fortress.exe is found).</p> Dwarf Fortress.exe is found).</p>
</div> </div>
<div class="section" id="drainaquifer"> <div class="section" id="drain-aquifer">
<h2><a class="toc-backref" href="#id135">drainaquifer</a></h2> <h2><a class="toc-backref" href="#id135">drain-aquifer</a></h2>
<p>Remove all 'aquifer' tag from the map blocks. Irreversible.</p> <p>Remove all 'aquifer' tag from the map blocks. Irreversible.</p>
</div> </div>
<div class="section" id="deathcause"> <div class="section" id="deathcause">
@ -3089,13 +3153,25 @@ alternatively pass cage IDs as arguments:</p>
stripcaged weapons 25321 34228 stripcaged weapons 25321 34228
</pre> </pre>
</div> </div>
<div class="section" id="teleport">
<h2><a class="toc-backref" href="#id154">teleport</a></h2>
<p>Teleports a unit to given coordinates.</p>
<p>Examples:</p>
<pre class="literal-block">
teleport showunitid - prints unitid beneath cursor
teleport showpos - prints coordinates beneath cursor
teleport unit 1234 x 56 y 115 z 26 - teleports unit 1234 to 56,115,26
</pre>
<p>One or both of <tt class="docutils literal">unit</tt> and <tt class="docutils literal">x</tt>/<tt class="docutils literal">y</tt>/<tt class="docutils literal">z</tt> coordinate positions must be
specified. If one is omitted, the unit or position beneath the cursor is used.</p>
</div>
<div class="section" id="undump-buildings"> <div class="section" id="undump-buildings">
<h2><a class="toc-backref" href="#id154">undump-buildings</a></h2> <h2><a class="toc-backref" href="#id155">undump-buildings</a></h2>
<p>Undesignates building base materials for dumping.</p> <p>Undesignates building base materials for dumping.</p>
</div> </div>
</div> </div>
<div class="section" id="modtools"> <div class="section" id="modtools">
<h1><a class="toc-backref" href="#id155">modtools</a></h1> <h1><a class="toc-backref" href="#id156">modtools</a></h1>
<p>These scripts are mostly useful for raw modders and scripters. They all have standard arguments: arguments are of the form <tt class="docutils literal">tool <span class="pre">-argName1</span> argVal1 <span class="pre">-argName2</span> argVal2</tt>. This is equivalent to <tt class="docutils literal">tool <span class="pre">-argName2</span> argVal2 <span class="pre">-argName1</span> argVal1</tt>. It is not necessary to provide a value to an argument name: <tt class="docutils literal">tool <span class="pre">-argName3</span></tt> is fine. Supplying the same argument name multiple times will result in an error. Argument names are preceded with a dash. The <tt class="docutils literal"><span class="pre">-help</span></tt> argument will print a descriptive usage string describing the nature of the arguments. For multiple word argument values, brackets must be used: <tt class="docutils literal">tool <span class="pre">-argName4</span> [ sadf1 sadf2 sadf3 ]</tt>. In order to allow passing literal braces as part of the argument, backslashes are used: <tt class="docutils literal">tool <span class="pre">-argName4</span> [ \] asdf \foo ]</tt> sets <tt class="docutils literal">argName4</tt> to <tt class="docutils literal">\] asdf foo</tt>. The <tt class="docutils literal"><span class="pre">*-trigger</span></tt> scripts have a similar policy with backslashes.</p> <p>These scripts are mostly useful for raw modders and scripters. They all have standard arguments: arguments are of the form <tt class="docutils literal">tool <span class="pre">-argName1</span> argVal1 <span class="pre">-argName2</span> argVal2</tt>. This is equivalent to <tt class="docutils literal">tool <span class="pre">-argName2</span> argVal2 <span class="pre">-argName1</span> argVal1</tt>. It is not necessary to provide a value to an argument name: <tt class="docutils literal">tool <span class="pre">-argName3</span></tt> is fine. Supplying the same argument name multiple times will result in an error. Argument names are preceded with a dash. The <tt class="docutils literal"><span class="pre">-help</span></tt> argument will print a descriptive usage string describing the nature of the arguments. For multiple word argument values, brackets must be used: <tt class="docutils literal">tool <span class="pre">-argName4</span> [ sadf1 sadf2 sadf3 ]</tt>. In order to allow passing literal braces as part of the argument, backslashes are used: <tt class="docutils literal">tool <span class="pre">-argName4</span> [ \] asdf \foo ]</tt> sets <tt class="docutils literal">argName4</tt> to <tt class="docutils literal">\] asdf foo</tt>. The <tt class="docutils literal"><span class="pre">*-trigger</span></tt> scripts have a similar policy with backslashes.</p>
<ul> <ul>
<li><p class="first">add-syndrome</p> <li><p class="first">add-syndrome</p>
@ -3152,7 +3228,7 @@ stripcaged weapons 25321 34228
</ul> </ul>
</div> </div>
<div class="section" id="in-game-interface-tools"> <div class="section" id="in-game-interface-tools">
<h1><a class="toc-backref" href="#id156">In-game interface tools</a></h1> <h1><a class="toc-backref" href="#id157">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">
@ -3167,7 +3243,7 @@ guideline because it arguably just fixes small usability bugs in the game UI.</p
you must enable the plugins which provide them.</p> you must enable the plugins which provide them.</p>
</div> </div>
<div class="section" id="dwarf-manipulator"> <div class="section" id="dwarf-manipulator">
<h2><a class="toc-backref" href="#id157">Dwarf Manipulator</a></h2> <h2><a class="toc-backref" href="#id158">Dwarf Manipulator</a></h2>
<p>Implemented by the 'manipulator' plugin.</p> <p>Implemented by the 'manipulator' plugin.</p>
<p>To activate, open the unit screen and press 'l'.</p> <p>To activate, open the unit screen and press 'l'.</p>
<img alt="images/manipulator.png" src="images/manipulator.png" /> <img alt="images/manipulator.png" src="images/manipulator.png" />
@ -3208,7 +3284,7 @@ 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="#id158">Search</a></h2> <h2><a class="toc-backref" href="#id159">Search</a></h2>
<p>Implemented by the 'search' plugin.</p> <p>Implemented by the 'search' plugin.</p>
<p>The search plugin adds search to the Stocks, Animals, Trading, Stockpile, <p>The search plugin adds search to the Stocks, Animals, Trading, Stockpile,
Noble (assignment candidates), Military (position candidates), Burrows Noble (assignment candidates), Military (position candidates), Burrows
@ -3239,7 +3315,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="#id159">AutoMaterial</a></h2> <h2><a class="toc-backref" href="#id160">AutoMaterial</a></h2>
<p>Implemented by the 'automaterial' plugin.</p> <p>Implemented by the 'automaterial' plugin.</p>
<p>This makes building constructions (walls, floors, fortifications, etc) a little bit <p>This makes building constructions (walls, floors, fortifications, etc) a little bit
easier by saving you from having to trawl through long lists of materials each time easier by saving you from having to trawl through long lists of materials each time
@ -3267,7 +3343,7 @@ 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="stockpile-automation"> <div class="section" id="stockpile-automation">
<h2><a class="toc-backref" href="#idautomelt">Stockpile Automation</a></h2> <h2><a class="toc-backref" href="#id161">Stockpile Automation</a></h2>
<p>Enable the automelt plugin in your dfhack.init with:</p> <p>Enable the automelt plugin in your dfhack.init with:</p>
<pre class="literal-block"> <pre class="literal-block">
enable automelt enable automelt
@ -3275,8 +3351,22 @@ enable automelt
<p>When querying a stockpile an option will appear to toggle automelt for this stockpile. <p>When querying a stockpile an option will appear to toggle automelt for this stockpile.
Any items placed in this stockpile will be designated to be melted.</p> Any items placed in this stockpile will be designated to be melted.</p>
</div> </div>
<div class="section" id="track-stop-menu">
<h2><a class="toc-backref" href="#id162">Track Stop Menu</a></h2>
<p>The <cite>q</cite> menu of track stops is completely blank by default. To enable one:</p>
<pre class="literal-block">
enable trackstop
</pre>
<p>This allows you to view and/or change the track stop's friction and dump direction settings.
It re-uses the keybindings from the track stop building interface:</p>
<ul class="simple">
<li>BUILDING_TRACK_STOP_FRICTION_UP</li>
<li>BUILDING_TRACK_STOP_FRICTION_DOWN</li>
<li>BUILDING_TRACK_STOP_DUMP</li>
</ul>
</div>
<div class="section" id="gui-advfort"> <div class="section" id="gui-advfort">
<h2><a class="toc-backref" href="#id160">gui/advfort</a></h2> <h2><a class="toc-backref" href="#id163">gui/advfort</a></h2>
<p>This script allows to perform jobs in adventure mode. For more complete help <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 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> keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:</p>
@ -3295,7 +3385,7 @@ implies -a</li>
</div> </div>
</div> </div>
<div class="section" id="gui-assign-rack"> <div class="section" id="gui-assign-rack">
<h2><a class="toc-backref" href="#id161">gui/assign-rack</a></h2> <h2><a class="toc-backref" href="#id164">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" />
@ -3319,7 +3409,7 @@ 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-choose-weapons"> <div class="section" id="gui-choose-weapons">
<h2><a class="toc-backref" href="#id162">gui/choose-weapons</a></h2> <h2><a class="toc-backref" href="#id165">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
@ -3330,14 +3420,14 @@ 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-clone-uniform"> <div class="section" id="gui-clone-uniform">
<h2><a class="toc-backref" href="#id163">gui/clone-uniform</a></h2> <h2><a class="toc-backref" href="#id166">gui/clone-uniform</a></h2>
<p>Bind to a key (the example config uses Ctrl-C), and activate in the Uniforms <p>Bind to a key (the example config uses Ctrl-C), and activate in the Uniforms
page of the military screen with the cursor in the leftmost list.</p> page of the military screen with the cursor in the leftmost list.</p>
<p>When invoked, the script duplicates the currently selected uniform template, <p>When invoked, the script duplicates the currently selected uniform template,
and selects the newly created copy.</p> and selects the newly created copy.</p>
</div> </div>
<div class="section" id="gui-companion-order"> <div class="section" id="gui-companion-order">
<h2><a class="toc-backref" href="#id164">gui/companion-order</a></h2> <h2><a class="toc-backref" href="#id167">gui/companion-order</a></h2>
<p>A script to issue orders for companions. Select companions with lower case chars, issue orders with upper <p>A script to issue orders for companions. Select companions with lower case chars, issue orders with upper
case. Must be in look or talk mode to issue command on tile.</p> case. Must be in look or talk mode to issue command on tile.</p>
<img alt="images/companion-order.png" src="images/companion-order.png" /> <img alt="images/companion-order.png" src="images/companion-order.png" />
@ -3353,7 +3443,7 @@ case. Must be in look or talk mode to issue command on tile.</p>
</ul> </ul>
</div> </div>
<div class="section" id="gui-gm-editor"> <div class="section" id="gui-gm-editor">
<h2><a class="toc-backref" href="#id165">gui/gm-editor</a></h2> <h2><a class="toc-backref" href="#id168">gui/gm-editor</a></h2>
<p>There are three ways to open this editor:</p> <p>There are three ways to open this editor:</p>
<ul class="simple"> <ul class="simple">
<li>using gui/gm-editor command/keybinding - opens editor on what is selected <li>using gui/gm-editor command/keybinding - opens editor on what is selected
@ -3368,7 +3458,7 @@ the same as version above.</li>
in-game help.</p> in-game help.</p>
</div> </div>
<div class="section" id="hotkeys"> <div class="section" id="hotkeys">
<h2><a class="toc-backref" href="#idhotkeys">Hotkeys</a></h2> <h2><a class="toc-backref" href="#id169">Hotkeys</a></h2>
<p>Opens an in-game screen showing DFHack keybindings that are valid in the current mode.</p> <p>Opens an in-game screen showing DFHack keybindings that are valid in the current mode.</p>
<img alt="images/hotkeys.png" src="images/hotkeys.png" /> <img alt="images/hotkeys.png" src="images/hotkeys.png" />
<p>Type <tt class="docutils literal">hotkeys</tt> into the DFHack console to open the screen, or bind the command to a <p>Type <tt class="docutils literal">hotkeys</tt> into the DFHack console to open the screen, or bind the command to a
@ -3378,7 +3468,7 @@ keybinding add Ctrl-F1 hotkeys
</pre> </pre>
</div> </div>
<div class="section" id="gui-liquids"> <div class="section" id="gui-liquids">
<h2><a class="toc-backref" href="#id166">gui/liquids</a></h2> <h2><a class="toc-backref" href="#id170">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>This script is a gui front-end to the liquids plugin and works similar to it, <p>This script is a gui front-end to the liquids plugin and works similar to it,
@ -3398,7 +3488,7 @@ rivers power water wheels even when full and technically not flowing.</p>
<p>After setting up the desired operations using the described keys, use <tt class="docutils literal">Enter</tt> to apply them.</p> <p>After setting up the desired operations using the described keys, use <tt class="docutils literal">Enter</tt> to apply them.</p>
</div> </div>
<div class="section" id="gui-mechanisms"> <div class="section" id="gui-mechanisms">
<h2><a class="toc-backref" href="#id167">gui/mechanisms</a></h2> <h2><a class="toc-backref" href="#id171">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
@ -3408,13 +3498,13 @@ 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-mod-manager"> <div class="section" id="gui-mod-manager">
<h2><a class="toc-backref" href="#id168">gui/mod-manager</a></h2> <h2><a class="toc-backref" href="#id172">gui/mod-manager</a></h2>
<p>A way to simply install and remove small mods. It looks for specially formatted mods in <p>A way to simply install and remove small mods. It looks for specially formatted mods in
df subfolder 'mods'. Mods are not included, for example mods see: <a class="reference external" href="https://github.com/warmist/df-mini-mods">github mini mod repository</a></p> df subfolder 'mods'. Mods are not included, for example mods see: <a class="reference external" href="https://github.com/warmist/df-mini-mods">github mini mod repository</a></p>
<img alt="images/mod-manager.png" src="images/mod-manager.png" /> <img alt="images/mod-manager.png" src="images/mod-manager.png" />
</div> </div>
<div class="section" id="gui-rename"> <div class="section" id="gui-rename">
<h2><a class="toc-backref" href="#id169">gui/rename</a></h2> <h2><a class="toc-backref" href="#id173">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>
@ -3437,7 +3527,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="#id170">gui/room-list</a></h2> <h2><a class="toc-backref" href="#id174">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" />
@ -3445,7 +3535,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-guide-path"> <div class="section" id="gui-guide-path">
<h2><a class="toc-backref" href="#id171">gui/guide-path</a></h2> <h2><a class="toc-backref" href="#id175">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" />
@ -3453,7 +3543,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-workflow"> <div class="section" id="gui-workflow">
<h2><a class="toc-backref" href="#id172">gui/workflow</a></h2> <h2><a class="toc-backref" href="#id176">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" />
@ -3500,7 +3590,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-workshop-job"> <div class="section" id="gui-workshop-job">
<h2><a class="toc-backref" href="#id173">gui/workshop-job</a></h2> <h2><a class="toc-backref" href="#id177">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" />
@ -3537,7 +3627,7 @@ you have to unset the material first.</p>
</div> </div>
</div> </div>
<div class="section" id="behavior-mods"> <div class="section" id="behavior-mods">
<h1><a class="toc-backref" href="#id174">Behavior Mods</a></h1> <h1><a class="toc-backref" href="#id178">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>
@ -3548,20 +3638,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="#id175">Siege Engine</a></h2> <h2><a class="toc-backref" href="#id179">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="#id176">Rationale</a></h3> <h3><a class="toc-backref" href="#id180">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="#id177">Configuration UI</a></h3> <h3><a class="toc-backref" href="#id181">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>
@ -3584,7 +3674,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="#id178">Power Meter</a></h2> <h2><a class="toc-backref" href="#id182">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
@ -3595,11 +3685,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="#id179">Steam Engine</a></h2> <h2><a class="toc-backref" href="#id183">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="#id180">Rationale</a></h3> <h3><a class="toc-backref" href="#id184">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
@ -3610,7 +3700,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="#id181">Construction</a></h3> <h3><a class="toc-backref" href="#id185">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>
@ -3634,7 +3724,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="#id182">Operation</a></h3> <h3><a class="toc-backref" href="#id186">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
@ -3665,7 +3755,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="#id183">Explosions</a></h3> <h3><a class="toc-backref" href="#id187">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
@ -3674,7 +3764,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="#id184">Save files</a></h3> <h3><a class="toc-backref" href="#id188">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
@ -3685,7 +3775,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="#id185">Add Spatter</a></h2> <h2><a class="toc-backref" href="#id189">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

@ -1258,11 +1258,6 @@ One-shot subcommands:
Subcommands that persist until disabled or DF quit: Subcommands that persist until disabled or DF quit:
:stable-cursor: Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode. :stable-cursor: Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.
:patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts.
Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
:stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates.
In very item-heavy forts with big stockpiles this can improve FPS by 50-100%
:fast-heat: Further improves temperature update performance by ensuring that 1 degree :fast-heat: Further improves temperature update performance by ensuring that 1 degree
of item temperature is crossed in no more than specified number of frames of item temperature is crossed in no more than specified number of frames
when updating from the environment temperature. This reduces the time it when updating from the environment temperature. This reduces the time it
@ -1286,7 +1281,12 @@ Subcommands that persist until disabled or DF quit:
:adamantine-cloth-wear: Prevents adamantine clothing from wearing out while being worn (bug 6481). :adamantine-cloth-wear: Prevents adamantine clothing from wearing out while being worn (bug 6481).
:confirm-embark: Adds a prompt before embarking (on the "prepare carefully" screen). :farm-plot-select: Adds "Select all" and "Deselect all" options to farm plot menus
:import-priority-category: Allows changing the priority of all goods in a
category when discussing an import agreement with the liaison
:manager-quantity: Removes the limit of 30 jobs per manager order
:civ-view-agreement: Fixes overlapping text on the "view agreement" screen
:nestbox-color: Fixes the color of built nestboxes
fix-armory fix-armory
---------- ----------
@ -1626,10 +1626,31 @@ Fortress activity management
seedwatch seedwatch
--------- ---------
Tool for turning cooking of seeds and plants on/off depending on how much you Watches the numbers of seeds available and enables/disables seed and plant cooking.
have of them.
Each plant type can be assigned a limit. If their number falls below that limit,
the plants and seeds of that type will be excluded from cookery.
If the number rises above the limit + 20, then cooking will be allowed.
The plugin needs a fortress to be loaded and will deactivate automatically otherwise.
You have to reactivate with 'seedwatch start' after you load the game.
Options:
:all: Adds all plants from the abbreviation list to the watch list.
:start: Start watching.
:stop: Stop watching.
:info: Display whether seedwatch is watching, and the watch list.
:clear: Clears the watch list.
Examples:
See 'seedwatch help' for detailed description. ``seedwatch MUSHROOM_HELMET_PLUMP 30``
add ``MUSHROOM_HELMET_PLUMP`` to the watch list, limit = 30
``seedwatch MUSHROOM_HELMET_PLUMP``
removes ``MUSHROOM_HELMET_PLUMP`` from the watch list.
``seedwatch all 30``
adds all plants from the abbreviation list to the watch list, the limit being 30.
zone zone
---- ----
@ -2149,8 +2170,8 @@ as an offset for the pattern: instead of starting at the cursor, it will start
The script takes the plan filename, starting from the root df folder (where The script takes the plan filename, starting from the root df folder (where
Dwarf Fortress.exe is found). Dwarf Fortress.exe is found).
drainaquifer drain-aquifer
============ =============
Remove all 'aquifer' tag from the map blocks. Irreversible. Remove all 'aquifer' tag from the map blocks. Irreversible.
deathcause deathcause
@ -2655,6 +2676,19 @@ Enable the automelt plugin in your dfhack.init with::
When querying a stockpile an option will appear to toggle automelt for this stockpile. When querying a stockpile an option will appear to toggle automelt for this stockpile.
Any items placed in this stockpile will be designated to be melted. Any items placed in this stockpile will be designated to be melted.
Track Stop Menu
===============
The `q` menu of track stops is completely blank by default. To enable one::
enable trackstop
This allows you to view and/or change the track stop's friction and dump direction settings.
It re-uses the keybindings from the track stop building interface:
* BUILDING_TRACK_STOP_FRICTION_UP
* BUILDING_TRACK_STOP_FRICTION_DOWN
* BUILDING_TRACK_STOP_DUMP
gui/advfort gui/advfort
=========== ===========

@ -139,9 +139,6 @@ keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack
# stabilize the cursor of dwarfmode when switching menus # stabilize the cursor of dwarfmode when switching menus
tweak stable-cursor tweak stable-cursor
# stop military from considering training as 'patrol duty'
tweak patrol-duty
# stop stacked liquid/bar/thread/cloth items from lasting forever # stop stacked liquid/bar/thread/cloth items from lasting forever
# if used in reactions that use only a fraction of the dimension. # if used in reactions that use only a fraction of the dimension.
# might be fixed by DF # might be fixed by DF
@ -169,6 +166,15 @@ tweak craft-age-wear
# stop adamantine clothing from wearing out (bug 6481) # stop adamantine clothing from wearing out (bug 6481)
#tweak adamantine-cloth-wear #tweak adamantine-cloth-wear
# Add "Select all" and "Deselect all" options to farm plot menus
tweak farm-plot-select
# Add Shift-Left/Right controls to import agreement screen
tweak import-priority-category
# Misc. UI tweaks
tweak civ-view-agreement
########################### ###########################
# Globally acting plugins # # Globally acting plugins #
########################### ###########################
@ -183,7 +189,7 @@ enable search
enable automaterial enable automaterial
# Other interface improvement tools # Other interface improvement tools
enable dwarfmonitor mousequery automelt autotrade buildingplan resume zone enable dwarfmonitor mousequery automelt autotrade buildingplan resume trackstop zone stocks autochop
# allow the fortress bookkeeper to queue jobs through the manager # allow the fortress bookkeeper to queue jobs through the manager
stockflow enable stockflow enable

@ -61,6 +61,7 @@ using namespace DFHack;
#include "df/world_data.h" #include "df/world_data.h"
#include "df/interfacest.h" #include "df/interfacest.h"
#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_game_cleanerst.h"
#include "df/viewscreen_loadgamest.h" #include "df/viewscreen_loadgamest.h"
#include "df/viewscreen_savegamest.h" #include "df/viewscreen_savegamest.h"
#include <df/graphic.h> #include <df/graphic.h>
@ -1027,12 +1028,15 @@ bool Core::Init()
cerr << "Initializing Console.\n"; cerr << "Initializing Console.\n";
// init the console. // init the console.
bool is_text_mode = false; bool is_text_mode = (init && init->display.flag.is_set(init_display_flags::TEXT));
if(init && init->display.flag.is_set(init_display_flags::TEXT)) if (is_text_mode || getenv("DFHACK_DISABLE_CONSOLE"))
{ {
is_text_mode = true;
con.init(true); con.init(true);
cerr << "Console is not available. Use dfhack-run to send commands.\n"; cerr << "Console is not available. Use dfhack-run to send commands.\n";
if (!is_text_mode)
{
cout << "Console disabled.\n";
}
} }
else if(con.init(false)) else if(con.init(false))
cerr << "Console is running.\n"; cerr << "Console is running.\n";
@ -1280,6 +1284,7 @@ void Core::doUpdate(color_ostream &out, bool first_update)
} }
bool is_load_save = bool is_load_save =
strict_virtual_cast<df::viewscreen_game_cleanerst>(screen) ||
strict_virtual_cast<df::viewscreen_loadgamest>(screen) || strict_virtual_cast<df::viewscreen_loadgamest>(screen) ||
strict_virtual_cast<df::viewscreen_savegamest>(screen); strict_virtual_cast<df::viewscreen_savegamest>(screen);

@ -67,10 +67,7 @@ void *type_identity::allocate() {
bool type_identity::copy(void *tgt, const void *src) { bool type_identity::copy(void *tgt, const void *src) {
if (can_allocate() && tgt && src) if (can_allocate() && tgt && src)
{ return do_copy(tgt, src);
do_copy(tgt, src);
return true;
}
else else
return false; return false;
} }

@ -39,6 +39,14 @@ namespace df {
stl_bit_vector_identity identity_traits<std::vector<bool> >::identity; stl_bit_vector_identity identity_traits<std::vector<bool> >::identity;
bit_array_identity identity_traits<BitArray<int> >::identity; bit_array_identity identity_traits<BitArray<int> >::identity;
static void *fstream_allocator_fn(void *out, const void *in) {
if (out) { /* *(T*)out = *(const T*)in;*/ return NULL; }
else if (in) { delete (std::fstream*)in; return (std::fstream*)in; }
else return new std::fstream();
}
opaque_identity identity_traits<std::fstream>::identity(
sizeof(std::fstream), fstream_allocator_fn, "fstream");
buffer_container_identity buffer_container_identity::base_instance; buffer_container_identity buffer_container_identity::base_instance;
#undef NUMBER_IDENTITY_TRAITS #undef NUMBER_IDENTITY_TRAITS

@ -1256,8 +1256,11 @@ static void MakePrimitiveMetatable(lua_State *state, type_identity *type)
// Index the fields // Index the fields
lua_newtable(state); lua_newtable(state);
if (type->type() != IDTYPE_OPAQUE)
{
EnableMetaField(state, base+2, "value", type); EnableMetaField(state, base+2, "value", type);
AssociateId(state, base+3, 1, "value"); AssociateId(state, base+3, 1, "value");
}
// Add the iteration metamethods // Add the iteration metamethods
PushStructMethod(state, base+1, base+3, meta_struct_next); PushStructMethod(state, base+1, base+3, meta_struct_next);

@ -61,7 +61,8 @@ namespace DFHack
IDTYPE_STRUCT, IDTYPE_STRUCT,
IDTYPE_CLASS, IDTYPE_CLASS,
IDTYPE_BUFFER, IDTYPE_BUFFER,
IDTYPE_STL_PTR_VECTOR IDTYPE_STL_PTR_VECTOR,
IDTYPE_OPAQUE
}; };
typedef void *(*TAllocateFn)(void*,const void*); typedef void *(*TAllocateFn)(void*,const void*);
@ -78,7 +79,7 @@ namespace DFHack
virtual bool can_allocate() { return true; } virtual bool can_allocate() { return true; }
virtual void *do_allocate() { return do_allocate_pod(); } virtual void *do_allocate() { return do_allocate_pod(); }
virtual void do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); } virtual bool do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); return true; }
virtual bool do_destroy(void *obj) { return do_destroy_pod(obj); } virtual bool do_destroy(void *obj) { return do_destroy_pod(obj); }
public: public:
@ -116,7 +117,7 @@ namespace DFHack
virtual bool can_allocate() { return (allocator != NULL); } virtual bool can_allocate() { return (allocator != NULL); }
virtual void *do_allocate() { return allocator(NULL,NULL); } virtual void *do_allocate() { return allocator(NULL,NULL); }
virtual void do_copy(void *tgt, const void *src) { allocator(tgt,src); } virtual bool do_copy(void *tgt, const void *src) { return allocator(tgt,src) == tgt; }
virtual bool do_destroy(void *obj) { return allocator(NULL,obj) == obj; } virtual bool do_destroy(void *obj) { return allocator(NULL,obj) == obj; }
public: public:
virtual bool isPrimitive() { return false; } virtual bool isPrimitive() { return false; }
@ -166,7 +167,7 @@ namespace DFHack
protected: protected:
virtual bool can_allocate() { return true; } virtual bool can_allocate() { return true; }
virtual void *do_allocate() { return do_allocate_pod(); } virtual void *do_allocate() { return do_allocate_pod(); }
virtual void do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); } virtual bool do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); return true; }
virtual bool do_destroy(void *obj) { return do_destroy_pod(obj); } virtual bool do_destroy(void *obj) { return do_destroy_pod(obj); }
public: public:
@ -199,7 +200,7 @@ namespace DFHack
protected: protected:
virtual bool can_allocate() { return true; } virtual bool can_allocate() { return true; }
virtual void *do_allocate(); virtual void *do_allocate();
virtual void do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); } virtual bool do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); return true; }
virtual bool do_destroy(void *obj) { return do_destroy_pod(obj); } virtual bool do_destroy(void *obj) { return do_destroy_pod(obj); }
public: public:

@ -65,6 +65,17 @@ namespace DFHack
virtual identity_type type() { return IDTYPE_PRIMITIVE; } virtual identity_type type() { return IDTYPE_PRIMITIVE; }
}; };
class DFHACK_EXPORT opaque_identity : public constructed_identity {
std::string name;
public:
opaque_identity(size_t size, TAllocateFn alloc, const std::string &name)
: constructed_identity(size, alloc), name(name) {};
virtual std::string getFullName() { return name; }
virtual identity_type type() { return IDTYPE_OPAQUE; }
};
class DFHACK_EXPORT pointer_identity : public primitive_identity { class DFHACK_EXPORT pointer_identity : public primitive_identity {
type_identity *target; type_identity *target;
@ -170,6 +181,7 @@ namespace df
{ {
using DFHack::function_identity_base; using DFHack::function_identity_base;
using DFHack::primitive_identity; using DFHack::primitive_identity;
using DFHack::opaque_identity;
using DFHack::pointer_identity; using DFHack::pointer_identity;
using DFHack::container_identity; using DFHack::container_identity;
using DFHack::ptr_container_identity; using DFHack::ptr_container_identity;
@ -488,6 +500,11 @@ namespace df
static stl_string_identity *get() { return &identity; } static stl_string_identity *get() { return &identity; }
}; };
template<> struct DFHACK_EXPORT identity_traits<std::fstream> {
static opaque_identity identity;
static opaque_identity *get() { return &identity; }
};
template<> struct DFHACK_EXPORT identity_traits<char*> { template<> struct DFHACK_EXPORT identity_traits<char*> {
static ptr_string_identity identity; static ptr_string_identity identity;
static ptr_string_identity *get() { return &identity; } static ptr_string_identity *get() { return &identity; }

@ -0,0 +1,12 @@
file_compressorst& operator=(const file_compressorst &in) {
compressed = in.compressed;
/* fstream cannot be assigned */
in_buffer = in.in_buffer;
in_buffersize = in.in_buffersize;
in_buffer_amount_loaded = in.in_buffer_amount_loaded;
in_buffer_position = in.in_buffer_position;
out_buffer = in.out_buffer;
out_buffersize = in.out_buffersize;
out_buffer_amount_written = in.out_buffer_amount_written;
return *this;
}

@ -119,7 +119,7 @@ function isValidTarget(unit,syndrome)
end end
for caste,creature in ipairs(syndrome.syn_affected_creature) do for caste,creature in ipairs(syndrome.syn_affected_creature) do
local affectedCreature = creature.value local affectedCreature = creature.value
local affectedCaste = syndrome.syn_affectedCaste[caste].value local affectedCaste = syndrome.syn_affected_caste[caste].value
if affectedCreature == unitRaceName and (affectedCaste == unitCasteName or affectedCaste == "ALL") then if affectedCreature == unitRaceName and (affectedCaste == unitCasteName or affectedCaste == "ALL") then
affected = true affected = true
end end

@ -320,7 +320,7 @@ void DFHack::EventManager::manageEvents(color_ostream& out) {
} }
else eventFrequency = 1; else eventFrequency = 1;
if ( tick - eventLastTick[a] < eventFrequency ) if ( tick >= eventLastTick[a] && tick - eventLastTick[a] < eventFrequency )
continue; continue;
eventManager[a](out); eventManager[a](out);

@ -154,7 +154,7 @@ void Units::CopyCreature(df::unit * source, t_unit & furball)
// profession // profession
furball.profession = source->profession; furball.profession = source->profession;
// happiness // happiness
furball.happiness = source->status.happiness; furball.happiness = 100;//source->status.happiness;
// physical attributes // physical attributes
memcpy(&furball.strength, source->body.physical_attrs, sizeof(source->body.physical_attrs)); memcpy(&furball.strength, source->body.physical_attrs, sizeof(source->body.physical_attrs));

@ -1 +1 @@
Subproject commit 93606b34b78a94b201704adb5c9ae28af304cdb1 Subproject commit d19695846518a3cb62f35e70d49a5e9064e10af1

@ -110,7 +110,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(digFlood digFlood.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp)
add_subdirectory(diggingInvaders) add_subdirectory(diggingInvaders)
DFHACK_PLUGIN(drybuckets drybuckets.cpp) DFHACK_PLUGIN(drybuckets drybuckets.cpp)
DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp) #DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp)
DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(embark-tools embark-tools.cpp)
DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp)
@ -132,7 +132,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua)
DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(manipulator manipulator.cpp)
DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mode mode.cpp)
DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(misery misery.cpp)
DFHACK_PLUGIN(mousequery mousequery.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp)
DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) DFHACK_PLUGIN(petcapRemover petcapRemover.cpp)
DFHACK_PLUGIN(plants plants.cpp) DFHACK_PLUGIN(plants plants.cpp)
@ -156,9 +156,10 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(stocks stocks.cpp) DFHACK_PLUGIN(stocks stocks.cpp)
DFHACK_PLUGIN(strangemood strangemood.cpp) DFHACK_PLUGIN(strangemood strangemood.cpp)
DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h)
DFHACK_PLUGIN(trackstop trackstop.cpp)
# DFHACK_PLUGIN(treefarm treefarm.cpp) # DFHACK_PLUGIN(treefarm treefarm.cpp)
DFHACK_PLUGIN(tubefill tubefill.cpp) DFHACK_PLUGIN(tubefill tubefill.cpp)
DFHACK_PLUGIN(tweak tweak.cpp) add_subdirectory(tweak)
DFHACK_PLUGIN(weather weather.cpp) DFHACK_PLUGIN(weather weather.cpp)
DFHACK_PLUGIN(workflow workflow.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(workflow workflow.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(workNow workNow.cpp) DFHACK_PLUGIN(workNow workNow.cpp)

@ -819,7 +819,7 @@ static void assign_labor(unit_labor::unit_labor labor,
// bias by happiness // bias by happiness
value += dwarfs[dwarf]->status.happiness; //value += dwarfs[dwarf]->status.happiness;
values[dwarf] = value; values[dwarf] = value;

@ -7,6 +7,7 @@
#include "df/building_def.h" #include "df/building_def.h"
#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dwarfmodest.h"
#include "df/building_stockpilest.h" #include "df/building_stockpilest.h"
#include "modules/Buildings.h"
#include "modules/Items.h" #include "modules/Items.h"
#include "df/ui.h" #include "df/ui.h"
#include "modules/Maps.h" #include "modules/Maps.h"
@ -23,10 +24,36 @@ DFHACK_PLUGIN("automelt");
static const string PERSISTENCE_KEY = "automelt/stockpiles"; static const string PERSISTENCE_KEY = "automelt/stockpiles";
static void mark_all_in_stockpiles(vector<PersistentStockpileInfo> &stockpiles) static int mark_item(df::item *item, df::item_flags bad_flags, int32_t stockpile_id)
{ {
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY]; if (item->flags.whole & bad_flags.whole)
return 0;
if (item->isAssignedToThisStockpile(stockpile_id)) {
size_t marked_count = 0;
std::vector<df::item*> contents;
Items::getContainedItems(item, &contents);
for (auto child = contents.begin(); child != contents.end(); child++)
{
marked_count += mark_item(*child, bad_flags, stockpile_id);
}
return marked_count;
}
if (!can_melt(item))
return 0;
if (is_set_to_melt(item))
return 0;
insert_into_vector(world->items.other[items_other_id::ANY_MELT_DESIGNATED], &df::item::id, item);
item->flags.bits.melt = true;
return 1;
}
static void mark_all_in_stockpiles(vector<PersistentStockpileInfo> &stockpiles)
{
// Precompute a bitmask with the bad flags // Precompute a bitmask with the bad flags
df::item_flags bad_flags; df::item_flags bad_flags;
bad_flags.whole = 0; bad_flags.whole = 0;
@ -39,27 +66,16 @@ static void mark_all_in_stockpiles(vector<PersistentStockpileInfo> &stockpiles)
#undef F #undef F
size_t marked_count = 0; size_t marked_count = 0;
for (size_t i = 0; i < items.size(); i++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
if (!can_melt(item))
continue;
if (is_set_to_melt(item))
continue;
auto &melting_items = world->items.other[items_other_id::ANY_MELT_DESIGNATED];
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++) for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
{ {
if (!it->inStockpile(item)) if (!it->isValid())
continue; continue;
++marked_count; auto spid = it->getId();
insert_into_vector(melting_items, &df::item::id, item); Buildings::StockpileIterator stored;
item->flags.bits.melt = true; for (stored.begin(it->getStockpile()); !stored.done(); ++stored)
{
marked_count += mark_item(*stored, bad_flags, spid);
} }
} }

@ -455,7 +455,9 @@ struct tradeview_hook : public df::viewscreen_tradegoodsst
{ {
for (int i = 0; i < trader_selected.size(); i++) for (int i = 0; i < trader_selected.size(); i++)
{ {
trader_selected[i] = 1; // Only mark containers, not their contents.
// Granted, this behaves poorly with the search plugin...
trader_selected[i] = !trader_items[i]->flags.bits.in_inventory;
} }
} }
else if (input->count(interface_key::CUSTOM_U)) else if (input->count(interface_key::CUSTOM_U))
@ -469,7 +471,8 @@ struct tradeview_hook : public df::viewscreen_tradegoodsst
{ {
for (int i = 0; i < broker_selected.size(); i++) for (int i = 0; i < broker_selected.size(); i++)
{ {
broker_selected[i] = 1; // Only mark containers, not their contents.
broker_selected[i] = !broker_items[i]->flags.bits.in_inventory;
} }
} }
else if (input->count(interface_key::CUSTOM_SHIFT_U)) else if (input->count(interface_key::CUSTOM_SHIFT_U))

@ -72,7 +72,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" digFlood digAll0\n" " digFlood digAll0\n"
" disable digAll mode\n" " disable digAll mode\n"
"\n" "\n"
"Note that while order matters, multiple commands can be sequenced in one line. It is recommended to alter your dfhack.init file so that you won't have to type in every mineral type you want to dig every time you start the game. Material names are case sensitive.\n" "Note that while order matters, multiple commands can be sequenced in one line. It is recommended to alter your save-specific regionX/raw/onLoad.init or global onLoadWorld.init file so that you won't have to type in every mineral type you want to dig every time you start the game. Material names are case sensitive.\n"
)); ));
return CR_OK; return CR_OK;
} }

@ -11,98 +11,13 @@
#include <VTableInterpose.h> #include <VTableInterpose.h>
#include "ColorText.h" #include "ColorText.h"
#include "uicommon.h"
#include "df/viewscreen_choose_start_sitest.h" #include "df/viewscreen_choose_start_sitest.h"
#include "df/interface_key.h" #include "df/interface_key.h"
using namespace DFHack; using namespace DFHack;
struct EmbarkTool #define FOR_ITER_TOOLS(iter) for(auto iter = tools.begin(); iter != tools.end(); iter++)
{
std::string id;
std::string name;
std::string desc;
bool enabled;
df::interface_key toggle_key;
};
EmbarkTool embark_tools[] = {
{"anywhere", "Embark anywhere", "Allows embarking anywhere on the world map",
false, df::interface_key::CUSTOM_A},
{"nano", "Nano embark", "Allows the embark size to be decreased below 2x2",
false, df::interface_key::CUSTOM_N},
{"sand", "Sand indicator", "Displays an indicator when sand is present on the given embark site",
false, df::interface_key::CUSTOM_S},
{"sticky", "Stable position", "Maintains the selected local area while navigating the world map",
false, df::interface_key::CUSTOM_P},
};
#define NUM_TOOLS int(sizeof(embark_tools) / sizeof(EmbarkTool))
command_result embark_tools_cmd (color_ostream &out, std::vector <std::string> & parameters);
void OutputString (int8_t color, int &x, int y, const std::string &text);
bool tool_exists (std::string tool_name);
bool tool_enabled (std::string tool_name);
bool tool_enable (std::string tool_name, bool enable_state);
void tool_update (std::string tool_name);
class embark_tools_settings : public dfhack_viewscreen
{
public:
embark_tools_settings () { };
~embark_tools_settings () { };
void help () { };
std::string getFocusString () { return "embark-tools/options"; };
void render ()
{
parent->render();
int x;
auto dim = Screen::getWindowSize();
int width = 50,
height = 4 + 1 + NUM_TOOLS, // Padding + lower row
min_x = (dim.x - width) / 2,
max_x = (dim.x + width) / 2,
min_y = (dim.y - height) / 2,
max_y = min_y + height;
Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_DARKGREY), min_x, min_y, max_x, max_y);
Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), min_x + 1, min_y + 1, max_x - 1, max_y - 1);
x = min_x + 2;
OutputString(COLOR_LIGHTRED, x, max_y - 2, Screen::getKeyDisplay(df::interface_key::SELECT));
OutputString(COLOR_WHITE, x, max_y - 2, "/");
OutputString(COLOR_LIGHTRED, x, max_y - 2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN));
OutputString(COLOR_WHITE, x, max_y - 2, ": Done");
for (int i = 0, y = min_y + 2; i < NUM_TOOLS; i++, y++)
{
EmbarkTool t = embark_tools[i];
x = min_x + 2;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(t.toggle_key));
OutputString(COLOR_WHITE, x, y, ": " + t.name + (t.enabled ? ": Enabled" : ": Disabled"));
}
};
void feed (std::set<df::interface_key> * input)
{
if (input->count(df::interface_key::SELECT) || input->count(df::interface_key::LEAVESCREEN))
{
Screen::dismiss(this);
return;
}
for (auto iter = input->begin(); iter != input->end(); iter++)
{
df::interface_key key = *iter;
for (int i = 0; i < NUM_TOOLS; i++)
{
if (embark_tools[i].toggle_key == key)
{
embark_tools[i].enabled = !embark_tools[i].enabled;
}
}
}
};
};
/*
* Logic
*/
void update_embark_sidebar (df::viewscreen_choose_start_sitest * screen) void update_embark_sidebar (df::viewscreen_choose_start_sitest * screen)
{ {
@ -159,88 +74,81 @@ void resize_embark (df::viewscreen_choose_start_sitest * screen, int dx, int dy)
update_embark_sidebar(screen); update_embark_sidebar(screen);
} }
std::string sand_indicator = ""; typedef df::viewscreen_choose_start_sitest start_sitest;
bool sand_dirty = true; // Flag set when update is needed typedef std::set<df::interface_key> ikey_set;
void sand_update (df::viewscreen_choose_start_sitest * screen)
{
CoreSuspendClaimer suspend;
buffered_color_ostream out;
Core::getInstance().runCommand(out, "prospect");
auto fragments = out.fragments();
sand_indicator = "";
for (auto iter = fragments.begin(); iter != fragments.end(); iter++)
{
std::string fragment = iter->second;
if (fragment.find("SAND_") != std::string::npos)
{
sand_indicator = "Sand";
break;
}
}
sand_dirty = false;
}
int sticky_pos[] = {0, 0, 3, 3}; class EmbarkTool
bool sticky_moved = false;
void sticky_save (df::viewscreen_choose_start_sitest * screen)
{ {
sticky_pos[0] = screen->location.embark_pos_min.x; protected:
sticky_pos[1] = screen->location.embark_pos_max.x; bool enabled;
sticky_pos[2] = screen->location.embark_pos_min.y; public:
sticky_pos[3] = screen->location.embark_pos_max.y; EmbarkTool()
} :enabled(false)
{ }
void sticky_apply (df::viewscreen_choose_start_sitest * screen) virtual bool getEnabled() { return enabled; }
{ virtual void setEnabled(bool state) { enabled = state; }
if (screen->finder.finder_state != -1) virtual void toggleEnabled() { setEnabled(!enabled); }
{ virtual std::string getId() = 0;
// Site finder is active - don't override default local position virtual std::string getName() = 0;
return; virtual std::string getDesc() = 0;
} virtual df::interface_key getToggleKey() = 0;
screen->location.embark_pos_min.x = sticky_pos[0]; virtual void before_render(start_sitest* screen) { };
screen->location.embark_pos_max.x = sticky_pos[1]; virtual void after_render(start_sitest* screen) { };
screen->location.embark_pos_min.y = sticky_pos[2]; virtual void before_feed(start_sitest* screen, ikey_set* input, bool &cancel) { };
screen->location.embark_pos_max.y = sticky_pos[3]; virtual void after_feed(start_sitest* screen, ikey_set* input) { };
update_embark_sidebar(screen); };
} std::vector<EmbarkTool*> tools;
/* /*
* Viewscreen hooks
*/
void OutputString (int8_t color, int &x, int y, const std::string &text) class SampleTool : public EmbarkTool
{ {
Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); virtual std::string getId() { return "id"; }
x += text.length(); virtual std::string getName() { return "Name"; }
} virtual std::string getDesc() { return "Description"; }
virtual df::interface_key getToggleKey() { return df::interface_key::KEY; }
virtual void before_render(start_sitest* screen) { }
virtual void after_render(start_sitest* screen) { }
virtual void before_feed(start_sitest* screen, ikey_set* input, bool &cancel) { };
virtual void after_feed(start_sitest* screen, ikey_set* input) { };
};
struct choose_start_site_hook : df::viewscreen_choose_start_sitest */
{
typedef df::viewscreen_choose_start_sitest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input)) class EmbarkAnywhere : public EmbarkTool
{ {
bool prevent_default = false; public:
if (tool_enabled("anywhere")) virtual std::string getId() { return "anywhere"; }
{ virtual std::string getName() { return "Embark anywhere"; }
for (auto iter = input->begin(); iter != input->end(); iter++) virtual std::string getDesc() { return "Allows embarking anywhere on the world map"; }
virtual df::interface_key getToggleKey() { return df::interface_key::CUSTOM_A; }
virtual void after_render(start_sitest* screen)
{ {
df::interface_key key = *iter; auto dim = Screen::getWindowSize();
if (key == df::interface_key::SETUP_EMBARK) int x = 20, y = dim.y - 2;
if (screen->page >= 0 && screen->page <= 4)
{ {
prevent_default = true; OutputString(COLOR_WHITE, x, y, ": Embark!");
this->in_embark_normal = 1;
}
} }
} }
virtual void before_feed(start_sitest* screen, ikey_set *input, bool &cancel)
if (input->count(df::interface_key::CUSTOM_S))
{ {
Screen::show(new embark_tools_settings); if (input->count(df::interface_key::SETUP_EMBARK))
return; {
cancel = true;
screen->in_embark_normal = 1;
} }
};
};
if (tool_enabled("nano")) class NanoEmbark : public EmbarkTool
{
public:
virtual std::string getId() { return "nano"; }
virtual std::string getName() { return "Nano embark"; }
virtual std::string getDesc() { return "Allows the embark size to be decreased below 2x2"; }
virtual df::interface_key getToggleKey() { return df::interface_key::CUSTOM_N; }
virtual void before_feed(start_sitest* screen, ikey_set* input, bool &cancel)
{ {
for (auto iter = input->begin(); iter != input->end(); iter++) for (auto iter = input->begin(); iter != input->end(); iter++)
{ {
@ -266,14 +174,117 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
} }
if (is_resize) if (is_resize)
{ {
prevent_default = true; cancel = true;
resize_embark(this, dx, dy); resize_embark(screen, dx, dy);
return;
} }
} }
};
};
class SandIndicator : public EmbarkTool
{
protected:
bool dirty;
std::string indicator;
void update_indicator()
{
CoreSuspendClaimer suspend;
buffered_color_ostream out;
Core::getInstance().runCommand(out, "prospect");
auto fragments = out.fragments();
indicator = "";
for (auto iter = fragments.begin(); iter != fragments.end(); iter++)
{
std::string fragment = iter->second;
if (fragment.find("SAND_") != std::string::npos)
{
indicator = "Sand";
break;
}
}
dirty = false;
}
public:
SandIndicator()
:EmbarkTool(),
dirty(true),
indicator("")
{ }
virtual void setEnabled(bool state)
{
EmbarkTool::setEnabled(state);
dirty = true;
}
virtual std::string getId() { return "sand"; }
virtual std::string getName() { return "Sand indicator"; }
virtual std::string getDesc() { return "Displays an indicator when sand is present on the given embark site"; }
virtual df::interface_key getToggleKey() { return df::interface_key::CUSTOM_S; }
virtual void after_render(start_sitest* screen)
{
if (dirty)
update_indicator();
auto dim = Screen::getWindowSize();
int x = dim.x - 28,
y = 13;
if (screen->page == 0)
{
OutputString(COLOR_YELLOW, x, y, indicator);
}
} }
virtual void after_feed(start_sitest* screen, ikey_set* input)
{
dirty = true;
};
};
if (tool_enabled("sticky")) class StablePosition : public EmbarkTool
{
protected:
int prev_position[4];
bool moved_position;
void save_position(start_sitest* screen)
{
prev_position[0] = screen->location.embark_pos_min.x;
prev_position[1] = screen->location.embark_pos_max.x;
prev_position[2] = screen->location.embark_pos_min.y;
prev_position[3] = screen->location.embark_pos_max.y;
}
void restore_position(start_sitest* screen)
{ {
if (screen->finder.finder_state != -1)
{
// Site finder is active - don't override default local position
return;
}
screen->location.embark_pos_min.x = prev_position[0];
screen->location.embark_pos_max.x = prev_position[1];
screen->location.embark_pos_min.y = prev_position[2];
screen->location.embark_pos_max.y = prev_position[3];
update_embark_sidebar(screen);
}
public:
StablePosition()
:EmbarkTool(),
moved_position(false)
{
prev_position[0] = 0;
prev_position[1] = 0;
prev_position[2] = 3;
prev_position[3] = 3;
}
virtual std::string getId() { return "sticky"; }
virtual std::string getName() { return "Stable position"; }
virtual std::string getDesc() { return "Maintains the selected local area while navigating the world map"; }
virtual df::interface_key getToggleKey() { return df::interface_key::CUSTOM_P; }
virtual void before_render(start_sitest* screen) {
if (moved_position)
{
restore_position(screen);
moved_position = false;
}
}
virtual void before_feed(start_sitest* screen, ikey_set* input, bool &cancel) {
for (auto iter = input->begin(); iter != input->end(); iter++) for (auto iter = input->begin(); iter != input->end(); iter++)
{ {
df::interface_key key = *iter; df::interface_key key = *iter;
@ -299,46 +310,130 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
case df::interface_key::CURSOR_DOWNRIGHT_FAST: case df::interface_key::CURSOR_DOWNRIGHT_FAST:
is_motion = true; is_motion = true;
break; break;
default:
is_motion = false;
} }
if (is_motion && !sticky_moved) if (is_motion && !moved_position)
{ {
sticky_save(this); save_position(screen);
sticky_moved = true; moved_position = true;
} }
} }
};
};
class embark_tools_settings : public dfhack_viewscreen
{
public:
embark_tools_settings () { };
~embark_tools_settings () { };
void help () { };
std::string getFocusString () { return "embark-tools/options"; };
void render ()
{
parent->render();
int x, y;
auto dim = Screen::getWindowSize();
int width = 50,
height = 4 + 1 + tools.size(), // Padding + lower row
min_x = (dim.x - width) / 2,
max_x = (dim.x + width) / 2,
min_y = (dim.y - height) / 2,
max_y = min_y + height;
Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_DARKGREY), min_x, min_y, max_x, max_y);
Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), min_x + 1, min_y + 1, max_x - 1, max_y - 1);
x = min_x + 2;
y = max_y - 2;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT));
OutputString(COLOR_WHITE, x, y, "/");
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN));
OutputString(COLOR_WHITE, x, y, ": Done");
y = min_y + 2;
FOR_ITER_TOOLS(iter)
{
EmbarkTool* t = *iter;
x = min_x + 2;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(t->getToggleKey()));
OutputString(COLOR_WHITE, x, y, ": " + t->getName() +
(t->getEnabled() ? ": Enabled" : ": Disabled"));
y++;
}
};
void feed (std::set<df::interface_key> * input)
{
if (input->count(df::interface_key::SELECT) || input->count(df::interface_key::LEAVESCREEN))
{
Screen::dismiss(this);
return;
}
for (auto iter = input->begin(); iter != input->end(); iter++)
{
df::interface_key key = *iter;
FOR_ITER_TOOLS(iter)
{
EmbarkTool* t = *iter;
if (t->getToggleKey() == key)
{
t->toggleEnabled();
} }
}
}
};
};
if (tool_enabled("sand")) bool tool_exists (std::string tool_name)
{
FOR_ITER_TOOLS(iter)
{ {
sand_dirty = true; EmbarkTool* tool = *iter;
if (tool->getId() == tool_name)
return true;
} }
if (!prevent_default) return false;
INTERPOSE_NEXT(feed)(input); }
bool tool_enabled (std::string tool_name)
{
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
if (tool->getId() == tool_name)
return tool->getEnabled();
} }
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, render, ()) bool tool_enable (std::string tool_name, bool enable_state)
{
int n = 0;
FOR_ITER_TOOLS(iter)
{ {
if (tool_enabled("sticky") && sticky_moved) EmbarkTool* tool = *iter;
if (tool->getId() == tool_name || tool_name == "all")
{ {
sticky_apply(this); tool->setEnabled(enable_state);
sticky_moved = false; n++;
} }
}
return (bool)n;
}
INTERPOSE_NEXT(render)(); struct choose_start_site_hook : df::viewscreen_choose_start_sitest
{
typedef df::viewscreen_choose_start_sitest interpose_base;
void display_tool_status()
{
auto dim = Screen::getWindowSize(); auto dim = Screen::getWindowSize();
int x = 1, int x = 1,
y = dim.y - 5; y = dim.y - 5;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_S)); OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_S));
OutputString(COLOR_WHITE, x, y, ": Enabled: "); OutputString(COLOR_WHITE, x, y, ": Enabled: ");
std::list<std::string> parts; std::list<std::string> parts;
for (int i = 0; i < NUM_TOOLS; i++) FOR_ITER_TOOLS(iter)
{ {
if (embark_tools[i].enabled) EmbarkTool* tool = *iter;
if (tool->getEnabled())
{ {
parts.push_back(embark_tools[i].name); parts.push_back(tool->getName());
parts.push_back(", "); parts.push_back(", ");
} }
} }
@ -354,97 +449,77 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
{ {
OutputString(COLOR_LIGHTMAGENTA, x, y, "(none)"); OutputString(COLOR_LIGHTMAGENTA, x, y, "(none)");
} }
}
if (tool_enabled("anywhere")) void display_settings()
{
x = 20; y = dim.y - 2;
if (this->page >= 0 && this->page <= 4)
{ {
// Only display on five map pages, not on site finder or notes Screen::show(new embark_tools_settings);
OutputString(COLOR_WHITE, x, y, ": Embark!");
} }
inline bool is_valid_page()
{
return (page >= 0 && page <= 4);
} }
if (tool_enabled("sand"))
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{ {
if (sand_dirty) bool cancel = false;
FOR_ITER_TOOLS(iter)
{ {
sand_update(this); EmbarkTool* tool = *iter;
if (tool->getEnabled())
tool->before_feed(this, input, cancel);
} }
x = dim.x - 28; y = 13; if (cancel)
if (this->page == 0) return;
INTERPOSE_NEXT(feed)(input);
if (input->count(df::interface_key::CUSTOM_S) && is_valid_page())
display_settings();
FOR_ITER_TOOLS(iter)
{ {
OutputString(COLOR_YELLOW, x, y, sand_indicator); EmbarkTool* tool = *iter;
if (tool->getEnabled())
tool->after_feed(this, input);
} }
} }
} DEFINE_VMETHOD_INTERPOSE(void, render, ())
};
IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, render);
/*
* Tool management
*/
bool tool_exists (std::string tool_name)
{
for (int i = 0; i < NUM_TOOLS; i++)
{ {
if (embark_tools[i].id == tool_name) FOR_ITER_TOOLS(iter)
return true;
}
return false;
}
bool tool_enabled (std::string tool_name)
{
for (int i = 0; i < NUM_TOOLS; i++)
{ {
if (embark_tools[i].id == tool_name) EmbarkTool* tool = *iter;
return embark_tools[i].enabled; if (tool->getEnabled())
tool->before_render(this);
} }
return false; INTERPOSE_NEXT(render)();
} display_tool_status();
FOR_ITER_TOOLS(iter)
bool tool_enable (std::string tool_name, bool enable_state)
{
int n = 0;
for (int i = 0; i < NUM_TOOLS; i++)
{
if (embark_tools[i].id == tool_name || tool_name == "all")
{ {
embark_tools[i].enabled = enable_state; EmbarkTool* tool = *iter;
tool_update(tool_name); if (tool->getEnabled())
n++; tool->after_render(this);
}
} }
return (bool)n;
}
void tool_update (std::string tool_name)
{
// Called whenever a tool is enabled/disabled
if (tool_name == "sand")
{
sand_dirty = true;
} }
} };
IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, feed);
/* IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, render);
* Plugin management
*/
DFHACK_PLUGIN("embark-tools"); DFHACK_PLUGIN("embark-tools");
DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFHACK_PLUGIN_IS_ENABLED(is_enabled);
command_result embark_tools_cmd (color_ostream &out, std::vector <std::string> & parameters);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{ {
tools.push_back(new EmbarkAnywhere);
tools.push_back(new NanoEmbark);
tools.push_back(new SandIndicator);
tools.push_back(new StablePosition);
std::string help = ""; std::string help = "";
help += "embark-tools (enable/disable) tool [tool...]\n" help += "embark-tools (enable/disable) tool [tool...]\n"
"Tools:\n"; "Tools:\n";
for (int i = 0; i < NUM_TOOLS; i++) FOR_ITER_TOOLS(iter)
{ {
help += (" " + embark_tools[i].id + ": " + embark_tools[i].desc + "\n"); help += (" " + (*iter)->getId() + ": " + (*iter)->getDesc() + "\n");
} }
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"embark-tools", "embark-tools",
@ -504,10 +579,11 @@ command_result embark_tools_cmd (color_ostream &out, std::vector <std::string> &
if (is_enabled) if (is_enabled)
{ {
out << "Tool status:" << std::endl; out << "Tool status:" << std::endl;
for (int i = 0; i < NUM_TOOLS; i++) FOR_ITER_TOOLS(iter)
{ {
EmbarkTool t = embark_tools[i]; EmbarkTool* t = *iter;
out << t.name << " (" << t.id << "): " << (t.enabled ? "Enabled" : "Disabled") << std::endl; out << t->getName() << " (" << t->getId() << "): "
<< (t->getEnabled() ? "Enabled" : "Disabled") << std::endl;
} }
} }
else else

@ -113,8 +113,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
action->data.move.timer = 1; action->data.move.timer = 1;
break; break;
case unit_action_type::Attack: case unit_action_type::Attack:
// Attacks are executed when timer1 reaches zero, which will be
// on the following tick.
action->data.attack.timer1 = 1; action->data.attack.timer1 = 1;
action->data.attack.timer2 = 1; // Attack actions are completed, and new ones generated, when
// timer2 reaches zero. If set to 1 this never seems to occur.
// Setting to zero makes next tick generate a new attack action
// every time, thereby allowing target enemy/body part re-selection
// take place.
action->data.attack.timer2 = 0;
break; break;
case unit_action_type::Hold: case unit_action_type::Hold:
action->data.hold.timer = 1; action->data.hold.timer = 1;

@ -270,7 +270,7 @@ struct UnitInfo
enum altsort_mode { enum altsort_mode {
ALTSORT_NAME, ALTSORT_NAME,
ALTSORT_PROFESSION_OR_SQUAD, ALTSORT_PROFESSION_OR_SQUAD,
ALTSORT_HAPPINESS, ALTSORT_STRESS,
ALTSORT_ARRIVAL, ALTSORT_ARRIVAL,
ALTSORT_MAX ALTSORT_MAX
}; };
@ -311,12 +311,17 @@ bool sortBySquad (const UnitInfo *d1, const UnitInfo *d2)
return descending ? gt : !gt; return descending ? gt : !gt;
} }
bool sortByHappiness (const UnitInfo *d1, const UnitInfo *d2) bool sortByStress (const UnitInfo *d1, const UnitInfo *d2)
{ {
if (!d1->unit->status.current_soul)
return !descending;
if (!d2->unit->status.current_soul)
return descending;
if (descending) if (descending)
return (d1->unit->status.happiness > d2->unit->status.happiness); return (d1->unit->status.current_soul->personality.stress_level > d2->unit->status.current_soul->personality.stress_level);
else else
return (d1->unit->status.happiness < d2->unit->status.happiness); return (d1->unit->status.current_soul->personality.stress_level < d2->unit->status.current_soul->personality.stress_level);
} }
bool sortByArrival (const UnitInfo *d1, const UnitInfo *d2) bool sortByArrival (const UnitInfo *d1, const UnitInfo *d2)
@ -363,11 +368,11 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2)
else else
return d1->unit->status.labors[sort_labor] < d2->unit->status.labors[sort_labor]; return d1->unit->status.labors[sort_labor] < d2->unit->status.labors[sort_labor];
} }
return sortByName(d1, d2); return false;
} }
enum display_columns { enum display_columns {
DISP_COLUMN_HAPPINESS, DISP_COLUMN_STRESS,
DISP_COLUMN_NAME, DISP_COLUMN_NAME,
DISP_COLUMN_PROFESSION_OR_SQUAD, DISP_COLUMN_PROFESSION_OR_SQUAD,
DISP_COLUMN_LABORS, DISP_COLUMN_LABORS,
@ -509,13 +514,13 @@ void viewscreen_unitlaborsst::calcSize()
// min/max width of columns // min/max width of columns
int col_minwidth[DISP_COLUMN_MAX]; int col_minwidth[DISP_COLUMN_MAX];
int col_maxwidth[DISP_COLUMN_MAX]; int col_maxwidth[DISP_COLUMN_MAX];
col_minwidth[DISP_COLUMN_HAPPINESS] = 4; col_minwidth[DISP_COLUMN_STRESS] = 6;
col_maxwidth[DISP_COLUMN_HAPPINESS] = 4; col_maxwidth[DISP_COLUMN_STRESS] = 6;
col_minwidth[DISP_COLUMN_NAME] = 16; col_minwidth[DISP_COLUMN_NAME] = 16;
col_maxwidth[DISP_COLUMN_NAME] = 16; // adjusted in the loop below col_maxwidth[DISP_COLUMN_NAME] = 16; // adjusted in the loop below
col_minwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = 10; col_minwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = 10;
col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = 10; // adjusted in the loop below col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = 10; // adjusted in the loop below
col_minwidth[DISP_COLUMN_LABORS] = num_columns*3/5; // 60% col_minwidth[DISP_COLUMN_LABORS] = 1;
col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS; col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS;
// get max_name/max_prof from strings length // get max_name/max_prof from strings length
@ -778,10 +783,10 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
switch (click_header) switch (click_header)
{ {
case DISP_COLUMN_HAPPINESS: case DISP_COLUMN_STRESS:
if (enabler->mouse_lbut || enabler->mouse_rbut) if (enabler->mouse_lbut || enabler->mouse_rbut)
{ {
input_sort = ALTSORT_HAPPINESS; input_sort = ALTSORT_STRESS;
if (enabler->mouse_lbut) if (enabler->mouse_lbut)
events->insert(interface_key::SECONDSCROLL_PAGEUP); events->insert(interface_key::SECONDSCROLL_PAGEUP);
if (enabler->mouse_rbut) if (enabler->mouse_rbut)
@ -825,7 +830,7 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
switch (click_body) switch (click_body)
{ {
case DISP_COLUMN_HAPPINESS: case DISP_COLUMN_STRESS:
// do nothing // do nothing
break; break;
@ -917,7 +922,7 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
descending = events->count(interface_key::SECONDSCROLL_UP); descending = events->count(interface_key::SECONDSCROLL_UP);
sort_skill = columns[input_column].skill; sort_skill = columns[input_column].skill;
sort_labor = columns[input_column].labor; sort_labor = columns[input_column].labor;
std::sort(units.begin(), units.end(), sortBySkill); std::stable_sort(units.begin(), units.end(), sortBySkill);
} }
if (events->count(interface_key::SECONDSCROLL_PAGEUP) || events->count(interface_key::SECONDSCROLL_PAGEDOWN)) if (events->count(interface_key::SECONDSCROLL_PAGEUP) || events->count(interface_key::SECONDSCROLL_PAGEDOWN))
@ -926,16 +931,16 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
switch (input_sort) switch (input_sort)
{ {
case ALTSORT_NAME: case ALTSORT_NAME:
std::sort(units.begin(), units.end(), sortByName); std::stable_sort(units.begin(), units.end(), sortByName);
break; break;
case ALTSORT_PROFESSION_OR_SQUAD: case ALTSORT_PROFESSION_OR_SQUAD:
std::sort(units.begin(), units.end(), show_squad ? sortBySquad : sortByProfession); std::stable_sort(units.begin(), units.end(), show_squad ? sortBySquad : sortByProfession);
break; break;
case ALTSORT_HAPPINESS: case ALTSORT_STRESS:
std::sort(units.begin(), units.end(), sortByHappiness); std::stable_sort(units.begin(), units.end(), sortByStress);
break; break;
case ALTSORT_ARRIVAL: case ALTSORT_ARRIVAL:
std::sort(units.begin(), units.end(), sortByArrival); std::stable_sort(units.begin(), units.end(), sortByArrival);
break; break;
} }
} }
@ -947,9 +952,9 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
altsort = ALTSORT_PROFESSION_OR_SQUAD; altsort = ALTSORT_PROFESSION_OR_SQUAD;
break; break;
case ALTSORT_PROFESSION_OR_SQUAD: case ALTSORT_PROFESSION_OR_SQUAD:
altsort = ALTSORT_HAPPINESS; altsort = ALTSORT_STRESS;
break; break;
case ALTSORT_HAPPINESS: case ALTSORT_STRESS:
altsort = ALTSORT_ARRIVAL; altsort = ALTSORT_ARRIVAL;
break; break;
case ALTSORT_ARRIVAL: case ALTSORT_ARRIVAL:
@ -1000,7 +1005,7 @@ void viewscreen_unitlaborsst::render()
Screen::clear(); Screen::clear();
Screen::drawBorder(" Dwarf Manipulator - Manage Labors "); Screen::drawBorder(" Dwarf Manipulator - Manage Labors ");
Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_HAPPINESS], 2, "Hap."); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_STRESS], 2, "Stress");
Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_NAME], 2, "Name"); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_NAME], 2, "Name");
Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION_OR_SQUAD], 2, show_squad ? "Squad" : "Profession"); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION_OR_SQUAD], 2, show_squad ? "Squad" : "Profession");
@ -1043,23 +1048,22 @@ void viewscreen_unitlaborsst::render()
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
int8_t fg = 15, bg = 0; int8_t fg = 15, bg = 0;
int happy = cur->unit->status.happiness; int stress_lvl = unit->status.current_soul ? unit->status.current_soul->personality.stress_level : 0;
string happiness = stl_sprintf("%4i", happy); // cap at 6 digits
if (happy == 0) // miserable if (stress_lvl < -99999) stress_lvl = -99999;
if (stress_lvl > 999999) stress_lvl = 999999;
string stress = stl_sprintf("%6i", stress_lvl);
if (stress_lvl >= 500000)
fg = 13; // 5:1 fg = 13; // 5:1
else if (happy <= 25) // very unhappy else if (stress_lvl >= 250000)
fg = 12; // 4:1 fg = 12; // 4:1
else if (happy <= 50) // unhappy else if (stress_lvl >= 100000)
fg = 4; // 4:0
else if (happy < 75) // fine
fg = 14; // 6:1 fg = 14; // 6:1
else if (happy < 125) // quite content else if (stress_lvl >= 0)
fg = 6; // 6:0
else if (happy < 150) // happy
fg = 2; // 2:0 fg = 2; // 2:0
else // ecstatic else
fg = 10; // 2:1 fg = 10; // 2:1
Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_HAPPINESS], 4 + row, happiness); Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_STRESS], 4 + row, stress);
fg = 15; fg = 15;
if (row_offset == sel_row) if (row_offset == sel_row)
@ -1232,8 +1236,8 @@ void viewscreen_unitlaborsst::render()
case ALTSORT_PROFESSION_OR_SQUAD: case ALTSORT_PROFESSION_OR_SQUAD:
OutputString(15, x, y, show_squad ? "Squad" : "Profession"); OutputString(15, x, y, show_squad ? "Squad" : "Profession");
break; break;
case ALTSORT_HAPPINESS: case ALTSORT_STRESS:
OutputString(15, x, y, "Happiness"); OutputString(15, x, y, "Stress Level");
break; break;
case ALTSORT_ARRIVAL: case ALTSORT_ARRIVAL:
OutputString(15, x, y, "Arrival"); OutputString(15, x, y, "Arrival");

@ -673,6 +673,8 @@ void lightingEngineViewscreen::doOcupancyAndLights()
} }
//df::tile_occupancy o = b->OccupancyAt(gpos); //df::tile_occupancy o = b->OccupancyAt(gpos);
df::tiletype_shape shape = ENUM_ATTR(tiletype,shape,type); df::tiletype_shape shape = ENUM_ATTR(tiletype,shape,type);
bool is_wall=!ENUM_ATTR(tiletype_shape,passable_high,shape);
bool is_floor=!ENUM_ATTR(tiletype_shape,passable_low,shape);
df::tiletype_shape_basic basic_shape = ENUM_ATTR(tiletype_shape, basic_shape, shape); df::tiletype_shape_basic basic_shape = ENUM_ATTR(tiletype_shape, basic_shape, shape);
df::tiletype_material tileMat= ENUM_ATTR(tiletype,material,type); df::tiletype_material tileMat= ENUM_ATTR(tiletype,material,type);
@ -685,7 +687,7 @@ void lightingEngineViewscreen::doOcupancyAndLights()
{ {
curCell=rgbf(0,0,0); curCell=rgbf(0,0,0);
} }
else if(shape==df::tiletype_shape::WALL) else if(is_wall)
{ {
if(tileMat==df::tiletype_material::FROZEN_LIQUID) if(tileMat==df::tiletype_material::FROZEN_LIQUID)
applyMaterial(tile,matIce); applyMaterial(tile,matIce);
@ -700,8 +702,7 @@ void lightingEngineViewscreen::doOcupancyAndLights()
{ {
applyMaterial(tile,matLava,(float)d.bits.flow_size/7.0f,(float)d.bits.flow_size/7.0f); applyMaterial(tile,matLava,(float)d.bits.flow_size/7.0f,(float)d.bits.flow_size/7.0f);
} }
else if(shape==df::tiletype_shape::EMPTY || shape==df::tiletype_shape::RAMP_TOP else if(!is_floor)
|| shape==df::tiletype_shape::STAIR_DOWN || shape==df::tiletype_shape::STAIR_UPDOWN)
{ {
if(bDown) if(bDown)
{ {
@ -749,20 +750,6 @@ void lightingEngineViewscreen::doOcupancyAndLights()
} }
} }
//plants
for(int i=0;i<block->plants.size();i++)
{
df::plant* cPlant=block->plants[i];
if (cPlant->grow_counter <180000) //todo maybe smaller light/oclusion?
continue;
df::coord2d pos=cPlant->pos;
pos=worldToViewportCoord(pos,vp,window2d);
int tile=getIndex(pos.x,pos.y);
if(isInRect(pos,vp))
{
applyMaterial(tile,419,cPlant->material);
}
}
//blood and other goo //blood and other goo
for(int i=0;i<block->block_events.size();i++) for(int i=0;i<block->block_events.size();i++)
{ {

@ -595,6 +595,9 @@ sub get_field_align {
$al = get_field_align($tg); $al = get_field_align($tg);
} elsif ($meta eq 'bytes') { } elsif ($meta eq 'bytes') {
$al = $field->getAttribute('alignment') || 1; $al = $field->getAttribute('alignment') || 1;
} elsif ($meta eq 'primitive') {
my $subtype = $field->getAttribute('ld:subtype');
if ($subtype eq 'stl-fstream' and $os eq 'windows') { $al = 8; }
} }
return $al; return $al;
@ -735,6 +738,14 @@ sub sizeof {
print "sizeof stl-string on $os\n"; print "sizeof stl-string on $os\n";
} }
print "sizeof stl-string\n"; print "sizeof stl-string\n";
} elsif ($subtype eq 'stl-fstream') { if ($os eq 'linux') {
return 284;
} elsif ($os eq 'windows') {
return 184;
} else {
print "sizeof stl-fstream on $os\n";
}
print "sizeof stl-fstream\n";
} else { } else {
print "sizeof primitive $subtype\n"; print "sizeof primitive $subtype\n";
} }
@ -1011,6 +1022,7 @@ sub render_item_primitive {
my $subtype = $item->getAttribute('ld:subtype'); my $subtype = $item->getAttribute('ld:subtype');
if ($subtype eq 'stl-string') { if ($subtype eq 'stl-string') {
push @lines_rb, "stl_string"; push @lines_rb, "stl_string";
} elsif ($subtype eq 'stl-fstream') {
} else { } else {
print "no render primitive $subtype\n"; print "no render primitive $subtype\n";
} }

@ -543,6 +543,18 @@ static VALUE rb_dfprint_str(VALUE self, VALUE s)
return Qnil; return Qnil;
} }
static VALUE rb_dfprint_color(VALUE self, VALUE c, VALUE s)
{
if (r_console) {
color_value old_col = r_console->color();
r_console->color(color_value(rb_num2ulong(c)));
r_console->print("%s", rb_string_value_ptr(&s));
r_console->color(old_col);
} else
console_proxy->print("%s", rb_string_value_ptr(&s));
return Qnil;
}
static VALUE rb_dfprint_err(VALUE self, VALUE s) static VALUE rb_dfprint_err(VALUE self, VALUE s)
{ {
printerr("%s", rb_string_value_ptr(&s)); printerr("%s", rb_string_value_ptr(&s));
@ -1072,6 +1084,7 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1); rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1); rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1);
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1); rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1);
rb_define_singleton_method(rb_cDFHack, "print_color", RUBY_METHOD_FUNC(rb_dfprint_color), 2);
rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1); rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1);
rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1); rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);
rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1); rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1);

@ -179,7 +179,7 @@ module DFHack
if not onlylastpart if not onlylastpart
out << name.first_name if name.first_name != '' out << name.first_name if name.first_name != ''
if name.nickname != '' if name.nickname != ''
case respond_to?(:d_init) && d_init.nickname_dwarf case respond_to?(:d_init) && d_init.nickname[gametype]
when :REPLACE_ALL; return "`#{name.nickname}'" when :REPLACE_ALL; return "`#{name.nickname}'"
when :REPLACE_FIRST; out.pop when :REPLACE_FIRST; out.pop
end end

@ -1 +1 @@
Subproject commit 53159a11598bab7c6728b462e2ff9d34c59f1045 Subproject commit e579cac9fef8f89f382d8b9e832abe2392379897

@ -631,7 +631,11 @@ command_result df_strangemood (color_ostream &out, vector <string> & parameters)
// If no mood type was specified, pick one randomly // If no mood type was specified, pick one randomly
if (type == mood_type::None) if (type == mood_type::None)
{ {
if (rng.df_trandom(100) > unit->status.happiness) if (soul && (
(soul->personality.stress_level >= 500000) ||
(soul->personality.stress_level >= 250000 && !rng.df_trandom(2)) ||
(soul->personality.stress_level >= 100000 && !rng.df_trandom(10))
))
{ {
switch (rng.df_trandom(2)) switch (rng.df_trandom(2))
{ {
@ -691,7 +695,6 @@ command_result df_strangemood (color_ostream &out, vector <string> & parameters)
unit->relations.mood_copy = unit->mood; unit->relations.mood_copy = unit->mood;
Gui::showAutoAnnouncement(announcement_type::STRANGE_MOOD, unit->pos, msg, color, bright); Gui::showAutoAnnouncement(announcement_type::STRANGE_MOOD, unit->pos, msg, color, bright);
unit->status.happiness = 100;
// TODO: make sure unit drops any wrestle items // TODO: make sure unit drops any wrestle items
unit->job.mood_timeout = 50000; unit->job.mood_timeout = 50000;
unit->flags1.bits.has_mood = true; unit->flags1.bits.has_mood = true;
@ -1144,7 +1147,7 @@ command_result df_strangemood (color_ostream &out, vector <string> & parameters)
{ {
if ((job->job_type == job_type::StrangeMoodBrooding) && (rng.df_trandom(2))) if ((job->job_type == job_type::StrangeMoodBrooding) && (rng.df_trandom(2)))
{ {
switch (rng.df_trandom(3)) switch (rng.df_trandom(2))
{ {
case 0: case 0:
job->job_items.push_back(item = new df::job_item()); job->job_items.push_back(item = new df::job_item());
@ -1159,10 +1162,6 @@ command_result df_strangemood (color_ostream &out, vector <string> & parameters)
item->flags2.bits.body_part = true; item->flags2.bits.body_part = true;
item->quantity = 1; item->quantity = 1;
break; break;
case 2:
// in older versions, they would request additional skulls
// in 0.34.11, the request becomes "nothing"
break;
} }
} }
else else

@ -0,0 +1,301 @@
/*
* Trackstop plugin.
* Shows track stop friction and direction in its 'q' menu.
*/
#include "uicommon.h"
#include "LuaTools.h"
#include "df/building_rollersst.h"
#include "df/building_trapst.h"
#include "df/viewscreen_dwarfmodest.h"
#include "modules/Gui.h"
using namespace DFHack;
using namespace std;
using df::global::world;
using df::global::ui;
using df::building_rollersst;
using df::building_trapst;
using df::enums::trap_type::trap_type;
using df::enums::screw_pump_direction::screw_pump_direction;
DFHACK_PLUGIN("trackstop");
#define AUTOENABLE false
DFHACK_PLUGIN_IS_ENABLED(enabled);
/*
* Interface hooks
*/
struct trackstop_hook : public df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
enum Friction {
Lowest = 10,
Low = 50,
Medium = 500,
High = 10000,
Highest = 50000
};
building_trapst *get_selected_trackstop() {
if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) || ui->main.mode != ui_sidebar_mode::QueryBuilding) {
return nullptr;
}
building_trapst *ts = virtual_cast<building_trapst>(world->selected_building);
if (ts && ts->trap_type == df::trap_type::TrackStop && ts->construction_stage) {
return ts;
}
return nullptr;
}
bool handleInput(set<df::interface_key> *input) {
building_trapst *ts = get_selected_trackstop();
if (!ts) {
return false;
}
if (input->count(interface_key::BUILDING_TRACK_STOP_DUMP)) {
// Change track stop dump direction.
// There might be a more elegant way to do this.
if (!ts->use_dump) {
// No -> North
ts->use_dump = 1;
ts->dump_x_shift = 0;
ts->dump_y_shift = -1;
} else if (ts->dump_x_shift == 0 && ts->dump_y_shift == -1) {
// North -> South
ts->dump_x_shift = 0;
ts->dump_y_shift = 1;
} else if (ts->dump_x_shift == 0 && ts->dump_y_shift == 1) {
// South -> East
ts->dump_x_shift = 1;
ts->dump_y_shift = 0;
} else if (ts->dump_x_shift == 1 && ts->dump_y_shift == 0) {
// East -> West
ts->dump_x_shift = -1;
ts->dump_y_shift = 0;
} else {
// West (or Elsewhere) -> No
ts->use_dump = 0;
ts->dump_x_shift = 0;
ts->dump_y_shift = 0;
}
return true;
} else if (input->count(interface_key::BUILDING_TRACK_STOP_FRICTION_UP)) {
ts->friction = (
(ts->friction < Friction::Lowest)? Friction::Lowest:
(ts->friction < Friction::Low)? Friction::Low:
(ts->friction < Friction::Medium)? Friction::Medium:
(ts->friction < Friction::High)? Friction::High:
(ts->friction < Friction::Highest)? Friction::Highest:
ts->friction
);
return true;
} else if (input->count(interface_key::BUILDING_TRACK_STOP_FRICTION_DOWN)) {
ts->friction = (
(ts->friction > Friction::Highest)? Friction::Highest:
(ts->friction > Friction::High)? Friction::High:
(ts->friction > Friction::Medium)? Friction::Medium:
(ts->friction > Friction::Low)? Friction::Low:
(ts->friction > Friction::Lowest)? Friction::Lowest:
ts->friction
);
return true;
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input)) {
if (!handleInput(input)) {
INTERPOSE_NEXT(feed)(input);
}
}
DEFINE_VMETHOD_INTERPOSE(void, render, ()) {
INTERPOSE_NEXT(render)();
building_trapst *ts = get_selected_trackstop();
if (ts) {
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
int y = dims.y1 + 1;
OutputString(COLOR_WHITE, x, y, "Track Stop", true, left_margin);
y += 3;
OutputString(COLOR_WHITE, x, y, "Friction: ", false);
OutputString(COLOR_WHITE, x, y, (
(ts->friction <= Friction::Lowest)? "Lowest":
(ts->friction <= Friction::Low)? "Low":
(ts->friction <= Friction::Medium)? "Medium":
(ts->friction <= Friction::High)? "High":
"Highest"
), true, left_margin);
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_TRACK_STOP_FRICTION_DOWN));
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_TRACK_STOP_FRICTION_UP));
OutputString(COLOR_WHITE, x, y, ": Change Friction", true, left_margin);
y += 1;
OutputString(COLOR_WHITE, x, y, "Dump on arrival: ", false);
OutputString(COLOR_WHITE, x, y, (
(!ts->use_dump)? "No":
(ts->dump_x_shift == 0 && ts->dump_y_shift == -1)? "North":
(ts->dump_x_shift == 0 && ts->dump_y_shift == 1)? "South":
(ts->dump_x_shift == 1 && ts->dump_y_shift == 0)? "East":
(ts->dump_x_shift == -1 && ts->dump_y_shift == 0)? "West":
"Elsewhere"
), true, left_margin);
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_TRACK_STOP_DUMP));
OutputString(COLOR_WHITE, x, y, ": Activate/change direction", true, left_margin);
}
}
};
struct roller_hook : public df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
enum Speed {
Lowest = 10000,
Low = 20000,
Medium = 30000,
High = 40000,
Highest = 50000
};
building_rollersst *get_selected_roller() {
if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) || ui->main.mode != ui_sidebar_mode::QueryBuilding) {
return nullptr;
}
building_rollersst *roller = virtual_cast<building_rollersst>(world->selected_building);
if (roller && roller->construction_stage) {
return roller;
}
return nullptr;
}
bool handleInput(set<df::interface_key> *input) {
building_rollersst *roller = get_selected_roller();
if (!roller) {
return false;
}
if (input->count(interface_key::BUILDING_ORIENT_NONE)) {
// Flip roller orientation.
// Long rollers can only be oriented along their length.
// Todo: Only add 1 to 1x1 rollers: x ^= ((x&1)<<1)|1
// Todo: This could have been elegant without all the casting,
// but as an enum it might be better off listing each case.
roller->direction = (df::enums::screw_pump_direction::screw_pump_direction)(((int8_t)roller->direction) ^ 2);
return true;
} else if (input->count(interface_key::BUILDING_ROLLERS_SPEED_UP)) {
if (roller->speed < Speed::Highest) {
roller->speed += Speed::Lowest;
}
return true;
} else if (input->count(interface_key::BUILDING_ROLLERS_SPEED_DOWN)) {
if (roller->speed > Speed::Lowest) {
roller->speed -= Speed::Lowest;
}
return true;
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input)) {
if (!handleInput(input)) {
INTERPOSE_NEXT(feed)(input);
}
}
DEFINE_VMETHOD_INTERPOSE(void, render, ()) {
INTERPOSE_NEXT(render)();
building_rollersst *roller = get_selected_roller();
if (roller) {
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
int y = dims.y1 + 6;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_ORIENT_NONE));
OutputString(COLOR_WHITE, x, y, ": Rolls ", false);
OutputString(COLOR_WHITE, x, y, (
(roller->direction == df::screw_pump_direction::FromNorth)? "Southward":
(roller->direction == df::screw_pump_direction::FromEast)? "Westward":
(roller->direction == df::screw_pump_direction::FromSouth)? "Northward":
(roller->direction == df::screw_pump_direction::FromWest)? "Eastward":
""
), true, left_margin);
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_ROLLERS_SPEED_DOWN));
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::BUILDING_ROLLERS_SPEED_UP));
OutputString(COLOR_WHITE, x, y, ": ");
OutputString(COLOR_WHITE, x, y, (
(roller->speed <= Speed::Lowest)? "Lowest":
(roller->speed <= Speed::Low)? "Low":
(roller->speed <= Speed::Medium)? "Medium":
(roller->speed <= Speed::High)? "High":
"Highest"
));
OutputString(COLOR_WHITE, x, y, " Speed", true, left_margin);
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(trackstop_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(trackstop_hook, render);
IMPLEMENT_VMETHOD_INTERPOSE(roller_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(roller_hook, render);
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) {
// Accept the "enable trackstop" / "disable trackstop" commands.
if (enable != enabled) {
// Check for global variables that, if missing, result in total failure.
// Missing enabler and ui_menu_width also produce visible effects, but not nearly as severe.
// This could be moved to the plugin_init step, but that's louder for no real benefit.
if (!(gps && ui && world)) {
out.printerr("trackstop: Missing required global variables.\n");
return CR_FAILURE;
}
if (!INTERPOSE_HOOK(trackstop_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(trackstop_hook, render).apply(enable) ||
!INTERPOSE_HOOK(roller_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(roller_hook, render).apply(enable)) {
out.printerr("Could not %s trackstop hooks!\n", enable? "insert": "remove");
return CR_FAILURE;
}
enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
return plugin_enable(out, AUTOENABLE);
}
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
return plugin_enable(out, false);
}

@ -0,0 +1,6 @@
PROJECT (tweak)
SET(PROJECT_SRCS
tweak.cpp
)
DFHACK_PLUGIN(tweak ${PROJECT_SRCS})

@ -12,11 +12,13 @@
#include "modules/Items.h" #include "modules/Items.h"
#include "modules/Job.h" #include "modules/Job.h"
#include "modules/Materials.h" #include "modules/Materials.h"
#include "modules/MapCache.h"
#include "MiscUtils.h" #include "MiscUtils.h"
#include "DataDefs.h" #include "DataDefs.h"
#include <VTableInterpose.h> #include <VTableInterpose.h>
#include "../uicommon.h"
#include "df/ui.h" #include "df/ui.h"
#include "df/world.h" #include "df/world.h"
#include "df/squad.h" #include "df/squad.h"
@ -68,6 +70,20 @@
//#include "df/building_hivest.h" //#include "df/building_hivest.h"
#include <stdlib.h> #include <stdlib.h>
#include <unordered_map>
#include "tweaks/adamantine-cloth-wear.h"
#include "tweaks/advmode-contained.h"
#include "tweaks/civ-agreement-ui.h"
#include "tweaks/craft-age-wear.h"
#include "tweaks/farm-plot-select.h"
#include "tweaks/fast-heat.h"
#include "tweaks/fast-trade.h"
#include "tweaks/import-priority-category.h"
#include "tweaks/manager-quantity.h"
#include "tweaks/military-assign.h"
#include "tweaks/nestbox-color.h"
#include "tweaks/stable-cursor.h"
using std::set; using std::set;
using std::vector; using std::vector;
@ -83,12 +99,15 @@ using df::global::ui_menu_width;
using df::global::ui_area_map_width; using df::global::ui_area_map_width;
using namespace DFHack::Gui; using namespace DFHack::Gui;
using Screen::Pen;
static command_result tweak(color_ostream &out, vector <string> & parameters); static command_result tweak(color_ostream &out, vector <string> & parameters);
static std::multimap<std::string, VMethodInterposeLinkBase> tweak_hooks;
DFHACK_PLUGIN("tweak"); DFHACK_PLUGIN("tweak");
#define TWEAK_HOOK(tweak, cls, func) tweak_hooks.insert(std::pair<std::string, VMethodInterposeLinkBase>\
(tweak, INTERPOSE_HOOK(cls, func)))
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{ {
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
@ -115,42 +134,81 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" tweak stable-cursor [disable]\n" " tweak stable-cursor [disable]\n"
" Keeps exact position of dwarfmode cursor during exits to main menu.\n" " Keeps exact position of dwarfmode cursor during exits to main menu.\n"
" E.g. allows switching between t/q/k/d without losing position.\n" " E.g. allows switching between t/q/k/d without losing position.\n"
" tweak patrol-duty [disable]\n"
" Causes 'Train' orders to no longer be considered 'patrol duty' so\n"
" soldiers will stop getting unhappy thoughts. Does NOT fix the problem\n"
" when soldiers go off-duty (i.e. civilian).\n"
" tweak confirm-embark [disable]\n"
" Asks for confirmation on the embark setup screen before embarking\n"
" tweak stable-temp [disable]\n"
" Fixes performance bug 6012 by squashing jitter in temperature updates.\n"
" tweak fast-heat <max-ticks>\n"
" Further improves temperature updates by ensuring that 1 degree of\n"
" item temperature is crossed in no more than specified number of frames\n"
" when updating from the environment temperature. Use 0 to disable.\n"
/*" tweak fix-dimensions [disable]\n" /*" tweak fix-dimensions [disable]\n"
" Fixes subtracting small amount of thread/cloth/liquid from a stack\n" " Fixes subtracting small amount of thread/cloth/liquid from a stack\n"
" by splitting the stack and subtracting from the remaining single item.\n"*/ " by splitting the stack and subtracting from the remaining single item.\n"*/
" tweak adamantine-cloth-wear [disable]\n"
" Stops adamantine clothing from wearing out while being worn (bug 6481).\n"
" tweak advmode-contained [disable]\n" " tweak advmode-contained [disable]\n"
" Fixes custom reactions with container inputs in advmode. The issue is\n" " Fixes custom reactions with container inputs in advmode. The issue is\n"
" that the screen tries to force you to select the contents separately\n" " that the screen tries to force you to select the contents separately\n"
" from the container. This forcefully skips child reagents.\n" " from the container. This forcefully skips child reagents.\n"
" tweak civ-view-agreement\n"
" Fixes overlapping text on the \"view agreement\" screen\n"
" tweak craft-age-wear [disable]\n"
" Makes cloth and leather items wear out at the correct rate (bug 6003).\n"
" tweak farm-plot-select [disable]\n"
" Adds \"Select all\" and \"Deselect all\" options to farm plot menus\n"
" tweak fast-heat <max-ticks>\n"
" Further improves temperature updates by ensuring that 1 degree of\n"
" item temperature is crossed in no more than specified number of frames\n"
" when updating from the environment temperature. Use 0 to disable.\n"
" tweak fast-trade [disable]\n" " tweak fast-trade [disable]\n"
" Makes Shift-Enter in the Move Goods to Depot and Trade screens select\n" " Makes Shift-Enter in the Move Goods to Depot and Trade screens select\n"
" the current item (fully, in case of a stack), and scroll down one line.\n" " the current item (fully, in case of a stack), and scroll down one line.\n"
" tweak import-priority-category [disable]\n"
" When meeting with a liaison, makes Shift+Left/Right arrow adjust\n"
" the priority of an entire category of imports.\n"
" tweak manager-quantity [disable]\n"
" Removes the limit of 30 jobs per manager order\n"
" tweak nestbox-color [disable]\n"
" Makes built nestboxes use the color of their material\n"
" tweak military-color-assigned [disable]\n"
" Color squad candidates already assigned to other squads in brown/green\n"
" to make them stand out more in the list.\n"
" tweak military-stable-assign [disable]\n" " tweak military-stable-assign [disable]\n"
" Preserve list order and cursor position when assigning to squad,\n" " Preserve list order and cursor position when assigning to squad,\n"
" i.e. stop the rightmost list of the Positions page of the military\n" " i.e. stop the rightmost list of the Positions page of the military\n"
" screen from constantly jumping to the top.\n" " screen from constantly jumping to the top.\n"
" tweak military-color-assigned [disable]\n"
" Color squad candidates already assigned to other squads in brown/green\n"
" to make them stand out more in the list.\n"
// " tweak military-training [disable]\n" // " tweak military-training [disable]\n"
// " Speed up melee squad training, removing inverse dependency on unit count.\n" // " Speed up melee squad training, removing inverse dependency on unit count.\n"
" tweak craft-age-wear [disable]\n"
" Makes cloth and leather items wear out at the correct rate (bug 6003).\n"
" tweak adamantine-cloth-wear [disable]\n"
" Stops adamantine clothing from wearing out while being worn (bug 6481).\n"
)); ));
TWEAK_HOOK("adamantine-cloth-wear", adamantine_cloth_wear_armor_hook, incWearTimer);
TWEAK_HOOK("adamantine-cloth-wear", adamantine_cloth_wear_helm_hook, incWearTimer);
TWEAK_HOOK("adamantine-cloth-wear", adamantine_cloth_wear_gloves_hook, incWearTimer);
TWEAK_HOOK("adamantine-cloth-wear", adamantine_cloth_wear_shoes_hook, incWearTimer);
TWEAK_HOOK("adamantine-cloth-wear", adamantine_cloth_wear_pants_hook, incWearTimer);
TWEAK_HOOK("advmode-contained", advmode_contained_hook, feed);
TWEAK_HOOK("civ-view-agreement", civ_agreement_view_hook, render);
TWEAK_HOOK("craft-age-wear", craft_age_wear_hook, ageItem);
TWEAK_HOOK("farm-plot-select", farm_select_hook, feed);
TWEAK_HOOK("farm-plot-select", farm_select_hook, render);
TWEAK_HOOK("fast-heat", fast_heat_hook, updateTempFromMap);
TWEAK_HOOK("fast-heat", fast_heat_hook, updateTemperature);
TWEAK_HOOK("fast-heat", fast_heat_hook, adjustTemperature);
TWEAK_HOOK("fast-trade", fast_trade_assign_hook, feed);
TWEAK_HOOK("fast-trade", fast_trade_select_hook, feed);
TWEAK_HOOK("import-priority-category", takerequest_hook, feed);
TWEAK_HOOK("import-priority-category", takerequest_hook, render);
TWEAK_HOOK("manager-quantity", manager_quantity_hook, feed);
TWEAK_HOOK("military-color-assigned", military_assign_hook, render);
TWEAK_HOOK("military-stable-assign", military_assign_hook, feed);
TWEAK_HOOK("nestbox-color", nestbox_color_hook, drawBuilding);
TWEAK_HOOK("stable-cursor", stable_cursor_hook, feed);
return CR_OK; return CR_OK;
} }
@ -216,271 +274,6 @@ command_result fix_clothing_ownership(color_ostream &out, df::unit* unit)
return CR_OK; return CR_OK;
} }
/*
* Save or restore cursor position on change to/from main dwarfmode menu.
*/
static df::coord last_view, last_cursor;
struct stable_cursor_hook : df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
bool check_default()
{
switch (ui->main.mode) {
case ui_sidebar_mode::Default:
return true;
case ui_sidebar_mode::Build:
return ui_build_selector &&
(ui_build_selector->building_type < 0 ||
ui_build_selector->stage < 1);
default:
return false;
}
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
bool was_default = check_default();
df::coord view = Gui::getViewportPos();
df::coord cursor = Gui::getCursorPos();
INTERPOSE_NEXT(feed)(input);
bool is_default = check_default();
df::coord cur_cursor = Gui::getCursorPos();
if (is_default && !was_default)
{
last_view = view; last_cursor = cursor;
}
else if (!is_default && was_default &&
Gui::getViewportPos() == last_view &&
last_cursor.isValid() && cur_cursor.isValid())
{
Gui::setCursorCoords(last_cursor.x, last_cursor.y, last_cursor.z);
// Force update of ui state
set<df::interface_key> tmp;
if (last_cursor.z < 2)
tmp.insert(interface_key::CURSOR_UP_Z);
else
tmp.insert(interface_key::CURSOR_DOWN_Z);
INTERPOSE_NEXT(feed)(&tmp);
tmp.clear();
if (last_cursor.z < 2)
tmp.insert(interface_key::CURSOR_DOWN_Z);
else
tmp.insert(interface_key::CURSOR_UP_Z);
INTERPOSE_NEXT(feed)(&tmp);
}
else if (!is_default && cur_cursor.isValid())
{
last_cursor = df::coord();
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(stable_cursor_hook, feed);
struct patrol_duty_hook : df::squad_order_trainst
{
typedef df::squad_order_trainst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, isPatrol, ())
{
return false;
}
};
IMPLEMENT_VMETHOD_INTERPOSE(patrol_duty_hook, isPatrol);
enum confirm_embark_states
{
ECS_INACTIVE = 0,
ECS_CONFIRM,
ECS_ACCEPTED
};
static confirm_embark_states confirm_embark_state = ECS_INACTIVE;
struct confirm_embark_hook : df::viewscreen_setupdwarfgamest
{
typedef df::viewscreen_setupdwarfgamest interpose_base;
void OutputString(int8_t fg, int &x, int y, std::string text)
{
Screen::paintString(Screen::Pen(' ', fg, COLOR_BLACK), x, y, text);
x += text.length();
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
bool intercept = false;
if (this->show_play_now == 0)
{
if (confirm_embark_state == ECS_INACTIVE)
{
if (input->count(df::interface_key::SETUP_EMBARK))
{
confirm_embark_state = ECS_CONFIRM;
intercept = true;
}
}
else if (confirm_embark_state == ECS_CONFIRM)
{
intercept = true;
if (input->count(df::interface_key::MENU_CONFIRM))
confirm_embark_state = ECS_ACCEPTED;
else if (input->size())
confirm_embark_state = ECS_INACTIVE;
}
}
if (!intercept)
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key))
{
if (confirm_embark_state == ECS_CONFIRM)
{
if (key == df::interface_key::OPTIONS)
return true;
}
return INTERPOSE_NEXT(key_conflict)(key);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
auto dim = Screen::getWindowSize();
int x = 0, y = 0;
if (confirm_embark_state != ECS_INACTIVE)
{
Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), 0, 0, dim.x - 1, dim.y - 1);
}
if (confirm_embark_state == ECS_CONFIRM)
{
x = 2, y = 2;
OutputString(COLOR_WHITE, x, y, "Really embark? (");
OutputString(COLOR_LIGHTGREEN, x, y, Screen::getKeyDisplay(df::interface_key::MENU_CONFIRM));
OutputString(COLOR_WHITE, x, y, " = yes, other = no)");
x = 2, y = 4;
int32_t points = this->points_remaining;
OutputString(COLOR_WHITE, x, y, "Points left: ");
OutputString((points ? COLOR_YELLOW : COLOR_LIGHTGREEN), x, y, std::to_string((unsigned long long/*won't compile on windows otherwise*/)points));
x = dim.x - 10, y = dim.y - 1;
OutputString(COLOR_WHITE, x, y, "DFHack");
}
else if (confirm_embark_state == ECS_ACCEPTED)
{
std::set<df::interface_key> input;
input.insert(df::interface_key::SETUP_EMBARK);
this->feed(&input);
confirm_embark_state = ECS_INACTIVE;
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(confirm_embark_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(confirm_embark_hook, key_conflict);
IMPLEMENT_VMETHOD_INTERPOSE(confirm_embark_hook, render);
struct stable_temp_hook : df::item_actual {
typedef df::item_actual interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t rate_mult))
{
if (temperature.whole != temp)
{
// Bug 6012 is caused by fixed-point precision mismatch jitter
// when an item is being pushed by two sources at N and N+1.
// This check suppresses it altogether.
if (temp == temperature.whole+1 ||
(temp == temperature.whole-1 && temperature.fraction == 0))
temp = temperature.whole;
// When SPEC_HEAT is NONE, the original function seems to not
// change the temperature, yet return true, which is silly.
else if (getSpecHeat() == 60001)
temp = temperature.whole;
}
return INTERPOSE_NEXT(adjustTemperature)(temp, rate_mult);
}
DEFINE_VMETHOD_INTERPOSE(bool, updateContaminants, ())
{
if (contaminants)
{
// Force 1-degree difference in contaminant temperature to 0
for (size_t i = 0; i < contaminants->size(); i++)
{
auto obj = (*contaminants)[i];
if (abs(obj->temperature.whole - temperature.whole) == 1)
{
obj->temperature.whole = temperature.whole;
obj->temperature.fraction = temperature.fraction;
}
}
}
return INTERPOSE_NEXT(updateContaminants)();
}
};
IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, adjustTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, updateContaminants);
static int map_temp_mult = -1;
static int max_heat_ticks = 0;
struct fast_heat_hook : df::item_actual {
typedef df::item_actual interpose_base;
DEFINE_VMETHOD_INTERPOSE(
bool, updateTempFromMap,
(bool local, bool contained, bool adjust, int32_t rate_mult)
) {
int cmult = map_temp_mult;
map_temp_mult = rate_mult;
bool rv = INTERPOSE_NEXT(updateTempFromMap)(local, contained, adjust, rate_mult);
map_temp_mult = cmult;
return rv;
}
DEFINE_VMETHOD_INTERPOSE(
bool, updateTemperature,
(uint16_t temp, bool local, bool contained, bool adjust, int32_t rate_mult)
) {
// Some items take ages to cross the last degree, so speed them up
if (map_temp_mult > 0 && temp != temperature.whole && max_heat_ticks > 0)
{
int spec = getSpecHeat();
if (spec != 60001)
rate_mult = std::max(map_temp_mult, spec/max_heat_ticks/abs(temp - temperature.whole));
}
return INTERPOSE_NEXT(updateTemperature)(temp, local, contained, adjust, rate_mult);
}
DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t rate_mult))
{
if (map_temp_mult > 0)
rate_mult = map_temp_mult;
return INTERPOSE_NEXT(adjustTemperature)(temp, rate_mult);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, updateTempFromMap);
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, updateTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, adjustTemperature);
static void correct_dimension(df::item_actual *self, int32_t &delta, int32_t dim) static void correct_dimension(df::item_actual *self, int32_t &delta, int32_t dim)
{ {
// Zero dimension or remainder? // Zero dimension or remainder?
@ -570,201 +363,7 @@ struct dimension_cloth_hook : df::item_clothst {
IMPLEMENT_VMETHOD_INTERPOSE(dimension_cloth_hook, subtractDimension); IMPLEMENT_VMETHOD_INTERPOSE(dimension_cloth_hook, subtractDimension);
struct advmode_contained_hook : df::viewscreen_layer_unit_actionst { /*
typedef df::viewscreen_layer_unit_actionst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
auto old_reaction = cur_reaction;
auto old_reagent = reagent;
INTERPOSE_NEXT(feed)(input);
if (cur_reaction && (cur_reaction != old_reaction || reagent != old_reagent))
{
old_reagent = reagent;
// Skip reagents already contained by others
while (reagent < (int)cur_reaction->reagents.size()-1)
{
if (!cur_reaction->reagents[reagent]->flags.bits.IN_CONTAINER)
break;
reagent++;
}
if (old_reagent != reagent)
{
// Reproduces a tiny part of the orginal screen code
choice_items.clear();
auto preagent = cur_reaction->reagents[reagent];
reagent_amnt_left = preagent->quantity;
for (int i = held_items.size()-1; i >= 0; i--)
{
if (!preagent->matchesRoot(held_items[i], cur_reaction->index))
continue;
if (linear_index(sel_items, held_items[i]) >= 0)
continue;
choice_items.push_back(held_items[i]);
}
layer_objects[6]->setListLength(choice_items.size());
if (!choice_items.empty())
{
layer_objects[4]->active = layer_objects[5]->active = false;
layer_objects[6]->active = true;
}
else if (layer_objects[6]->active)
{
layer_objects[6]->active = false;
layer_objects[5]->active = true;
}
}
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(advmode_contained_hook, feed);
struct fast_trade_assign_hook : df::viewscreen_layer_assigntradest {
typedef df::viewscreen_layer_assigntradest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (layer_objects[1]->active && input->count(interface_key::SELECT_ALL))
{
set<df::interface_key> tmp; tmp.insert(interface_key::SELECT);
INTERPOSE_NEXT(feed)(&tmp);
tmp.clear(); tmp.insert(interface_key::STANDARDSCROLL_DOWN);
INTERPOSE_NEXT(feed)(&tmp);
}
else
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(fast_trade_assign_hook, feed);
struct fast_trade_select_hook : df::viewscreen_tradegoodsst {
typedef df::viewscreen_tradegoodsst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (!(is_unloading || !has_traders || in_edit_count)
&& input->count(interface_key::SELECT_ALL))
{
set<df::interface_key> tmp; tmp.insert(interface_key::SELECT);
INTERPOSE_NEXT(feed)(&tmp);
if (in_edit_count)
INTERPOSE_NEXT(feed)(&tmp);
tmp.clear(); tmp.insert(interface_key::STANDARDSCROLL_DOWN);
INTERPOSE_NEXT(feed)(&tmp);
}
else
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(fast_trade_select_hook, feed);
struct military_assign_hook : df::viewscreen_layer_militaryst {
typedef df::viewscreen_layer_militaryst interpose_base;
inline bool inPositionsMode() {
return page == Positions && !(in_create_squad || in_new_squad);
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (inPositionsMode() && !layer_objects[0]->active)
{
auto pos_list = layer_objects[1];
auto plist = layer_objects[2];
auto &cand = positions.candidates;
// Save the candidate list and cursors
std::vector<df::unit*> copy = cand;
int cursor = plist->getListCursor();
int pos_cursor = pos_list->getListCursor();
INTERPOSE_NEXT(feed)(input);
if (inPositionsMode() && !layer_objects[0]->active)
{
bool is_select = input->count(interface_key::SELECT);
// Resort the candidate list and restore cursor
// on add to squad OR scroll in the position list.
if (!plist->active || is_select)
{
// Since we don't know the actual sorting order, preserve
// the ordering of the items in the list before keypress.
// This does the right thing even if the list was sorted
// with sort-units.
std::set<df::unit*> prev, next;
prev.insert(copy.begin(), copy.end());
next.insert(cand.begin(), cand.end());
std::vector<df::unit*> out;
// (old-before-cursor) (new) |cursor| (old-after-cursor)
for (int i = 0; i < cursor && i < (int)copy.size(); i++)
if (next.count(copy[i])) out.push_back(copy[i]);
for (size_t i = 0; i < cand.size(); i++)
if (!prev.count(cand[i])) out.push_back(cand[i]);
int new_cursor = out.size();
for (int i = cursor; i < (int)copy.size(); i++)
if (next.count(copy[i])) out.push_back(copy[i]);
cand.swap(out);
plist->setListLength(cand.size());
if (new_cursor < (int)cand.size())
plist->setListCursor(new_cursor);
}
// Preserve the position list index on remove from squad
if (pos_list->active && is_select)
pos_list->setListCursor(pos_cursor);
}
}
else
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (inPositionsMode())
{
auto plist = layer_objects[2];
int x1 = plist->getX1(), y1 = plist->getY1();
int x2 = plist->getX2(), y2 = plist->getY2();
int i1 = plist->getFirstVisible(), i2 = plist->getLastVisible();
int si = plist->getListCursor();
for (int y = y1, i = i1; i <= i2; i++, y++)
{
auto unit = vector_get(positions.candidates, i);
if (!unit || unit->military.squad_id < 0)
continue;
for (int x = x1; x <= x2; x++)
{
Pen cur_tile = Screen::readTile(x, y);
if (!cur_tile.valid()) continue;
cur_tile.fg = (i == si) ? COLOR_BROWN : COLOR_GREEN;
Screen::paintTile(cur_tile, x, y);
}
}
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, render);
// Unit updates are executed based on an action divisor variable, // Unit updates are executed based on an action divisor variable,
// which is computed from the alive unit count and has range 10-100. // which is computed from the alive unit count and has range 10-100.
static int adjust_unit_divisor(int value) { static int adjust_unit_divisor(int value) {
@ -943,6 +542,7 @@ struct military_training_ct_hook : df::activity_event_combat_trainingst {
} }
} }
}; };
*/
/* /*
IMPLEMENT_VMETHOD_INTERPOSE(military_training_ct_hook, process); IMPLEMENT_VMETHOD_INTERPOSE(military_training_ct_hook, process);
@ -1007,104 +607,6 @@ struct military_training_id_hook : df::activity_event_individual_skill_drillst {
IMPLEMENT_VMETHOD_INTERPOSE(military_training_id_hook, process); IMPLEMENT_VMETHOD_INTERPOSE(military_training_id_hook, process);
*/ */
struct craft_age_wear_hook : df::item_crafted {
typedef df::item_crafted interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, ageItem, (int amount))
{
int orig_age = age;
age += amount;
if (age > 200000000)
age = 200000000;
if (age == orig_age)
return false;
MaterialInfo mat(mat_type, mat_index);
if (!mat.isValid())
return false;
int wear = 0;
if (mat.material->flags.is_set(material_flags::WOOD))
wear = 5;
else if (mat.material->flags.is_set(material_flags::LEATHER) ||
mat.material->flags.is_set(material_flags::THREAD_PLANT) ||
mat.material->flags.is_set(material_flags::SILK) ||
mat.material->flags.is_set(material_flags::YARN))
wear = 1;
else
return false;
wear = ((orig_age % wear) + (age - orig_age)) / wear;
if (wear > 0)
return incWearTimer(wear);
else
return false;
}
};
IMPLEMENT_VMETHOD_INTERPOSE(craft_age_wear_hook, ageItem);
static bool inc_wear_timer (df::item_constructed *item, int amount)
{
if (item->flags.bits.artifact)
return false;
MaterialInfo mat(item->mat_type, item->mat_index);
if (mat.isInorganic() && mat.inorganic->flags.is_set(inorganic_flags::DEEP_SPECIAL))
return false;
item->wear_timer += amount;
return (item->wear_timer > 806400);
}
struct adamantine_cloth_wear_armor_hook : df::item_armorst {
typedef df::item_armorst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, incWearTimer, (int amount))
{
return inc_wear_timer(this, amount);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(adamantine_cloth_wear_armor_hook, incWearTimer);
struct adamantine_cloth_wear_helm_hook : df::item_helmst {
typedef df::item_helmst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, incWearTimer, (int amount))
{
return inc_wear_timer(this, amount);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(adamantine_cloth_wear_helm_hook, incWearTimer);
struct adamantine_cloth_wear_gloves_hook : df::item_glovesst {
typedef df::item_glovesst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, incWearTimer, (int amount))
{
return inc_wear_timer(this, amount);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(adamantine_cloth_wear_gloves_hook, incWearTimer);
struct adamantine_cloth_wear_shoes_hook : df::item_shoesst {
typedef df::item_shoesst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, incWearTimer, (int amount))
{
return inc_wear_timer(this, amount);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(adamantine_cloth_wear_shoes_hook, incWearTimer);
struct adamantine_cloth_wear_pants_hook : df::item_pantsst {
typedef df::item_pantsst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, incWearTimer, (int amount))
{
return inc_wear_timer(this, amount);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(adamantine_cloth_wear_pants_hook, incWearTimer);
static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector <string> &parameters) static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector <string> &parameters)
{ {
if (vector_get(parameters, 1) == "disable") if (vector_get(parameters, 1) == "disable")
@ -1121,6 +623,26 @@ static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vect
} }
} }
static command_result enable_tweak(string tweak, color_ostream &out, vector <string> &parameters)
{
bool recognized = false;
string cmd = parameters[0];
for (auto it = tweak_hooks.begin(); it != tweak_hooks.end(); ++it)
{
if (it->first == cmd)
{
recognized = true;
enable_hook(out, it->second, parameters);
}
}
if (!recognized)
{
out.printerr("Unrecognized tweak: %s\n", cmd.c_str());
return CR_WRONG_USAGE;
}
return CR_OK;
}
static command_result tweak(color_ostream &out, vector <string> &parameters) static command_result tweak(color_ostream &out, vector <string> &parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;
@ -1219,25 +741,6 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
unit->profession2 = df::profession::TRADER; unit->profession2 = df::profession::TRADER;
return fix_clothing_ownership(out, unit); return fix_clothing_ownership(out, unit);
} }
else if (cmd == "stable-cursor")
{
enable_hook(out, INTERPOSE_HOOK(stable_cursor_hook, feed), parameters);
}
else if (cmd == "patrol-duty")
{
enable_hook(out, INTERPOSE_HOOK(patrol_duty_hook, isPatrol), parameters);
}
else if (cmd == "confirm-embark")
{
enable_hook(out, INTERPOSE_HOOK(confirm_embark_hook, feed), parameters);
enable_hook(out, INTERPOSE_HOOK(confirm_embark_hook, key_conflict), parameters);
enable_hook(out, INTERPOSE_HOOK(confirm_embark_hook, render), parameters);
}
else if (cmd == "stable-temp")
{
enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, adjustTemperature), parameters);
enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, updateContaminants), parameters);
}
else if (cmd == "fast-heat") else if (cmd == "fast-heat")
{ {
if (parameters.size() < 2) if (parameters.size() < 2)
@ -1245,9 +748,8 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
max_heat_ticks = atoi(parameters[1].c_str()); max_heat_ticks = atoi(parameters[1].c_str());
if (max_heat_ticks <= 0) if (max_heat_ticks <= 0)
parameters[1] = "disable"; parameters[1] = "disable";
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, updateTempFromMap), parameters); enable_tweak(cmd, out, parameters);
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, updateTemperature), parameters); return CR_OK;
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, adjustTemperature), parameters);
} }
/*else if (cmd == "fix-dimensions") /*else if (cmd == "fix-dimensions")
{ {
@ -1257,23 +759,6 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters); enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters);
enable_hook(out, INTERPOSE_HOOK(dimension_cloth_hook, subtractDimension), parameters); enable_hook(out, INTERPOSE_HOOK(dimension_cloth_hook, subtractDimension), parameters);
}*/ }*/
else if (cmd == "advmode-contained")
{
enable_hook(out, INTERPOSE_HOOK(advmode_contained_hook, feed), parameters);
}
else if (cmd == "fast-trade")
{
enable_hook(out, INTERPOSE_HOOK(fast_trade_assign_hook, feed), parameters);
enable_hook(out, INTERPOSE_HOOK(fast_trade_select_hook, feed), parameters);
}
else if (cmd == "military-stable-assign")
{
enable_hook(out, INTERPOSE_HOOK(military_assign_hook, feed), parameters);
}
else if (cmd == "military-color-assigned")
{
enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters);
}
/* /*
else if (cmd == "military-training") else if (cmd == "military-training")
{ {
@ -1282,20 +767,10 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
enable_hook(out, INTERPOSE_HOOK(military_training_sp_hook, process), parameters); enable_hook(out, INTERPOSE_HOOK(military_training_sp_hook, process), parameters);
enable_hook(out, INTERPOSE_HOOK(military_training_id_hook, process), parameters); enable_hook(out, INTERPOSE_HOOK(military_training_id_hook, process), parameters);
}*/ }*/
else if (cmd == "craft-age-wear") else
{
enable_hook(out, INTERPOSE_HOOK(craft_age_wear_hook, ageItem), parameters);
}
else if (cmd == "adamantine-cloth-wear")
{ {
enable_hook(out, INTERPOSE_HOOK(adamantine_cloth_wear_armor_hook, incWearTimer), parameters); return enable_tweak(cmd, out, parameters);
enable_hook(out, INTERPOSE_HOOK(adamantine_cloth_wear_helm_hook, incWearTimer), parameters);
enable_hook(out, INTERPOSE_HOOK(adamantine_cloth_wear_gloves_hook, incWearTimer), parameters);
enable_hook(out, INTERPOSE_HOOK(adamantine_cloth_wear_shoes_hook, incWearTimer), parameters);
enable_hook(out, INTERPOSE_HOOK(adamantine_cloth_wear_pants_hook, incWearTimer), parameters);
} }
else
return CR_WRONG_USAGE;
return CR_OK; return CR_OK;
} }

@ -0,0 +1,65 @@
using namespace DFHack;
using namespace df::enums;
static bool inc_wear_timer (df::item_constructed *item, int amount)
{
if (item->flags.bits.artifact)
return false;
MaterialInfo mat(item->mat_type, item->mat_index);
if (mat.isInorganic() && mat.inorganic->flags.is_set(inorganic_flags::DEEP_SPECIAL))
return false;
item->wear_timer += amount;
return (item->wear_timer > 806400);
}
struct adamantine_cloth_wear_armor_hook : df::item_armorst {
typedef df::item_armorst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, incWearTimer, (int amount))
{
return inc_wear_timer(this, amount);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(adamantine_cloth_wear_armor_hook, incWearTimer);
struct adamantine_cloth_wear_helm_hook : df::item_helmst {
typedef df::item_helmst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, incWearTimer, (int amount))
{
return inc_wear_timer(this, amount);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(adamantine_cloth_wear_helm_hook, incWearTimer);
struct adamantine_cloth_wear_gloves_hook : df::item_glovesst {
typedef df::item_glovesst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, incWearTimer, (int amount))
{
return inc_wear_timer(this, amount);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(adamantine_cloth_wear_gloves_hook, incWearTimer);
struct adamantine_cloth_wear_shoes_hook : df::item_shoesst {
typedef df::item_shoesst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, incWearTimer, (int amount))
{
return inc_wear_timer(this, amount);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(adamantine_cloth_wear_shoes_hook, incWearTimer);
struct adamantine_cloth_wear_pants_hook : df::item_pantsst {
typedef df::item_pantsst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, incWearTimer, (int amount))
{
return inc_wear_timer(this, amount);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(adamantine_cloth_wear_pants_hook, incWearTimer);

@ -0,0 +1,59 @@
using namespace std;
struct advmode_contained_hook : df::viewscreen_layer_unit_actionst {
typedef df::viewscreen_layer_unit_actionst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
auto old_reaction = cur_reaction;
auto old_reagent = reagent;
INTERPOSE_NEXT(feed)(input);
if (cur_reaction && (cur_reaction != old_reaction || reagent != old_reagent))
{
old_reagent = reagent;
// Skip reagents already contained by others
while (reagent < (int)cur_reaction->reagents.size()-1)
{
if (!cur_reaction->reagents[reagent]->flags.bits.IN_CONTAINER)
break;
reagent++;
}
if (old_reagent != reagent)
{
// Reproduces a tiny part of the orginal screen code
choice_items.clear();
auto preagent = cur_reaction->reagents[reagent];
reagent_amnt_left = preagent->quantity;
for (int i = held_items.size()-1; i >= 0; i--)
{
if (!preagent->matchesRoot(held_items[i], cur_reaction->index))
continue;
if (linear_index(sel_items, held_items[i]) >= 0)
continue;
choice_items.push_back(held_items[i]);
}
layer_objects[6]->setListLength(choice_items.size());
if (!choice_items.empty())
{
layer_objects[4]->active = layer_objects[5]->active = false;
layer_objects[6]->active = true;
}
else if (layer_objects[6]->active)
{
layer_objects[6]->active = false;
layer_objects[5]->active = true;
}
}
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(advmode_contained_hook, feed);

@ -0,0 +1,34 @@
#include "df/meeting_event.h"
#include "df/viewscreen_entityst.h"
using namespace std;
using namespace df::enums;
using namespace DFHack::Gui;
using namespace DFHack::Screen;
#define DLOG DFHack::Core::getInstance().getConsole().printerr
struct civ_agreement_view_hook : df::viewscreen_entityst {
typedef df::viewscreen_entityst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (page == 2)
{
fillRect(Pen(0, 0, ' '), 2, 22, 22, 22);
int x, y;
getWindowSize(x, y);
x = 2;
y -= 3;
OutputString(COLOR_LIGHTGREEN, x, y, getKeyDisplay(interface_key::CHANGETAB));
OutputString(COLOR_WHITE, x, y, " to change modes.");
x = 2;
y++;
OutputString(COLOR_LIGHTGREEN, x, y, getKeyDisplay(interface_key::SELECT));
OutputString(COLOR_WHITE, x, y, ": View agreement");
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(civ_agreement_view_hook, render);

@ -0,0 +1,34 @@
struct craft_age_wear_hook : df::item_crafted {
typedef df::item_crafted interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, ageItem, (int amount))
{
int orig_age = age;
age += amount;
if (age > 200000000)
age = 200000000;
if (age == orig_age)
return false;
MaterialInfo mat(mat_type, mat_index);
if (!mat.isValid())
return false;
int wear = 0;
if (mat.material->flags.is_set(material_flags::WOOD))
wear = 5;
else if (mat.material->flags.is_set(material_flags::LEATHER) ||
mat.material->flags.is_set(material_flags::THREAD_PLANT) ||
mat.material->flags.is_set(material_flags::SILK) ||
mat.material->flags.is_set(material_flags::YARN))
wear = 1;
else
return false;
wear = ((orig_age % wear) + (age - orig_age)) / wear;
if (wear > 0)
return incWearTimer(wear);
else
return false;
}
};
IMPLEMENT_VMETHOD_INTERPOSE(craft_age_wear_hook, ageItem);

@ -0,0 +1,96 @@
#include "BitArray.h"
#include "df/building_farmplotst.h"
#include "df/plant_raw.h"
using namespace df::enums;
using df::global::ui;
using df::global::ui_building_item_cursor;
using df::global::world;
struct farm_select_hook : df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
df::building_farmplotst* getFarmPlot()
{
if (ui->main.mode != ui_sidebar_mode::QueryBuilding)
return NULL;
VIRTUAL_CAST_VAR(farm_plot, df::building_farmplotst, world->selected_building);
return farm_plot;
}
bool isValidCrop (int32_t crop_id, int season, df::building_farmplotst* farm_plot)
{
// Adapted from autofarm
using namespace df::enums::plant_raw_flags;
// Discovered?
if (ui->tasks.discovered_plants[crop_id])
{
// Possible to plant?
df::plant_raw* raws = world->raws.plants.all[crop_id];
if (raws->flags.is_set(SEED) && raws->flags.is_set((df::plant_raw_flags)season))
{
// Right depth?
DFCoord cursor (farm_plot->centerx, farm_plot->centery, farm_plot->z);
MapExtras::MapCache mc;
MapExtras::Block * b = mc.BlockAt(cursor / 16);
if (!b || !b->is_valid())
return false;
auto &block = *b->getRaw();
df::tile_designation &des =
block.designation[farm_plot->centerx % 16][farm_plot->centery % 16];
if ((raws->underground_depth_min == 0 || raws->underground_depth_max == 0) != des.bits.subterranean)
{
return true;
}
}
}
return false;
}
inline int32_t getSelectedCropId() { return ui->selected_farm_crops[*ui_building_item_cursor]; }
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
df::building_farmplotst* farm_plot = getFarmPlot();
if (farm_plot)
{
if (input->count(interface_key::SELECT_ALL))
{
int32_t crop_id = getSelectedCropId();
for (int season = 0; season < 4; season++)
{
if (isValidCrop(crop_id, season, farm_plot))
{
farm_plot->plant_id[season] = crop_id;
}
}
}
else if (input->count(interface_key::DESELECT_ALL))
{
for (int season = 0; season < 4; season++)
{
farm_plot->plant_id[season] = -1;
}
}
}
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (!getFarmPlot())
return;
auto dims = Gui::getDwarfmodeViewDims();
int x = dims.menu_x1 + 1,
y = dims.y2 - 5,
left = x;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::SELECT_ALL));
OutputString(COLOR_WHITE, x, y, ": All seasons", true, left);
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(interface_key::DESELECT_ALL));
OutputString(COLOR_WHITE, x, y, ": Fallow all seasons", true, left);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(farm_select_hook, render);
IMPLEMENT_VMETHOD_INTERPOSE(farm_select_hook, feed);

@ -0,0 +1,48 @@
using namespace df::enums;
static int map_temp_mult = -1;
static int max_heat_ticks = 0;
struct fast_heat_hook : df::item_actual {
typedef df::item_actual interpose_base;
DEFINE_VMETHOD_INTERPOSE(
bool, updateTempFromMap,
(bool local, bool contained, bool adjust, int32_t rate_mult)
) {
int cmult = map_temp_mult;
map_temp_mult = rate_mult;
bool rv = INTERPOSE_NEXT(updateTempFromMap)(local, contained, adjust, rate_mult);
map_temp_mult = cmult;
return rv;
}
DEFINE_VMETHOD_INTERPOSE(
bool, updateTemperature,
(uint16_t temp, bool local, bool contained, bool adjust, int32_t rate_mult)
) {
// Some items take ages to cross the last degree, so speed them up
if (map_temp_mult > 0 && temp != temperature.whole && max_heat_ticks > 0)
{
int spec = getSpecHeat();
if (spec != 60001)
rate_mult = std::max(map_temp_mult, spec/max_heat_ticks/abs(temp - temperature.whole));
}
return INTERPOSE_NEXT(updateTemperature)(temp, local, contained, adjust, rate_mult);
}
DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t rate_mult))
{
if (map_temp_mult > 0)
rate_mult = map_temp_mult;
return INTERPOSE_NEXT(adjustTemperature)(temp, rate_mult);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, updateTempFromMap);
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, updateTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, adjustTemperature);

@ -0,0 +1,43 @@
using namespace std;
struct fast_trade_assign_hook : df::viewscreen_layer_assigntradest {
typedef df::viewscreen_layer_assigntradest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (layer_objects[1]->active && input->count(interface_key::SELECT_ALL))
{
set<df::interface_key> tmp; tmp.insert(interface_key::SELECT);
INTERPOSE_NEXT(feed)(&tmp);
tmp.clear(); tmp.insert(interface_key::STANDARDSCROLL_DOWN);
INTERPOSE_NEXT(feed)(&tmp);
}
else
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(fast_trade_assign_hook, feed);
struct fast_trade_select_hook : df::viewscreen_tradegoodsst {
typedef df::viewscreen_tradegoodsst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (!(is_unloading || !has_traders || in_edit_count)
&& input->count(interface_key::SELECT_ALL))
{
set<df::interface_key> tmp; tmp.insert(interface_key::SELECT);
INTERPOSE_NEXT(feed)(&tmp);
if (in_edit_count)
INTERPOSE_NEXT(feed)(&tmp);
tmp.clear(); tmp.insert(interface_key::STANDARDSCROLL_DOWN);
INTERPOSE_NEXT(feed)(&tmp);
}
else
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(fast_trade_select_hook, feed);

@ -0,0 +1,39 @@
#include "df/meeting_diplomat_info.h"
#include "df/entity_sell_requests.h"
#include "df/viewscreen_topicmeeting_takerequestsst.h"
using namespace std;
using namespace DFHack;
using namespace df::enums;
struct takerequest_hook : df::viewscreen_topicmeeting_takerequestsst {
typedef df::viewscreen_topicmeeting_takerequestsst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key>* input))
{
if (input->count(interface_key::CURSOR_RIGHT_FAST) ||
input->count(interface_key::CURSOR_LEFT_FAST))
{
int delta = 0 + input->count(interface_key::CURSOR_RIGHT_FAST)
- input->count(interface_key::CURSOR_LEFT_FAST);
vector<int8_t> &cur_priorities = meeting->sell_requests->priority[type_categories[type_idx]];
for (size_t i = 0; i < cur_priorities.size(); i++)
{
cur_priorities[i] += delta;
if (cur_priorities[i] > 4)
cur_priorities[i] = 4;
if (cur_priorities[i] < 0)
cur_priorities[i] = 0;
}
}
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
int x = 45, y = 23;
OutputString(COLOR_LIGHTRED, x, y, "Shift+Left/Right");
OutputString(COLOR_GREY, x, y, ": Adjust category");
}
};
IMPLEMENT_VMETHOD_INTERPOSE(takerequest_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(takerequest_hook, render);

@ -0,0 +1,53 @@
#include <stdexcept>
#include "df/viewscreen_createquotast.h"
#include "df/manager_order.h"
using df::global::world;
struct manager_quantity_hook : df::viewscreen_createquotast {
typedef df::viewscreen_createquotast interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key>* input))
{
bool cancel = false;
bool wanted_quantity = want_quantity;
if (want_quantity)
{
for (auto it = input->begin(); it != input->end(); ++it)
{
char c = DFHack::Screen::keyToChar(*it);
if (c >= '0' && c <= '9')
{
cancel = true;
size_t len = strlen(str_filter);
if (len < 5)
{
str_filter[len] = c;
str_filter[len + 1] = '\0';
}
}
}
}
if (cancel)
return;
// Native feed() adds manager order, updates want_quantity, and removes SELECT from input
int select = input->count(df::interface_key::SELECT);
INTERPOSE_NEXT(feed)(input);
if (wanted_quantity && select && strlen(str_filter) > 0)
{
df::manager_order* order = world->manager_orders[world->manager_orders.size() - 1];
int16_t count = 1;
try
{
count = std::stoi(str_filter);
}
catch (...) { }
if (count < 1)
count = 1;
order->amount_total = count;
order->amount_left = count;
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(manager_quantity_hook, feed);

@ -0,0 +1,99 @@
using namespace DFHack;
using namespace df::enums;
using Screen::Pen;
struct military_assign_hook : df::viewscreen_layer_militaryst {
typedef df::viewscreen_layer_militaryst interpose_base;
inline bool inPositionsMode() {
return page == Positions && !(in_create_squad || in_new_squad);
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (inPositionsMode() && !layer_objects[0]->active)
{
auto pos_list = layer_objects[1];
auto plist = layer_objects[2];
auto &cand = positions.candidates;
// Save the candidate list and cursors
std::vector<df::unit*> copy = cand;
int cursor = plist->getListCursor();
int pos_cursor = pos_list->getListCursor();
INTERPOSE_NEXT(feed)(input);
if (inPositionsMode() && !layer_objects[0]->active)
{
bool is_select = input->count(interface_key::SELECT);
// Resort the candidate list and restore cursor
// on add to squad OR scroll in the position list.
if (!plist->active || is_select)
{
// Since we don't know the actual sorting order, preserve
// the ordering of the items in the list before keypress.
// This does the right thing even if the list was sorted
// with sort-units.
std::set<df::unit*> prev, next;
prev.insert(copy.begin(), copy.end());
next.insert(cand.begin(), cand.end());
std::vector<df::unit*> out;
// (old-before-cursor) (new) |cursor| (old-after-cursor)
for (int i = 0; i < cursor && i < (int)copy.size(); i++)
if (next.count(copy[i])) out.push_back(copy[i]);
for (size_t i = 0; i < cand.size(); i++)
if (!prev.count(cand[i])) out.push_back(cand[i]);
int new_cursor = out.size();
for (int i = cursor; i < (int)copy.size(); i++)
if (next.count(copy[i])) out.push_back(copy[i]);
cand.swap(out);
plist->setListLength(cand.size());
if (new_cursor < (int)cand.size())
plist->setListCursor(new_cursor);
}
// Preserve the position list index on remove from squad
if (pos_list->active && is_select)
pos_list->setListCursor(pos_cursor);
}
}
else
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (inPositionsMode())
{
auto plist = layer_objects[2];
int x1 = plist->getX1(), y1 = plist->getY1();
int x2 = plist->getX2(), y2 = plist->getY2();
int i1 = plist->getFirstVisible(), i2 = plist->getLastVisible();
int si = plist->getListCursor();
for (int y = y1, i = i1; i <= i2; i++, y++)
{
auto unit = vector_get(positions.candidates, i);
if (!unit || unit->military.squad_id < 0)
continue;
for (int x = x1; x <= x2; x++)
{
Pen cur_tile = Screen::readTile(x, y);
if (!cur_tile.valid()) continue;
cur_tile.fg = (i == si) ? COLOR_BROWN : COLOR_GREEN;
Screen::paintTile(cur_tile, x, y);
}
}
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, render);

@ -0,0 +1,22 @@
#include "df/building_flags.h"
#include "df/building_drawbuffer.h"
#include "df/building_nest_boxst.h"
using namespace df::enums;
struct nestbox_color_hook : df::building_nest_boxst {
typedef df::building_nest_boxst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer* db, int16_t unk))
{
INTERPOSE_NEXT(drawBuilding)(db, unk);
if (flags.bits.exists)
{
MaterialInfo mat(mat_type, mat_index);
db->fore[0][0] = mat.material->build_color[0];
db->back[0][0] = mat.material->build_color[1];
db->bright[0][0] = mat.material->build_color[2];
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(nestbox_color_hook, drawBuilding);

@ -0,0 +1,77 @@
/*
* Save or restore cursor position on change to/from main dwarfmode menu.
*/
using namespace std;
using namespace DFHack;
using namespace df::enums;
using df::global::ui;
using df::global::ui_build_selector;
using df::global::ui_menu_width;
static df::coord last_view, last_cursor;
struct stable_cursor_hook : df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
bool check_default()
{
switch (ui->main.mode) {
case ui_sidebar_mode::Default:
return true;
case ui_sidebar_mode::Build:
return ui_build_selector &&
(ui_build_selector->building_type < 0 ||
ui_build_selector->stage < 1);
default:
return false;
}
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
bool was_default = check_default();
df::coord view = Gui::getViewportPos();
df::coord cursor = Gui::getCursorPos();
INTERPOSE_NEXT(feed)(input);
bool is_default = check_default();
df::coord cur_cursor = Gui::getCursorPos();
if (is_default && !was_default)
{
last_view = view; last_cursor = cursor;
}
else if (!is_default && was_default &&
Gui::getViewportPos() == last_view &&
last_cursor.isValid() && cur_cursor.isValid())
{
Gui::setCursorCoords(last_cursor.x, last_cursor.y, last_cursor.z);
// Force update of ui state
set<df::interface_key> tmp;
if (last_cursor.z < 2)
tmp.insert(interface_key::CURSOR_UP_Z);
else
tmp.insert(interface_key::CURSOR_DOWN_Z);
INTERPOSE_NEXT(feed)(&tmp);
tmp.clear();
if (last_cursor.z < 2)
tmp.insert(interface_key::CURSOR_DOWN_Z);
else
tmp.insert(interface_key::CURSOR_UP_Z);
INTERPOSE_NEXT(feed)(&tmp);
}
else if (!is_default && cur_cursor.isValid())
{
last_cursor = df::coord();
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(stable_cursor_hook, feed);

@ -131,15 +131,24 @@ local function zoomed_searcher(startn, end_or_sz)
end end
local finder_searches = {} local finder_searches = {}
local function exec_finder(finder, names) local function exec_finder(finder, names, validators)
if type(names) ~= 'table' then if type(names) ~= 'table' then
names = { names } names = { names }
end end
if type(validators) ~= 'table' then
validators = { validators }
end
local search = force_scan['all'] local search = force_scan['all']
for _,v in ipairs(names) do for k,v in ipairs(names) do
if force_scan[v] or not is_known(v) then if force_scan[v] or not is_known(v) then
table.insert(finder_searches, v) table.insert(finder_searches, v)
search = true search = true
elseif validators[k] then
if not validators[k](df.global[v]) then
dfhack.printerr('Validation failed for '..v..', will try to find again')
table.insert(finder_searches, v)
search = true
end
end end
end end
if search then if search then
@ -505,6 +514,7 @@ end
local function is_valid_world(world) local function is_valid_world(world)
if not ms.is_valid_vector(world.units.all, 4) if not ms.is_valid_vector(world.units.all, 4)
or not ms.is_valid_vector(world.units.active, 4)
or not ms.is_valid_vector(world.units.bad, 4) or not ms.is_valid_vector(world.units.bad, 4)
or not ms.is_valid_vector(world.history.figures, 4) or not ms.is_valid_vector(world.history.figures, 4)
or not ms.is_valid_vector(world.features.map_features, 4) or not ms.is_valid_vector(world.features.map_features, 4)
@ -776,11 +786,6 @@ end
-- --
local function find_current_weather() local function find_current_weather()
print('\nPlease load the save previously processed with prepare-save.')
if not utils.prompt_yes_no('Proceed?', true) then
return
end
local zone local zone
if os_type == 'windows' then if os_type == 'windows' then
zone = zoomed_searcher('crime_next_id', 512) zone = zoomed_searcher('crime_next_id', 512)
@ -839,7 +844,14 @@ local function find_ui_selected_unit()
end end
for i,unit in ipairs(df.global.world.units.active) do for i,unit in ipairs(df.global.world.units.active) do
dfhack.units.setNickname(unit, i) -- This function does a lot of things and accesses histfigs, souls and so on:
--dfhack.units.setNickname(unit, i)
-- Instead use just a simple bit of code that only requires the start of the
-- unit to be valid. It may not work properly with vampires or reset later
-- if unpaused, but is sufficient for this script and won't crash:
unit.name.nickname = tostring(i)
unit.name.has_name = true
end end
local addr = searcher:find_menu_cursor([[ local addr = searcher:find_menu_cursor([[
@ -1506,17 +1518,23 @@ print('\nInitial globals (need title screen):\n')
exec_finder(find_gview, 'gview') exec_finder(find_gview, 'gview')
exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' }) exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' })
exec_finder(find_announcements, 'announcements') exec_finder(find_announcements, 'announcements')
exec_finder(find_d_init, 'd_init') exec_finder(find_d_init, 'd_init', is_valid_d_init)
exec_finder(find_enabler, 'enabler') exec_finder(find_enabler, 'enabler', is_valid_enabler)
exec_finder(find_gps, 'gps') exec_finder(find_gps, 'gps', is_valid_gps)
print('\nCompound globals (need loaded world):\n') print('\nCompound globals (need loaded world):\n')
exec_finder(find_world, 'world') print('\nPlease load the save previously processed with prepare-save.')
exec_finder(find_ui, 'ui') if not utils.prompt_yes_no('Proceed?', true) then
searcher:reset()
return
end
exec_finder(find_world, 'world', is_valid_world)
exec_finder(find_ui, 'ui', is_valid_ui)
exec_finder(find_ui_sidebar_menus, 'ui_sidebar_menus') exec_finder(find_ui_sidebar_menus, 'ui_sidebar_menus')
exec_finder(find_ui_build_selector, 'ui_build_selector') exec_finder(find_ui_build_selector, 'ui_build_selector')
exec_finder(find_init, 'init') exec_finder(find_init, 'init', is_valid_init)
print('\nPrimitive globals:\n') print('\nPrimitive globals:\n')

@ -0,0 +1,33 @@
-- Remove all aquifers from the map
local function drain()
local layers = {}
local layer_count = 0
local tile_count = 0
for k, block in ipairs(df.global.world.map.map_blocks) do
if block.flags.has_aquifer then
block.flags.has_aquifer = false
block.flags.check_aquifer = false
for x, row in ipairs(block.designation) do
for y, tile in ipairs(row) do
if tile.water_table then
tile.water_table = false
tile_count = tile_count + 1
end
end
end
if not layers[block.map_pos.z] then
layers[block.map_pos.z] = true
layer_count = layer_count + 1
end
end
end
print("Cleared "..tile_count.." aquifer tile"..((tile_count ~= 1) and "s" or "")..
" in "..layer_count.." layer"..((layer_count ~= 1) and "s" or "")..".")
end
drain(...)

@ -1,13 +0,0 @@
# remove all aquifers from the map
count = 0
df.each_map_block { |b|
if b.designation[0][0].water_table or b.designation[8][8].water_table
count += 1
df.each_map_block_z(b.map_pos.z) { |bz|
bz.designation.each { |dx| dx.each { |dy| dy.water_table = false } }
}
end
}
puts "cleared #{count} aquifer#{'s' if count > 1}"

@ -31,7 +31,7 @@ target file:
will be displayed if the script is successful. will be displayed if the script is successful.
character encoding: character encoding:
The text will likely be using system-default encoding, and as such The text will likely be using system-default encoding, and as such
will likely NOT display special characters (eg:é,õ,ç) correctly. To will likely NOT display special characters (eg:È,ı,Á) correctly. To
fix this, you need to modify the character set that you are reading fix this, you need to modify the character set that you are reading
the document with. 'Notepad++' is a freely available program which the document with. 'Notepad++' is a freely available program which
can do this using the following steps: can do this using the following steps:
@ -99,22 +99,15 @@ local function format_for_forum(strin)
end end
if flerb == 'textviewer' then if flerb == 'textviewer' then
print(scrn) local lines = scrn.src_text
printall(scrn)
local lines = scrn.formatted_text
local line = "" local line = ""
if lines ~= nil then if lines ~= nil then
local log = io.open('forumdwarves.txt', 'a') local log = io.open('forumdwarves.txt', 'a')
log:write("[color=silver]") log:write("[color=silver]")
for n,x in ipairs(lines) do for n,x in ipairs(lines) do
print(x) if x ~= nil and x.value ~= nil then
printall(x) log:write(format_for_forum(x.value), ' ')
print(x.text)
printall(x.text)
if (x ~= nil) and (x.text ~= nil) then
log:write(format_for_forum(x.text), ' ')
--log:write(x[0],'\n')
end end
end end
log:write("[/color]\n") log:write("[/color]\n")

@ -98,7 +98,7 @@ function equipHandler(unit, item, isEquip)
local table = {} local table = {}
table.mode = mode table.mode = mode
table.item = df.item.find(item) table.item = df.item.find(item)
table.unit = unit table.unit = df.unit.find(unit)
handler(table) handler(table)
end end
@ -201,7 +201,7 @@ arguments:
\\CONTAMINANT_MATERIAL_INDEX \\CONTAMINANT_MATERIAL_INDEX
\\MODE \\MODE
\\UNIT_ID \\UNIT_ID
\\anything -> anything \\anything -> \anything
anything -> anything anything -> anything
]]) ]])
return return

@ -24,13 +24,13 @@ if args.help then
print this help message print this help message
repeat -cancel bob repeat -cancel bob
cancels the repetition with the name bob cancels the repetition with the name bob
repeat -name jim -time delay -timeUnits units -printResult true -command printArgs 3 1 2 repeat -name jim -time delay -timeUnits units -printResult true -command [ printArgs 3 1 2 ]
-name sets the name for the purposes of cancelling and making sure you don't schedule the same repeating event twice -name sets the name for the purposes of cancelling and making sure you don't schedule the same repeating event twice
if not specified, it's set to the first argument after -command if not specified, it's set to the first argument after -command
-time delay -timeUnits units -time delay -timeUnits units
delay is some positive integer delay is some positive integer
units is some valid time unit for dfhack.timeout(delay,timeUnits,function) units is some valid time unit for dfhack.timeout(delay,timeUnits,function)
-command ... -command [ ... ]
specify the command to be run specify the command to be run
]]) ]])
return return

@ -16,9 +16,12 @@ when 'add'
$superdwarf_ids.each { |id| $superdwarf_ids.each { |id|
if u = df.unit_find(id) and not u.flags1.dead if u = df.unit_find(id) and not u.flags1.dead
# faster walk/work # faster walk/work
if u.counters.job_counter > 0 u.actions.each { |a|
u.counters.job_counter = 0 case a.type
when :Move, :Climb, :Job, :Job2
a.data.send(a.type.to_s.downcase).timer = 0
end end
}
# no sleep # no sleep
if u.counters2.sleepiness_timer > 10000 if u.counters2.sleepiness_timer > 10000