Merge remote-tracking branch 'origin/develop' into tweak-reorganization

Conflicts:
	NEWS
	Readme.html
	plugins/tweak/tweak.cpp
develop
lethosor 2014-11-02 11:40:53 -05:00
commit 14b947837c
36 changed files with 1088 additions and 505 deletions

@ -58,8 +58,8 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac
endif()
# set up versioning.
set(DF_VERSION "0.40.13")
SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.")
set(DF_VERSION "0.40.14")
SET(DFHACK_RELEASE "r0" CACHE STRING "Current release revision.")
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")

@ -1,4 +1,9 @@
DFHack future
Fixes:
- replaced drainaquifer.rb with a faster less buggy drain-aquifer.lua
- autotrade: "Mark all" no longer double-marks bin contents
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
@ -10,8 +15,10 @@ DFHack future
DFHack 0.40.13-r1
Internals:
- unified spatter structs
- added ruby df.print_color(color, string) method for dfhack console
Fixes:
- no more -e after terminating
- fixed superdwarf
DFHack 0.40.12-r1
Fixes:

@ -525,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="#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="#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="#dfstatus" id="id137">dfstatus</a></li>
<li><a class="reference internal" href="#embark" id="id138">embark</a></li>
@ -554,39 +554,40 @@ access DF memory and allow for easier development of new tools.</p>
<li><a class="reference internal" href="#search" id="id159">Search</a></li>
<li><a class="reference internal" href="#automaterial" id="id160">AutoMaterial</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="id162">gui/advfort</a></li>
<li><a class="reference internal" href="#gui-assign-rack" id="id163">gui/assign-rack</a></li>
<li><a class="reference internal" href="#gui-choose-weapons" id="id164">gui/choose-weapons</a></li>
<li><a class="reference internal" href="#gui-clone-uniform" id="id165">gui/clone-uniform</a></li>
<li><a class="reference internal" href="#gui-companion-order" id="id166">gui/companion-order</a></li>
<li><a class="reference internal" href="#gui-gm-editor" id="id167">gui/gm-editor</a></li>
<li><a class="reference internal" href="#hotkeys" id="id168">Hotkeys</a></li>
<li><a class="reference internal" href="#gui-liquids" id="id169">gui/liquids</a></li>
<li><a class="reference internal" href="#gui-mechanisms" id="id170">gui/mechanisms</a></li>
<li><a class="reference internal" href="#gui-mod-manager" id="id171">gui/mod-manager</a></li>
<li><a class="reference internal" href="#gui-rename" id="id172">gui/rename</a></li>
<li><a class="reference internal" href="#gui-room-list" id="id173">gui/room-list</a></li>
<li><a class="reference internal" href="#gui-guide-path" id="id174">gui/guide-path</a></li>
<li><a class="reference internal" href="#gui-workflow" id="id175">gui/workflow</a></li>
<li><a class="reference internal" href="#gui-workshop-job" id="id176">gui/workshop-job</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-advfort" id="id163">gui/advfort</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-choose-weapons" id="id165">gui/choose-weapons</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-companion-order" id="id167">gui/companion-order</a></li>
<li><a class="reference internal" href="#gui-gm-editor" id="id168">gui/gm-editor</a></li>
<li><a class="reference internal" href="#hotkeys" id="id169">Hotkeys</a></li>
<li><a class="reference internal" href="#gui-liquids" id="id170">gui/liquids</a></li>
<li><a class="reference internal" href="#gui-mechanisms" id="id171">gui/mechanisms</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-rename" id="id173">gui/rename</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-guide-path" id="id175">gui/guide-path</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>
</li>
<li><a class="reference internal" href="#behavior-mods" id="id177">Behavior Mods</a><ul>
<li><a class="reference internal" href="#siege-engine" id="id178">Siege Engine</a><ul>
<li><a class="reference internal" href="#rationale" id="id179">Rationale</a></li>
<li><a class="reference internal" href="#configuration-ui" id="id180">Configuration UI</a></li>
<li><a class="reference internal" href="#behavior-mods" id="id178">Behavior Mods</a><ul>
<li><a class="reference internal" href="#siege-engine" id="id179">Siege Engine</a><ul>
<li><a class="reference internal" href="#rationale" id="id180">Rationale</a></li>
<li><a class="reference internal" href="#configuration-ui" id="id181">Configuration UI</a></li>
</ul>
</li>
<li><a class="reference internal" href="#power-meter" id="id181">Power Meter</a></li>
<li><a class="reference internal" href="#steam-engine" id="id182">Steam Engine</a><ul>
<li><a class="reference internal" href="#id1" id="id183">Rationale</a></li>
<li><a class="reference internal" href="#construction" id="id184">Construction</a></li>
<li><a class="reference internal" href="#operation" id="id185">Operation</a></li>
<li><a class="reference internal" href="#explosions" id="id186">Explosions</a></li>
<li><a class="reference internal" href="#save-files" id="id187">Save files</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="id183">Steam Engine</a><ul>
<li><a class="reference internal" href="#id1" id="id184">Rationale</a></li>
<li><a class="reference internal" href="#construction" id="id185">Construction</a></li>
<li><a class="reference internal" href="#operation" id="id186">Operation</a></li>
<li><a class="reference internal" href="#explosions" id="id187">Explosions</a></li>
<li><a class="reference internal" href="#save-files" id="id188">Save files</a></li>
</ul>
</li>
<li><a class="reference internal" href="#add-spatter" id="id188">Add Spatter</a></li>
<li><a class="reference internal" href="#add-spatter" id="id189">Add Spatter</a></li>
</ul>
</li>
</ul>
@ -2344,9 +2345,38 @@ workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
<h2><a class="toc-backref" href="#id109">Fortress activity management</a></h2>
<div class="section" id="seedwatch">
<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
have of them.</p>
<p>See 'seedwatch help' for detailed description.</p>
<p>Watches the numbers of seeds available and enables/disables seed and plant cooking.</p>
<p>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.</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 class="section" id="zone">
<h3><a class="toc-backref" href="#id111">zone</a></h3>
@ -2907,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
Dwarf Fortress.exe is found).</p>
</div>
<div class="section" id="drainaquifer">
<h2><a class="toc-backref" href="#id135">drainaquifer</a></h2>
<div class="section" id="drain-aquifer">
<h2><a class="toc-backref" href="#id135">drain-aquifer</a></h2>
<p>Remove all 'aquifer' tag from the map blocks. Irreversible.</p>
</div>
<div class="section" id="deathcause">
@ -3321,8 +3351,22 @@ enable automelt
<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>
</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">
<h2><a class="toc-backref" href="#id162">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
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>
@ -3341,7 +3385,7 @@ implies -a</li>
</div>
</div>
<div class="section" id="gui-assign-rack">
<h2><a class="toc-backref" href="#id163">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
rack in the 'q' mode.</p>
<img alt="images/assign-rack.png" src="images/assign-rack.png" />
@ -3365,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>
</div>
<div class="section" id="gui-choose-weapons">
<h2><a class="toc-backref" href="#id164">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
page of the military screen.</p>
<p>Depending on the cursor location, it rewrites all 'individual choice weapon' entries
@ -3376,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>
</div>
<div class="section" id="gui-clone-uniform">
<h2><a class="toc-backref" href="#id165">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
page of the military screen with the cursor in the leftmost list.</p>
<p>When invoked, the script duplicates the currently selected uniform template,
and selects the newly created copy.</p>
</div>
<div class="section" id="gui-companion-order">
<h2><a class="toc-backref" href="#id166">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
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" />
@ -3399,7 +3443,7 @@ case. Must be in look or talk mode to issue command on tile.</p>
</ul>
</div>
<div class="section" id="gui-gm-editor">
<h2><a class="toc-backref" href="#id167">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>
<ul class="simple">
<li>using gui/gm-editor command/keybinding - opens editor on what is selected
@ -3414,7 +3458,7 @@ the same as version above.</li>
in-game help.</p>
</div>
<div class="section" id="hotkeys">
<h2><a class="toc-backref" href="#id168">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>
<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
@ -3424,7 +3468,7 @@ keybinding add Ctrl-F1 hotkeys
</pre>
</div>
<div class="section" id="gui-liquids">
<h2><a class="toc-backref" href="#id169">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>
<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,
@ -3444,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>
</div>
<div class="section" id="gui-mechanisms">
<h2><a class="toc-backref" href="#id170">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>
<img alt="images/mechanisms.png" src="images/mechanisms.png" />
<p>Lists mechanisms connected to the building, and their links. Navigating the list centers
@ -3454,13 +3498,13 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter
re-entering the mechanisms ui.</p>
</div>
<div class="section" id="gui-mod-manager">
<h2><a class="toc-backref" href="#id171">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
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" />
</div>
<div class="section" id="gui-rename">
<h2><a class="toc-backref" href="#id172">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
via a simple dialog in the game ui.</p>
<ul>
@ -3483,7 +3527,7 @@ their species string.</p>
unit profession change to Ctrl-Shift-T.</p>
</div>
<div class="section" id="gui-room-list">
<h2><a class="toc-backref" href="#id173">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,
either immediately or after opening the assign owner page.</p>
<img alt="images/room-list.png" src="images/room-list.png" />
@ -3491,7 +3535,7 @@ either immediately or after opening the assign owner page.</p>
list, and allows unassigning them.</p>
</div>
<div class="section" id="gui-guide-path">
<h2><a class="toc-backref" href="#id174">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
the cursor over a Guide order.</p>
<img alt="images/guide-path.png" src="images/guide-path.png" />
@ -3499,7 +3543,7 @@ the cursor over a Guide order.</p>
computes it when the order is executed for the first time.</p>
</div>
<div class="section" id="gui-workflow">
<h2><a class="toc-backref" href="#id175">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
in a workshop in the 'q' mode.</p>
<img alt="images/workflow.png" src="images/workflow.png" />
@ -3546,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>
</div>
<div class="section" id="gui-workshop-job">
<h2><a class="toc-backref" href="#id176">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
a workshop in the 'q' mode.</p>
<img alt="images/workshop-job.png" src="images/workshop-job.png" />
@ -3583,7 +3627,7 @@ you have to unset the material first.</p>
</div>
</div>
<div class="section" id="behavior-mods">
<h1><a class="toc-backref" href="#id177">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
structures in RAWs, modify the game engine behavior concerning the target
objects to add features not otherwise present.</p>
@ -3594,20 +3638,20 @@ technical challenge, and do not represent any long-term plans to produce more
similar modifications of the game.</p>
</div>
<div class="section" id="siege-engine">
<h2><a class="toc-backref" href="#id178">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
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
just stones.</p>
<div class="section" id="rationale">
<h3><a class="toc-backref" href="#id179">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
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,
e.g. like making siegers bring their own, are something only Toady can do.</p>
</div>
<div class="section" id="configuration-ui">
<h3><a class="toc-backref" href="#id180">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
script. Bind it to a key (the example config uses Alt-A) and activate after selecting
a siege engine in 'q' mode.</p>
@ -3630,7 +3674,7 @@ menu.</p>
</div>
</div>
<div class="section" id="power-meter">
<h2><a class="toc-backref" href="#id181">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
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
@ -3641,11 +3685,11 @@ in the build menu.</p>
configuration page, but configures parameters relevant to the modded power meter building.</p>
</div>
<div class="section" id="steam-engine">
<h2><a class="toc-backref" href="#id182">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
their token, and turns them into real steam engines.</p>
<div class="section" id="id1">
<h3><a class="toc-backref" href="#id183">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
power, but windmills give relatively little power, and water wheels require
flowing water, which must either be a real river and thus immovable and
@ -3656,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>
</div>
<div class="section" id="construction">
<h3><a class="toc-backref" href="#id184">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
passable floor tile below it, like usual magma workshops do.
The magma version also needs magma.</p>
@ -3680,7 +3724,7 @@ short axles that can be built later than both of the engines.</p>
</div>
</div>
<div class="section" id="operation">
<h3><a class="toc-backref" href="#id185">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
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
@ -3711,7 +3755,7 @@ decrease it by further 4%, and also decrease the whole steam
use rate by 10%.</p>
</div>
<div class="section" id="explosions">
<h3><a class="toc-backref" href="#id186">Explosions</a></h3>
<h3><a class="toc-backref" href="#id187">Explosions</a></h3>
<p>The engine must be constructed using barrel, pipe and piston
from fire-safe, or in the magma version magma-safe metals.</p>
<p>During operation weak parts get gradually worn out, and
@ -3720,7 +3764,7 @@ toppled during operation by a building destroyer, or a
tantruming dwarf.</p>
</div>
<div class="section" id="save-files">
<h3><a class="toc-backref" href="#id187">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
from a DF version without DFHack installed, except that in such
case the engines won't work. However actually making modifications
@ -3731,7 +3775,7 @@ being generated.</p>
</div>
</div>
<div class="section" id="add-spatter">
<h2><a class="toc-backref" href="#id188">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>
produce contaminants on the items instead of improvements. The produced
contaminants are immune to being washed away by water or destroyed by

