Merge remote-tracking branch 'q/master' & sync structures

develop
Kelly Martin 2012-10-31 14:08:07 -05:00
commit 9ab0547af3
45 changed files with 2801 additions and 418 deletions

@ -145,7 +145,7 @@ include_directories(depends/clsocket/src)
add_subdirectory(depends)
find_package(Docutils)
#find_package(Docutils)
#set (RST_FILES
#"Readme"

@ -1239,6 +1239,12 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.items.getContainedItems(item)</tt></p>
<p>Returns a list of items contained in this one.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getHolderBuilding(item)</tt></p>
<p>Returns the holder building or <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getHolderUnit(item)</tt></p>
<p>Returns the holder unit or <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.moveToGround(item,pos)</tt></p>
<p>Move the item to the ground at position. Returns <em>false</em> if impossible.</p>
</li>
@ -1714,6 +1720,15 @@ global environment, persistent between calls to the script.</p>
If destination overlaps a completely invalid memory region, or another error
occurs, returns false.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.patchBytes(write_table[, verify_table])</tt></p>
<p>The first argument must be a lua table, which is interpreted as a mapping from
memory addresses to byte values that should be stored there. The second argument
may be a similar table of values that need to be checked before writing anything.</p>
<p>The function takes care to either apply all of <tt class="docutils literal">write_table</tt>, or none of it.
An empty <tt class="docutils literal">write_table</tt> with a nonempty <tt class="docutils literal">verify_table</tt> can be used to reasonably
safely check if the memory contains certain values.</p>
<p>Returns <em>true</em> if successful, or <em>nil, error_msg, address</em> if not.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.memmove(dest,src,count)</tt></p>
<p>Wraps the standard memmove function. Accepts both numbers and refs as pointers.</p>
</li>

@ -1019,6 +1019,14 @@ Items module
Returns a list of items contained in this one.
* ``dfhack.items.getHolderBuilding(item)``
Returns the holder building or *nil*.
* ``dfhack.items.getHolderUnit(item)``
Returns the holder unit or *nil*.
* ``dfhack.items.moveToGround(item,pos)``
Move the item to the ground at position. Returns *false* if impossible.
@ -1581,6 +1589,18 @@ and are only documented here for completeness:
If destination overlaps a completely invalid memory region, or another error
occurs, returns false.
* ``dfhack.internal.patchBytes(write_table[, verify_table])``
The first argument must be a lua table, which is interpreted as a mapping from
memory addresses to byte values that should be stored there. The second argument
may be a similar table of values that need to be checked before writing anything.
The function takes care to either apply all of ``write_table``, or none of it.
An empty ``write_table`` with a nonempty ``verify_table`` can be used to reasonably
safely check if the memory contains certain values.
Returns *true* if successful, or *nil, error_msg, address* if not.
* ``dfhack.internal.memmove(dest,src,count)``
Wraps the standard memmove function. Accepts both numbers and refs as pointers.

@ -10,9 +10,17 @@ DFHack future
- fastdwarf: new mode using debug flags, and some internal consistency fixes.
- added a small stand-alone utility for applying and removing binary patches.
- removebadthoughts: add --dry-run option
New commands:
- fix-armory: activates a plugin that makes armor stands and weapon racks be used again.
New GUI scripts:
- gui/guide-path: displays the cached path for minecart Guide orders.
- gui/workshop-job: displays inputs of a workshop job and allows tweaking them.
- gui/workflow: a front-end for the workflow plugin.
- gui/assign-rack: works together with a binary patch to fix weapon racks.
Workflow plugin:
- properly considers minecarts assigned to routes busy.
- code for deducing job outputs rewritten in lua for flexibility.
- logic fix: collecting webs produces silk, and ungathered webs are not thread.
DFHack v0.34.11-r2