@ -1626,10 +1626,31 @@ Fortress activity management
seedwatch
---------
Tool for turning cooking of seeds and plants on/off depending on how much you
have of them.
Watches the numbers of seeds available and enables/disables seed and plant cooking.
See 'seedwatch help' for detailed description.
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:
``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
----
@ -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
Dwarf Fortress.exe is found).
drainaquifer
============
drain-aquifer
=============
Remove all 'aquifer' tag from the map blocks. Irreversible.
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.
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
===========

@ -189,7 +189,7 @@ enable search
enable automaterial
# 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
stockflow enable

@ -61,6 +61,7 @@ using namespace DFHack;
#include "df/world_data.h"
#include "df/interfacest.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_game_cleanerst.h"
#include "df/viewscreen_loadgamest.h"
#include "df/viewscreen_savegamest.h"
#include <df/graphic.h>
@ -1027,12 +1028,15 @@ bool Core::Init()
cerr << "Initializing Console.\n";
// init the console.
bool is_text_mode = false;
if(init && init->display.flag.is_set(init_display_flags::TEXT))
bool is_text_mode = (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);
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))
cerr << "Console is running.\n";
@ -1280,6 +1284,7 @@ void Core::doUpdate(color_ostream &out, bool first_update)
}
bool is_load_save =
strict_virtual_cast<df::viewscreen_game_cleanerst>(screen) ||
strict_virtual_cast<df::viewscreen_loadgamest>(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) {
if (can_allocate() && tgt && src)
{
do_copy(tgt, src);
return true;
}
return do_copy(tgt, src);
else
return false;
}

@ -39,6 +39,14 @@ namespace df {
stl_bit_vector_identity identity_traits<std::vector<bool> >::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;
#undef NUMBER_IDENTITY_TRAITS

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

@ -61,7 +61,8 @@ namespace DFHack
IDTYPE_STRUCT,
IDTYPE_CLASS,
IDTYPE_BUFFER,
IDTYPE_STL_PTR_VECTOR
IDTYPE_STL_PTR_VECTOR,
IDTYPE_OPAQUE
};
typedef void *(*TAllocateFn)(void*,const void*);
@ -78,7 +79,7 @@ namespace DFHack
virtual bool can_allocate() { return true; }
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); }
public:
@ -116,7 +117,7 @@ namespace DFHack
virtual bool can_allocate() { return (allocator != 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; }
public:
virtual bool isPrimitive() { return false; }
@ -166,7 +167,7 @@ namespace DFHack
protected:
virtual bool can_allocate() { return true; }
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); }
public:
@ -199,7 +200,7 @@ namespace DFHack
protected:
virtual bool can_allocate() { return true; }
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); }
public:

@ -65,6 +65,17 @@ namespace DFHack
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 {
type_identity *target;
@ -170,6 +181,7 @@ namespace df
{
using DFHack::function_identity_base;
using DFHack::primitive_identity;
using DFHack::opaque_identity;
using DFHack::pointer_identity;
using DFHack::container_identity;
using DFHack::ptr_container_identity;
@ -488,6 +500,11 @@ namespace df
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*> {
static ptr_string_identity 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
for caste,creature in ipairs(syndrome.syn_affected_creature) do
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
affected = true
end

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

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

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

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

@ -7,6 +7,7 @@
#include "df/building_def.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/building_stockpilest.h"
#include "modules/Buildings.h"
#include "modules/Items.h"
#include "df/ui.h"
#include "modules/Maps.h"
@ -23,10 +24,36 @@ DFHACK_PLUGIN("automelt");
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
df::item_flags bad_flags;
bad_flags.whole = 0;
@ -39,27 +66,16 @@ static void mark_all_in_stockpiles(vector<PersistentStockpileInfo> &stockpiles)
#undef F
size_t marked_count = 0;
for (size_t i = 0; i < items.size(); i++)
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
if (!can_melt(item))
continue;
if (is_set_to_melt(item))
if (!it->isValid())
continue;
auto &melting_items = world->items.other[items_other_id::ANY_MELT_DESIGNATED];
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
auto spid = it->getId();
Buildings::StockpileIterator stored;
for (stored.begin(it->getStockpile()); !stored.done(); ++stored)
{
if (!it->inStockpile(item))
continue;
++marked_count;
insert_into_vector(melting_items, &df::item::id, item);
item->flags.bits.melt = true;
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++)
{
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))
@ -469,7 +471,8 @@ struct tradeview_hook : public df::viewscreen_tradegoodsst
{
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))

@ -72,7 +72,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" digFlood digAll0\n"
" disable digAll mode\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;
}