@ -434,94 +434,99 @@ access DF memory and allow for easier development of new tools.</p>
<li><a class="reference internal" href="#fixmerchants" id="id79">fixmerchants</a></li>
<li><a class="reference internal" href="#fixveins" id="id80">fixveins</a></li>
<li><a class="reference internal" href="#tweak" id="id81">tweak</a></li>
<li><a class="reference internal" href="#fix-armory" id="id82">fix-armory</a></li>
</ul>
</li>
<li><a class="reference internal" href="#mode-switch-and-reclaim" id="id82">Mode switch and reclaim</a><ul>
<li><a class="reference internal" href="#lair" id="id83">lair</a></li>
<li><a class="reference internal" href="#mode" id="id84">mode</a></li>
<li><a class="reference internal" href="#mode-switch-and-reclaim" id="id83">Mode switch and reclaim</a><ul>
<li><a class="reference internal" href="#lair" id="id84">lair</a></li>
<li><a class="reference internal" href="#mode" id="id85">mode</a></li>
</ul>
</li>
<li><a class="reference internal" href="#visualizer-and-data-export" id="id85">Visualizer and data export</a><ul>
<li><a class="reference internal" href="#ssense-stonesense" id="id86">ssense / stonesense</a></li>
<li><a class="reference internal" href="#mapexport" id="id87">mapexport</a></li>
<li><a class="reference internal" href="#dwarfexport" id="id88">dwarfexport</a></li>
<li><a class="reference internal" href="#visualizer-and-data-export" id="id86">Visualizer and data export</a><ul>
<li><a class="reference internal" href="#ssense-stonesense" id="id87">ssense / stonesense</a></li>
<li><a class="reference internal" href="#mapexport" id="id88">mapexport</a></li>
<li><a class="reference internal" href="#dwarfexport" id="id89">dwarfexport</a></li>
</ul>
</li>
<li><a class="reference internal" href="#job-management" id="id89">Job management</a><ul>
<li><a class="reference internal" href="#job" id="id90">job</a></li>
<li><a class="reference internal" href="#job-material" id="id91">job-material</a></li>
<li><a class="reference internal" href="#job-duplicate" id="id92">job-duplicate</a></li>
<li><a class="reference internal" href="#workflow" id="id93">workflow</a><ul>
<li><a class="reference internal" href="#function" id="id94">Function</a></li>
<li><a class="reference internal" href="#constraint-examples" id="id95">Constraint examples</a></li>
<li><a class="reference internal" href="#job-management" id="id90">Job management</a><ul>
<li><a class="reference internal" href="#job" id="id91">job</a></li>
<li><a class="reference internal" href="#job-material" id="id92">job-material</a></li>
<li><a class="reference internal" href="#job-duplicate" id="id93">job-duplicate</a></li>
<li><a class="reference internal" href="#workflow" id="id94">workflow</a><ul>
<li><a class="reference internal" href="#function" id="id95">Function</a></li>
<li><a class="reference internal" href="#constraint-examples" id="id96">Constraint examples</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#fortress-activity-management" id="id96">Fortress activity management</a><ul>
<li><a class="reference internal" href="#seedwatch" id="id97">seedwatch</a></li>
<li><a class="reference internal" href="#zone" id="id98">zone</a><ul>
<li><a class="reference internal" href="#usage-with-single-units" id="id99">Usage with single units</a></li>
<li><a class="reference internal" href="#usage-with-filters" id="id100">Usage with filters</a></li>
<li><a class="reference internal" href="#mass-renaming" id="id101">Mass-renaming</a></li>
<li><a class="reference internal" href="#cage-zones" id="id102">Cage zones</a></li>
<li><a class="reference internal" href="#examples" id="id103">Examples</a></li>
<li><a class="reference internal" href="#fortress-activity-management" id="id97">Fortress activity management</a><ul>
<li><a class="reference internal" href="#seedwatch" id="id98">seedwatch</a></li>
<li><a class="reference internal" href="#zone" id="id99">zone</a><ul>
<li><a class="reference internal" href="#usage-with-single-units" id="id100">Usage with single units</a></li>
<li><a class="reference internal" href="#usage-with-filters" id="id101">Usage with filters</a></li>
<li><a class="reference internal" href="#mass-renaming" id="id102">Mass-renaming</a></li>
<li><a class="reference internal" href="#cage-zones" id="id103">Cage zones</a></li>
<li><a class="reference internal" href="#examples" id="id104">Examples</a></li>
</ul>
</li>
<li><a class="reference internal" href="#autonestbox" id="id104">autonestbox</a></li>
<li><a class="reference internal" href="#autobutcher" id="id105">autobutcher</a></li>
<li><a class="reference internal" href="#autolabor" id="id106">autolabor</a></li>
<li><a class="reference internal" href="#autonestbox" id="id105">autonestbox</a></li>
<li><a class="reference internal" href="#autobutcher" id="id106">autobutcher</a></li>
<li><a class="reference internal" href="#autolabor" id="id107">autolabor</a></li>
</ul>
</li>
<li><a class="reference internal" href="#other" id="id107">Other</a><ul>
<li><a class="reference internal" href="#catsplosion" id="id108">catsplosion</a></li>
<li><a class="reference internal" href="#dfusion" id="id109">dfusion</a></li>
<li><a class="reference internal" href="#misery" id="id110">misery</a></li>
<li><a class="reference internal" href="#other" id="id108">Other</a><ul>
<li><a class="reference internal" href="#catsplosion" id="id109">catsplosion</a></li>
<li><a class="reference internal" href="#dfusion" id="id110">dfusion</a></li>
<li><a class="reference internal" href="#misery" id="id111">misery</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#scripts" id="id111">Scripts</a><ul>
<li><a class="reference internal" href="#fix" id="id112">fix/*</a></li>
<li><a class="reference internal" href="#gui" id="id113">gui/*</a></li>
<li><a class="reference internal" href="#quicksave" id="id114">quicksave</a></li>
<li><a class="reference internal" href="#setfps" id="id115">setfps</a></li>
<li><a class="reference internal" href="#siren" id="id116">siren</a></li>
<li><a class="reference internal" href="#growcrops" id="id117">growcrops</a></li>
<li><a class="reference internal" href="#removebadthoughts" id="id118">removebadthoughts</a></li>
<li><a class="reference internal" href="#slayrace" id="id119">slayrace</a></li>
<li><a class="reference internal" href="#magmasource" id="id120">magmasource</a></li>
<li><a class="reference internal" href="#digfort" id="id121">digfort</a></li>
<li><a class="reference internal" href="#superdwarf" id="id122">superdwarf</a></li>
<li><a class="reference internal" href="#drainaquifer" id="id123">drainaquifer</a></li>
<li><a class="reference internal" href="#deathcause" id="id124">deathcause</a></li>
<li><a class="reference internal" href="#scripts" id="id112">Scripts</a><ul>
<li><a class="reference internal" href="#fix" id="id113">fix/*</a></li>
<li><a class="reference internal" href="#gui" id="id114">gui/*</a></li>
<li><a class="reference internal" href="#quicksave" id="id115">quicksave</a></li>
<li><a class="reference internal" href="#setfps" id="id116">setfps</a></li>
<li><a class="reference internal" href="#siren" id="id117">siren</a></li>
<li><a class="reference internal" href="#growcrops" id="id118">growcrops</a></li>
<li><a class="reference internal" href="#removebadthoughts" id="id119">removebadthoughts</a></li>
<li><a class="reference internal" href="#slayrace" id="id120">slayrace</a></li>
<li><a class="reference internal" href="#magmasource" id="id121">magmasource</a></li>
<li><a class="reference internal" href="#digfort" id="id122">digfort</a></li>
<li><a class="reference internal" href="#superdwarf" id="id123">superdwarf</a></li>
<li><a class="reference internal" href="#drainaquifer" id="id124">drainaquifer</a></li>
<li><a class="reference internal" href="#deathcause" id="id125">deathcause</a></li>
</ul>
</li>
<li><a class="reference internal" href="#in-game-interface-tools" id="id125">In-game interface tools</a><ul>
<li><a class="reference internal" href="#dwarf-manipulator" id="id126">Dwarf Manipulator</a></li>
<li><a class="reference internal" href="#gui-liquids" id="id127">gui/liquids</a></li>
<li><a class="reference internal" href="#gui-mechanisms" id="id128">gui/mechanisms</a></li>
<li><a class="reference internal" href="#gui-rename" id="id129">gui/rename</a></li>
<li><a class="reference internal" href="#gui-room-list" id="id130">gui/room-list</a></li>
<li><a class="reference internal" href="#gui-choose-weapons" id="id131">gui/choose-weapons</a></li>
<li><a class="reference internal" href="#in-game-interface-tools" id="id126">In-game interface tools</a><ul>
<li><a class="reference internal" href="#dwarf-manipulator" id="id127">Dwarf Manipulator</a></li>
<li><a class="reference internal" href="#gui-liquids" id="id128">gui/liquids</a></li>
<li><a class="reference internal" href="#gui-mechanisms" id="id129">gui/mechanisms</a></li>
<li><a class="reference internal" href="#gui-rename" id="id130">gui/rename</a></li>
<li><a class="reference internal" href="#gui-room-list" id="id131">gui/room-list</a></li>
<li><a class="reference internal" href="#gui-choose-weapons" id="id132">gui/choose-weapons</a></li>
<li><a class="reference internal" href="#gui-guide-path" id="id133">gui/guide-path</a></li>
<li><a class="reference internal" href="#gui-workshop-job" id="id134">gui/workshop-job</a></li>
<li><a class="reference internal" href="#gui-workflow" id="id135">gui/workflow</a></li>
<li><a class="reference internal" href="#gui-assign-rack" id="id136">gui/assign-rack</a></li>
</ul>
</li>
<li><a class="reference internal" href="#behavior-mods" id="id132">Behavior Mods</a><ul>
<li><a class="reference internal" href="#siege-engine" id="id133">Siege Engine</a><ul>
<li><a class="reference internal" href="#rationale" id="id134">Rationale</a></li>
<li><a class="reference internal" href="#configuration-ui" id="id135">Configuration UI</a></li>
<li><a class="reference internal" href="#behavior-mods" id="id137">Behavior Mods</a><ul>
<li><a class="reference internal" href="#siege-engine" id="id138">Siege Engine</a><ul>
<li><a class="reference internal" href="#rationale" id="id139">Rationale</a></li>
<li><a class="reference internal" href="#configuration-ui" id="id140">Configuration UI</a></li>
</ul>
</li>
<li><a class="reference internal" href="#power-meter" id="id136">Power Meter</a></li>
<li><a class="reference internal" href="#steam-engine" id="id137">Steam Engine</a><ul>
<li><a class="reference internal" href="#id1" id="id138">Rationale</a></li>
<li><a class="reference internal" href="#construction" id="id139">Construction</a></li>
<li><a class="reference internal" href="#operation" id="id140">Operation</a></li>
<li><a class="reference internal" href="#explosions" id="id141">Explosions</a></li>
<li><a class="reference internal" href="#save-files" id="id142">Save files</a></li>
<li><a class="reference internal" href="#power-meter" id="id141">Power Meter</a></li>
<li><a class="reference internal" href="#steam-engine" id="id142">Steam Engine</a><ul>
<li><a class="reference internal" href="#id1" id="id143">Rationale</a></li>
<li><a class="reference internal" href="#construction" id="id144">Construction</a></li>
<li><a class="reference internal" href="#operation" id="id145">Operation</a></li>
<li><a class="reference internal" href="#explosions" id="id146">Explosions</a></li>
<li><a class="reference internal" href="#save-files" id="id147">Save files</a></li>
</ul>
</li>
<li><a class="reference internal" href="#add-spatter" id="id143">Add Spatter</a></li>
<li><a class="reference internal" href="#add-spatter" id="id148">Add Spatter</a></li>
</ul>
</li>
</ul>
@ -732,6 +737,8 @@ by 'reveal hell'. This is nice for digging under rivers.</p>
<li>'fastdwarf 1 1' enables both</li>
<li>'fastdwarf 0' disables both</li>
<li>'fastdwarf 1' enables speedydwarf and disables teledwarf</li>
<li>'fastdwarf 2 ...' sets a native debug flag in the game memory
that implements an even more aggressive version of speedydwarf.</li>
</ul>
</blockquote>
</div>
@ -1846,11 +1853,49 @@ to make them stand out more in the list.</td>
</tbody>
</table>
</div>
<div class="section" id="fix-armory">
<h3><a class="toc-backref" href="#id82">fix-armory</a></h3>
<p>Enables a fix for storage of squad equipment in barracks.</p>
<p>Specifically, it prevents your haulers from moving that equipment
to stockpiles, and instead queues jobs to store it on weapon racks,
armor stands, and in containers.</p>
<div class="note">
<p class="first admonition-title">Note</p>
<p>In order to actually be used, weapon racks have to be patched and
assigned to a squad. See documentation for <tt class="docutils literal"><span class="pre">gui/assign-rack</span></tt> below.</p>
<p class="last">Also, the default capacity of armor stands is way too low, so check out
<a class="reference external" href="http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445">http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445</a>
for a patch addressing that too.</p>
</div>
<p>Note that the buildings in the armory are used as follows:</p>
<ul class="simple">
<li>Weapon racks when fixed are used to store any assigned weapons.
Each rack belongs to a specific squad, and can store up to 5 weapons.</li>
<li>Armor stands belong to specific squad members and are used for
armor and shields. By default one stand can store one item of each
type (hence one boot or gauntlet); if patched, the limit is raised to 2,
which should be sufficient.</li>
<li>Cabinets are used to store assigned clothing for a specific squad member.
They are <strong>never</strong> used to store owned clothing.</li>
<li>Chests (boxes, etc) are used for a flask, backpack or quiver assigned
to the squad member. Due to a bug, food is dropped out of the backpack
when it is stored.</li>
</ul>
<p>Contrary to the common misconception, all these uses are controlled by the
<em>Individual Equipment</em> usage flag; the Squad Equipment mode means nothing.</p>
<div class="warning">
<p class="first admonition-title">Warning</p>
<p class="last">Although armor stands, cabinets and chests properly belong only to one
squad member, the owner of the building used to create the barracks will
randomly use any containers inside the room. Thus, it is recommended to
always create the armory from a weapon rack.</p>
</div>
</div>
</div>
<div class="section" id="mode-switch-and-reclaim">
<h2><a class="toc-backref" href="#id82">Mode switch and reclaim</a></h2>
<h2><a class="toc-backref" href="#id83">Mode switch and reclaim</a></h2>
<div class="section" id="lair">
<h3><a class="toc-backref" href="#id83">lair</a></h3>
<h3><a class="toc-backref" href="#id84">lair</a></h3>
<p>This command allows you to mark the map as 'monster lair', preventing item
scatter on abandon. When invoked as 'lair reset', it does the opposite.</p>
<p>Unlike reveal, this command doesn't save the information about tiles - you
@ -1870,7 +1915,7 @@ won't be able to restore state of real monster lairs using 'lair reset'.</p>
</blockquote>
</div>
<div class="section" id="mode">
<h3><a class="toc-backref" href="#id84">mode</a></h3>
<h3><a class="toc-backref" href="#id85">mode</a></h3>
<p>This command lets you see and change the game mode directly.
Not all combinations are good for every situation and most of them will
produce undesirable results. There are a few good ones though.</p>
@ -1890,9 +1935,9 @@ You just created a returnable mountain home and gained an adventurer.</p>
</div>
</div>
<div class="section" id="visualizer-and-data-export">
<h2><a class="toc-backref" href="#id85">Visualizer and data export</a></h2>
<h2><a class="toc-backref" href="#id86">Visualizer and data export</a></h2>
<div class="section" id="ssense-stonesense">
<h3><a class="toc-backref" href="#id86">ssense / stonesense</a></h3>
<h3><a class="toc-backref" href="#id87">ssense / stonesense</a></h3>
<p>An isometric visualizer that runs in a second window. This requires working
graphics acceleration and at least a dual core CPU (otherwise it will slow
down DF).</p>
@ -1905,19 +1950,19 @@ thread: <a class="reference external" href="http://www.bay12forums.com/smf/index
<a class="reference external" href="http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository">http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository</a></p>
</div>
<div class="section" id="mapexport">
<h3><a class="toc-backref" href="#id87">mapexport</a></h3>
<h3><a class="toc-backref" href="#id88">mapexport</a></h3>
<p>Export the current loaded map as a file. This will be eventually usable
with visualizers.</p>
</div>
<div class="section" id="dwarfexport">
<h3><a class="toc-backref" href="#id88">dwarfexport</a></h3>
<h3><a class="toc-backref" href="#id89">dwarfexport</a></h3>
<p>Export dwarves to RuneSmith-compatible XML.</p>
</div>
</div>
<div class="section" id="job-management">
<h2><a class="toc-backref" href="#id89">Job management</a></h2>
<h2><a class="toc-backref" href="#id90">Job management</a></h2>
<div class="section" id="job">
<h3><a class="toc-backref" href="#id90">job</a></h3>
<h3><a class="toc-backref" href="#id91">job</a></h3>
<p>Command for general job query and manipulation.</p>
<dl class="docutils">
<dt>Options:</dt>
@ -1936,7 +1981,7 @@ in a workshop, or the unit/jobs screen.</dd>
</dl>
</div>
<div class="section" id="job-material">
<h3><a class="toc-backref" href="#id91">job-material</a></h3>
<h3><a class="toc-backref" href="#id92">job-material</a></h3>
<p>Alter the material of the selected job.</p>
<p>Invoked as:</p>
<pre class="literal-block">
@ -1954,7 +1999,7 @@ over the first available choice with the matching material.</li>
</blockquote>
</div>
<div class="section" id="job-duplicate">
<h3><a class="toc-backref" href="#id92">job-duplicate</a></h3>
<h3><a class="toc-backref" href="#id93">job-duplicate</a></h3>
<dl class="docutils">
<dt>Duplicate the selected job in a workshop:</dt>
<dd><ul class="first last simple">
@ -1965,7 +2010,7 @@ instantly duplicates the job.</li>
</dl>
</div>
<div class="section" id="workflow">
<h3><a class="toc-backref" href="#id93">workflow</a></h3>
<h3><a class="toc-backref" href="#id94">workflow</a></h3>
<p>Manage control of repeat jobs.</p>
<p>Usage:</p>
<blockquote>
@ -1989,7 +2034,7 @@ Otherwise, enables or disables any of the following options:</p>
</dl>
</blockquote>
<div class="section" id="function">
<h4><a class="toc-backref" href="#id94">Function</a></h4>
<h4><a class="toc-backref" href="#id95">Function</a></h4>
<p>When the plugin is enabled, it protects all repeat jobs from removal.
If they do disappear due to any cause, they are immediately re-added to their
workshop and suspended.</p>
@ -1998,9 +2043,11 @@ produce that kind of item are automatically suspended and resumed as the item
amount goes above or below the limit. The gap specifies how much below the limit
the amount has to drop before jobs are resumed; this is intended to reduce
the frequency of jobs being toggled.</p>
<p>Check out the <tt class="docutils literal">gui/workflow</tt> script below for a simple front-end integrated
in the game UI.</p>
</div>
<div class="section" id="constraint-examples">
<h4><a class="toc-backref" href="#id95">Constraint examples</a></h4>
<h4><a class="toc-backref" href="#id96">Constraint examples</a></h4>
<p>Keep metal bolts within 900-1000, and wood/bone within 150-200.</p>
<pre class="literal-block">
workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
@ -2039,15 +2086,15 @@ command.
</div>
</div>
<div class="section" id="fortress-activity-management">
<h2><a class="toc-backref" href="#id96">Fortress activity management</a></h2>
<h2><a class="toc-backref" href="#id97">Fortress activity management</a></h2>
<div class="section" id="seedwatch">
<h3><a class="toc-backref" href="#id97">seedwatch</a></h3>
<h3><a class="toc-backref" href="#id98">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>
</div>
<div class="section" id="zone">
<h3><a class="toc-backref" href="#id98">zone</a></h3>
<h3><a class="toc-backref" href="#id99">zone</a></h3>
<p>Helps a bit with managing activity zones (pens, pastures and pits) and cages.</p>
<p>Options:</p>
<blockquote>
@ -2146,7 +2193,7 @@ for war/hunt). Negatable.</td>
</table>
</blockquote>
<div class="section" id="usage-with-single-units">
<h4><a class="toc-backref" href="#id99">Usage with single units</a></h4>
<h4><a class="toc-backref" href="#id100">Usage with single units</a></h4>
<p>One convenient way to use the zone tool is to bind the command 'zone assign' to
a hotkey, maybe also the command 'zone set'. Place the in-game cursor over
a pen/pasture or pit, use 'zone set' to mark it. Then you can select units
@ -2155,7 +2202,7 @@ and use 'zone assign' to assign them to their new home. Allows pitting your
own dwarves, by the way.</p>
</div>
<div class="section" id="usage-with-filters">
<h4><a class="toc-backref" href="#id100">Usage with filters</a></h4>
<h4><a class="toc-backref" href="#id101">Usage with filters</a></h4>
<p>All filters can be used together with the 'assign' command.</p>
<p>Restrictions: It's not possible to assign units who are inside built cages
or chained because in most cases that won't be desirable anyways.
@ -2173,14 +2220,14 @@ are not properly added to your own stocks; slaughtering them should work).</p>
<p>Most filters can be negated (e.g. 'not grazer' -&gt; race is not a grazer).</p>
</div>
<div class="section" id="mass-renaming">
<h4><a class="toc-backref" href="#id101">Mass-renaming</a></h4>
<h4><a class="toc-backref" href="#id102">Mass-renaming</a></h4>
<p>Using the 'nick' command you can set the same nickname for multiple units.
If used without 'assign', 'all' or 'count' it will rename all units in the
current default target zone. Combined with 'assign', 'all' or 'count' (and
further optional filters) it will rename units matching the filter conditions.</p>
</div>
<div class="section" id="cage-zones">
<h4><a class="toc-backref" href="#id102">Cage zones</a></h4>
<h4><a class="toc-backref" href="#id103">Cage zones</a></h4>
<p>Using the 'tocages' command you can assign units to a set of cages, for example
a room next to your butcher shop(s). They will be spread evenly among available
cages to optimize hauling to and butchering from them. For this to work you need
@ -2191,7 +2238,7 @@ would make no sense, but can be used together with 'nick' or 'remnick' and all
the usual filters.</p>
</div>
<div class="section" id="examples">
<h4><a class="toc-backref" href="#id103">Examples</a></h4>
<h4><a class="toc-backref" href="#id104">Examples</a></h4>
<dl class="docutils">
<dt><tt class="docutils literal">zone assign all own ALPACA minage 3 maxage 10</tt></dt>
<dd>Assign all own alpacas who are between 3 and 10 years old to the selected
@ -2217,7 +2264,7 @@ on the current default zone.</dd>
</div>
</div>
<div class="section" id="autonestbox">
<h3><a class="toc-backref" href="#id104">autonestbox</a></h3>
<h3><a class="toc-backref" href="#id105">autonestbox</a></h3>
<p>Assigns unpastured female egg-layers to nestbox zones. Requires that you create
pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox
must be in the top left corner. Only 1 unit will be assigned per pen, regardless
@ -2246,7 +2293,7 @@ frames between runs.</td>
</blockquote>
</div>
<div class="section" id="autobutcher">
<h3><a class="toc-backref" href="#id105">autobutcher</a></h3>
<h3><a class="toc-backref" href="#id106">autobutcher</a></h3>
<p>Assigns lifestock for slaughter once it reaches a specific count. Requires that
you add the target race(s) to a watch list. Only tame units will be processed.</p>
<p>Named units will be completely ignored (to protect specific animals from
@ -2354,7 +2401,7 @@ autobutcher.bat
</pre>
</div>
<div class="section" id="autolabor">
<h3><a class="toc-backref" href="#id106">autolabor</a></h3>
<h3><a class="toc-backref" href="#id107">autolabor</a></h3>
<p>Automatically manage dwarf labors.</p>
<p>When enabled, autolabor periodically checks your dwarves and enables or
disables labors. It tries to keep as many dwarves as possible busy but
@ -2368,14 +2415,14 @@ while it is enabled.</p>
</div>
</div>
<div class="section" id="other">
<h2><a class="toc-backref" href="#id107">Other</a></h2>
<h2><a class="toc-backref" href="#id108">Other</a></h2>
<div class="section" id="catsplosion">
<h3><a class="toc-backref" href="#id108">catsplosion</a></h3>
<h3><a class="toc-backref" href="#id109">catsplosion</a></h3>
<p>Makes cats just <em>multiply</em>. It is not a good idea to run this more than once or
twice.</p>
</div>
<div class="section" id="dfusion">
<h3><a class="toc-backref" href="#id109">dfusion</a></h3>
<h3><a class="toc-backref" href="#id110">dfusion</a></h3>
<p>This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin.</p>
<p>See the bay12 thread for details: <a class="reference external" href="http://www.bay12forums.com/smf/index.php?topic=69682.15">http://www.bay12forums.com/smf/index.php?topic=69682.15</a></p>
<p>Confirmed working DFusion plugins:</p>
@ -2397,7 +2444,7 @@ twice.</p>
</div>
</div>
<div class="section" id="misery">
<h3><a class="toc-backref" href="#id110">misery</a></h3>
<h3><a class="toc-backref" href="#id111">misery</a></h3>
<p>When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).</p>
<p>Usage:</p>
<table class="docutils field-list" frame="void" rules="none">
@ -2421,7 +2468,7 @@ twice.</p>
</div>
</div>
<div class="section" id="scripts">
<h1><a class="toc-backref" href="#id111">Scripts</a></h1>
<h1><a class="toc-backref" href="#id112">Scripts</a></h1>
<p>Lua or ruby scripts placed in the hack/scripts/ directory are considered for
execution as if they were native DFHack commands. They are listed at the end
of the 'ls' command output.</p>
@ -2430,7 +2477,7 @@ only be listed by ls if called as 'ls -a'. This is intended as a way to hide
scripts that are obscure, developer-oriented, or should be used as keybindings.</p>
<p>Some notable scripts:</p>
<div class="section" id="fix">
<h2><a class="toc-backref" href="#id112">fix/*</a></h2>
<h2><a class="toc-backref" href="#id113">fix/*</a></h2>
<p>Scripts in this subdirectory fix various bugs and issues, some of them obscure.</p>
<ul>
<li><p class="first">fix/dead-units</p>
@ -2456,22 +2503,22 @@ caused by autodump bugs or other hacking mishaps.</p>
</ul>
</div>
<div class="section" id="gui">
<h2><a class="toc-backref" href="#id113">gui/*</a></h2>
<h2><a class="toc-backref" href="#id114">gui/*</a></h2>
<p>Scripts that implement dialogs inserted into the main game window are put in this
directory.</p>
</div>
<div class="section" id="quicksave">
<h2><a class="toc-backref" href="#id114">quicksave</a></h2>
<h2><a class="toc-backref" href="#id115">quicksave</a></h2>
<p>If called in dwarf mode, makes DF immediately auto-save the game by setting a flag
normally used in seasonal auto-save.</p>
</div>
<div class="section" id="setfps">
<h2><a class="toc-backref" href="#id115">setfps</a></h2>
<h2><a class="toc-backref" href="#id116">setfps</a></h2>
<p>Run <tt class="docutils literal">setfps &lt;number&gt;</tt> to set the FPS cap at runtime, in case you want to watch
combat in slow motion or something :)</p>
</div>
<div class="section" id="siren">
<h2><a class="toc-backref" href="#id116">siren</a></h2>
<h2><a class="toc-backref" href="#id117">siren</a></h2>
<p>Wakes up sleeping units, cancels breaks and stops parties either everywhere,
or in the burrows given as arguments. In return, adds bad thoughts about
noise, tiredness and lack of protection. Also, the units with interrupted
@ -2479,7 +2526,7 @@ breaks will go on break again a lot sooner. The script is intended for
emergencies, e.g. when a siege appears, and all your military is partying.</p>
</div>
<div class="section" id="growcrops">
<h2><a class="toc-backref" href="#id117">growcrops</a></h2>
<h2><a class="toc-backref" href="#id118">growcrops</a></h2>
<p>Instantly grow seeds inside farming plots.</p>
<p>With no argument, this command list the various seed types currently in
use in your farming plots.
@ -2491,7 +2538,7 @@ growcrops plump 40
</pre>
</div>
<div class="section" id="removebadthoughts">
<h2><a class="toc-backref" href="#id118">removebadthoughts</a></h2>
<h2><a class="toc-backref" href="#id119">removebadthoughts</a></h2>
<p>This script remove negative thoughts from your dwarves. Very useful against
tantrum spirals.</p>
<p>The script can target a single creature, when used with the <tt class="docutils literal">him</tt> argument,
@ -2505,7 +2552,7 @@ but in the short term your dwarves will get much more joyful.</p>
quickly after you unpause.</p>
</div>
<div class="section" id="slayrace">
<h2><a class="toc-backref" href="#id119">slayrace</a></h2>
<h2><a class="toc-backref" href="#id120">slayrace</a></h2>
<p>Kills any unit of a given race.</p>
<p>With no argument, lists the available races.</p>
<p>With the special argument <tt class="docutils literal">him</tt>, targets only the selected creature.</p>
@ -2531,7 +2578,7 @@ slayrace elve magma
</pre>
</div>
<div class="section" id="magmasource">
<h2><a class="toc-backref" href="#id120">magmasource</a></h2>
<h2><a class="toc-backref" href="#id121">magmasource</a></h2>
<p>Create an infinite magma source on a tile.</p>
<p>This script registers a map tile as a magma source, and every 12 game ticks
that tile receives 1 new unit of flowing magma.</p>
@ -2546,7 +2593,7 @@ To remove all placed sources, call <tt class="docutils literal">magmasource stop
<p>With no argument, this command shows an help message and list existing sources.</p>
</div>
<div class="section" id="digfort">
<h2><a class="toc-backref" href="#id121">digfort</a></h2>
<h2><a class="toc-backref" href="#id122">digfort</a></h2>
<p>A script to designate an area for digging according to a plan in csv format.</p>
<p>This script, inspired from quickfort, can designate an area for digging.
Your plan should be stored in a .csv file like this:</p>
@ -2564,7 +2611,7 @@ To skip a row in your design, use a single <tt class="docutils literal">;</tt>.<
<p>The script takes the plan filename, starting from the root df folder.</p>
</div>
<div class="section" id="superdwarf">
<h2><a class="toc-backref" href="#id122">superdwarf</a></h2>
<h2><a class="toc-backref" href="#id123">superdwarf</a></h2>
<p>Similar to fastdwarf, per-creature.</p>
<p>To make any creature superfast, target it ingame using 'v' and:</p>
<pre class="literal-block">
@ -2574,17 +2621,17 @@ superdwarf add
<p>This plugin also shortens the 'sleeping' and 'on break' periods of targets.</p>
</div>
<div class="section" id="drainaquifer">
<h2><a class="toc-backref" href="#id123">drainaquifer</a></h2>
<h2><a class="toc-backref" href="#id124">drainaquifer</a></h2>
<p>Remove all 'aquifer' tag from the map blocks. Irreversible.</p>
</div>
<div class="section" id="deathcause">
<h2><a class="toc-backref" href="#id124">deathcause</a></h2>
<h2><a class="toc-backref" href="#id125">deathcause</a></h2>
<p>Focus a body part ingame, and this script will display the cause of death of
the creature.</p>
</div>
</div>
<div class="section" id="in-game-interface-tools">
<h1><a class="toc-backref" href="#id125">In-game interface tools</a></h1>
<h1><a class="toc-backref" href="#id126">In-game interface tools</a></h1>
<p>These tools work by displaying dialogs or overlays in the game window, and
are mostly implemented by lua scripts.</p>
<div class="note">
@ -2595,7 +2642,7 @@ display the word &quot;DFHack&quot; on the screen somewhere while active.</p>
guideline because it arguably just fixes small usability bugs in the game UI.</p>
</div>
<div class="section" id="dwarf-manipulator">
<h2><a class="toc-backref" href="#id126">Dwarf Manipulator</a></h2>
<h2><a class="toc-backref" href="#id127">Dwarf Manipulator</a></h2>
<p>Implemented by the manipulator plugin. To activate, open the unit screen and
press 'l'.</p>
<p>This tool implements a Dwarf Therapist-like interface within the game UI. The
@ -2631,13 +2678,13 @@ cursor onto that cell instead of toggling it.</li>
directly to the main dwarf mode screen.</p>
</div>
<div class="section" id="gui-liquids">
<h2><a class="toc-backref" href="#id127">gui/liquids</a></h2>
<h2><a class="toc-backref" href="#id128">gui/liquids</a></h2>
<p>To use, bind to a key and activate in the 'k' mode.</p>
<p>While active, use the suggested keys to switch the usual liquids parameters, and Enter
to select the target area and apply changes.</p>
</div>
<div class="section" id="gui-mechanisms">
<h2><a class="toc-backref" href="#id128">gui/mechanisms</a></h2>
<h2><a class="toc-backref" href="#id129">gui/mechanisms</a></h2>
<p>To use, bind to a key and activate in the 'q' mode.</p>
<p>Lists mechanisms connected to the building, and their links. Navigating the list centers
the view on the relevant linked buildings.</p>
@ -2646,7 +2693,7 @@ 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-rename">
<h2><a class="toc-backref" href="#id129">gui/rename</a></h2>
<h2><a class="toc-backref" href="#id130">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>
@ -2662,14 +2709,14 @@ It is also possible to rename zones from the 'i' menu.</p>
<p>The <tt class="docutils literal">building</tt> or <tt class="docutils literal">unit</tt> options are automatically assumed when in relevant ui state.</p>
</div>
<div class="section" id="gui-room-list">
<h2><a class="toc-backref" href="#id130">gui/room-list</a></h2>
<h2><a class="toc-backref" href="#id131">gui/room-list</a></h2>
<p>To use, bind to a key and activate in the 'q' mode, either immediately or after opening
the assign owner page.</p>
<p>The script lists other rooms owned by the same owner, or by the unit selected in the assign
list, and allows unassigning them.</p>
</div>
<div class="section" id="gui-choose-weapons">
<h2><a class="toc-backref" href="#id131">gui/choose-weapons</a></h2>
<h2><a class="toc-backref" href="#id132">gui/choose-weapons</a></h2>
<p>Bind to a key, 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
in the selected squad or position to use a specific weapon type matching the assigned
@ -2678,9 +2725,84 @@ only that entry, and does it even if it is not 'individual choice'.</p>
<p>Rationale: individual choice seems to be unreliable when there is a weapon shortage,
and may lead to inappropriate weapons being selected.</p>
</div>
<div class="section" id="gui-guide-path">
<h2><a class="toc-backref" href="#id133">gui/guide-path</a></h2>
<p>Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.</p>
<p>The script displays the cached path that will be used by the order; the game
computes it when the order is executed for the first time.</p>
</div>
<div class="section" id="gui-workshop-job">
<h2><a class="toc-backref" href="#id134">gui/workshop-job</a></h2>
<p>Bind to a key, and activate with a job selected in a workshop in the 'q' mode.</p>
<p>The script shows a list of the input reagents of the selected job, and allows changing
them like the <tt class="docutils literal">job <span class="pre">item-type</span></tt> and <tt class="docutils literal">job <span class="pre">item-material</span></tt> commands.</p>
<p>Specifically, pressing the 'i' key pops up a dialog that lets you select an item
type from a list. Pressing 'm', unless the item type does not allow a material,
lets you choose a material.</p>
<div class="warning">
<p class="first admonition-title">Warning</p>
<p class="last">Due to the way input reagent matching works in DF, you must select an item type
if you select a material, or the material will be matched incorrectly in some cases.
If you press 'm' without choosing an item type, the script will auto-choose it
if there is only one valid choice, or pop up an error message box instead of the
material selection dialog.</p>
</div>
<p>Note that both materials and item types presented in the dialogs are filtered
by the job input flags, and even the selected item type for material selection,
or material for item type selection. Many jobs would let you select only one
input item type.</p>
<p>For example, if you choose a <em>plant</em> input item type for your prepare meal job,
it will only let you select cookable materials.</p>
<p>If you choose a <em>barrel</em> item instead (meaning things stored in barrels, like
drink or milk), it will let you select any material, since in this case the
material is matched against the barrel itself. Then, if you select, say, iron,
and then try to change the input item type, now it won't let you select <em>plant</em>;
you have to unset the material first.</p>
</div>
<div class="section" id="gui-workflow">
<h2><a class="toc-backref" href="#id135">gui/workflow</a></h2>
<p>Bind to a key, and activate with a job selected in a workshop in the 'q' mode.</p>
<p>This script provides a simple interface to constraints managed by the workflow
plugin. When active, it displays a list of all constraints applicable to the
current job, and their current status.</p>
<p>A constraint specifies a certain range to be compared against either individual
<em>item</em> or whole <em>stack</em> count, an item type and optionally a material. When the
current count is below the lower bound of the range, the job is resumed; if it
is above or equal to the top bound, it will be suspended. Within the range, the
specific constraint has no effect on the job; others may still affect it.</p>
<p>Pressing 'c' switches the current constraint between counting stacks or items.
Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the
bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting
items and expanding the range each gives a 5x bonus).</p>
<p>Pressing 'n' produces a list of possible outputs of this job as guessed by
workflow, and lets you create a new constraint by just choosing one. If you
don't see the choice you want in the list, it likely means you have to adjust
the job material first using <tt class="docutils literal">job <span class="pre">item-material</span></tt> or <tt class="docutils literal"><span class="pre">gui/workshop-job</span></tt>,
as described in <tt class="docutils literal">workflow</tt> documentation above. In this manner, this feature
can be used for troubleshooting jobs that don't match the right constraints.</p>
</div>
<div class="section" id="gui-assign-rack">
<h2><a class="toc-backref" href="#id136">gui/assign-rack</a></h2>
<p>Bind to a key, and activate when viewing a weapon rack in the 'q' mode.</p>
<p>This script is part of a group of related fixes to make the armory storage
work again. The existing issues are:</p>
<ul class="simple">
<li>Weapon racks have to each be assigned to a specific squad, like with
beds/boxes/armor stands and individual squad members, but nothing in
the game does this. This issue is what this script addresses.</li>
<li>Even if assigned by the script, <strong>the game will unassign the racks again without a binary patch</strong>.
Check the comments for this bug to get it:
<a class="reference external" href="http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445">http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445</a></li>
<li>Haulers still take equpment stored in the armory away to the stockpiles,
unless the <tt class="docutils literal"><span class="pre">fix-armory</span></tt> plugin above is used.</li>
</ul>
<p>The script interface simply lets you designate one of the squads that
are assigned to the barracks/armory containing the selected stand as
the intended user.</p>
</div>
</div>
<div class="section" id="behavior-mods">
<h1><a class="toc-backref" href="#id132">Behavior Mods</a></h1>
<h1><a class="toc-backref" href="#id137">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>
@ -2691,20 +2813,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="#id133">Siege Engine</a></h2>
<h2><a class="toc-backref" href="#id138">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="#id134">Rationale</a></h3>
<h3><a class="toc-backref" href="#id139">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="#id135">Configuration UI</a></h3>
<h3><a class="toc-backref" href="#id140">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 and activate after selecting a siege engine in 'q' mode.</p>
<p>The main mode displays the current target, selected ammo item type, linked stockpiles and
@ -2725,7 +2847,7 @@ menu.</p>
</div>
</div>
<div class="section" id="power-meter">
<h2><a class="toc-backref" href="#id136">Power Meter</a></h2>
<h2><a class="toc-backref" href="#id141">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
@ -2734,11 +2856,11 @@ key and activate after selecting Pressure Plate 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="#id137">Steam Engine</a></h2>
<h2><a class="toc-backref" href="#id142">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="#id138">Rationale</a></h3>
<h3><a class="toc-backref" href="#id143">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
@ -2749,7 +2871,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="#id139">Construction</a></h3>
<h3><a class="toc-backref" href="#id144">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>
@ -2773,7 +2895,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="#id140">Operation</a></h3>
<h3><a class="toc-backref" href="#id145">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
@ -2804,7 +2926,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="#id141">Explosions</a></h3>
<h3><a class="toc-backref" href="#id146">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
@ -2813,7 +2935,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="#id142">Save files</a></h3>
<h3><a class="toc-backref" href="#id147">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
@ -2824,7 +2946,7 @@ being generated.</p>
</div>
</div>
<div class="section" id="add-spatter">
<h2><a class="toc-backref" href="#id143">Add Spatter</a></h2>
<h2><a class="toc-backref" href="#id148">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

@ -231,6 +231,8 @@ Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and p
* 'fastdwarf 1 1' enables both
* 'fastdwarf 0' disables both
* 'fastdwarf 1' enables speedydwarf and disables teledwarf
* 'fastdwarf 2 ...' sets a native debug flag in the game memory
that implements an even more aggressive version of speedydwarf.
Game interface
==============
@ -1070,6 +1072,51 @@ Subcommands that persist until disabled or DF quit:
:military-color-assigned: Color squad candidates already assigned to other squads in brown/green
to make them stand out more in the list.
fix-armory
----------
Enables a fix for storage of squad equipment in barracks.
Specifically, it prevents your haulers from moving that equipment
to stockpiles, and instead queues jobs to store it on weapon racks,
armor stands, and in containers.
.. note::
In order to actually be used, weapon racks have to be patched and
assigned to a squad. See documentation for ``gui/assign-rack`` below.
Also, the default capacity of armor stands is way too low, so check out
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
for a patch addressing that too.
Note that the buildings in the armory are used as follows:
* Weapon racks when fixed are used to store any assigned weapons.
Each rack belongs to a specific squad, and can store up to 5 weapons.
* Armor stands belong to specific squad members and are used for
armor and shields. By default one stand can store one item of each
type (hence one boot or gauntlet); if patched, the limit is raised to 2,
which should be sufficient.
* Cabinets are used to store assigned clothing for a specific squad member.
They are **never** used to store owned clothing.
* Chests (boxes, etc) are used for a flask, backpack or quiver assigned
to the squad member. Due to a bug, food is dropped out of the backpack
when it is stored.
Contrary to the common misconception, all these uses are controlled by the
*Individual Equipment* usage flag; the Squad Equipment mode means nothing.
.. warning::
Although armor stands, cabinets and chests properly belong only to one
squad member, the owner of the building used to create the barracks will
randomly use any containers inside the room. Thus, it is recommended to
always create the armory from a weapon rack.
Mode switch and reclaim
=======================
@ -1213,6 +1260,9 @@ amount goes above or below the limit. The gap specifies how much below the limit
the amount has to drop before jobs are resumed; this is intended to reduce
the frequency of jobs being toggled.
Check out the ``gui/workflow`` script below for a simple front-end integrated
in the game UI.
Constraint examples
...................
@ -1896,6 +1946,102 @@ Rationale: individual choice seems to be unreliable when there is a weapon short
and may lead to inappropriate weapons being selected.
gui/guide-path
==============
Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.
The script displays the cached path that will be used by the order; the game
computes it when the order is executed for the first time.
gui/workshop-job
================
Bind to a key, and activate with a job selected in a workshop in the 'q' mode.
The script shows a list of the input reagents of the selected job, and allows changing
them like the ``job item-type`` and ``job item-material`` commands.
Specifically, pressing the 'i' key pops up a dialog that lets you select an item
type from a list. Pressing 'm', unless the item type does not allow a material,
lets you choose a material.
.. warning::
Due to the way input reagent matching works in DF, you must select an item type
if you select a material, or the material will be matched incorrectly in some cases.
If you press 'm' without choosing an item type, the script will auto-choose it
if there is only one valid choice, or pop up an error message box instead of the
material selection dialog.
Note that both materials and item types presented in the dialogs are filtered
by the job input flags, and even the selected item type for material selection,
or material for item type selection. Many jobs would let you select only one
input item type.
For example, if you choose a *plant* input item type for your prepare meal job,
it will only let you select cookable materials.
If you choose a *barrel* item instead (meaning things stored in barrels, like
drink or milk), it will let you select any material, since in this case the
material is matched against the barrel itself. Then, if you select, say, iron,
and then try to change the input item type, now it won't let you select *plant*;
you have to unset the material first.
gui/workflow
============
Bind to a key, and activate with a job selected in a workshop in the 'q' mode.
This script provides a simple interface to constraints managed by the workflow
plugin. When active, it displays a list of all constraints applicable to the
current job, and their current status.
A constraint specifies a certain range to be compared against either individual
*item* or whole *stack* count, an item type and optionally a material. When the
current count is below the lower bound of the range, the job is resumed; if it
is above or equal to the top bound, it will be suspended. Within the range, the
specific constraint has no effect on the job; others may still affect it.
Pressing 'c' switches the current constraint between counting stacks or items.
Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the
bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting
items and expanding the range each gives a 5x bonus).
Pressing 'n' produces a list of possible outputs of this job as guessed by
workflow, and lets you create a new constraint by just choosing one. If you
don't see the choice you want in the list, it likely means you have to adjust
the job material first using ``job item-material`` or ``gui/workshop-job``,
as described in ``workflow`` documentation above. In this manner, this feature
can be used for troubleshooting jobs that don't match the right constraints.
gui/assign-rack
===============
Bind to a key, and activate when viewing a weapon rack in the 'q' mode.
This script is part of a group of related fixes to make the armory storage
work again. The existing issues are:
* Weapon racks have to each be assigned to a specific squad, like with
beds/boxes/armor stands and individual squad members, but nothing in
the game does this. This issue is what this script addresses.
* Even if assigned by the script, **the game will unassign the racks again without a binary patch**.
Check the comments for this bug to get it:
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
* Haulers still take equpment stored in the armory away to the stockpiles,
unless the ``fix-armory`` plugin above is used.
The script interface simply lets you designate one of the squads that
are assigned to the barracks/armory containing the selected stand as
the intended user.
=============
Behavior Mods
=============

@ -78,6 +78,12 @@ keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path
# workshop job details
keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job
# workflow front-end
keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
# assign weapon racks to squads so that they can be used
keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack
############################
# UI and game logic tweaks #
############################

@ -869,7 +869,6 @@ bool Core::Init()
// Init global object pointers
df::global::InitGlobals();
init_screen_module(this);
cerr << "Initializing Console.\n";
// init the console.
@ -895,6 +894,7 @@ bool Core::Init()
*/
// initialize data defs
virtual_identity::Init(this);
init_screen_module(this);
// initialize common lua context
Lua::Core::Init(con);
@ -1615,15 +1615,27 @@ void ClassNameCheck::getKnownClassNames(std::vector<std::string> &names)
names.push_back(*it);
}
bool Process::patchMemory(void *target, const void* src, size_t count)
MemoryPatcher::MemoryPatcher(Process *p_) : p(p_)
{
if (!p)
p = Core::getInstance().p;
}
MemoryPatcher::~MemoryPatcher()
{
close();
}
bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write)
{
uint8_t *sptr = (uint8_t*)target;
uint8_t *eptr = sptr + count;
// Find the valid memory ranges
std::vector<t_memrange> ranges;
getMemRanges(ranges);
if (ranges.empty())
p->getMemRanges(ranges);
// Find the ranges that this area spans
unsigned start = 0;
while (start < ranges.size() && ranges[start].end <= sptr)
start++;
@ -1646,23 +1658,45 @@ bool Process::patchMemory(void *target, const void* src, size_t count)
return false;
// Apply writable permissions & update
bool ok = true;
for (unsigned i = start; i < end && ok; i++)
for (unsigned i = start; i < end; i++)
{
t_memrange perms = ranges[i];
auto &perms = ranges[i];
if ((perms.write || !write) && perms.read)
continue;
save.push_back(perms);
perms.write = perms.read = true;
if (!setPermisions(perms, perms))
ok = false;
if (!p->setPermisions(perms, perms))
return false;
}
if (ok)
memmove(target, src, count);
return true;
}
bool MemoryPatcher::write(void *target, const void *src, size_t size)
{
if (!makeWritable(target, size))
return false;
memmove(target, src, size);
return true;
}
for (unsigned i = start; i < end && ok; i++)
setPermisions(ranges[i], ranges[i]);
void MemoryPatcher::close()
{
for (size_t i = 0; i < save.size(); i++)
p->setPermisions(save[i], save[i]);
save.clear();
ranges.clear();
};
bool Process::patchMemory(void *target, const void* src, size_t count)
{
MemoryPatcher patcher(this);
return ok;
return patcher.write(target, src, count);
}
/*******************************************************************************

@ -500,7 +500,9 @@ static void OpenPersistent(lua_State *state)
* Material info lookup *
************************/
static void push_matinfo(lua_State *state, MaterialInfo &info)
static int DFHACK_MATINFO_TOKEN = 0;
void Lua::Push(lua_State *state, MaterialInfo &info)
{
if (!info.isValid())
{
@ -509,7 +511,7 @@ static void push_matinfo(lua_State *state, MaterialInfo &info)
}
lua_newtable(state);
lua_pushvalue(state, lua_upvalueindex(1));
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_MATINFO_TOKEN);
lua_setmetatable(state, -2);
lua_pushinteger(state, info.type);
@ -564,7 +566,7 @@ static int dfhack_matinfo_find(lua_State *state)
info.find(tokens);
}
push_matinfo(state, info);
Lua::Push(state, info);
return 1;
}
@ -632,7 +634,7 @@ static int dfhack_matinfo_decode(lua_State *state)
{
MaterialInfo info;
decode_matinfo(state, &info, true);
push_matinfo(state, info);
Lua::Push(state, info);
return 1;
}
@ -711,6 +713,9 @@ static void OpenMatinfo(lua_State *state)
{
luaL_getsubtable(state, lua_gettop(state), "matinfo");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_MATINFO_TOKEN);
lua_dup(state);
luaL_setfuncs(state, dfhack_matinfo_funcs, 1);
@ -937,6 +942,8 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getOwner),
WRAPM(Items, setOwner),
WRAPM(Items, getContainer),
WRAPM(Items, getHolderBuilding),
WRAPM(Items, getHolderUnit),
WRAPM(Items, getDescription),
WRAPM(Items, isCasteMaterial),
WRAPM(Items, getSubtypeCount),
@ -1530,6 +1537,81 @@ static int internal_patchMemory(lua_State *L)
return 1;
}
static int internal_patchBytes(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
lua_settop(L, 2);
MemoryPatcher patcher;
if (!lua_isnil(L, 2))
{
luaL_checktype(L, 2, LUA_TTABLE);
lua_pushnil(L);
while (lua_next(L, 2))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
int isnum;
uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum);
if (!isnum)
luaL_error(L, "invalid value in verify table");
lua_pop(L, 1);
if (!patcher.verifyAccess(addr, 1, false))
{
lua_pushnil(L);
lua_pushstring(L, "invalid verify address");
lua_pushvalue(L, -3);
return 3;
}
if (*addr != value)
{
lua_pushnil(L);
lua_pushstring(L, "wrong verify value");
lua_pushvalue(L, -3);
return 3;
}
}
}
lua_pushnil(L);
while (lua_next(L, 1))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
int isnum;
uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum);
if (!isnum)
luaL_error(L, "invalid value in write table");
lua_pop(L, 1);
if (!patcher.verifyAccess(addr, 1, true))
{
lua_pushnil(L);
lua_pushstring(L, "invalid write address");
lua_pushvalue(L, -3);
return 3;
}
}
lua_pushnil(L);
while (lua_next(L, 1))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
uint8_t value = (uint8_t)lua_tounsigned(L, -1);
lua_pop(L, 1);
*addr = value;
}
lua_pushboolean(L, true);
return 1;
}
static int internal_memmove(lua_State *L)
{
void *dest = checkaddr(L, 1);
@ -1621,6 +1703,7 @@ static const luaL_Reg dfhack_internal_funcs[] = {
{ "getVTable", internal_getVTable },
{ "getMemRanges", internal_getMemRanges },
{ "patchMemory", internal_patchMemory },
{ "patchBytes", internal_patchBytes },
{ "memmove", internal_memmove },
{ "memcmp", internal_memcmp },
{ "memscan", internal_memscan },

@ -107,7 +107,8 @@ static void signal_typeid_error(color_ostream *out, lua_State *state,
type_identity *type, const char *msg,
int val_index, bool perr, bool signal)
{
std::string error = stl_sprintf(msg, type->getFullName().c_str());
std::string typestr = type ? type->getFullName() : "any pointer";
std::string error = stl_sprintf(msg, typestr.c_str());
if (signal)
{
@ -134,6 +135,8 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_
if (lua_isnil(state, val_index))
return NULL;
if (lua_islightuserdata(state, val_index) && !lua_touserdata(state, val_index))
return NULL;
void *rv = get_object_internal(state, type, val_index, exact_type, false);

@ -148,6 +148,11 @@ bool prefix_matches(const std::string &prefix, const std::string &key, std::stri
return false;
}
int random_int(int max)
{
return int(int64_t(rand())*max/(int64_t(RAND_MAX)+1));
}
#ifdef LINUX_BUILD // Linux
uint64_t GetTimeMs64()
{

@ -166,12 +166,12 @@ void *virtual_identity::get_vmethod_ptr(int idx)
return vtable[idx];
}
bool virtual_identity::set_vmethod_ptr(int idx, void *ptr)
bool virtual_identity::set_vmethod_ptr(MemoryPatcher &patcher, int idx, void *ptr)
{
assert(idx >= 0);
void **vtable = (void**)vtable_ptr;
if (!vtable) return NULL;
return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*));
return patcher.write(&vtable[idx], &ptr, sizeof(void*));
}
/*
@ -344,7 +344,9 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
auto last = this;
while (last->prev) last = last->prev;
from->set_vmethod_ptr(vmethod_idx, last->saved_chain);
MemoryPatcher patcher;
from->set_vmethod_ptr(patcher, vmethod_idx, last->saved_chain);
// Unlink the chains
child_hosts.erase(from);
@ -379,13 +381,15 @@ bool VMethodInterposeLinkBase::apply(bool enable)
assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
// Apply the new method ptr
MemoryPatcher patcher;
set_chain(old_ptr);
if (next_link)
{
next_link->set_chain(interpose_method);
}
else if (!host->set_vmethod_ptr(vmethod_idx, interpose_method))
else if (!host->set_vmethod_ptr(patcher, vmethod_idx, interpose_method))
{
set_chain(NULL);
return false;
@ -459,7 +463,7 @@ bool VMethodInterposeLinkBase::apply(bool enable)
{
auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == old_link);
nhost->set_vmethod_ptr(vmethod_idx, interpose_method);
nhost->set_vmethod_ptr(patcher, vmethod_idx, interpose_method);
nhost->interpose_list[vmethod_idx] = this;
}
@ -496,9 +500,11 @@ void VMethodInterposeLinkBase::remove()
}
else
{
MemoryPatcher patcher;
// Remove from the list in the identity and vtable
host->interpose_list[vmethod_idx] = prev;
host->set_vmethod_ptr(vmethod_idx, saved_chain);
host->set_vmethod_ptr(patcher, vmethod_idx, saved_chain);
for (auto it = child_next.begin(); it != child_next.end(); ++it)
{
@ -515,7 +521,7 @@ void VMethodInterposeLinkBase::remove()
auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == this);
nhost->interpose_list[vmethod_idx] = prev;
nhost->set_vmethod_ptr(vmethod_idx, saved_chain);
nhost->set_vmethod_ptr(patcher, vmethod_idx, saved_chain);
if (prev)
prev->child_hosts.insert(nhost);
}

@ -171,8 +171,9 @@ BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len)
state |= Applied;
else
{
cerr << std::hex << bv.offset << ": " << bv.old_val << " " << bv.new_val
<< ", but currently " << cv << std::dec << endl;
cerr << std::hex << bv.offset << ": "
<< unsigned(bv.old_val) << " " << unsigned(bv.new_val)
<< ", but currently " << unsigned(cv) << std::dec << endl;
return Conflict;
}
}

@ -294,6 +294,7 @@ namespace DFHack
#endif
class DFHACK_EXPORT VMethodInterposeLinkBase;
class MemoryPatcher;
class DFHACK_EXPORT virtual_identity : public struct_identity {
static std::map<void*, virtual_identity*> known;
@ -313,7 +314,7 @@ namespace DFHack
bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); }
void *get_vmethod_ptr(int index);
bool set_vmethod_ptr(int index, void *ptr);
bool set_vmethod_ptr(MemoryPatcher &patcher, int index, void *ptr);
public:
virtual_identity(size_t size, TAllocateFn alloc,

@ -36,6 +36,7 @@ distribution.
namespace DFHack {
class function_identity_base;
struct MaterialInfo;
namespace Units {
struct NoblePosition;
@ -283,6 +284,7 @@ namespace DFHack {namespace Lua {
DFHACK_EXPORT void Push(lua_State *state, df::coord obj);
DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj);
void Push(lua_State *state, const Units::NoblePosition &pos);
DFHACK_EXPORT void Push(lua_State *state, MaterialInfo &info);
template<class T> inline void Push(lua_State *state, T *ptr) {
PushDFObject(state, ptr);
}

@ -315,5 +315,22 @@ namespace DFHack
// Get list of names given to ClassNameCheck constructors.
static void getKnownClassNames(std::vector<std::string> &names);
};
class DFHACK_EXPORT MemoryPatcher
{
Process *p;
std::vector<t_memrange> ranges, save;
public:
MemoryPatcher(Process *p = NULL);
~MemoryPatcher();
bool verifyAccess(void *target, size_t size, bool write = false);
bool makeWritable(void *target, size_t size) {
return verifyAccess(target, size, true);
}
bool write(void *target, const void *src, size_t size);
void close();
};
}
#endif

@ -331,6 +331,8 @@ inline T clip_range(T a, T1 minv, T2 maxv) {
return a;
}
DFHACK_EXPORT int random_int(int max);
/**
* Returns the amount of milliseconds elapsed since the UNIX epoch.
* Works on both windows and linux.

@ -151,6 +151,11 @@ DFHACK_EXPORT df::item *getContainer(df::item *item);
/// which items does it contain?
DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector<df::item*> *items);
/// which building holds it?
DFHACK_EXPORT df::building *getHolderBuilding(df::item *item);
/// which unit holds it?
DFHACK_EXPORT df::unit *getHolderUnit(df::item *item);
/// Returns the true position of the item.
DFHACK_EXPORT df::coord getPosition(df::item *item);
@ -161,7 +166,7 @@ DFHACK_EXPORT bool moveToGround(MapExtras::MapCache &mc, df::item *item, df::coo
DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container);
DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode);
DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit,
df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1);
df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Hauled, int body_part = -1);
/// Makes the item removed and marked for garbage collection
DFHACK_EXPORT bool remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat = false);

@ -65,10 +65,14 @@ end
local function apply_attrs(obj, attrs, init_table)
for k,v in pairs(attrs) do
if v == DEFAULT_NIL then
v = nil
local init_v = init_table[k]
if init_v ~= nil then
obj[k] = init_v
elseif v == DEFAULT_NIL then
obj[k] = nil
else
obj[k] = v
end
obj[k] = init_table[k] or v
end
end

@ -513,17 +513,18 @@ function Screen:sendInputToParent(...)
end
end
function Screen:show(below)
function Screen:show(parent)
if self._native then
error("This screen is already on display")
end
self:onAboutToShow(below)
if not dscreen.show(self, below) then
parent = parent or dfhack.gui.getCurViewscreen(true)
self:onAboutToShow(parent)
if not dscreen.show(self, parent.child) then
error('Could not show screen')
end
end
function Screen:onAboutToShow()
function Screen:onAboutToShow(parent)
end
function Screen:onShow()

@ -373,10 +373,8 @@ function DwarfOverlay:simulateCursorMovement(keys, anchor)
end
end
function DwarfOverlay:onAboutToShow(below)
local screen = dfhack.gui.getCurViewscreen()
if below then screen = below.parent end
if not df.viewscreen_dwarfmodest:is_instance(screen) then
function DwarfOverlay:onAboutToShow(parent)
if not df.viewscreen_dwarfmodest:is_instance(parent) then
error("This screen requires the main dwarfmode view")
end
end

@ -216,7 +216,7 @@ local function is_disabled(token)
(token.enabled ~= nil and not getval(token.enabled))
end
function render_text(obj,dc,x0,y0,pen,dpen)
function render_text(obj,dc,x0,y0,pen,dpen,disabled)
local width = 0
for iline,line in ipairs(obj.text_lines) do
local x = 0
@ -246,11 +246,12 @@ function render_text(obj,dc,x0,y0,pen,dpen)
local keypen
if dc then
if is_disabled(token) then
dc:pen(getval(token.dpen) or dpen)
local tpen = getval(token.pen)
if disabled or is_disabled(token) then
dc:pen(getval(token.dpen) or tpen or dpen)
keypen = COLOR_GREEN
else
dc:pen(getval(token.pen) or pen)
dc:pen(tpen or pen)
keypen = COLOR_LIGHTGREEN
end
end
@ -305,6 +306,8 @@ Label = defclass(Label, Widget)
Label.ATTRS{
text_pen = COLOR_WHITE,
text_dpen = COLOR_DARKGREY,
disabled = DEFAULT_NIL,
enabled = DEFAULT_NIL,
auto_height = true,
auto_width = false,
}
@ -346,11 +349,13 @@ function Label:getTextWidth()
end
function Label:onRenderBody(dc)
render_text(self,dc,0,0,self.text_pen,self.text_dpen)
render_text(self,dc,0,0,self.text_pen,self.text_dpen,is_disabled(self))
end
function Label:onInput(keys)
return check_text_keys(self, keys)
if not is_disabled(self) then
return check_text_keys(self, keys)
end
end
----------
@ -376,7 +381,6 @@ SECONDSCROLL = {
List.ATTRS{
text_pen = COLOR_CYAN,
cursor_pen = COLOR_LIGHTCYAN,
cursor_dpen = DEFAULT_NIL,
inactive_pen = DEFAULT_NIL,
on_select = DEFAULT_NIL,
on_submit = DEFAULT_NIL,
@ -417,7 +421,9 @@ function List:getChoices()
end
function List:getSelected()
return self.selected, self.choices[self.selected]
if #self.choices > 0 then
return self.selected, self.choices[self.selected]
end
end
function List:getContentWidth()
@ -485,29 +491,32 @@ function List:onRenderBody(dc)
for i = top,iend do
local obj = choices[i]
local current = (i == self.selected)
local cur_pen = self.text_pen
local cur_dpen = cur_pen
local cur_pen = self.cursor_pen
local cur_dpen = self.text_pen
if current and active then
cur_pen = self.cursor_pen
cur_dpen = self.cursor_dpen or self.text_pen
elseif current then
if not self.active then
cur_pen = self.inactive_pen or self.cursor_pen
cur_dpen = self.inactive_pen or self.text_pen
end
local y = (i - top)*self.row_height
if iw and obj.icon then
dc:seek(0, y)
if type(obj.icon) == 'table' then
dc:char(nil,obj.icon)
else
dc:string(obj.icon, obj.icon_pen or cur_pen)
local icon = getval(obj.icon)
if icon then
dc:seek(0, y)
if type(icon) == 'table' then
dc:char(nil,icon)
else
if current then
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
else
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
end
end
end
end
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen)
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current)
if obj.key then
local keystr = gui.getKeyDisplay(obj.key)
@ -620,7 +629,9 @@ end
function FilteredList:getSelected()
local i,v = self.list:getSelected()
return map_opttab(self.choice_index, i), v
if i then
return map_opttab(self.choice_index, i), v
end
end
function FilteredList:getContentWidth()

@ -283,6 +283,33 @@ function clone_with_default(obj,default,force)
return rv
end
-- Parse an integer value into a bitfield table
function parse_bitfield_int(value, type_ref)
if value == 0 then
return nil
end
local res = {}
for i,v in ipairs(type_ref) do
if bit32.extract(value, i) ~= 0 then
res[v] = true
end
end
return res
end
-- List the enabled flag names in the bitfield table
function list_bitfield_flags(bitfield, list)
list = list or {}
if bitfield then
for name,val in pairs(bitfield) do
if val then
table.insert(list, name)
end
end
end
return list
end
-- Sort a vector or lua table
function sort_vector(vector,field,cmp)
local fcmp = compare_field(field,cmp)
@ -304,16 +331,26 @@ end
-- Linear search
function linear_index(vector,obj)
function linear_index(vector,key,field)
local min,max
if df.isvalid(vector) then
min,max = 0,#vector-1
else
min,max = 1,#vector
end
for i=min,max do
if vector[i] == obj then
return i
if field then
for i=min,max do
local obj = vector[i]
if obj[field] == key then
return i, obj
end
end
else
for i=min,max do
local obj = vector[i]
if obj == key then
return i, obj
end
end
end
return nil

@ -762,7 +762,7 @@ static void markBuildingTiles(df::building *bld, bool remove)
static void linkRooms(df::building *bld)
{
auto &vec = world->buildings.other[buildings_other_id::ANY_FREE];
auto &vec = world->buildings.other[buildings_other_id::IN_PLAY];
bool changed = false;

@ -592,6 +592,20 @@ void Items::getContainedItems(df::item *item, std::vector<df::item*> *items)
}
}
df::building *Items::getHolderBuilding(df::item * item)
{
auto ref = getGeneralRef(item, general_ref_type::BUILDING_HOLDER);
return ref ? ref->getBuilding() : NULL;
}
df::unit *Items::getHolderUnit(df::item * item)
{
auto ref = getGeneralRef(item, general_ref_type::UNIT_HOLDER);
return ref ? ref->getUnit() : NULL;
}
df::coord Items::getPosition(df::item *item)
{
CHECK_NULL_POINTER(item);

@ -312,7 +312,7 @@ std::string MaterialInfo::getToken()
else if (index == 1)
return "COAL:CHARCOAL";
}
return material->id;
return material->id + ":NONE";
case Inorganic:
return "INORGANIC:" + inorganic->id;
case Creature:

@ -55,6 +55,7 @@ using namespace DFHack;
#include "df/item.h"
#include "df/job.h"
#include "df/building.h"
#include "df/renderer.h"
using namespace df::enums;
using df::global::init;
@ -312,6 +313,47 @@ class DFHACK_EXPORT enabler_inputst {
public:
std::string GetKeyDisplay(int binding);
};
class DFHACK_EXPORT renderer {
unsigned char *screen;
long *screentexpos;
char *screentexpos_addcolor;
unsigned char *screentexpos_grayscale;
unsigned char *screentexpos_cf;
unsigned char *screentexpos_cbr;
// For partial printing:
unsigned char *screen_old;
long *screentexpos_old;
char *screentexpos_addcolor_old;
unsigned char *screentexpos_grayscale_old;
unsigned char *screentexpos_cf_old;
unsigned char *screentexpos_cbr_old;
public:
virtual void update_tile(int x, int y) {};
virtual void update_all() {};
virtual void render() {};
virtual void set_fullscreen();
virtual void zoom(df::zoom_commands cmd);
virtual void resize(int w, int h) {};
virtual void grid_resize(int w, int h) {};
renderer() {
screen = NULL;
screentexpos = NULL;
screentexpos_addcolor = NULL;
screentexpos_grayscale = NULL;
screentexpos_cf = NULL;
screentexpos_cbr = NULL;
screen_old = NULL;
screentexpos_old = NULL;
screentexpos_addcolor_old = NULL;
screentexpos_grayscale_old = NULL;
screentexpos_cf_old = NULL;
screentexpos_cbr_old = NULL;
}
virtual ~renderer();
virtual bool get_mouse_coords(int &x, int &y) { return false; }
virtual bool uses_opengl();
};
#else
struct less_sz {
bool operator() (const string &a, const string &b) const {
@ -326,7 +368,9 @@ static std::map<df::interface_key,std::set<string,less_sz> > *keydisplay = NULL;
void init_screen_module(Core *core)
{
#ifdef _LINUX
core = core;
renderer tmp;
if (!strict_virtual_cast<df::renderer>((virtual_ptr)&tmp))
cerr << "Could not fetch the renderer vtable." << std::endl;
#else
if (!core->vinfo->getAddress("keydisplay", keydisplay))
keydisplay = NULL;
@ -639,7 +683,12 @@ dfhack_lua_viewscreen::~dfhack_lua_viewscreen()
void dfhack_lua_viewscreen::render()
{
if (Screen::isDismissed(this)) return;
if (Screen::isDismissed(this))
{
if (parent)
parent->render();
return;
}
dfhack_viewscreen::render();

@ -889,8 +889,7 @@ int Units::getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust
// Retrieve skill from unit soul:
df::enum_field<df::job_skill,int16_t> key(skill_id);
auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key);
auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, skill_id);
if (skill)
{

@ -1 +1 @@
Subproject commit e06438924929a8ecab751c0c233dad5767e91f7e
Subproject commit fcacacce7cf09cf70f011fea87b5be416da73457

@ -100,7 +100,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(stockpiles stockpiles.cpp)
DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename)
DFHACK_PLUGIN(jobutils jobutils.cpp)
DFHACK_PLUGIN(workflow workflow.cpp)
DFHACK_PLUGIN(workflow workflow.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(showmood showmood.cpp)
DFHACK_PLUGIN(fixveins fixveins.cpp)
DFHACK_PLUGIN(fixpositions fixpositions.cpp)
@ -124,9 +124,11 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(add-spatter add-spatter.cpp)
DFHACK_PLUGIN(fix-armory fix-armory.cpp)
# not yet. busy with other crud again...
#DFHACK_PLUGIN(versionosd versionosd.cpp)
DFHACK_PLUGIN(misery misery.cpp)
#DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread)
endif()

@ -246,7 +246,7 @@ struct product_hook : improvement_product {
(df::unit *unit, std::vector<df::item*> *out_items,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *in_items,
int32_t quantity, int16_t skill,
int32_t quantity, df::job_skill skill,
df::historical_entity *entity, df::world_site *site)
) {
if (auto product = products[this])
@ -279,7 +279,7 @@ struct product_hook : improvement_product {
break;
}
int rating = unit ? Units::getEffectiveSkill(unit, df::job_skill(skill)) : 0;
int rating = unit ? Units::getEffectiveSkill(unit, skill) : 0;
int size = int(probability*(1.0f + 0.06f*rating)); // +90% at legendary
object->addContaminant(

@ -321,7 +321,7 @@ std::string getUnitNameProfession(df::unit *unit)
}
enum InventoryMode {
INV_CARRIED,
INV_HAULED,
INV_WEAPON,
INV_WORN,
INV_IN_CONTAINER
@ -355,8 +355,8 @@ void listUnitInventory(std::vector<inv_item> *list, df::unit *unit)
InventoryMode mode;
switch (item->mode) {
case df::unit_inventory_item::Carried:
mode = INV_CARRIED;
case df::unit_inventory_item::Hauled:
mode = INV_HAULED;
break;
case df::unit_inventory_item::Weapon:
mode = INV_WEAPON;

@ -1547,7 +1547,7 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
}
std::vector<df::item*> &items = world->items.other[items_other_id::ANY_FREE];
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;

@ -135,7 +135,7 @@ static command_result stockcheck(color_ostream &out, vector <string> & parameter
}
std::vector<df::item*> &items = world->items.other[items_other_id::ANY_FREE];
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;

@ -0,0 +1,362 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "DataDefs.h"
#include "df/graphic.h"
#include "df/enabler.h"
#include "df/renderer.h"
#include <vector>
#include <string>
#include "PassiveSocket.h"
#include "tinythread.h"
using namespace DFHack;
using namespace df::enums;
using std::string;
using std::vector;
using df::global::gps;
using df::global::enabler;
// The error messages are taken from the clsocket source code
const char * translate_socket_error(CSimpleSocket::CSocketError err) {
switch (err) {
case CSimpleSocket::SocketError:
return "Generic socket error translates to error below.";
case CSimpleSocket::SocketSuccess:
return "No socket error.";
case CSimpleSocket::SocketInvalidSocket:
return "Invalid socket handle.";
case CSimpleSocket::SocketInvalidAddress:
return "Invalid destination address specified.";
case CSimpleSocket::SocketInvalidPort:
return "Invalid destination port specified.";
case CSimpleSocket::SocketConnectionRefused:
return "No server is listening at remote address.";
case CSimpleSocket::SocketTimedout:
return "Timed out while attempting operation.";
case CSimpleSocket::SocketEwouldblock:
return "Operation would block if socket were blocking.";
case CSimpleSocket::SocketNotconnected:
return "Currently not connected.";
case CSimpleSocket::SocketEinprogress:
return "Socket is non-blocking and the connection cannot be completed immediately";
case CSimpleSocket::SocketInterrupted:
return "Call was interrupted by a signal that was caught before a valid connection arrived.";
case CSimpleSocket::SocketConnectionAborted:
return "The connection has been aborted.";
case CSimpleSocket::SocketProtocolError:
return "Invalid protocol for operation.";
case CSimpleSocket::SocketFirewallError:
return "Firewall rules forbid connection.";
case CSimpleSocket::SocketInvalidSocketBuffer:
return "The receive buffer point outside the process's address space.";
case CSimpleSocket::SocketConnectionReset:
return "Connection was forcibly closed by the remote host.";
case CSimpleSocket::SocketAddressInUse:
return "Address already in use.";
case CSimpleSocket::SocketInvalidPointer:
return "Pointer type supplied as argument is invalid.";
case CSimpleSocket::SocketEunknown:
return "Unknown error please report to mark@carrierlabs.com";
default:
return "No such CSimpleSocket error";
}
}
// Owns the thread that accepts TCP connections and forwards messages to clients;
// has a mutex
class client_pool {
typedef tthread::mutex mutex;
mutex clients_lock;
std::vector<CActiveSocket *> clients;
// TODO - delete this at some point
tthread::thread * accepter;
static void accept_clients(void * client_pool_pointer) {
client_pool * p = reinterpret_cast<client_pool *>(client_pool_pointer);
CPassiveSocket socket;
socket.Initialize();
if (socket.Listen((const uint8_t *)"0.0.0.0", 8008)) {
std::cout << "Listening on a socket" << std::endl;
} else {
std::cout << "Not listening: " << socket.GetSocketError() << std::endl;
std::cout << translate_socket_error(socket.GetSocketError()) << std::endl;
}
while (true) {
CActiveSocket * client = socket.Accept();
if (client != 0) {
lock l(*p);
p->clients.push_back(client);
}
}
}
public:
class lock {
tthread::lock_guard<mutex> l;
public:
lock(client_pool & p)
: l(p.clients_lock)
{
}
};
friend class client_pool::lock;
client_pool() {
accepter = new tthread::thread(accept_clients, this);
}
// MUST have lock
bool has_clients() {
return !clients.empty();
}
// MUST have lock
void add_client(CActiveSocket * sock) {
clients.push_back(sock);
}
// MUST have lock
void broadcast(const std::string & message) {
unsigned int sz = htonl(message.size());
for (size_t i = 0; i < clients.size(); ++i) {
clients[i]->Send(reinterpret_cast<const uint8_t *>(&sz), sizeof(sz));
clients[i]->Send((const uint8_t *) message.c_str(), message.size());
}
}
};
// A decorator (in the design pattern sense) of the DF renderer class.
// Sends the screen contents to a client_pool.
class renderer_decorator : public df::renderer {
// the renderer we're decorating
df::renderer * inner;
// how many frames have passed since we last sent a frame
int framesNotPrinted;
// set to false in the destructor
bool * alive;
// clients to which we send the frame
client_pool clients;
// The following three methods facilitate copying of state to the inner object
void set_to_null() {
screen = NULL;
screentexpos = NULL;
screentexpos_addcolor = NULL;
screentexpos_grayscale = NULL;
screentexpos_cf = NULL;
screentexpos_cbr = NULL;
screen_old = NULL;
screentexpos_old = NULL;
screentexpos_addcolor_old = NULL;
screentexpos_grayscale_old = NULL;
screentexpos_cf_old = NULL;
screentexpos_cbr_old = NULL;
}
void copy_from_inner() {
screen = inner->screen;
screentexpos = inner->screentexpos;
screentexpos_addcolor = inner->screentexpos_addcolor;
screentexpos_grayscale = inner->screentexpos_grayscale;
screentexpos_cf = inner->screentexpos_cf;
screentexpos_cbr = inner->screentexpos_cbr;
screen_old = inner->screen_old;
screentexpos_old = inner->screentexpos_old;
screentexpos_addcolor_old = inner->screentexpos_addcolor_old;
screentexpos_grayscale_old = inner->screentexpos_grayscale_old;
screentexpos_cf_old = inner->screentexpos_cf_old;
screentexpos_cbr_old = inner->screentexpos_cbr_old;
}
void copy_to_inner() {
inner->screen = screen;
inner->screentexpos = screentexpos;
inner->screentexpos_addcolor = screentexpos_addcolor;
inner->screentexpos_grayscale = screentexpos_grayscale;
inner->screentexpos_cf = screentexpos_cf;
inner->screentexpos_cbr = screentexpos_cbr;
inner->screen_old = screen_old;
inner->screentexpos_old = screentexpos_old;
inner->screentexpos_addcolor_old = screentexpos_addcolor_old;
inner->screentexpos_grayscale_old = screentexpos_grayscale_old;
inner->screentexpos_cf_old = screentexpos_cf_old;
inner->screentexpos_cbr_old = screentexpos_cbr_old;
}
public:
renderer_decorator(df::renderer * inner, bool * alive)
: inner(inner)
, framesNotPrinted(0)
, alive(alive)
{
copy_from_inner();
}
virtual void update_tile(int x, int y) {
copy_to_inner();
inner->update_tile(x, y);
}
virtual void update_all() {
copy_to_inner();
inner->update_all();
}
virtual void render() {
copy_to_inner();
inner->render();
++framesNotPrinted;
int gfps = enabler->calculated_gfps;
if (gfps == 0) gfps = 1;
// send a frame roughly every 128 mibiseconds (1 second = 1024 mibiseconds)
if ((framesNotPrinted * 1024) / gfps <= 128) return;
client_pool::lock lock(clients);
if (!clients.has_clients()) return;
framesNotPrinted = 0;
std::stringstream frame;
frame << gps->dimx << ' ' << gps->dimy << " 0 0 " << gps->dimx << ' ' << gps->dimy << '\n';
unsigned char * sc_ = gps->screen;
for (int y = 0; y < gps->dimy; ++y) {
unsigned char * sc = sc_;
for (int x = 0; x < gps->dimx; ++x) {
unsigned char ch = sc[0];
unsigned char bold = (sc[3] != 0) * 8;
unsigned char translate[] =
{ 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 };
unsigned char fg = translate[(sc[1] + bold) % 16];
unsigned char bg = translate[sc[2] % 16]*16;
frame.put(ch);
frame.put(fg+bg);
sc += 4*gps->dimy;
}
sc_ += 4;
}
clients.broadcast(frame.str());
}
virtual void set_fullscreen() { inner->set_fullscreen(); }
virtual void zoom(df::zoom_commands cmd) {
copy_to_inner();
inner->zoom(cmd);
}
virtual void resize(int w, int h) {
copy_to_inner();
inner->resize(w, h);
copy_from_inner();
}
virtual void grid_resize(int w, int h) {
copy_to_inner();
inner->grid_resize(w, h);
copy_from_inner();
}
virtual ~renderer_decorator() {
*alive = false;
if (inner) {
copy_to_inner();
delete inner;
inner = 0;
}
set_to_null();
}
virtual bool get_mouse_coords(int *x, int *y) { return inner->get_mouse_coords(x, y); }
virtual bool uses_opengl() { return inner->uses_opengl(); }
static renderer_decorator * hook(df::renderer *& ptr, bool * alive) {
renderer_decorator * r = new renderer_decorator(ptr, alive);
ptr = r;
return r;
}
static void unhook(df::renderer *& ptr, renderer_decorator * dec, color_ostream & out) {
dec->copy_to_inner();
ptr = dec->inner;
dec->inner = 0;
delete dec;
}
};
DFHACK_PLUGIN("dfstream");
inline df::renderer *& active_renderer() {
return enabler->renderer;
}
// This class is a smart pointer around a renderer_decorator.
// It should only be assigned r_d pointers that use the alive-pointer of this
// instance.
// If the r_d has been deleted by an external force, this smart pointer doesn't
// redelete it.
class auto_renderer_decorator {
renderer_decorator * p;
public:
// pass this member to the ctor of renderer_decorator
bool alive;
auto_renderer_decorator()
: p(0)
{
}
~auto_renderer_decorator() {
reset();
}
void reset() {
if (*this) {
delete p;
p = 0;
}
}
operator bool() {
return (p != 0) && alive;
}
auto_renderer_decorator & operator=(renderer_decorator *p) {
reset();
this->p = p;
return *this;
}
renderer_decorator * get() {
return p;
}
renderer_decorator * operator->() {
return get();
}
};
auto_renderer_decorator decorator;
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{
if (!df::renderer::_identity.can_instantiate())
{
out.printerr("Cannot allocate a renderer\n");
return CR_OK;
}
if (!decorator) {
decorator = renderer_decorator::hook(active_renderer(), &decorator.alive);
}
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
if (decorator && active_renderer() == decorator.get())
{
renderer_decorator::unhook(active_renderer(), decorator.get(), out);
}
decorator.reset();
return CR_OK;
}
// vim:set sw=4 sts=4 et:

@ -0,0 +1,491 @@
// Fixes containers in barracks to actually work as intended.
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/Units.h"
#include "modules/Items.h"
#include "modules/Job.h"
#include "modules/World.h"
#include "modules/Maps.h"
#include "MiscUtils.h"
#include "DataDefs.h"
#include <VTableInterpose.h>
#include "df/ui.h"
#include "df/world.h"
#include "df/squad.h"
#include "df/unit.h"
#include "df/squad_position.h"
#include "df/items_other_id.h"
#include "df/item_weaponst.h"
#include "df/item_armorst.h"
#include "df/item_helmst.h"
#include "df/item_pantsst.h"
#include "df/item_shoesst.h"
#include "df/item_glovesst.h"
#include "df/item_shieldst.h"
#include "df/item_flaskst.h"
#include "df/item_backpackst.h"
#include "df/item_quiverst.h"
#include "df/building_weaponrackst.h"
#include "df/building_armorstandst.h"
#include "df/building_cabinetst.h"
#include "df/building_boxst.h"
#include "df/job.h"
#include "df/general_ref_building_holderst.h"
#include "df/barrack_preference_category.h"
#include <stdlib.h>
using std::vector;
using std::string;
using std::endl;
using namespace DFHack;
using namespace df::enums;
using df::global::ui;
using df::global::world;
using df::global::gamemode;
using df::global::ui_build_selector;
using df::global::ui_menu_width;
using df::global::ui_area_map_width;
using namespace DFHack::Gui;
using Screen::Pen;
static command_result fix_armory(color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("fix-armory");
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"fix-armory", "Enables or disables the fix-armory plugin.", fix_armory, false,
" fix-armory enable\n"
" Enables the tweaks.\n"
" fix-armory disable\n"
" Disables the tweaks. All equipment will be hauled off to stockpiles.\n"
));
if (Core::getInstance().isMapLoaded())
plugin_onstatechange(out, SC_MAP_LOADED);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
return CR_OK;
}
// Check if the item is assigned to any use controlled by the military tab
static bool is_assigned_item(df::item *item)
{
if (!ui)
return false;
auto type = item->getType();
int idx = binsearch_index(ui->equipment.items_assigned[type], item->id);
if (idx < 0)
return false;
// Exclude weapons used by miners, wood cutters etc
switch (type) {
case item_type::WEAPON:
if (binsearch_index(ui->equipment.work_weapons, item->id) >= 0)
return false;
break;
default:
break;
}
return true;
}
// Check if the item is assigned to the squad member who owns this armory building
static bool belongs_to_position(df::item *item, df::building *holder)
{
int sid = holder->getSpecificSquad();
if (sid < 0)
return false;
auto squad = df::squad::find(sid);
if (!squad)
return false;
int position = holder->getSpecificPosition();
// Weapon racks belong to the whole squad, i.e. can be used by any position
if (position == -1 && holder->getType() == building_type::Weaponrack)
{
for (size_t i = 0; i < squad->positions.size(); i++)
{
if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0)
return true;
}
}
else
{
auto cpos = vector_get(squad->positions, position);
if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0)
return true;
}
return false;
}
// Check if the item is appropriately stored in an armory building
static bool is_in_armory(df::item *item)
{
if (item->flags.bits.in_inventory || item->flags.bits.on_ground)
return false;
auto holder = Items::getHolderBuilding(item);
if (!holder)
return false;
// If indeed in a building, check if it is the right one
return belongs_to_position(item, holder);
}
template<class Item> struct armory_hook : Item {
typedef Item interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ())
{
// Normally this vmethod is used to prevent stockpiling of uncollected webs.
// This uses it to also block stockpiling of items in the armory.
if (is_in_armory(this))
return false;
// Also never let items in process of being removed from uniform be stockpiled at once
if (this->flags.bits.in_inventory)
{
auto holder = Items::getHolderUnit(this);
if (holder && ::binsearch_index(holder->military.uniform_drop, this->id) >= 0)
{
if (is_assigned_item(this))
return false;
}
}
return INTERPOSE_NEXT(isCollected)();
}
DEFINE_VMETHOD_INTERPOSE(bool, moveToGround, (int16_t x, int16_t y, int16_t z))
{
bool rv = INTERPOSE_NEXT(moveToGround)(x, y, z);
// Prevent instant restockpiling of dropped assigned items.
if (is_assigned_item(this))
{
// The original vmethod adds the item to this vector to force instant check
auto &ovec = world->items.other[items_other_id::ANY_RECENTLY_DROPPED];
// If it is indeed there, remove it
if (erase_from_vector(ovec, &df::item::id, this->id))
{
// and queue it to be checked normally in 0.5-1 in-game days
this->stockpile_countdown = 12 + random_int(12);
this->stockpile_delay = 0;
}
}
return rv;
}
};
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_weaponst>, isCollected);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_armorst>, isCollected);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_helmst>, isCollected);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_shoesst>, isCollected);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_pantsst>, isCollected);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_glovesst>, isCollected);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_shieldst>, isCollected);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_flaskst>, isCollected);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_backpackst>, isCollected);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_quiverst>, isCollected);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_weaponst>, moveToGround);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_armorst>, moveToGround);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_helmst>, moveToGround);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_shoesst>, moveToGround);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_pantsst>, moveToGround);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_glovesst>, moveToGround);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_shieldst>, moveToGround);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_flaskst>, moveToGround);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_backpackst>, moveToGround);
template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook<df::item_quiverst>, moveToGround);
// Check if this item is loose and can be moved to armory
static bool can_store_item(df::item *item)
{
if (item->flags.bits.in_job ||
item->flags.bits.removed ||
item->flags.bits.in_building ||
item->flags.bits.encased ||
item->flags.bits.owned ||
item->flags.bits.forbid ||
item->flags.bits.on_fire)
return false;
auto top = item;
while (top->flags.bits.in_inventory)
{
auto parent = Items::getContainer(top);
if (!parent) break;
top = parent;
}
if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER))
return false;
if (is_in_armory(item))
return false;
return true;
}
// Queue a job to store the item in the building, if possible
static bool try_store_item(df::building *target, df::item *item)
{
// Check if the dwarves can path between the target and the item
df::coord tpos(target->centerx, target->centery, target->z);
df::coord ipos = Items::getPosition(item);
if (!Maps::canWalkBetween(tpos, ipos))
return false;
// Check if the target has enough space left
if (!target->canStoreItem(item, true))
return false;
// Create the job
auto href = df::allocate<df::general_ref_building_holderst>();
if (!href)
return false;
auto job = new df::job();
job->pos = tpos;
// Choose the job type - correct matching is needed so that
// later canStoreItem calls would take the job into account.
switch (target->getType()) {
case building_type::Weaponrack:
job->job_type = job_type::StoreWeapon;
// Without this flag dwarves will pick up the item, and
// then dismiss the job and put it back into the stockpile:
job->flags.bits.specific_dropoff = true;
break;
case building_type::Armorstand:
job->job_type = job_type::StoreArmor;
job->flags.bits.specific_dropoff = true;
break;
case building_type::Cabinet:
job->job_type = job_type::StoreItemInCabinet;
break;
default:
job->job_type = job_type::StoreItemInChest;
break;
}
// job <-> item link
if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled))
{
delete job;
delete href;
return false;
}
// job <-> building link
href->building_id = target->id;
target->jobs.push_back(job);
job->references.push_back(href);
// add to job list
Job::linkIntoWorld(job);
return true;
}
// Store the item into the first building in the list that would accept it.
static void try_store_item(std::vector<int32_t> &vec, df::item *item)
{
for (size_t i = 0; i < vec.size(); i++)
{
auto target = df::building::find(vec[i]);
if (!target)
continue;
if (try_store_item(target, item))
return;
}
}
// Store the items into appropriate armory buildings
static void try_store_item_set(std::vector<int32_t> &items, df::squad *squad, df::squad_position *pos)
{
for (size_t j = 0; j < items.size(); j++)
{
auto item = df::item::find(items[j]);
// bad id, or cooldown timer still counting
if (!item || item->stockpile_countdown > 0)
continue;
// not loose
if (!can_store_item(item))
continue;
if (item->isWeapon())
try_store_item(squad->rack_combat, item);
else if (item->isClothing())
try_store_item(pos->preferences[barrack_preference_category::Cabinet], item);
else if (item->isArmorNotClothing())
try_store_item(pos->preferences[barrack_preference_category::Armorstand], item);
else
try_store_item(pos->preferences[barrack_preference_category::Box], item);
}
}
static bool is_enabled = false;
DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event)
{
if (!is_enabled)
return CR_OK;
// Process every 50th frame, sort of like regular stockpiling does
if (DF_GLOBAL_VALUE(cur_year_tick,1) % 50 != 0)
return CR_OK;
// Loop over squads
auto &squads = df::global::world->squads.all;
for (size_t si = 0; si < squads.size(); si++)
{
auto squad = squads[si];
for (size_t i = 0; i < squad->positions.size(); i++)
{
auto pos = squad->positions[i];
try_store_item_set(pos->assigned_items, squad, pos);
}
}
return CR_OK;
}
static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, bool enable)
{
if (!hook.apply(enable))
out.printerr("Could not %s hook.\n", enable?"activate":"deactivate");
}
static void enable_hooks(color_ostream &out, bool enable)
{
is_enabled = enable;
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_weaponst>, isCollected), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_armorst>, isCollected), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_helmst>, isCollected), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_pantsst>, isCollected), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_shoesst>, isCollected), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_glovesst>, isCollected), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_shieldst>, isCollected), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_flaskst>, isCollected), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_backpackst>, isCollected), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_quiverst>, isCollected), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_weaponst>, moveToGround), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_armorst>, moveToGround), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_helmst>, moveToGround), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_pantsst>, moveToGround), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_shoesst>, moveToGround), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_glovesst>, moveToGround), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_shieldst>, moveToGround), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_flaskst>, moveToGround), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_backpackst>, moveToGround), enable);
enable_hook(out, INTERPOSE_HOOK(armory_hook<df::item_quiverst>, moveToGround), enable);
}
static void enable_plugin(color_ostream &out)
{
auto entry = World::GetPersistentData("fix-armory/enabled", NULL);
if (!entry.isValid())
{
out.printerr("Could not save the status.\n");
return;
}
enable_hooks(out, true);
}
static void disable_plugin(color_ostream &out)
{
auto entry = World::GetPersistentData("fix-armory/enabled");
World::DeletePersistentData(entry);
enable_hooks(out, false);
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
if (!gamemode || *gamemode == game_mode::DWARF)
{
bool enable = World::GetPersistentData("fix-armory/enabled").isValid();
if (enable)
{
out.print("Enabling the fix-armory plugin.\n");
enable_hooks(out, true);
}
else
enable_hooks(out, false);
}
break;
case SC_MAP_UNLOADED:
enable_hooks(out, false);
default:
break;
}
return CR_OK;
}
static command_result fix_armory(color_ostream &out, vector <string> &parameters)
{
CoreSuspender suspend;
if (parameters.empty())
return CR_WRONG_USAGE;
string cmd = parameters[0];
if (cmd == "enable")
{
enable_plugin(out);
}
else if (cmd == "disable")
{
disable_plugin(out);
}
else
return CR_WRONG_USAGE;
return CR_OK;
}

@ -0,0 +1,326 @@
local _ENV = mkmodule('plugins.workflow')
local utils = require 'utils'
--[[
Native functions:
* isEnabled()
* setEnabled(enable)
* listConstraints([job]) -> {...}
* setConstraint(token[, by_count, goal, gap]) -> {...}
* deleteConstraint(token) -> true/false
--]]
local reaction_id_cache = nil
if dfhack.is_core_context then
dfhack.onStateChange[_ENV] = function(code)
if code == SC_MAP_LOADED then
reaction_id_cache = nil
end
end
end
local function get_reaction(name)
if not reaction_id_cache then
reaction_id_cache = {}
for i,v in ipairs(df.global.world.raws.reactions) do
reaction_id_cache[v.code] = i
end
end
local id = reaction_id_cache[name] or -1
return id, df.reaction.find(id)
end
local job_outputs = {}
function job_outputs.CustomReaction(callback, job)
local rid, r = get_reaction(job.reaction_name)
if not r then
return
end
for i,prod in ipairs(r.products) do
if df.reaction_product_itemst:is_instance(prod) then
local mat_type, mat_index = prod.mat_type, prod.mat_index
local mat_mask
local get_mat_prod = prod.flags.GET_MATERIAL_PRODUCT
if get_mat_prod or prod.flags.GET_MATERIAL_SAME then
local reagent_code = prod.get_material.reagent_code
local reagent_idx, src = utils.linear_index(r.reagents, reagent_code, 'code')
if not reagent_idx then goto continue end
local item_idx, jitem = utils.linear_index(job.job_items, reagent_idx, 'reagent_index')
if jitem then
mat_type, mat_index = jitem.mat_type, jitem.mat_index
else
if not df.reaction_reagent_itemst:is_instance(src) then goto continue end
mat_type, mat_index = src.mat_type, src.mat_index
end
if get_mat_prod then
local p_code = prod.get_material.product_code
local mat = dfhack.matinfo.decode(mat_type, mat_index)
mat_type, mat_index = -1, -1
if mat then
local rp = mat.material.reaction_product
local idx = utils.linear_index(rp.id, p_code)
if not idx then
goto continue
end
mat_type, mat_index = rp.material.mat_type[idx], rp.material.mat_index[idx]
else
if p_code == "SOAP_MAT" then
mat_mask = { soap = true }
end
end
end
end
callback{
is_craft = prod.flags.CRAFTS,
item_type = prod.item_type, item_subtype = prod.item_subtype,
mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask
}
end
::continue::
end
end
local function guess_job_material(job)
if job.job_type == df.job_type.PrepareMeal then
return -1, -1, nil
end
local mat_type, mat_index = job.mat_type, job.mat_index
local mask_whole = job.material_category.whole
local mat_mask
local jmat = df.job_type.attrs[job.job_type].material
if jmat then
mat_type, mat_index = df.builtin_mats[jmat] or -1, -1
if mat_type < 0 and df.dfhack_material_category[jmat] then
mat_mask = { [jmat] = true }
end
end
if not mat_mask and mask_whole ~= 0 then
mat_mask = utils.parse_bitfield_int(mask_whole, df.job_material_category)
if mat_mask.wood2 then
mat_mask.wood = true
mat_mask.wood2 = nil
end
end
if mat_type < 0 and #job.job_items > 0 then
local item0 = job.job_items[0]
if #job.job_items == 1 or item0.item_type == df.item_type.PLANT then
mat_type, mat_index = item0.mat_type, item0.mat_index
if item0.item_type == df.item_type.WOOD then
mat_mask = mat_mask or {}
mat_mask.wood = true
end
end
end
return mat_type, mat_index, mat_mask
end
function default_output(callback, job, mat_type, mat_index, mat_mask)
local itype = df.job_type.attrs[job.job_type].item
if itype >= 0 then
local subtype = nil
if df.item_type.attrs[itype].is_rawable then
subtype = job.item_subtype
end
callback{
item_type = itype, item_subtype = subtype,
mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask
}
end
end
function job_outputs.SmeltOre(callback, job)
local mat = dfhack.matinfo.decode(job.job_items[0])
if mat and mat.inorganic then
for i,v in ipairs(mat.inorganic.metal_ore.mat_index) do
callback{ item_type = df.item_type.BAR, mat_type = 0, mat_index = v }
end
else
callback{ item_type = df.item_type.BAR, mat_type = 0 }
end
end
function job_outputs.ExtractMetalStrands(callback, job)
local mat = dfhack.matinfo.decode(job.job_items[0])
if mat and mat.inorganic then
for i,v in ipairs(mat.inorganic.thread_metal.mat_index) do
callback{ item_type = df.item_type.THREAD, mat_type = 0, mat_index = v }
end
else
callback{ item_type = df.item_type.THREAD, mat_type = 0 }
end
end
function job_outputs.PrepareMeal(callback, job)
if job.mat_type ~= -1 then
for i,v in ipairs(df.global.world.raws.itemdefs.food) do
if v.level == job.mat_type then
callback{ item_type = df.item_type.FOOD, item_subtype = i }
end
end
else
callback{ item_type = df.item_type.FOOD }
end
end
function job_outputs.MakeCrafts(callback, job)
local mat_type, mat_index, mat_mask = guess_job_material(job)
callback{ is_craft = true, mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask }
end
local plant_products = {
BrewDrink = 'DRINK',
MillPlants = 'MILL',
ProcessPlants = 'THREAD',
ProcessPlantsBag = 'LEAVES',
ProcessPlantsBarrel = 'EXTRACT_BARREL',
ProcessPlantsVial = 'EXTRACT_VIAL',
ExtractFromPlants = 'EXTRACT_STILL_VIAL',
}
for job,flag in pairs(plant_products) do
local ttag = 'type_'..string.lower(flag)
local itag = 'idx_'..string.lower(flag)
job_outputs[job] = function(callback, job)
local mat_type, mat_index = -1, -1
local mat = dfhack.matinfo.decode(job.job_items[0])
if mat and mat.plant and mat.plant.flags[flag] then
mat_type = mat.plant.material_defs[ttag]
mat_index = mat.plant.material_defs[itag]
end
default_output(callback, job, mat_type, mat_index)
end
end
local function enum_job_outputs(callback, job)
local handler = job_outputs[df.job_type[job.job_type]]
if handler then
handler(callback, job)
else
default_output(callback, job, guess_job_material(job))
end
end
function doEnumJobOutputs(native_cb, job)
local function cb(info)
native_cb(
info.item_type, info.item_subtype,
info.mat_mask, info.mat_type, info.mat_index,
info.is_craft
)
end
enum_job_outputs(cb, job)
end
function listJobOutputs(job)
local res = {}
enum_job_outputs(curry(table.insert, res), job)
return res
end
function constraintToToken(cspec)
local token
if cspec.is_craft then
token = 'CRAFTS'
else
token = df.item_type[cspec.item_type] or error('invalid item type: '..cspec.item_type)
if cspec.item_subtype and cspec.item_subtype >= 0 then
local def = dfhack.items.getSubtypeDef(cspec.item_type, cspec.item_subtype)
if def then
token = token..':'..def.id
else
error('invalid subtype '..cspec.item_subtype..' of '..token)
end
end
end
local mask_part
if cspec.mat_mask then
mask_part = table.concat(utils.list_bitfield_flags(cspec.mat_mask), ',')
end
local mat_part
if cspec.mat_type and cspec.mat_type >= 0 then
local mat = dfhack.matinfo.decode(cspec.mat_type, cspec.mat_index or -1)
if mat then
mat_part = mat:getToken()
else
error('invalid material: '..cspec.mat_type..':'..(cspec.mat_index or -1))
end
end
local qpart
if cspec.quality and cspec.quality > 0 then
qpart = df.item_quality[cspec.quality] or error('invalid quality: '..cspec.quality)
end
if mask_part or mat_part or qpart then
token = token .. '/' .. (mask_part or '')
if mat_part or qpart then
token = token .. '/' .. (mat_part or '')
if qpart then
token = token .. '/' .. (qpart or '')
end
end
end
return token
end
function listWeakenedConstraints(outputs)
local variants = {}
local known = {}
local function register(cons)
cons.token = constraintToToken(cons)
if not known[cons.token] then
known[cons.token] = true
table.insert(variants, cons)
end
end
local generic = {}
local anymat = {}
for i,cons in ipairs(outputs) do
local mask = cons.mat_mask
if (cons.mat_type or -1) >= 0 then
cons.mat_mask = nil
end
register(cons)
if mask then
table.insert(generic, {
item_type = cons.item_type,
item_subtype = cons.item_subtype,
is_craft = cons.is_craft,
mat_mask = mask
})
end
table.insert(anymat, {
item_type = cons.item_type,
item_subtype = cons.item_subtype,
is_craft = cons.is_craft
})
end
for i,cons in ipairs(generic) do register(cons) end
for i,cons in ipairs(anymat) do register(cons) end
return variants
end
return _ENV

@ -298,8 +298,8 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2)
{
if (sort_skill != job_skill::NONE)
{
df::unit_skill *s1 = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill);
df::unit_skill *s2 = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(d2->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill);
df::unit_skill *s1 = binsearch_in_vector<df::unit_skill,df::job_skill>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill);
df::unit_skill *s2 = binsearch_in_vector<df::unit_skill,df::job_skill>(d2->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill);
int l1 = s1 ? s1->rating : 0;
int l2 = s2 ? s2->rating : 0;
int e1 = s1 ? s1->experience : 0;
@ -1030,7 +1030,7 @@ void viewscreen_unitlaborsst::render()
fg = 9;
if (columns[col_offset].skill != job_skill::NONE)
{
df::unit_skill *skill = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill);
df::unit_skill *skill = binsearch_in_vector<df::unit_skill,df::job_skill>(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill);
if ((skill != NULL) && (skill->rating || skill->experience))
{
int level = skill->rating;
@ -1086,7 +1086,7 @@ void viewscreen_unitlaborsst::render()
}
else
{
df::unit_skill *skill = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill);
df::unit_skill *skill = binsearch_in_vector<df::unit_skill,df::job_skill>(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill);
if (skill)
{
int level = skill->rating;

@ -227,7 +227,7 @@ module DFHack
# link bld into other rooms if it is inside their extents or vice versa
def building_linkrooms(bld)
world.buildings.other[:ANY_FREE].each { |ob|
world.buildings.other[:IN_PLAY].each { |ob|
next if ob.z != bld.z
if bld.is_room and bld.room.extents
next if ob.is_room or ob.x1 < bld.room.x or ob.x1 >= bld.room.x+bld.room.width or ob.y1 < bld.room.y or ob.y1 >= bld.room.y+bld.room.height

@ -132,11 +132,6 @@ static void orient_engine(df::building_siegeenginest *bld, df::coord target)
df::building_siegeenginest::Up;
}
static int random_int(int val)
{
return int(int64_t(rand())*val/RAND_MAX);
}
static int point_distance(df::coord speed)
{
return std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z)));
@ -493,7 +488,7 @@ static int setAmmoItem(lua_State *L)
if (!entry.isValid())
return 0;
engine->ammo_vector_id = job_item_vector_id::ANY_FREE;
engine->ammo_vector_id = job_item_vector_id::IN_PLAY;
engine->ammo_item_type = item_type;
FOR_ENUM_ITEMS(job_item_vector_id, id)

@ -10,6 +10,7 @@
#include "modules/Screen.h"
#include "modules/Units.h"
#include "modules/Items.h"
#include "modules/Job.h"
#include "MiscUtils.h"
@ -47,6 +48,9 @@
#include "df/viewscreen_layer_assigntradest.h"
#include "df/viewscreen_tradegoodsst.h"
#include "df/viewscreen_layer_militaryst.h"
#include "df/squad_position.h"
#include "df/job.h"
#include "df/general_ref_building_holderst.h"
#include <stdlib.h>

@ -4,6 +4,9 @@
#include "PluginManager.h"
#include "MiscUtils.h"
#include "LuaTools.h"
#include "DataFuncs.h"
#include "modules/Materials.h"
#include "modules/Items.h"
#include "modules/Gui.h"
@ -37,6 +40,7 @@
#include "df/plant_raw.h"
#include "df/inorganic_raw.h"
#include "df/builtin_mats.h"
#include "df/vehicle.h"
using std::vector;
using std::string;
@ -303,8 +307,9 @@ public:
void setGoalCount(int v) { config.ival(0) = v; }
int goalGap() {
int gcnt = std::max(1, goalCount()/2);
return std::min(gcnt, config.ival(1) <= 0 ? 5 : config.ival(1));
int cval = (config.ival(1) <= 0) ? 5 : config.ival(1);
int cmax = std::max(goalCount()-5, goalCount()/2);
return std::max(1, std::min(cmax, cval));
}
void setGoalGap(int v) { config.ival(1) = v; }
@ -319,6 +324,7 @@ public:
void init(const std::string &str)
{
config.val() = str;
config.ival(0) = 10;
config.ival(2) = 0;
}
@ -374,13 +380,6 @@ static bool isSupportedJob(df::job *job)
job->job_type == job_type::CollectSand);
}
static void enumLiveJobs(std::map<int, df::job*> &rv)
{
df::job_list_link *p = world->job_list.next;
for (; p; p = p->next)
rv[p->item->id] = p->item;
}
static bool isOptionEnabled(unsigned flag)
{
return config.isValid() && (config.ival(0) & flag) != 0;
@ -743,6 +742,20 @@ static void delete_constraint(ItemConstraint *cv)
delete cv;
}
static bool deleteConstraint(std::string name)
{
for (size_t i = 0; i < constraints.size(); i++)
{
if (constraints[i]->config.val() != name)
continue;
delete_constraint(constraints[i]);
return true;
}
return false;
}
/******************************
* JOB-CONSTRAINT MAPPING *
******************************/
@ -809,71 +822,6 @@ static void link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t i
}
}
static void compute_custom_job(ProtectedJob *pj, df::job *job)
{
if (pj->reaction_id < 0)
pj->reaction_id = linear_index(df::reaction::get_vector(),
&df::reaction::code, job->reaction_name);
df::reaction *r = df::reaction::find(pj->reaction_id);
if (!r)
return;
for (size_t i = 0; i < r->products.size(); i++)
{
using namespace df::enums::reaction_product_item_flags;
VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]);
if (!prod || (prod->item_type < (df::item_type)0 && !prod->flags.is_set(CRAFTS)))
continue;
MaterialInfo mat(prod);
df::dfhack_material_category mat_mask(0);
bool get_mat_prod = prod->flags.is_set(GET_MATERIAL_PRODUCT);
if (get_mat_prod || prod->flags.is_set(GET_MATERIAL_SAME))
{
int reagent_idx = linear_index(r->reagents, &df::reaction_reagent::code,
prod->get_material.reagent_code);
if (reagent_idx < 0)
continue;
int item_idx = linear_index(job->job_items, &df::job_item::reagent_index, reagent_idx);
if (item_idx >= 0)
mat.decode(job->job_items[item_idx]);
else
{
VIRTUAL_CAST_VAR(src, df::reaction_reagent_itemst, r->reagents[reagent_idx]);
if (!src)
continue;
mat.decode(src);
}
if (get_mat_prod)
{
std::string code = prod->get_material.product_code;
if (mat.isValid())
{
int idx = linear_index(mat.material->reaction_product.id, code);
if (idx < 0)
continue;
mat.decode(mat.material->reaction_product.material, idx);
}
else
{
if (code == "SOAP_MAT")
mat_mask.bits.soap = true;
}
}
}
link_job_constraint(pj, prod->item_type, prod->item_subtype,
mat_mask, mat.type, mat.index, prod->flags.is_set(CRAFTS));
}
}
static void guess_job_material(df::job *job, MaterialInfo &mat, df::dfhack_material_category &mat_mask)
{
using namespace df::enums::job_type;
@ -915,100 +863,24 @@ static void guess_job_material(df::job *job, MaterialInfo &mat, df::dfhack_mater
}
}
static void compute_job_outputs(color_ostream &out, ProtectedJob *pj)
static int cbEnumJobOutputs(lua_State *L)
{
using namespace df::enums::job_type;
// Custom reactions handled in another function
df::job *job = pj->job_copy;
if (job->job_type == CustomReaction)
{
compute_custom_job(pj, job);
return;
}
// Item type & subtype
df::item_type itype = ENUM_ATTR(job_type, item, job->job_type);
int16_t isubtype = job->item_subtype;
if (itype == item_type::NONE && job->job_type != MakeCrafts)
return;
// Item material & material category
MaterialInfo mat;
df::dfhack_material_category mat_mask;
guess_job_material(job, mat, mat_mask);
// Job-specific code
switch (job->job_type)
{
case SmeltOre:
if (mat.inorganic)
{
std::vector<int16_t> &ores = mat.inorganic->metal_ore.mat_index;
for (size_t i = 0; i < ores.size(); i++)
link_job_constraint(pj, item_type::BAR, -1, 0, 0, ores[i]);
}
return;
case ExtractMetalStrands:
if (mat.inorganic)
{
std::vector<int16_t> &threads = mat.inorganic->thread_metal.mat_index;
for (size_t i = 0; i < threads.size(); i++)
link_job_constraint(pj, item_type::THREAD, -1, 0, 0, threads[i]);
}
return;
auto pj = (ProtectedJob*)lua_touserdata(L, lua_upvalueindex(1));
case PrepareMeal:
if (job->mat_type != -1)
{
std::vector<df::itemdef_foodst*> &food = df::itemdef_foodst::get_vector();
for (size_t i = 0; i < food.size(); i++)
if (food[i]->level == job->mat_type)
link_job_constraint(pj, item_type::FOOD, i, 0, -1, -1);
return;
}
break;
case MakeCrafts:
link_job_constraint(pj, item_type::NONE, -1, mat_mask, mat.type, mat.index, true);
return;
lua_settop(L, 6);
#define PLANT_PROCESS_MAT(flag, tag) \
if (mat.plant && mat.plant->flags.is_set(plant_raw_flags::flag)) \
mat.decode(mat.plant->material_defs.type_##tag, \
mat.plant->material_defs.idx_##tag); \
else mat.decode(-1);
case BrewDrink:
PLANT_PROCESS_MAT(DRINK, drink);
break;
case MillPlants:
PLANT_PROCESS_MAT(MILL, mill);
break;
case ProcessPlants:
PLANT_PROCESS_MAT(THREAD, thread);
break;
case ProcessPlantsBag:
PLANT_PROCESS_MAT(LEAVES, leaves);
break;
case ProcessPlantsBarrel:
PLANT_PROCESS_MAT(EXTRACT_BARREL, extract_barrel);
break;
case ProcessPlantsVial:
PLANT_PROCESS_MAT(EXTRACT_VIAL, extract_vial);
break;
case ExtractFromPlants:
PLANT_PROCESS_MAT(EXTRACT_STILL_VIAL, extract_still_vial);
break;
#undef PLANT_PROCESS_MAT
df::dfhack_material_category mat_mask(0);
if (!lua_isnil(L, 3))
Lua::CheckDFAssign(L, &mat_mask, 3);
default:
break;
}
link_job_constraint(
pj,
(df::item_type)luaL_optint(L, 1, -1), luaL_optint(L, 2, -1),
mat_mask, luaL_optint(L, 4, -1), luaL_optint(L, 5, -1),
lua_toboolean(L, 6)
);
link_job_constraint(pj, itype, isubtype, mat_mask, mat.type, mat.index);
return 0;
}
static void map_job_constraints(color_ostream &out)
@ -1021,19 +893,32 @@ static void map_job_constraints(color_ostream &out)
constraints[i]->is_active = false;
}
auto L = Lua::Core::State;
Lua::StackUnwinder frame(L);
bool ok = Lua::PushModulePublic(out, L, "plugins.workflow", "doEnumJobOutputs");
if (!ok)
out.printerr("The workflow lua module is not available.\n");
for (TKnownJobs::const_iterator it = known_jobs.begin(); it != known_jobs.end(); ++it)
{
ProtectedJob *pj = it->second;
pj->constraints.clear();
if (!pj->isLive())
if (!ok || !pj->isLive())
continue;
if (!melt_active && pj->actual_job->job_type == job_type::MeltMetalObject)
melt_active = pj->isResumed();
compute_job_outputs(out, pj);
// Call the lua module
lua_pushvalue(L, -1);
lua_pushlightuserdata(L, pj);
lua_pushcclosure(L, cbEnumJobOutputs, 1);
Lua::PushDFObject(L, pj->job_copy);
Lua::SafeCall(out, L, 2, 0);
}
}
@ -1110,6 +995,15 @@ static bool itemInRealJob(df::item *item)
!= job_type_class::Hauling;
}
static bool isRouteVehicle(df::item *item)
{
int id = item->getVehicleID();
if (id < 0) return false;
auto vehicle = df::vehicle::find(id);
return vehicle && vehicle->route_id >= 0;
}
static void map_job_items(color_ostream &out)
{
for (size_t i = 0; i < constraints.size(); i++)
@ -1133,7 +1027,7 @@ static void map_job_items(color_ostream &out)
bool dry_buckets = isOptionEnabled(CF_DRYBUCKETS);
std::vector<df::item*> &items = world->items.other[items_other_id::ANY_FREE];
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
for (size_t i = 0; i < items.size(); i++)
{
@ -1161,6 +1055,8 @@ static void map_job_items(color_ostream &out)
break;
case item_type::THREAD:
if (item->flags.bits.spider_web)
continue;
if (item->getTotalDimension() < 15000)
is_invalid = true;
break;
@ -1219,6 +1115,7 @@ static void map_job_items(color_ostream &out)
item->flags.bits.owned ||
item->flags.bits.in_chest ||
item->isAssignedToStockpile() ||
isRouteVehicle(item) ||
itemInRealJob(item) ||
itemBusy(item))
{
@ -1347,6 +1244,160 @@ static void process_constraints(color_ostream &out)
update_jobs_by_constraints(out);
}
static void update_data_structures(color_ostream &out)
{
if (enabled) {
check_lost_jobs(out, 0);
recover_jobs(out);
update_job_data(out);
map_job_constraints(out);
map_job_items(out);
}
}
/*************
* LUA API *
*************/
static bool isEnabled() { return enabled; }
static void setEnabled(color_ostream &out, bool enable)
{
if (enable && !enabled)
{
enable_plugin(out);
}
else if (!enable && enabled)
{
enabled = false;
setOptionEnabled(CF_ENABLED, false);
stop_protect(out);
}
}
static void push_constraint(lua_State *L, ItemConstraint *cv)
{
lua_newtable(L);
int ctable = lua_gettop(L);
Lua::SetField(L, cv->config.entry_id(), ctable, "id");
Lua::SetField(L, cv->config.val(), ctable, "token");
// Constraint key
Lua::SetField(L, cv->item.type, ctable, "item_type");
Lua::SetField(L, cv->item.subtype, ctable, "item_subtype");
Lua::SetField(L, cv->is_craft, ctable, "is_craft");
Lua::PushDFObject(L, &cv->mat_mask);
lua_setfield(L, -2, "mat_mask");
Lua::SetField(L, cv->material.type, ctable, "mat_type");
Lua::SetField(L, cv->material.index, ctable, "mat_index");
Lua::SetField(L, (int)cv->min_quality, ctable, "min_quality");
// Constraint value
Lua::SetField(L, cv->goalByCount(), ctable, "goal_by_count");
Lua::SetField(L, cv->goalCount(), ctable, "goal_value");
Lua::SetField(L, cv->goalGap(), ctable, "goal_gap");
Lua::SetField(L, cv->item_amount, ctable, "cur_amount");
Lua::SetField(L, cv->item_count, ctable, "cur_count");
Lua::SetField(L, cv->item_inuse, ctable, "cur_in_use");
// Current state value
if (cv->request_resume)
Lua::SetField(L, "resume", ctable, "request");
else if (cv->request_suspend)
Lua::SetField(L, "suspend", ctable, "request");
lua_newtable(L);
for (size_t i = 0, j = 0; i < cv->jobs.size(); i++)
{
if (!cv->jobs[i]->isLive()) continue;
Lua::PushDFObject(L, cv->jobs[i]->actual_job);
lua_rawseti(L, -2, ++j);
}
lua_setfield(L, ctable, "jobs");
}
static int listConstraints(lua_State *L)
{
auto job = Lua::CheckDFObject<df::job>(L, 1);
lua_pushnil(L);
if (!enabled || (job && !isSupportedJob(job)))
return 1;
color_ostream &out = *Lua::GetOutput(L);
update_data_structures(out);
ProtectedJob *pj = NULL;
if (job)
{
pj = get_known(job->id);
if (!pj)
return 1;
}
lua_newtable(L);
auto &vec = (pj ? pj->constraints : constraints);
for (size_t i = 0; i < vec.size(); i++)
{
push_constraint(L, vec[i]);
lua_rawseti(L, -2, i+1);
}
return 1;
}
static int setConstraint(lua_State *L)
{
auto token = luaL_checkstring(L, 1);
bool by_count = lua_toboolean(L, 2);
int count = luaL_optint(L, 3, -1);
int gap = luaL_optint(L, 4, -1);
color_ostream &out = *Lua::GetOutput(L);
ItemConstraint *icv = get_constraint(out, token);
if (!icv)
luaL_error(L, "invalid constraint: %s", token);
if (!lua_isnil(L, 2))
icv->setGoalByCount(by_count);
if (!lua_isnil(L, 3))
icv->setGoalCount(count);
if (!lua_isnil(L, 4))
icv->setGoalGap(gap);
process_constraints(out);
push_constraint(L, icv);
return 1;
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(isEnabled),
DFHACK_LUA_FUNCTION(setEnabled),
DFHACK_LUA_FUNCTION(deleteConstraint),
DFHACK_LUA_END
};
DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(listConstraints),
DFHACK_LUA_COMMAND(setConstraint),
DFHACK_LUA_END
};
/******************************
* PRINTING AND THE COMMAND *
******************************/
@ -1490,13 +1541,7 @@ static command_result workflow_cmd(color_ostream &out, vector <string> & paramet
return CR_FAILURE;
}
if (enabled) {
check_lost_jobs(out, 0);
recover_jobs(out);
update_job_data(out);
map_job_constraints(out);
map_job_items(out);
}
update_data_structures(out);
df::building *workshop = NULL;
//FIXME: unused variable!
@ -1514,18 +1559,11 @@ static command_result workflow_cmd(color_ostream &out, vector <string> & paramet
if (cmd == "enable" || cmd == "disable")
{
bool enable = (cmd == "enable");
if (enable && !enabled)
if (enable)
setEnabled(out, true);
else if (parameters.size() == 1)
{
enable_plugin(out);
}
else if (!enable && parameters.size() == 1)
{
if (enabled)
{
enabled = false;
setOptionEnabled(CF_ENABLED, false);
stop_protect(out);
}
setEnabled(out, false);
out << "The plugin is disabled." << endl;
return CR_OK;
@ -1643,14 +1681,8 @@ static command_result workflow_cmd(color_ostream &out, vector <string> & paramet
if (parameters.size() != 2)
return CR_WRONG_USAGE;
for (size_t i = 0; i < constraints.size(); i++)
{
if (constraints[i]->config.val() != parameters[1])
continue;
delete_constraint(constraints[i]);
if (deleteConstraint(parameters[1]))
return CR_OK;
}
out.printerr("Constraint not found: %s\n", parameters[1].c_str());
return CR_FAILURE;

@ -0,0 +1,202 @@
-- Assign weapon racks to squads. Requires patch from bug 1445.
--[[
Required patches:
v0.34.11 linux: http://pastebin.com/mt5EUgFZ
v0.34.11 windows: http://pastebin.com/09nRCybe
]]
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local widgets = require 'gui.widgets'
local dlg = require 'gui.dialogs'
AssignRack = defclass(AssignRack, guidm.MenuOverlay)
AssignRack.focus_path = 'assign-rack'
AssignRack.ATTRS {
building = DEFAULT_NIL,
frame_inset = 1,
frame_background = COLOR_BLACK,
}
function list_squads(building,squad_table,squad_list)
local sqlist = building:getSquads()
if not sqlist then
return
end
for i,v in ipairs(sqlist) do
local obj = df.squad.find(v.squad_id)
if obj then
if not squad_table[v.squad_id] then
squad_table[v.squad_id] = { id = v.squad_id, obj = obj }
table.insert(squad_list, squad_table[v.squad_id])
end
-- Set specific use flags
for n,ok in pairs(v.mode) do
if ok then
squad_table[v.squad_id][n] = true
end
end
-- Check if any use is possible
local btype = building:getType()
if btype == df.building_type.Bed then
if v.mode.sleep then
squad_table[v.squad_id].any = true
end
elseif btype == df.building.Weaponrack then
if v.mode.train or v.mode.indiv_eq then
squad_table[v.squad_id].any = true
end
else
if v.mode.indiv_eq then
squad_table[v.squad_id].any = true
end
end
end
end
for i,v in ipairs(building.parents) do
list_squads(v, squad_table, squad_list)
end
end
function filter_invalid(list, id)
for i=#list-1,0,-1 do
local bld = df.building.find(list[i])
if not bld or bld:getSpecificSquad() ~= id then
list:erase(i)
end
end
end
function AssignRack:init(args)
self.squad_table = {}
self.squad_list = {}
list_squads(self.building, self.squad_table, self.squad_list)
table.sort(self.squad_list, function(a,b) return a.id < b.id end)
self.choices = {}
for i,v in ipairs(self.squad_list) do
if v.any and (v.train or v.indiv_eq) then
local name = v.obj.alias
if name == '' then
name = dfhack.TranslateName(v.obj.name, true)
end
filter_invalid(v.obj.rack_combat, v.id)
filter_invalid(v.obj.rack_training, v.id)
table.insert(self.choices, {
icon = self:callback('isSelected', v),
icon_pen = COLOR_LIGHTGREEN,
obj = v,
text = {
name, NEWLINE, ' ',
{ text = function()
return string.format('%d combat, %d training', #v.obj.rack_combat, #v.obj.rack_training)
end }
}
})
end
end
self:addviews{
widgets.Label{
frame = { l = 0, t = 0 },
text = {
'Assign Weapon Rack'
}
},
widgets.List{
view_id = 'list',
frame = { t = 2, b = 2 },
icon_width = 2, row_height = 2,
scroll_keys = widgets.SECONDSCROLL,
choices = self.choices,
on_submit = self:callback('onSubmit'),
},
widgets.Label{
frame = { l = 0, t = 2 },
text_pen = COLOR_LIGHTRED,
text = 'No appropriate barracks\n\nNote: weapon racks use the\nIndividual equipment flag',
visible = (#self.choices == 0),
},
widgets.Label{
frame = { l = 0, b = 0 },
text = {
{ key = 'LEAVESCREEN', text = ': Back',
on_activate = self:callback('dismiss') }
}
},
}
end
function AssignRack:isSelected(info)
if self.building.specific_squad == info.id then
return '\xfb'
else
return nil
end
end
function AssignRack:onSubmit(idx, choice)
local rid = self.building.id
local curid = self.building.specific_squad
local cur = df.squad.find(curid)
if cur then
utils.erase_sorted(cur.rack_combat, rid)
utils.erase_sorted(cur.rack_training, rid)
end
self.building.specific_squad = -1
df.global.ui.equipment.update.buildings = true
local new = df.squad.find(choice.obj.id)
if new and choice.obj.id ~= curid then
self.building.specific_squad = choice.obj.id
if choice.obj.indiv_eq then
utils.insert_sorted(new.rack_combat, rid)
end
if choice.obj.train then
utils.insert_sorted(new.rack_training, rid)
end
end
end
function AssignRack:onInput(keys)
if self:propagateMoveKeys(keys) then
if df.global.world.selected_building ~= self.building then
self:dismiss()
end
else
AssignRack.super.onInput(self, keys)
end
end
if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some/Weaponrack' then
qerror("This script requires a weapon rack selected in the 'q' mode")
end
AssignRack{ building = dfhack.gui.getSelectedBuilding() }:show()
if not already_warned then
already_warned = true
dlg.showMessage(
'BUG ALERT',
{ 'This script requires a binary patch from', NEWLINE,
'bug 1445 on the tracker. Otherwise the game', NEWLINE,
'will lose your settings due to a bug.' },
COLOR_YELLOW
)
end

@ -0,0 +1,358 @@
-- A GUI front-end for the workflow plugin.
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local guimat = require 'gui.materials'
local widgets = require 'gui.widgets'
local dlg = require 'gui.dialogs'
local workflow = require 'plugins.workflow'
function check_enabled(cb)
if workflow.isEnabled() then
return cb()
else
dlg.showYesNoPrompt(
'Enable Plugin',
{ 'The workflow plugin is not enabled currently.', NEWLINE, NEWLINE,
'Press ', { key = 'MENU_CONFIRM' }, ' to enable it.' },
COLOR_YELLOW,
function()
workflow.setEnabled(true)
return cb()
end
)
end
end
function check_repeat(job, cb)
if job.flags['repeat'] then
return cb()
else
dlg.showYesNoPrompt(
'Not Repeat Job',
{ 'Workflow only tracks repeating jobs.', NEWLINE, NEWLINE,
'Press ', { key = 'MENU_CONFIRM' }, ' to make this one repeat.' },
COLOR_YELLOW,
function()
job.flags['repeat'] = true
return cb()
end
)
end
end
JobConstraints = defclass(JobConstraints, guidm.MenuOverlay)
JobConstraints.focus_path = 'workflow-job'
JobConstraints.ATTRS {
job = DEFAULT_NIL,
frame_inset = 1,
frame_background = COLOR_BLACK,
}
local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false }
function JobConstraints:init(args)
self.building = dfhack.job.getHolder(self.job)
self:addviews{
widgets.Label{
frame = { l = 0, t = 0 },
text = {
'Workflow Constraints'
}
},
widgets.List{
view_id = 'list',
frame = { t = 2, b = 6 },
row_height = 4,
scroll_keys = widgets.SECONDSCROLL,
},
widgets.Label{
frame = { l = 0, b = 3 },
enabled = self:callback('isAnySelected'),
text = {
{ key = 'BUILDING_TRIGGER_ENABLE_CREATURE',
text = function()
local cons = self:getCurConstraint() or null_cons
if cons.goal_by_count then
return ': Count stacks '
else
return ': Count items '
end
end,
on_activate = self:callback('onChangeUnit') },
{ key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify',
on_activate = self:callback('onEditRange') },
NEWLINE, ' ',
{ key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN',
on_activate = self:callback('onIncRange', 'goal_gap', 5) },
{ key = 'BUILDING_TRIGGER_MIN_SIZE_UP',
on_activate = self:callback('onIncRange', 'goal_gap', -1) },
{ text = function()
local cons = self:getCurConstraint() or null_cons
return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap)
end },
{ key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN',
on_activate = self:callback('onIncRange', 'goal_value', -1) },
{ key = 'BUILDING_TRIGGER_MAX_SIZE_UP',
on_activate = self:callback('onIncRange', 'goal_value', 5) },
{ text = function()
local cons = self:getCurConstraint() or null_cons
return string.format(': Max %-4d', cons.goal_value)
end },
}
},
widgets.Label{
frame = { l = 0, b = 0 },
text = {
{ key = 'CUSTOM_N', text = ': New limit, ',
on_activate = self:callback('onNewConstraint') },
{ key = 'CUSTOM_X', text = ': Delete',
enabled = self:callback('isAnySelected'),
on_activate = self:callback('onDeleteConstraint') },
NEWLINE, NEWLINE,
{ key = 'LEAVESCREEN', text = ': Back',
on_activate = self:callback('dismiss') }
}
},
}
self:initListChoices(args.clist)
end
function JobConstraints:onGetSelectedBuilding()
return self.building
end
function JobConstraints:onGetSelectedJob()
return self.job
end
function describe_item_type(iobj)
local itemline = 'any item'
if iobj.is_craft then
itemline = 'any craft'
elseif iobj.item_type >= 0 then
itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type
local subtype = iobj.item_subtype or -1
local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype)
local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype)
if def then
itemline = def.name
elseif count >= 0 then
itemline = 'any '..itemline
end
end
return itemline
end
function is_caste_mat(iobj)
return dfhack.items.isCasteMaterial(iobj.item_type or -1)
end
function describe_material(iobj)
local matline = 'any material'
if is_caste_mat(iobj) then
matline = 'no material'
elseif (iobj.mat_type or -1) >= 0 then
local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index)
if info then
matline = info:toString()
else
matline = iobj.mat_type..':'..iobj.mat_index
end
end
return matline
end
function JobConstraints:initListChoices(clist, sel_token)
clist = clist or workflow.listConstraints(self.job)
local choices = {}
for i,cons in ipairs(clist) do
local goal = (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value
local curval
if cons.goal_by_count then
goal = goal .. ' stacks'
curval = cons.cur_count
else
goal = goal .. ' items'
curval = cons.cur_amount
end
local order_pen = COLOR_GREY
if cons.request == 'resume' then
order_pen = COLOR_GREEN
elseif cons.request == 'suspend' then
order_pen = COLOR_BLUE
end
local itemstr = describe_item_type(cons)
if cons.min_quality > 0 then
itemstr = itemstr .. ' ('..df.item_quality[cons.min_quality]..')'
end
local matstr = describe_material(cons)
local matflagstr = ''
local matflags = utils.list_bitfield_flags(cons.mat_mask)
if #matflags > 0 then
matflags[1] = 'any '..matflags[1]
if matstr == 'any material' then
matstr = table.concat(matflags, ', ')
matflags = {}
end
end
if #matflags > 0 then
matflagstr = table.concat(matflags, ', ')
end
table.insert(choices, {
text = {
goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE,
' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', matflagstr
},
token = cons.token,
obj = cons
})
end
local selidx = nil
if sel_token then
selidx = utils.linear_index(choices, sel_token, 'token')
end
self.subviews.list:setChoices(choices, selidx)
end
function JobConstraints:isAnySelected()
return self.subviews.list:getSelected() ~= nil
end
function JobConstraints:getCurConstraint()
local i,v = self.subviews.list:getSelected()
if v then return v.obj end
end
function JobConstraints:saveConstraint(cons)
local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap)
self:initListChoices(nil, out.token)
end
function JobConstraints:onChangeUnit()
local cons = self:getCurConstraint()
cons.goal_by_count = not cons.goal_by_count
self:saveConstraint(cons)
end
function JobConstraints:onEditRange()
local cons = self:getCurConstraint()
dlg.showInputPrompt(
'Input Range',
'Enter the new constraint range:',
COLOR_WHITE,
(cons.goal_value-cons.goal_gap)..'-'..cons.goal_value,
function(text)
local maxv = string.match(text, '^%s*(%d+)%s*$')
if maxv then
cons.goal_value = maxv
return self:saveConstraint(cons)
end
local minv,maxv = string.match(text, '^%s*(%d+)-(%d+)%s*$')
if minv and maxv and minv ~= maxv then
cons.goal_value = math.max(minv,maxv)
cons.goal_gap = math.abs(maxv-minv)
return self:saveConstraint(cons)
end
dlg.showMessage('Invalid Range', 'This range is invalid: '..text, COLOR_LIGHTRED)
end
)
end
function JobConstraints:onIncRange(field, delta)
local cons = self:getCurConstraint()
if not cons.goal_by_count then
delta = delta * 5
end
cons[field] = math.max(1, cons[field] + delta)
self:saveConstraint(cons)
end
function JobConstraints:onNewConstraint()
local outputs = workflow.listJobOutputs(self.job)
if #outputs == 0 then
dlg.showMessage('Unsupported', 'Workflow cannot guess the outputs of this job.', COLOR_LIGHTRED)
return
end
local variants = workflow.listWeakenedConstraints(outputs)
local choices = {}
for i,cons in ipairs(variants) do
local itemstr = describe_item_type(cons)
local matstr = describe_material(cons)
local matflags = utils.list_bitfield_flags(cons.mat_mask)
if #matflags > 0 then
local fstr = table.concat(matflags, '/')
if matstr == 'any material' then
matstr = 'any '..fstr
else
matstr = 'any '..fstr..' '..matstr
end
end
table.insert(choices, { text = itemstr..' of '..matstr, obj = cons })
end
dlg.showListPrompt(
'Job Outputs',
'Select one of the possible outputs:',
COLOR_WHITE,
choices,
function(idx,item)
self:saveConstraint(item.obj)
end
)
end
function JobConstraints:onDeleteConstraint()
local cons = self:getCurConstraint()
dlg.showYesNoPrompt(
'Delete Constraint',
'Really delete the current constraint?',
COLOR_YELLOW,
function()
workflow.deleteConstraint(cons.token)
self:initListChoices()
end
)
end
function JobConstraints:onInput(keys)
if self:propagateMoveKeys(keys) then
if df.global.world.selected_building ~= self.building then
self:dismiss()
end
else
JobConstraints.super.onInput(self, keys)
end
end
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then
qerror("This script requires a workshop job selected in the 'q' mode")
end
local job = dfhack.gui.getSelectedJob()
check_enabled(function()
check_repeat(job, function()
local clist = workflow.listConstraints(job)
if not clist then
dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED)
return
end
JobConstraints{ job = job, clist = clist }:show()
end)
end)

@ -220,18 +220,40 @@ function JobDetails:setMaterial(obj, mat_type, mat_index)
obj.iobj.mat_index = mat_index
end
function JobDetails:findUnambiguousItem(iobj)
local count = 0
local itype
for i = 0,df.item_type._last_item do
if dfhack.job.isSuitableItem(iobj, i, -1) then
count = count + 1
if count > 1 then return nil end
itype = i
end
end
return itype
end
function JobDetails:onChangeMat()
local idx, obj = self.subviews.list:getSelected()
if obj.iobj.item_type == -1 and obj.iobj.mat_type == -1 then
dlg.showMessage(
'Bug Alert',
{ 'Please set a specific item type first.\n\n',
'Otherwise the material will be matched\n',
'incorrectly due to a limitation in DF code.' },
COLOR_YELLOW
)
return
-- If the job allows only one specific item type, use it
local vitype = self:findUnambiguousItem(obj.iobj)
if vitype then
obj.iobj.item_type = vitype
else
dlg.showMessage(
'Bug Alert',
{ 'Please set a specific item type first.\n\n',
'Otherwise the material will be matched\n',
'incorrectly due to a limitation in DF code.' },
COLOR_YELLOW
)
return
end
end
guimat.MaterialDialog{