@ -11,98 +11,13 @@
#include <VTableInterpose.h>
#include "ColorText.h"
#include "uicommon.h"
#include "df/viewscreen_choose_start_sitest.h"
#include "df/interface_key.h"
using namespace DFHack;
struct EmbarkTool
{
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
*/
#define FOR_ITER_TOOLS(iter) for(auto iter = tools.begin(); iter != tools.end(); iter++)
void update_embark_sidebar (df::viewscreen_choose_start_sitest * screen)
{
@ -159,238 +74,317 @@ void resize_embark (df::viewscreen_choose_start_sitest * screen, int dx, int dy)
update_embark_sidebar(screen);
}
std::string sand_indicator = "";
bool sand_dirty = true; // Flag set when update is needed
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;
}
typedef df::viewscreen_choose_start_sitest start_sitest;
typedef std::set<df::interface_key> ikey_set;
int sticky_pos[] = {0, 0, 3, 3};
bool sticky_moved = false;
void sticky_save (df::viewscreen_choose_start_sitest * screen)
class EmbarkTool
{
sticky_pos[0] = screen->location.embark_pos_min.x;
sticky_pos[1] = screen->location.embark_pos_max.x;
sticky_pos[2] = screen->location.embark_pos_min.y;
sticky_pos[3] = screen->location.embark_pos_max.y;
}
void sticky_apply (df::viewscreen_choose_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 = sticky_pos[0];
screen->location.embark_pos_max.x = sticky_pos[1];
screen->location.embark_pos_min.y = sticky_pos[2];
screen->location.embark_pos_max.y = sticky_pos[3];
update_embark_sidebar(screen);
}
protected:
bool enabled;
public:
EmbarkTool()
:enabled(false)
{ }
virtual bool getEnabled() { return enabled; }
virtual void setEnabled(bool state) { enabled = state; }
virtual void toggleEnabled() { setEnabled(!enabled); }
virtual std::string getId() = 0;
virtual std::string getName() = 0;
virtual std::string getDesc() = 0;
virtual df::interface_key getToggleKey() = 0;
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) { };
};
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);
x += text.length();
}
virtual std::string getId() { return "id"; }
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
{
public:
virtual std::string getId() { return "anywhere"; }
virtual std::string getName() { return "Embark anywhere"; }
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)
{
bool prevent_default = false;
if (tool_enabled("anywhere"))
auto dim = Screen::getWindowSize();
int x = 20, y = dim.y - 2;
if (screen->page >= 0 && screen->page <= 4)
{
for (auto iter = input->begin(); iter != input->end(); iter++)
{
df::interface_key key = *iter;
if (key == df::interface_key::SETUP_EMBARK)
{
prevent_default = true;
this->in_embark_normal = 1;
}
}
OutputString(COLOR_WHITE, x, y, ": Embark!");
}
if (input->count(df::interface_key::CUSTOM_S))
}
virtual void before_feed(start_sitest* screen, ikey_set *input, bool &cancel)
{
if (input->count(df::interface_key::SETUP_EMBARK))
{
Screen::show(new embark_tools_settings);
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++)
df::interface_key key = *iter;
bool is_resize = true;
int dx = 0, dy = 0;
switch (key)
{
df::interface_key key = *iter;
bool is_resize = true;
int dx = 0, dy = 0;
switch (key)
{
case df::interface_key::SETUP_LOCAL_Y_UP:
dy = 1;
break;
case df::interface_key::SETUP_LOCAL_Y_DOWN:
dy = -1;
break;
case df::interface_key::SETUP_LOCAL_X_UP:
dx = 1;
break;
case df::interface_key::SETUP_LOCAL_X_DOWN:
dx = -1;
break;
default:
is_resize = false;
}
if (is_resize)
{
prevent_default = true;
resize_embark(this, dx, dy);
}
case df::interface_key::SETUP_LOCAL_Y_UP:
dy = 1;
break;
case df::interface_key::SETUP_LOCAL_Y_DOWN:
dy = -1;
break;
case df::interface_key::SETUP_LOCAL_X_UP:
dx = 1;
break;
case df::interface_key::SETUP_LOCAL_X_DOWN:
dx = -1;
break;
default:
is_resize = false;
}
if (is_resize)
{
cancel = true;
resize_embark(screen, dx, dy);
return;
}
}
};
};
if (tool_enabled("sticky"))
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++)
{
for (auto iter = input->begin(); iter != input->end(); iter++)
std::string fragment = iter->second;
if (fragment.find("SAND_") != std::string::npos)
{
df::interface_key key = *iter;
bool is_motion = false;
int dx = 0, dy = 0;
switch (key)
{
case df::interface_key::CURSOR_UP:
case df::interface_key::CURSOR_DOWN:
case df::interface_key::CURSOR_LEFT:
case df::interface_key::CURSOR_RIGHT:
case df::interface_key::CURSOR_UPLEFT:
case df::interface_key::CURSOR_UPRIGHT:
case df::interface_key::CURSOR_DOWNLEFT:
case df::interface_key::CURSOR_DOWNRIGHT:
case df::interface_key::CURSOR_UP_FAST:
case df::interface_key::CURSOR_DOWN_FAST:
case df::interface_key::CURSOR_LEFT_FAST:
case df::interface_key::CURSOR_RIGHT_FAST:
case df::interface_key::CURSOR_UPLEFT_FAST:
case df::interface_key::CURSOR_UPRIGHT_FAST:
case df::interface_key::CURSOR_DOWNLEFT_FAST:
case df::interface_key::CURSOR_DOWNRIGHT_FAST:
is_motion = true;
break;
default:
is_motion = false;
}
if (is_motion && !sticky_moved)
{
sticky_save(this);
sticky_moved = true;
}
indicator = "Sand";
break;
}
}
if (tool_enabled("sand"))
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)
{
sand_dirty = true;
OutputString(COLOR_YELLOW, x, y, indicator);
}
if (!prevent_default)
INTERPOSE_NEXT(feed)(input);
}
virtual void after_feed(start_sitest* screen, ikey_set* input)
{
dirty = true;
};
};
DEFINE_VMETHOD_INTERPOSE(void, render, ())
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 (tool_enabled("sticky") && sticky_moved)
if (screen->finder.finder_state != -1)
{
sticky_apply(this);
sticky_moved = false;
// Site finder is active - don't override default local position
return;
}
INTERPOSE_NEXT(render)();
auto dim = Screen::getWindowSize();
int x = 1,
y = dim.y - 5;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_S));
OutputString(COLOR_WHITE, x, y, ": Enabled: ");
std::list<std::string> parts;
for (int i = 0; i < NUM_TOOLS; i++)
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)
{
if (embark_tools[i].enabled)
{
parts.push_back(embark_tools[i].name);
parts.push_back(", ");
}
restore_position(screen);
moved_position = false;
}
if (parts.size())
}
virtual void before_feed(start_sitest* screen, ikey_set* input, bool &cancel) {
for (auto iter = input->begin(); iter != input->end(); iter++)
{
parts.pop_back(); // Remove trailing comma
for (auto iter = parts.begin(); iter != parts.end(); iter++)
df::interface_key key = *iter;
bool is_motion = false;
int dx = 0, dy = 0;
switch (key)
{
OutputString(COLOR_LIGHTMAGENTA, x, y, *iter);
case df::interface_key::CURSOR_UP:
case df::interface_key::CURSOR_DOWN:
case df::interface_key::CURSOR_LEFT:
case df::interface_key::CURSOR_RIGHT:
case df::interface_key::CURSOR_UPLEFT:
case df::interface_key::CURSOR_UPRIGHT:
case df::interface_key::CURSOR_DOWNLEFT:
case df::interface_key::CURSOR_DOWNRIGHT:
case df::interface_key::CURSOR_UP_FAST:
case df::interface_key::CURSOR_DOWN_FAST:
case df::interface_key::CURSOR_LEFT_FAST:
case df::interface_key::CURSOR_RIGHT_FAST:
case df::interface_key::CURSOR_UPLEFT_FAST:
case df::interface_key::CURSOR_UPRIGHT_FAST:
case df::interface_key::CURSOR_DOWNLEFT_FAST:
case df::interface_key::CURSOR_DOWNRIGHT_FAST:
is_motion = true;
break;
}
if (is_motion && !moved_position)
{
save_position(screen);
moved_position = true;
}
}
else
};
};
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)
{
OutputString(COLOR_LIGHTMAGENTA, x, y, "(none)");
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++;
}
if (tool_enabled("anywhere"))
};
void feed (std::set<df::interface_key> * input)
{
if (input->count(df::interface_key::SELECT) || input->count(df::interface_key::LEAVESCREEN))
{
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
OutputString(COLOR_WHITE, x, y, ": Embark!");
}
Screen::dismiss(this);
return;
}
if (tool_enabled("sand"))
for (auto iter = input->begin(); iter != input->end(); iter++)
{
if (sand_dirty)
{
sand_update(this);
}
x = dim.x - 28; y = 13;
if (this->page == 0)
df::interface_key key = *iter;
FOR_ITER_TOOLS(iter)
{
OutputString(COLOR_YELLOW, x, y, sand_indicator);
EmbarkTool* t = *iter;
if (t->getToggleKey() == key)
{
t->toggleEnabled();
}
}
}
}
};
};
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++)
FOR_ITER_TOOLS(iter)
{
if (embark_tools[i].id == tool_name)
EmbarkTool* tool = *iter;
if (tool->getId() == tool_name)
return true;
}
return false;
@ -398,10 +392,11 @@ bool tool_exists (std::string tool_name)
bool tool_enabled (std::string tool_name)
{
for (int i = 0; i < NUM_TOOLS; i++)
FOR_ITER_TOOLS(iter)
{
if (embark_tools[i].id == tool_name)
return embark_tools[i].enabled;
EmbarkTool* tool = *iter;
if (tool->getId() == tool_name)
return tool->getEnabled();
}
return false;
}
@ -409,42 +404,122 @@ bool tool_enabled (std::string tool_name)
bool tool_enable (std::string tool_name, bool enable_state)
{
int n = 0;
for (int i = 0; i < NUM_TOOLS; i++)
FOR_ITER_TOOLS(iter)
{
if (embark_tools[i].id == tool_name || tool_name == "all")
EmbarkTool* tool = *iter;
if (tool->getId() == tool_name || tool_name == "all")
{
embark_tools[i].enabled = enable_state;
tool_update(tool_name);
tool->setEnabled(enable_state);
n++;
}
}
return (bool)n;
}
void tool_update (std::string tool_name)
struct choose_start_site_hook : df::viewscreen_choose_start_sitest
{
// Called whenever a tool is enabled/disabled
if (tool_name == "sand")
typedef df::viewscreen_choose_start_sitest interpose_base;
void display_tool_status()
{
auto dim = Screen::getWindowSize();
int x = 1,
y = dim.y - 5;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_S));
OutputString(COLOR_WHITE, x, y, ": Enabled: ");
std::list<std::string> parts;
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
if (tool->getEnabled())
{
parts.push_back(tool->getName());
parts.push_back(", ");
}
}
if (parts.size())
{
parts.pop_back(); // Remove trailing comma
for (auto iter = parts.begin(); iter != parts.end(); iter++)
{
OutputString(COLOR_LIGHTMAGENTA, x, y, *iter);
}
}
else
{
OutputString(COLOR_LIGHTMAGENTA, x, y, "(none)");
}
}
void display_settings()
{
sand_dirty = true;
Screen::show(new embark_tools_settings);
}
}
/*
* Plugin management
*/
inline bool is_valid_page()
{
return (page >= 0 && page <= 4);
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{
bool cancel = false;
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
if (tool->getEnabled())
tool->before_feed(this, input, cancel);
}
if (cancel)
return;
INTERPOSE_NEXT(feed)(input);
if (input->count(df::interface_key::CUSTOM_S) && is_valid_page())
display_settings();
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
if (tool->getEnabled())
tool->after_feed(this, input);
}
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
if (tool->getEnabled())
tool->before_render(this);
}
INTERPOSE_NEXT(render)();
display_tool_status();
FOR_ITER_TOOLS(iter)
{
EmbarkTool* tool = *iter;
if (tool->getEnabled())
tool->after_render(this);
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, render);
DFHACK_PLUGIN("embark-tools");
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)
{
tools.push_back(new EmbarkAnywhere);
tools.push_back(new NanoEmbark);
tools.push_back(new SandIndicator);
tools.push_back(new StablePosition);
std::string help = "";
help += "embark-tools (enable/disable) tool [tool...]\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(
"embark-tools",
@ -504,10 +579,11 @@ command_result embark_tools_cmd (color_ostream &out, std::vector <std::string> &
if (is_enabled)
{
out << "Tool status:" << std::endl;
for (int i = 0; i < NUM_TOOLS; i++)
FOR_ITER_TOOLS(iter)
{
EmbarkTool t = embark_tools[i];
out << t.name << " (" << t.id << "): " << (t.enabled ? "Enabled" : "Disabled") << std::endl;
EmbarkTool* t = *iter;
out << t->getName() << " (" << t->getId() << "): "
<< (t->getEnabled() ? "Enabled" : "Disabled") << std::endl;
}
}
else

@ -113,8 +113,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
action->data.move.timer = 1;
break;
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.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;
case unit_action_type::Hold:
action->data.hold.timer = 1;

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

@ -673,6 +673,8 @@ void lightingEngineViewscreen::doOcupancyAndLights()
}
//df::tile_occupancy o = b->OccupancyAt(gpos);
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_material tileMat= ENUM_ATTR(tiletype,material,type);
@ -685,7 +687,7 @@ void lightingEngineViewscreen::doOcupancyAndLights()
{
curCell=rgbf(0,0,0);
}
else if(shape==df::tiletype_shape::WALL)
else if(is_wall)
{
if(tileMat==df::tiletype_material::FROZEN_LIQUID)
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);
}
else if(shape==df::tiletype_shape::EMPTY || shape==df::tiletype_shape::RAMP_TOP
|| shape==df::tiletype_shape::STAIR_DOWN || shape==df::tiletype_shape::STAIR_UPDOWN)
else if(!is_floor)
{
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
for(int i=0;i<block->block_events.size();i++)
{

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

@ -543,6 +543,18 @@ static VALUE rb_dfprint_str(VALUE self, VALUE s)
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)
{
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, "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_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, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);
rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1);

@ -179,7 +179,7 @@ module DFHack
if not onlylastpart
out << name.first_name if name.first_name != ''
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_FIRST; out.pop
end

@ -631,7 +631,11 @@ command_result df_strangemood (color_ostream &out, vector <string> & parameters)
// If no mood type was specified, pick one randomly
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))
{
@ -691,7 +695,6 @@ command_result df_strangemood (color_ostream &out, vector <string> & parameters)
unit->relations.mood_copy = unit->mood;
Gui::showAutoAnnouncement(announcement_type::STRANGE_MOOD, unit->pos, msg, color, bright);
unit->status.happiness = 100;
// TODO: make sure unit drops any wrestle items
unit->job.mood_timeout = 50000;
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)))
{
switch (rng.df_trandom(3))
switch (rng.df_trandom(2))
{
case 0:
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->quantity = 1;
break;
case 2:
// in older versions, they would request additional skulls
// in 0.34.11, the request becomes "nothing"
break;
}
}
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 == 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 == screw_pump_direction::FromNorth)? "Southward":
(roller->direction == screw_pump_direction::FromEast)? "Westward":
(roller->direction == screw_pump_direction::FromSouth)? "Northward":
(roller->direction == 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);
}

@ -363,6 +363,7 @@ struct dimension_cloth_hook : df::item_clothst {
IMPLEMENT_VMETHOD_INTERPOSE(dimension_cloth_hook, subtractDimension);
/*
// Unit updates are executed based on an action divisor variable,
// which is computed from the alive unit count and has range 10-100.
static int adjust_unit_divisor(int value) {
@ -541,6 +542,7 @@ struct military_training_ct_hook : df::activity_event_combat_trainingst {
}
}
};
*/
/*
IMPLEMENT_VMETHOD_INTERPOSE(military_training_ct_hook, process);

@ -131,15 +131,24 @@ local function zoomed_searcher(startn, end_or_sz)
end
local finder_searches = {}
local function exec_finder(finder, names)
local function exec_finder(finder, names, validators)
if type(names) ~= 'table' then
names = { names }
end
if type(validators) ~= 'table' then
validators = { validators }
end
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
table.insert(finder_searches, v)
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
if search then
@ -505,6 +514,7 @@ end
local function is_valid_world(world)
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.history.figures, 4)
or not ms.is_valid_vector(world.features.map_features, 4)
@ -776,11 +786,6 @@ end
--
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
if os_type == 'windows' then
zone = zoomed_searcher('crime_next_id', 512)
@ -839,7 +844,14 @@ local function find_ui_selected_unit()
end
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
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_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' })
exec_finder(find_announcements, 'announcements')
exec_finder(find_d_init, 'd_init')
exec_finder(find_enabler, 'enabler')
exec_finder(find_gps, 'gps')
exec_finder(find_d_init, 'd_init', is_valid_d_init)
exec_finder(find_enabler, 'enabler', is_valid_enabler)
exec_finder(find_gps, 'gps', is_valid_gps)
print('\nCompound globals (need loaded world):\n')
exec_finder(find_world, 'world')
exec_finder(find_ui, 'ui')
print('\nPlease load the save previously processed with prepare-save.')
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_build_selector, 'ui_build_selector')
exec_finder(find_init, 'init')
exec_finder(find_init, 'init', is_valid_init)
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.
character encoding:
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
the document with. 'Notepad++' is a freely available program which
can do this using the following steps:
@ -99,23 +99,16 @@ local function format_for_forum(strin)
end
if flerb == 'textviewer' then
print(scrn)
printall(scrn)
local lines = scrn.formatted_text
local lines = scrn.src_text
local line = ""
if lines ~= nil then
local log = io.open('forumdwarves.txt', 'a')
log:write("[color=silver]")
for n,x in ipairs(lines) do
print(x)
printall(x)
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
if x ~= nil and x.value ~= nil then
log:write(format_for_forum(x.value), ' ')
end
end
log:write("[/color]\n")
log:close()

@ -24,13 +24,13 @@ if args.help then
print this help message
repeat -cancel 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
if not specified, it's set to the first argument after -command
-time delay -timeUnits units
delay is some positive integer
units is some valid time unit for dfhack.timeout(delay,timeUnits,function)
-command ...
-command [ ... ]
specify the command to be run
]])
return

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