Merge branch 'master' into digSmart

develop
expwnent 2013-05-30 22:22:16 -04:00
commit 4ba73bc171
85 changed files with 11374 additions and 2024 deletions

3
.gitmodules vendored

@ -1,6 +1,9 @@
[submodule "plugins/stonesense"]
path = plugins/stonesense
url = git://github.com/peterix/stonesense.git
[submodule "plugins/isoworld"]
path = plugins/isoworld
url = git://github.com/peterix/isoworld.git
[submodule "plugins/df2mc"]
path = plugins/df2mc
url = git://github.com/peterix/DF2MC.git

@ -109,7 +109,8 @@ IF(UNIX)
SET(CMAKE_C_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic")
ELSEIF(MSVC)
# for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP")
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od")
ENDIF()
# use shared libraries for protobuf

@ -3,13 +3,13 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.9.1: http://docutils.sourceforge.net/" />
<meta name="generator" content="Docutils 0.10: http://docutils.sourceforge.net/" />
<title>Building DFHACK</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7434 2012-05-11 21:06:27Z milde $
:Id: $Id: html4css1.css 7514 2012-09-14 14:27:12Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
@ -77,7 +77,7 @@ div.tip p.admonition-title {
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title {
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
@ -253,13 +253,14 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { /* line numbers */
color: grey;
}
.code {
background-color: #eeeeee
}
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
@ -334,21 +335,24 @@ ul.auto-toc {
<li><a class="reference internal" href="#build" id="id7">Build</a></li>
</ul>
</li>
<li><a class="reference internal" href="#mac-os-x" id="id8">Mac OS X</a></li>
<li><a class="reference internal" href="#windows" id="id9">Windows</a><ul>
<li><a class="reference internal" href="#id1" id="id10">How to get the code</a></li>
<li><a class="reference internal" href="#id2" id="id11">Dependencies</a></li>
<li><a class="reference internal" href="#id3" id="id12">Build</a></li>
<li><a class="reference internal" href="#mac-os-x" id="id8">Mac OS X</a><ul>
<li><a class="reference internal" href="#snow-leopard-changes" id="id9">Snow Leopard Changes</a></li>
</ul>
</li>
<li><a class="reference internal" href="#windows" id="id10">Windows</a><ul>
<li><a class="reference internal" href="#id1" id="id11">How to get the code</a></li>
<li><a class="reference internal" href="#id2" id="id12">Dependencies</a></li>
<li><a class="reference internal" href="#id3" id="id13">Build</a></li>
</ul>
</li>
<li><a class="reference internal" href="#build-types" id="id13">Build types</a></li>
<li><a class="reference internal" href="#using-the-library-as-a-developer" id="id14">Using the library as a developer</a><ul>
<li><a class="reference internal" href="#df-data-structure-definitions" id="id15">DF data structure definitions</a></li>
<li><a class="reference internal" href="#remote-access-interface" id="id16">Remote access interface</a></li>
<li><a class="reference internal" href="#contributing-to-dfhack" id="id17">Contributing to DFHack</a><ul>
<li><a class="reference internal" href="#coding-style" id="id18">Coding style</a></li>
<li><a class="reference internal" href="#how-to-get-new-code-into-dfhack" id="id19">How to get new code into DFHack</a></li>
<li><a class="reference internal" href="#memory-research" id="id20">Memory research</a></li>
<li><a class="reference internal" href="#build-types" id="id14">Build types</a></li>
<li><a class="reference internal" href="#using-the-library-as-a-developer" id="id15">Using the library as a developer</a><ul>
<li><a class="reference internal" href="#df-data-structure-definitions" id="id16">DF data structure definitions</a></li>
<li><a class="reference internal" href="#remote-access-interface" id="id17">Remote access interface</a></li>
<li><a class="reference internal" href="#contributing-to-dfhack" id="id18">Contributing to DFHack</a><ul>
<li><a class="reference internal" href="#coding-style" id="id19">Coding style</a></li>
<li><a class="reference internal" href="#how-to-get-new-code-into-dfhack" id="id20">How to get new code into DFHack</a></li>
<li><a class="reference internal" href="#memory-research" id="id21">Memory research</a></li>
</ul>
</li>
</ul>
@ -406,6 +410,7 @@ program.</p>
</div>
<div class="section" id="mac-os-x">
<h1><a class="toc-backref" href="#id8">Mac OS X</a></h1>
<p>If you are building on 10.6, please read the subsection below titled &quot;Snow Leopard Changes&quot; FIRST.</p>
<ol class="arabic">
<li><p class="first">Download and unpack a copy of the latest DF</p>
</li>
@ -458,12 +463,34 @@ make install
</pre>
</li>
</ol>
<div class="section" id="snow-leopard-changes">
<h2><a class="toc-backref" href="#id9">Snow Leopard Changes</a></h2>
<ol class="arabic">
<li><dl class="first docutils">
<dt>Add a step 6.2a (before Install XML::LibXSLT)::</dt>
<dd><p class="first last">In a separate Terminal window or tab, run:
<tt class="docutils literal">sudo ln <span class="pre">-s</span> /usr/include/libxml2/libxml /usr/include/libxml</tt></p>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>Add a step 7a (before building)::</dt>
<dd><dl class="first last docutils">
<dt>In &lt;dfhack directory&gt;/library/LuaTypes.cpp, change line 467 to</dt>
<dd><p class="first last"><tt class="docutils literal">int len = <span class="pre">strlen((char*)ptr);</span></tt></p>
</dd>
</dl>
</dd>
</dl>
</li>
</ol>
</div>
</div>
<div class="section" id="windows">
<h1><a class="toc-backref" href="#id9">Windows</a></h1>
<h1><a class="toc-backref" href="#id10">Windows</a></h1>
<p>On Windows, DFHack replaces the SDL library distributed with DF.</p>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id10">How to get the code</a></h2>
<h2><a class="toc-backref" href="#id11">How to get the code</a></h2>
<p>DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git.
You will need some sort of Windows port of git, or a GUI. Some examples:</p>
<blockquote>
@ -484,7 +511,7 @@ git submodule update
<p>If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode).</p>
</div>
<div class="section" id="id2">
<h2><a class="toc-backref" href="#id11">Dependencies</a></h2>
<h2><a class="toc-backref" href="#id12">Dependencies</a></h2>
<p>First, you need <tt class="docutils literal">cmake</tt>. Get the win32 installer version from the official
site: <a class="reference external" href="http://www.cmake.org/cmake/resources/software.html">http://www.cmake.org/cmake/resources/software.html</a></p>
<p>It has the usual installer wizard. Make sure you let it add its binary folder
@ -501,7 +528,7 @@ Grab it from Microsoft's site.</p>
<p>If you already have a different version of perl (for example the one from cygwin), you can run into some trouble. Either remove the other perl install from PATH, or install libxml and libxslt for it instead. Strawberry perl works though and has all the required packages.</p>
</div>
<div class="section" id="id3">
<h2><a class="toc-backref" href="#id12">Build</a></h2>
<h2><a class="toc-backref" href="#id13">Build</a></h2>
<p>There are several different batch files in the <tt class="docutils literal">build</tt> folder along with a script that's used for picking the DF path.</p>
<p>First, run set_df_path.vbs and point the dialog that pops up at your DF folder that you want to use for development.
Next, run one of the scripts with <tt class="docutils literal">generate</tt> prefix. These create the MSVC solution file(s):</p>
@ -523,7 +550,7 @@ So pick either Release or RelWithDebInfo build and build the INSTALL target.</p>
</div>
</div>
<div class="section" id="build-types">
<h1><a class="toc-backref" href="#id13">Build types</a></h1>
<h1><a class="toc-backref" href="#id14">Build types</a></h1>
<p><tt class="docutils literal">cmake</tt> allows you to pick a build type by changing this
variable: <tt class="docutils literal">CMAKE_BUILD_TYPE</tt></p>
<pre class="literal-block">
@ -535,7 +562,7 @@ cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE
'RelWithDebInfo'. 'Debug' is not available on Windows.</p>
</div>
<div class="section" id="using-the-library-as-a-developer">
<h1><a class="toc-backref" href="#id14">Using the library as a developer</a></h1>
<h1><a class="toc-backref" href="#id15">Using the library as a developer</a></h1>
<p>Currently, the most direct way to use the library is to write a plugin that can be loaded by it.
All the plugins can be found in the 'plugins' folder. There's no in-depth documentation
on how to write one yet, but it should be easy enough to copy one and just follow the pattern.</p>
@ -553,29 +580,29 @@ The main license is zlib/libpng, some bits are MIT licensed, and some are BSD li
<p>Feel free to add your own extensions and plugins. Contributing back to
the dfhack repository is welcome and the right thing to do :)</p>
<div class="section" id="df-data-structure-definitions">
<h2><a class="toc-backref" href="#id15">DF data structure definitions</a></h2>
<h2><a class="toc-backref" href="#id16">DF data structure definitions</a></h2>
<p>DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule.</p>
<p>Data structure layouts are described in files following the df.*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code.</p>
<p>Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime.</p>
</div>
<div class="section" id="remote-access-interface">
<h2><a class="toc-backref" href="#id16">Remote access interface</a></h2>
<h2><a class="toc-backref" href="#id17">Remote access interface</a></h2>
<p>DFHack supports remote access by exchanging Google protobuf messages via a TCP socket. Both the core and plugins can define remotely accessible methods. The <tt class="docutils literal"><span class="pre">dfhack-run</span></tt> command uses this interface to invoke ordinary console commands.</p>
<p>Currently the supported set of requests is limited, because the developers don't know what exactly is most useful.</p>
<p>Protocol client implementations exist for Java and C#.</p>
</div>
<div class="section" id="contributing-to-dfhack">
<h2><a class="toc-backref" href="#id17">Contributing to DFHack</a></h2>
<h2><a class="toc-backref" href="#id18">Contributing to DFHack</a></h2>
<p>Several things should be kept in mind when contributing to DFHack.</p>
<div class="section" id="coding-style">
<h3><a class="toc-backref" href="#id18">Coding style</a></h3>
<h3><a class="toc-backref" href="#id19">Coding style</a></h3>
<p>DFhack uses ANSI formatting and four spaces as indentation. Line
endings are UNIX. The files use UTF-8 encoding. Code not following this
won't make me happy, because I'll have to fix it. There's a good chance
I'll make <em>you</em> fix it ;)</p>
</div>
<div class="section" id="how-to-get-new-code-into-dfhack">
<h3><a class="toc-backref" href="#id19">How to get new code into DFHack</a></h3>
<h3><a class="toc-backref" href="#id20">How to get new code into DFHack</a></h3>
<p>You can send patches or make a clone of the github repo and ask me on
the IRC channel to pull your code in. I'll review it and see if there
are any problems. I'll fix them if they are minor.</p>
@ -585,7 +612,7 @@ this is also a good place to dump new ideas and/or bugs that need
fixing.</p>
</div>
<div class="section" id="memory-research">
<h3><a class="toc-backref" href="#id20">Memory research</a></h3>
<h3><a class="toc-backref" href="#id21">Memory research</a></h3>
<p>If you want to do memory research, you'll need some tools and some knowledge.
In general, you'll need a good memory viewer and optionally something
to look at machine code without getting crazy :)</p>

@ -63,6 +63,23 @@ extra options.
You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui
program.
Fixing the libstdc++ version bug
================================
When compiling dfhack yourself, it builds against your system libc.
When Dwarf Fortress runs, it uses a libstdc++ shipped with the binary, which
is usually way older, and incompatible with your dfhack. This manifests with
the error message::
./libs/Dwarf_Fortress: /pathToDF/libs/libstdc++.so.6: version
`GLIBCXX_3.4.15' not found (required by ./hack/libdfhack.so)
To fix this, simply remove the libstdc++ shipped with DF, it will fall back
to your system lib and everything will work fine::
cd /path/to/DF/
rm libs/libstdc++.so.6
========
Mac OS X
========

37
NEWS

@ -1,7 +1,38 @@
DFHack future
Is not yet known.
New commands:
- restrictliquid - Restrict traffic on every visible square with liquid.
- restrictice - Restrict traffic on squares above visible ice.
- treefarm - automatically chop trees and dig obsidian
New scripts:
- masspit: designate caged creatures in a zone for pitting
- locate_ore: scan the map for unmined ore veins
- multicmd: run a sequence of dfhack commands, separated by ';'
- autobutcher: A GUI front-end for the autobutcher plugin.
Misc improvements:
- exterminate: renamed from slayrace, add help message, add butcher mode
- ruby: add df.dfhack_run "somecommand"
- magmasource: rename to source, allow water/magma sources/drains
- autoSyndrome:
disable by default
reorganized special tags
minimized error spam
reset policies: if the target already has an instance of the syndrome you can skip, add another instance, reset the timer, or add the full duration to the time remaining
- syndromeTrigger: replaces and extends trueTransformation. Can trigger things when syndromes are added for any reason.
- fastdwarf: fixed bug involving fastdwarf and teledwarf being on at the same time
- workNow: can optionally look for jobs when jobs are completed
New plugins:
- buildingplan: Place furniture before it's built
- outsideOnly: make raw-specified buildings impossible to build inside
- resume: A plugin to help display and resume suspended constructions conveniently
- dwarfmonitor: Records dwarf activity to measure fort efficiency
- mousequery: Look and poke at the map elements with the mouse.
- autotrade: Automatically send items in marked stockpiles to trade depot, when trading is possible.
- stocks: An improved stocks display screen.
Internals:
- EventManager: fixed job completion detection
- Once: easy way to make sure something happens once per run of DF, such as an error message
DFHack v0.34.11-r3
Internals:
@ -10,9 +41,11 @@ DFHack v0.34.11-r3
- Maps::canStepBetween: returns whether you can walk between two tiles in one step.
- EventManager: monitors various in game events centrally so that individual plugins
don't have to monitor the same things redundantly.
- Now works with OSX 10.6.8
Notable bugfixes:
- autobutcher can be re-enabled again after being stopped.
- stopped Dwarf Manipulator from unmasking vampires.
- Stonesense is now fixed on OSX
Misc improvements:
- fastdwarf: new mode using debug flags, and some internal consistency fixes.
- added a small stand-alone utility for applying and removing binary patches.

@ -424,129 +424,132 @@ access DF memory and allow for easier development of new tools.</p>
<li><a class="reference internal" href="#digtype" id="id68">digtype</a></li>
<li><a class="reference internal" href="#filltraffic" id="id69">filltraffic</a></li>
<li><a class="reference internal" href="#alltraffic" id="id70">alltraffic</a></li>
<li><a class="reference internal" href="#getplants" id="id71">getplants</a></li>
<li><a class="reference internal" href="#restrictliquid" id="id71">restrictliquid</a></li>
<li><a class="reference internal" href="#restrictice" id="id72">restrictice</a></li>
<li><a class="reference internal" href="#getplants" id="id73">getplants</a></li>
</ul>
</li>
<li><a class="reference internal" href="#cleanup-and-garbage-disposal" id="id72">Cleanup and garbage disposal</a><ul>
<li><a class="reference internal" href="#clean" id="id73">clean</a></li>
<li><a class="reference internal" href="#spotclean" id="id74">spotclean</a></li>
<li><a class="reference internal" href="#autodump" id="id75">autodump</a></li>
<li><a class="reference internal" href="#autodump-destroy-here" id="id76">autodump-destroy-here</a></li>
<li><a class="reference internal" href="#autodump-destroy-item" id="id77">autodump-destroy-item</a></li>
<li><a class="reference internal" href="#cleanowned" id="id78">cleanowned</a></li>
<li><a class="reference internal" href="#cleanup-and-garbage-disposal" id="id74">Cleanup and garbage disposal</a><ul>
<li><a class="reference internal" href="#clean" id="id75">clean</a></li>
<li><a class="reference internal" href="#spotclean" id="id76">spotclean</a></li>
<li><a class="reference internal" href="#autodump" id="id77">autodump</a></li>
<li><a class="reference internal" href="#autodump-destroy-here" id="id78">autodump-destroy-here</a></li>
<li><a class="reference internal" href="#autodump-destroy-item" id="id79">autodump-destroy-item</a></li>
<li><a class="reference internal" href="#cleanowned" id="id80">cleanowned</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bugfixes" id="id79">Bugfixes</a><ul>
<li><a class="reference internal" href="#drybuckets" id="id80">drybuckets</a></li>
<li><a class="reference internal" href="#fixdiplomats" id="id81">fixdiplomats</a></li>
<li><a class="reference internal" href="#fixmerchants" id="id82">fixmerchants</a></li>
<li><a class="reference internal" href="#fixveins" id="id83">fixveins</a></li>
<li><a class="reference internal" href="#tweak" id="id84">tweak</a></li>
<li><a class="reference internal" href="#fix-armory" id="id85">fix-armory</a></li>
<li><a class="reference internal" href="#bugfixes" id="id81">Bugfixes</a><ul>
<li><a class="reference internal" href="#drybuckets" id="id82">drybuckets</a></li>
<li><a class="reference internal" href="#fixdiplomats" id="id83">fixdiplomats</a></li>
<li><a class="reference internal" href="#fixmerchants" id="id84">fixmerchants</a></li>
<li><a class="reference internal" href="#fixveins" id="id85">fixveins</a></li>
<li><a class="reference internal" href="#tweak" id="id86">tweak</a></li>
<li><a class="reference internal" href="#fix-armory" id="id87">fix-armory</a></li>
</ul>
</li>
<li><a class="reference internal" href="#mode-switch-and-reclaim" id="id86">Mode switch and reclaim</a><ul>
<li><a class="reference internal" href="#lair" id="id87">lair</a></li>
<li><a class="reference internal" href="#mode" id="id88">mode</a></li>
<li><a class="reference internal" href="#mode-switch-and-reclaim" id="id88">Mode switch and reclaim</a><ul>
<li><a class="reference internal" href="#lair" id="id89">lair</a></li>
<li><a class="reference internal" href="#mode" id="id90">mode</a></li>
</ul>
</li>
<li><a class="reference internal" href="#visualizer-and-data-export" id="id89">Visualizer and data export</a><ul>
<li><a class="reference internal" href="#ssense-stonesense" id="id90">ssense / stonesense</a></li>
<li><a class="reference internal" href="#mapexport" id="id91">mapexport</a></li>
<li><a class="reference internal" href="#dwarfexport" id="id92">dwarfexport</a></li>
<li><a class="reference internal" href="#visualizer-and-data-export" id="id91">Visualizer and data export</a><ul>
<li><a class="reference internal" href="#ssense-stonesense" id="id92">ssense / stonesense</a></li>
<li><a class="reference internal" href="#mapexport" id="id93">mapexport</a></li>
<li><a class="reference internal" href="#dwarfexport" id="id94">dwarfexport</a></li>
</ul>
</li>
<li><a class="reference internal" href="#job-management" id="id93">Job management</a><ul>
<li><a class="reference internal" href="#job" id="id94">job</a></li>
<li><a class="reference internal" href="#job-material" id="id95">job-material</a></li>
<li><a class="reference internal" href="#job-duplicate" id="id96">job-duplicate</a></li>
<li><a class="reference internal" href="#workflow" id="id97">workflow</a><ul>
<li><a class="reference internal" href="#function" id="id98">Function</a></li>
<li><a class="reference internal" href="#constraint-format" id="id99">Constraint format</a></li>
<li><a class="reference internal" href="#constraint-examples" id="id100">Constraint examples</a></li>
<li><a class="reference internal" href="#job-management" id="id95">Job management</a><ul>
<li><a class="reference internal" href="#job" id="id96">job</a></li>
<li><a class="reference internal" href="#job-material" id="id97">job-material</a></li>
<li><a class="reference internal" href="#job-duplicate" id="id98">job-duplicate</a></li>
<li><a class="reference internal" href="#workflow" id="id99">workflow</a><ul>
<li><a class="reference internal" href="#function" id="id100">Function</a></li>
<li><a class="reference internal" href="#constraint-format" id="id101">Constraint format</a></li>
<li><a class="reference internal" href="#constraint-examples" id="id102">Constraint examples</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#fortress-activity-management" id="id101">Fortress activity management</a><ul>
<li><a class="reference internal" href="#seedwatch" id="id102">seedwatch</a></li>
<li><a class="reference internal" href="#zone" id="id103">zone</a><ul>
<li><a class="reference internal" href="#usage-with-single-units" id="id104">Usage with single units</a></li>
<li><a class="reference internal" href="#usage-with-filters" id="id105">Usage with filters</a></li>
<li><a class="reference internal" href="#mass-renaming" id="id106">Mass-renaming</a></li>
<li><a class="reference internal" href="#cage-zones" id="id107">Cage zones</a></li>
<li><a class="reference internal" href="#examples" id="id108">Examples</a></li>
<li><a class="reference internal" href="#fortress-activity-management" id="id103">Fortress activity management</a><ul>
<li><a class="reference internal" href="#seedwatch" id="id104">seedwatch</a></li>
<li><a class="reference internal" href="#zone" id="id105">zone</a><ul>
<li><a class="reference internal" href="#usage-with-single-units" id="id106">Usage with single units</a></li>
<li><a class="reference internal" href="#usage-with-filters" id="id107">Usage with filters</a></li>
<li><a class="reference internal" href="#mass-renaming" id="id108">Mass-renaming</a></li>
<li><a class="reference internal" href="#cage-zones" id="id109">Cage zones</a></li>
<li><a class="reference internal" href="#examples" id="id110">Examples</a></li>
</ul>
</li>
<li><a class="reference internal" href="#autonestbox" id="id109">autonestbox</a></li>
<li><a class="reference internal" href="#autobutcher" id="id110">autobutcher</a></li>
<li><a class="reference internal" href="#autolabor" id="id111">autolabor</a></li>
<li><a class="reference internal" href="#autonestbox" id="id111">autonestbox</a></li>
<li><a class="reference internal" href="#autobutcher" id="id112">autobutcher</a></li>
<li><a class="reference internal" href="#autolabor" id="id113">autolabor</a></li>
</ul>
</li>
<li><a class="reference internal" href="#other" id="id112">Other</a><ul>
<li><a class="reference internal" href="#catsplosion" id="id113">catsplosion</a></li>
<li><a class="reference internal" href="#dfusion" id="id114">dfusion</a></li>
<li><a class="reference internal" href="#misery" id="id115">misery</a></li>
<li><a class="reference internal" href="#other" id="id114">Other</a><ul>
<li><a class="reference internal" href="#catsplosion" id="id115">catsplosion</a></li>
<li><a class="reference internal" href="#dfusion" id="id116">dfusion</a></li>
<li><a class="reference internal" href="#misery" id="id117">misery</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#scripts" id="id116">Scripts</a><ul>
<li><a class="reference internal" href="#fix" id="id117">fix/*</a></li>
<li><a class="reference internal" href="#gui" id="id118">gui/*</a></li>
<li><a class="reference internal" href="#binpatch" id="id119">binpatch</a></li>
<li><a class="reference internal" href="#quicksave" id="id120">quicksave</a></li>
<li><a class="reference internal" href="#setfps" id="id121">setfps</a></li>
<li><a class="reference internal" href="#siren" id="id122">siren</a></li>
<li><a class="reference internal" href="#growcrops" id="id123">growcrops</a></li>
<li><a class="reference internal" href="#removebadthoughts" id="id124">removebadthoughts</a></li>
<li><a class="reference internal" href="#slayrace" id="id125">slayrace</a></li>
<li><a class="reference internal" href="#magmasource" id="id126">magmasource</a></li>
<li><a class="reference internal" href="#digfort" id="id127">digfort</a></li>
<li><a class="reference internal" href="#superdwarf" id="id128">superdwarf</a></li>
<li><a class="reference internal" href="#drainaquifer" id="id129">drainaquifer</a></li>
<li><a class="reference internal" href="#deathcause" id="id130">deathcause</a></li>
<li><a class="reference internal" href="#lua" id="id131">lua</a></li>
<li><a class="reference internal" href="#embark" id="id132">embark</a></li>
<li><a class="reference internal" href="#lever" id="id133">lever</a></li>
<li><a class="reference internal" href="#stripcaged" id="id134">stripcaged</a></li>
<li><a class="reference internal" href="#create-items" id="id135">create-items</a></li>
<li><a class="reference internal" href="#soundsense-season" id="id136">soundsense-season</a></li>
<li><a class="reference internal" href="#scripts" id="id118">Scripts</a><ul>
<li><a class="reference internal" href="#fix" id="id119">fix/*</a></li>
<li><a class="reference internal" href="#gui" id="id120">gui/*</a></li>
<li><a class="reference internal" href="#binpatch" id="id121">binpatch</a></li>
<li><a class="reference internal" href="#quicksave" id="id122">quicksave</a></li>
<li><a class="reference internal" href="#setfps" id="id123">setfps</a></li>
<li><a class="reference internal" href="#siren" id="id124">siren</a></li>
<li><a class="reference internal" href="#growcrops" id="id125">growcrops</a></li>
<li><a class="reference internal" href="#removebadthoughts" id="id126">removebadthoughts</a></li>
<li><a class="reference internal" href="#slayrace" id="id127">slayrace</a></li>
<li><a class="reference internal" href="#magmasource" id="id128">magmasource</a></li>
<li><a class="reference internal" href="#digfort" id="id129">digfort</a></li>
<li><a class="reference internal" href="#superdwarf" id="id130">superdwarf</a></li>
<li><a class="reference internal" href="#drainaquifer" id="id131">drainaquifer</a></li>
<li><a class="reference internal" href="#deathcause" id="id132">deathcause</a></li>
<li><a class="reference internal" href="#lua" id="id133">lua</a></li>
<li><a class="reference internal" href="#embark" id="id134">embark</a></li>
<li><a class="reference internal" href="#lever" id="id135">lever</a></li>
<li><a class="reference internal" href="#stripcaged" id="id136">stripcaged</a></li>
<li><a class="reference internal" href="#create-items" id="id137">create-items</a></li>
<li><a class="reference internal" href="#soundsense-season" id="id138">soundsense-season</a></li>
</ul>
</li>
<li><a class="reference internal" href="#in-game-interface-tools" id="id137">In-game interface tools</a><ul>
<li><a class="reference internal" href="#dwarf-manipulator" id="id138">Dwarf Manipulator</a></li>
<li><a class="reference internal" href="#search" id="id139">Search</a></li>
<li><a class="reference internal" href="#automaterial" id="id140">AutoMaterial</a></li>
<li><a class="reference internal" href="#gui-liquids" id="id141">gui/liquids</a></li>
<li><a class="reference internal" href="#gui-mechanisms" id="id142">gui/mechanisms</a></li>
<li><a class="reference internal" href="#gui-rename" id="id143">gui/rename</a></li>
<li><a class="reference internal" href="#gui-room-list" id="id144">gui/room-list</a></li>
<li><a class="reference internal" href="#gui-choose-weapons" id="id145">gui/choose-weapons</a></li>
<li><a class="reference internal" href="#gui-guide-path" id="id146">gui/guide-path</a></li>
<li><a class="reference internal" href="#gui-workshop-job" id="id147">gui/workshop-job</a></li>
<li><a class="reference internal" href="#gui-workflow" id="id148">gui/workflow</a></li>
<li><a class="reference internal" href="#gui-assign-rack" id="id149">gui/assign-rack</a></li>
<li><a class="reference internal" href="#gui-advfort" id="id150">gui/advfort</a></li>
<li><a class="reference internal" href="#gui-gm-editor" id="id151">gui/gm-editor</a></li>
<li><a class="reference internal" href="#in-game-interface-tools" id="id139">In-game interface tools</a><ul>
<li><a class="reference internal" href="#dwarf-manipulator" id="id140">Dwarf Manipulator</a></li>
<li><a class="reference internal" href="#search" id="id141">Search</a></li>
<li><a class="reference internal" href="#automaterial" id="id142">AutoMaterial</a></li>
<li><a class="reference internal" href="#gui-liquids" id="id143">gui/liquids</a></li>
<li><a class="reference internal" href="#gui-mechanisms" id="id144">gui/mechanisms</a></li>
<li><a class="reference internal" href="#gui-rename" id="id145">gui/rename</a></li>
<li><a class="reference internal" href="#gui-room-list" id="id146">gui/room-list</a></li>
<li><a class="reference internal" href="#gui-choose-weapons" id="id147">gui/choose-weapons</a></li>
<li><a class="reference internal" href="#gui-guide-path" id="id148">gui/guide-path</a></li>
<li><a class="reference internal" href="#gui-workshop-job" id="id149">gui/workshop-job</a></li>
<li><a class="reference internal" href="#gui-workflow" id="id150">gui/workflow</a></li>
<li><a class="reference internal" href="#gui-assign-rack" id="id151">gui/assign-rack</a></li>
<li><a class="reference internal" href="#gui-advfort" id="id152">gui/advfort</a></li>
<li><a class="reference internal" href="#gui-companion-order" id="id153">gui/companion-order</a></li>
<li><a class="reference internal" href="#gui-gm-editor" id="id154">gui/gm-editor</a></li>
</ul>
</li>
<li><a class="reference internal" href="#behavior-mods" id="id152">Behavior Mods</a><ul>
<li><a class="reference internal" href="#siege-engine" id="id153">Siege Engine</a><ul>
<li><a class="reference internal" href="#rationale" id="id154">Rationale</a></li>
<li><a class="reference internal" href="#configuration-ui" id="id155">Configuration UI</a></li>
<li><a class="reference internal" href="#behavior-mods" id="id155">Behavior Mods</a><ul>
<li><a class="reference internal" href="#siege-engine" id="id156">Siege Engine</a><ul>
<li><a class="reference internal" href="#rationale" id="id157">Rationale</a></li>
<li><a class="reference internal" href="#configuration-ui" id="id158">Configuration UI</a></li>
</ul>
</li>
<li><a class="reference internal" href="#power-meter" id="id156">Power Meter</a></li>
<li><a class="reference internal" href="#steam-engine" id="id157">Steam Engine</a><ul>
<li><a class="reference internal" href="#id1" id="id158">Rationale</a></li>
<li><a class="reference internal" href="#construction" id="id159">Construction</a></li>
<li><a class="reference internal" href="#operation" id="id160">Operation</a></li>
<li><a class="reference internal" href="#explosions" id="id161">Explosions</a></li>
<li><a class="reference internal" href="#save-files" id="id162">Save files</a></li>
<li><a class="reference internal" href="#power-meter" id="id159">Power Meter</a></li>
<li><a class="reference internal" href="#steam-engine" id="id160">Steam Engine</a><ul>
<li><a class="reference internal" href="#id1" id="id161">Rationale</a></li>
<li><a class="reference internal" href="#construction" id="id162">Construction</a></li>
<li><a class="reference internal" href="#operation" id="id163">Operation</a></li>
<li><a class="reference internal" href="#explosions" id="id164">Explosions</a></li>
<li><a class="reference internal" href="#save-files" id="id165">Save files</a></li>
</ul>
</li>
<li><a class="reference internal" href="#add-spatter" id="id163">Add Spatter</a></li>
<li><a class="reference internal" href="#add-spatter" id="id166">Add Spatter</a></li>
</ul>
</li>
</ul>
@ -554,9 +557,9 @@ access DF memory and allow for easier development of new tools.</p>
</div>
<div class="section" id="getting-dfhack">
<h1><a class="toc-backref" href="#id3">Getting DFHack</a></h1>
<p>The project is currently hosted on <a class="reference external" href="http://www.github.com/">github</a>, for both source and
binaries at <a class="reference external" href="http://github.com/peterix/dfhack">http://github.com/peterix/dfhack</a></p>
<p>Releases can be downloaded from here: <a class="reference external" href="https://github.com/peterix/dfhack/downloads">https://github.com/peterix/dfhack/downloads</a></p>
<p>The project is currently hosted on <a class="reference external" href="http://www.github.com/">github</a>
at <a class="reference external" href="http://github.com/peterix/dfhack">http://github.com/peterix/dfhack</a></p>
<p>Releases can be downloaded from here: <a class="reference external" href="http://dethware.org/dfhack/download">http://dethware.org/dfhack/download</a></p>
<p>All new releases are announced in the bay12 thread: <a class="reference external" href="http://tinyurl.com/dfhack-ng">http://tinyurl.com/dfhack-ng</a></p>
</div>
<div class="section" id="compatibility">
@ -1668,8 +1671,16 @@ If an argument is given, the designation of the selected tile is ignored, and al
<blockquote>
'alltraffic N' - Set traffic to 'normal' for all tiles.</blockquote>
</div>
<div class="section" id="restrictliquid">
<h3><a class="toc-backref" href="#id71">restrictliquid</a></h3>
<p>Restrict traffic on all visible tiles with liquid.</p>
</div>
<div class="section" id="restrictice">
<h3><a class="toc-backref" href="#id72">restrictice</a></h3>
<p>Restrict traffic on all tiles on top of visible ice.</p>
</div>
<div class="section" id="getplants">
<h3><a class="toc-backref" href="#id71">getplants</a></h3>
<h3><a class="toc-backref" href="#id73">getplants</a></h3>
<p>This tool allows plant gathering and tree cutting by RAW ID. Specify the types
of trees to cut down and/or shrubs to gather by their plant names, separated
by spaces.</p>
@ -1696,9 +1707,9 @@ all valid plant IDs will be listed.</p>
</div>
</div>
<div class="section" id="cleanup-and-garbage-disposal">
<h2><a class="toc-backref" href="#id72">Cleanup and garbage disposal</a></h2>
<h2><a class="toc-backref" href="#id74">Cleanup and garbage disposal</a></h2>
<div class="section" id="clean">
<h3><a class="toc-backref" href="#id73">clean</a></h3>
<h3><a class="toc-backref" href="#id75">clean</a></h3>
<p>Cleans all the splatter that get scattered all over the map, items and
creatures. In an old fortress, this can significantly reduce FPS lag. It can
also spoil your !!FUN!!, so think before you use it.</p>
@ -1732,12 +1743,12 @@ also spoil your !!FUN!!, so think before you use it.</p>
</blockquote>
</div>
<div class="section" id="spotclean">
<h3><a class="toc-backref" href="#id74">spotclean</a></h3>
<h3><a class="toc-backref" href="#id76">spotclean</a></h3>
<p>Works like 'clean map snow mud', but only for the tile under the cursor. Ideal
if you want to keep that bloody entrance 'clean map' would clean up.</p>
</div>
<div class="section" id="autodump">
<h3><a class="toc-backref" href="#id75">autodump</a></h3>
<h3><a class="toc-backref" href="#id77">autodump</a></h3>
<p>This utility lets you quickly move all items designated to be dumped.
Items are instantly moved to the cursor position, the dump flag is unset,
and the forbid flag is set, as if it had been dumped normally.
@ -1764,17 +1775,17 @@ Be aware that any active dump item tasks still point at the item.</p>
</blockquote>
</div>
<div class="section" id="autodump-destroy-here">
<h3><a class="toc-backref" href="#id76">autodump-destroy-here</a></h3>
<h3><a class="toc-backref" href="#id78">autodump-destroy-here</a></h3>
<p>Destroy items marked for dumping under cursor. Identical to autodump
destroy-here, but intended for use as keybinding.</p>
</div>
<div class="section" id="autodump-destroy-item">
<h3><a class="toc-backref" href="#id77">autodump-destroy-item</a></h3>
<h3><a class="toc-backref" href="#id79">autodump-destroy-item</a></h3>
<p>Destroy the selected item. The item may be selected in the 'k' list, or inside
a container. If called again before the game is resumed, cancels destroy.</p>
</div>
<div class="section" id="cleanowned">
<h3><a class="toc-backref" href="#id78">cleanowned</a></h3>
<h3><a class="toc-backref" href="#id80">cleanowned</a></h3>
<p>Confiscates items owned by dwarfs. By default, owned food on the floor
and rotten items are confistacted and dumped.</p>
<p>Options:</p>
@ -1808,13 +1819,13 @@ worn items with 'X' damage and above.</dd>
</div>
</div>
<div class="section" id="bugfixes">
<h2><a class="toc-backref" href="#id79">Bugfixes</a></h2>
<h2><a class="toc-backref" href="#id81">Bugfixes</a></h2>
<div class="section" id="drybuckets">
<h3><a class="toc-backref" href="#id80">drybuckets</a></h3>
<h3><a class="toc-backref" href="#id82">drybuckets</a></h3>
<p>This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye.</p>
</div>
<div class="section" id="fixdiplomats">
<h3><a class="toc-backref" href="#id81">fixdiplomats</a></h3>
<h3><a class="toc-backref" href="#id83">fixdiplomats</a></h3>
<p>Up to version 0.31.12, Elves only sent Diplomats to your fortress to propose
tree cutting quotas due to a bug; once that bug was fixed, Elves stopped caring
about excess tree cutting. This command adds a Diplomat position to all Elven
@ -1823,19 +1834,19 @@ to violate them and potentially start wars) in case you haven't already modified
your raws accordingly.</p>
</div>
<div class="section" id="fixmerchants">
<h3><a class="toc-backref" href="#id82">fixmerchants</a></h3>
<h3><a class="toc-backref" href="#id84">fixmerchants</a></h3>
<p>This command adds the Guild Representative position to all Human civilizations,
allowing them to make trade agreements (just as they did back in 0.28.181.40d
and earlier) in case you haven't already modified your raws accordingly.</p>
</div>
<div class="section" id="fixveins">
<h3><a class="toc-backref" href="#id83">fixveins</a></h3>
<h3><a class="toc-backref" href="#id85">fixveins</a></h3>
<p>Removes invalid references to mineral inclusions and restores missing ones.
Use this if you broke your embark with tools like tiletypes, or if you
accidentally placed a construction on top of a valuable mineral floor.</p>
</div>
<div class="section" id="tweak">
<h3><a class="toc-backref" href="#id84">tweak</a></h3>
<h3><a class="toc-backref" href="#id86">tweak</a></h3>
<p>Contains various tweaks for minor bugs.</p>
<p>One-shot subcommands:</p>
<table class="docutils field-list" frame="void" rules="none">
@ -1941,7 +1952,7 @@ the units spar more.</p>
</table>
</div>
<div class="section" id="fix-armory">
<h3><a class="toc-backref" href="#id85">fix-armory</a></h3>
<h3><a class="toc-backref" href="#id87">fix-armory</a></h3>
<p>Enables a fix for storage of squad equipment in barracks.</p>
<p>Specifically, it prevents your haulers from moving squad equipment
to stockpiles, and instead queues jobs to store it on weapon racks,
@ -1995,9 +2006,9 @@ these rules is intended by Toady; the rest are invented by this plugin.</p>
</div>
</div>
<div class="section" id="mode-switch-and-reclaim">
<h2><a class="toc-backref" href="#id86">Mode switch and reclaim</a></h2>
<h2><a class="toc-backref" href="#id88">Mode switch and reclaim</a></h2>
<div class="section" id="lair">
<h3><a class="toc-backref" href="#id87">lair</a></h3>
<h3><a class="toc-backref" href="#id89">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
@ -2017,7 +2028,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="#id88">mode</a></h3>
<h3><a class="toc-backref" href="#id90">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>
@ -2037,9 +2048,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="#id89">Visualizer and data export</a></h2>
<h2><a class="toc-backref" href="#id91">Visualizer and data export</a></h2>
<div class="section" id="ssense-stonesense">
<h3><a class="toc-backref" href="#id90">ssense / stonesense</a></h3>
<h3><a class="toc-backref" href="#id92">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>
@ -2052,19 +2063,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="#id91">mapexport</a></h3>
<h3><a class="toc-backref" href="#id93">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="#id92">dwarfexport</a></h3>
<h3><a class="toc-backref" href="#id94">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="#id93">Job management</a></h2>
<h2><a class="toc-backref" href="#id95">Job management</a></h2>
<div class="section" id="job">
<h3><a class="toc-backref" href="#id94">job</a></h3>
<h3><a class="toc-backref" href="#id96">job</a></h3>
<p>Command for general job query and manipulation.</p>
<dl class="docutils">
<dt>Options:</dt>
@ -2083,7 +2094,7 @@ in a workshop, or the unit/jobs screen.</dd>
</dl>
</div>
<div class="section" id="job-material">
<h3><a class="toc-backref" href="#id95">job-material</a></h3>
<h3><a class="toc-backref" href="#id97">job-material</a></h3>
<p>Alter the material of the selected job.</p>
<p>Invoked as:</p>
<pre class="literal-block">
@ -2101,7 +2112,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="#id96">job-duplicate</a></h3>
<h3><a class="toc-backref" href="#id98">job-duplicate</a></h3>
<dl class="docutils">
<dt>Duplicate the selected job in a workshop:</dt>
<dd><ul class="first last simple">
@ -2112,7 +2123,7 @@ instantly duplicates the job.</li>
</dl>
</div>
<div class="section" id="workflow">
<h3><a class="toc-backref" href="#id97">workflow</a></h3>
<h3><a class="toc-backref" href="#id99">workflow</a></h3>
<p>Manage control of repeat jobs.</p>
<p>Usage:</p>
<blockquote>
@ -2144,7 +2155,7 @@ this list can be copied to a file, and then reloaded using the
</dl>
</blockquote>
<div class="section" id="function">
<h4><a class="toc-backref" href="#id98">Function</a></h4>
<h4><a class="toc-backref" href="#id100">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>
@ -2157,7 +2168,7 @@ the frequency of jobs being toggled.</p>
in the game UI.</p>
</div>
<div class="section" id="constraint-format">
<h4><a class="toc-backref" href="#id99">Constraint format</a></h4>
<h4><a class="toc-backref" href="#id101">Constraint format</a></h4>
<p>The contstraint spec consists of 4 parts, separated with '/' characters:</p>
<pre class="literal-block">
ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,&lt;quality&gt;]
@ -2186,7 +2197,7 @@ be used to ignore imported items or items below a certain quality.</p>
</ul>
</div>
<div class="section" id="constraint-examples">
<h4><a class="toc-backref" href="#id100">Constraint examples</a></h4>
<h4><a class="toc-backref" href="#id102">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
@ -2235,15 +2246,15 @@ workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
</div>
</div>
<div class="section" id="fortress-activity-management">
<h2><a class="toc-backref" href="#id101">Fortress activity management</a></h2>
<h2><a class="toc-backref" href="#id103">Fortress activity management</a></h2>
<div class="section" id="seedwatch">
<h3><a class="toc-backref" href="#id102">seedwatch</a></h3>
<h3><a class="toc-backref" href="#id104">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="#id103">zone</a></h3>
<h3><a class="toc-backref" href="#id105">zone</a></h3>
<p>Helps a bit with managing activity zones (pens, pastures and pits) and cages.</p>
<p>Options:</p>
<blockquote>
@ -2342,7 +2353,7 @@ for war/hunt). Negatable.</td>
</table>
</blockquote>
<div class="section" id="usage-with-single-units">
<h4><a class="toc-backref" href="#id104">Usage with single units</a></h4>
<h4><a class="toc-backref" href="#id106">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
@ -2351,7 +2362,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="#id105">Usage with filters</a></h4>
<h4><a class="toc-backref" href="#id107">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.
@ -2369,14 +2380,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="#id106">Mass-renaming</a></h4>
<h4><a class="toc-backref" href="#id108">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="#id107">Cage zones</a></h4>
<h4><a class="toc-backref" href="#id109">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
@ -2387,7 +2398,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="#id108">Examples</a></h4>
<h4><a class="toc-backref" href="#id110">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
@ -2413,7 +2424,7 @@ on the current default zone.</dd>
</div>
</div>
<div class="section" id="autonestbox">
<h3><a class="toc-backref" href="#id109">autonestbox</a></h3>
<h3><a class="toc-backref" href="#id111">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
@ -2442,7 +2453,7 @@ frames between runs.</td>
</blockquote>
</div>
<div class="section" id="autobutcher">
<h3><a class="toc-backref" href="#id110">autobutcher</a></h3>
<h3><a class="toc-backref" href="#id112">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
@ -2550,28 +2561,29 @@ autobutcher.bat
</pre>
</div>
<div class="section" id="autolabor">
<h3><a class="toc-backref" href="#id111">autolabor</a></h3>
<h3><a class="toc-backref" href="#id113">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
also tries to have dwarves specialize in specific skills.</p>
<div class="note">
<p class="first admonition-title">Note</p>
<p class="last">Warning: autolabor will override any manual changes you make to labors
<p>Warning: autolabor will override any manual changes you make to labors
while it is enabled.</p>
<p class="last">To prevent particular dwarves from being managed by autolabor, put them in any burrow.</p>
</div>
<p>For detailed usage information, see 'help autolabor'.</p>
</div>
</div>
<div class="section" id="other">
<h2><a class="toc-backref" href="#id112">Other</a></h2>
<h2><a class="toc-backref" href="#id114">Other</a></h2>
<div class="section" id="catsplosion">
<h3><a class="toc-backref" href="#id113">catsplosion</a></h3>
<h3><a class="toc-backref" href="#id115">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="#id114">dfusion</a></h3>
<h3><a class="toc-backref" href="#id116">dfusion</a></h3>
<dl class="docutils">
<dt>This is the DFusion lua plugin system by Warmist, running as a DFHack plugin. There are two parts to this plugin: an interactive script that shows a text based menu and lua modules. Some of the functionality of is intentionaly left out of the menu:</dt>
<dd><table class="first last docutils field-list" frame="void" rules="none">
@ -2596,7 +2608,7 @@ twice.</p>
</div>
</div>
<div class="section" id="misery">
<h3><a class="toc-backref" href="#id115">misery</a></h3>
<h3><a class="toc-backref" href="#id117">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">
@ -2620,7 +2632,7 @@ twice.</p>
</div>
</div>
<div class="section" id="scripts">
<h1><a class="toc-backref" href="#id116">Scripts</a></h1>
<h1><a class="toc-backref" href="#id118">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>
@ -2629,7 +2641,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="#id117">fix/*</a></h2>
<h2><a class="toc-backref" href="#id119">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>
@ -2661,12 +2673,12 @@ in <tt class="docutils literal">dfhack.init</tt> makes it run automatically.</p>
</ul>
</div>
<div class="section" id="gui">
<h2><a class="toc-backref" href="#id118">gui/*</a></h2>
<h2><a class="toc-backref" href="#id120">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="binpatch">
<h2><a class="toc-backref" href="#id119">binpatch</a></h2>
<h2><a class="toc-backref" href="#id121">binpatch</a></h2>
<p>Checks, applies or removes binary patches directly in memory at runtime:</p>
<pre class="literal-block">
binpatch check/apply/remove &lt;patchname&gt;
@ -2676,17 +2688,17 @@ script uses <tt class="docutils literal"><span class="pre">hack/patches/&lt;df-v
the version appropriate for the currently loaded executable.</p>
</div>
<div class="section" id="quicksave">
<h2><a class="toc-backref" href="#id120">quicksave</a></h2>
<h2><a class="toc-backref" href="#id122">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="#id121">setfps</a></h2>
<h2><a class="toc-backref" href="#id123">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="#id122">siren</a></h2>
<h2><a class="toc-backref" href="#id124">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
@ -2694,7 +2706,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="#id123">growcrops</a></h2>
<h2><a class="toc-backref" href="#id125">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.
@ -2706,7 +2718,7 @@ growcrops plump 40
</pre>
</div>
<div class="section" id="removebadthoughts">
<h2><a class="toc-backref" href="#id124">removebadthoughts</a></h2>
<h2><a class="toc-backref" href="#id126">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,
@ -2720,7 +2732,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="#id125">slayrace</a></h2>
<h2><a class="toc-backref" href="#id127">slayrace</a></h2>
<p>Kills any unit of a given race.</p>
<p>With no argument, lists the available races and count eligible targets.</p>
<p>With the special argument <tt class="docutils literal">him</tt>, targets only the selected creature.</p>
@ -2748,7 +2760,7 @@ slayrace elve magma
</pre>
</div>
<div class="section" id="magmasource">
<h2><a class="toc-backref" href="#id126">magmasource</a></h2>
<h2><a class="toc-backref" href="#id128">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>
@ -2763,7 +2775,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="#id127">digfort</a></h2>
<h2><a class="toc-backref" href="#id129">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>
@ -2781,7 +2793,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="#id128">superdwarf</a></h2>
<h2><a class="toc-backref" href="#id130">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">
@ -2791,17 +2803,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="#id129">drainaquifer</a></h2>
<h2><a class="toc-backref" href="#id131">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="#id130">deathcause</a></h2>
<h2><a class="toc-backref" href="#id132">deathcause</a></h2>
<p>Focus a body part ingame, and this script will display the cause of death of
the creature.
Also works when selecting units from the 'u'nitlist viewscreen.</p>
</div>
<div class="section" id="lua">
<h2><a class="toc-backref" href="#id131">lua</a></h2>
<h2><a class="toc-backref" href="#id133">lua</a></h2>
<p>There are the following ways to invoke this command:</p>
<ol class="arabic">
<li><p class="first"><tt class="docutils literal">lua</tt> (without any parameters)</p>
@ -2820,11 +2832,11 @@ directory. If the filename is not supplied, it loads &quot;dfhack.lua&quot;.</p>
</ol>
</div>
<div class="section" id="embark">
<h2><a class="toc-backref" href="#id132">embark</a></h2>
<h2><a class="toc-backref" href="#id134">embark</a></h2>
<p>Allows to embark anywhere. Currently windows only.</p>
</div>
<div class="section" id="lever">
<h2><a class="toc-backref" href="#id133">lever</a></h2>
<h2><a class="toc-backref" href="#id135">lever</a></h2>
<p>Allow manipulation of in-game levers from the dfhack console.</p>
<p>Can list levers, including state and links, with:</p>
<pre class="literal-block">
@ -2838,7 +2850,7 @@ lever pull 42 --now
</pre>
</div>
<div class="section" id="stripcaged">
<h2><a class="toc-backref" href="#id134">stripcaged</a></h2>
<h2><a class="toc-backref" href="#id136">stripcaged</a></h2>
<p>For dumping items inside cages. Will mark selected items for dumping, then
a dwarf may come and actually dump it. See also <tt class="docutils literal">autodump</tt>.</p>
<p>With the <tt class="docutils literal">items</tt> argument, only dumps items laying in the cage, excluding
@ -2856,7 +2868,7 @@ stripcaged weapons 25321 34228
</pre>
</div>
<div class="section" id="create-items">
<h2><a class="toc-backref" href="#id135">create-items</a></h2>
<h2><a class="toc-backref" href="#id137">create-items</a></h2>
<p>Spawn arbitrary items under the cursor.</p>
<p>The first argument gives the item category, the second gives the material,
and the optionnal third gives the number of items to create (defaults to 20).</p>
@ -2878,7 +2890,7 @@ create-items bar adamantine
</pre>
</div>
<div class="section" id="soundsense-season">
<h2><a class="toc-backref" href="#id136">soundsense-season</a></h2>
<h2><a class="toc-backref" href="#id138">soundsense-season</a></h2>
<p>It is a well known issue that Soundsense cannot detect the correct
current season when a savegame is loaded and has to play random
season music until a season switch occurs.</p>
@ -2888,7 +2900,7 @@ call the script from <tt class="docutils literal">dfhack.init</tt>.</p>
</div>
</div>
<div class="section" id="in-game-interface-tools">
<h1><a class="toc-backref" href="#id137">In-game interface tools</a></h1>
<h1><a class="toc-backref" href="#id139">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">
@ -2901,7 +2913,7 @@ existing DF screens, they deliberately use red instead of green for the key.</p>
guideline because it arguably just fixes small usability bugs in the game UI.</p>
</div>
<div class="section" id="dwarf-manipulator">
<h2><a class="toc-backref" href="#id138">Dwarf Manipulator</a></h2>
<h2><a class="toc-backref" href="#id140">Dwarf Manipulator</a></h2>
<p>Implemented by the manipulator plugin. To activate, open the unit screen and
press 'l'.</p>
<img alt="images/manipulator.png" src="images/manipulator.png" />
@ -2940,7 +2952,7 @@ cursor onto that cell instead of toggling it.</li>
directly to the main dwarf mode screen.</p>
</div>
<div class="section" id="search">
<h2><a class="toc-backref" href="#id139">Search</a></h2>
<h2><a class="toc-backref" href="#id141">Search</a></h2>
<p>The search plugin adds search to the Stocks, Animals, Trading, Stockpile,
Noble (assignment candidates), Military (position candidates), Burrows
(unit list), Rooms, Announcements, Job List and Unit List screens.</p>
@ -2970,7 +2982,7 @@ only fat or tallow by forbidding fats, then searching for fat/tallow, and
using Permit Fats again while the list is filtered.</p>
</div>
<div class="section" id="automaterial">
<h2><a class="toc-backref" href="#id140">AutoMaterial</a></h2>
<h2><a class="toc-backref" href="#id142">AutoMaterial</a></h2>
<p>The automaterial plugin makes building constructions (walls, floors, fortifications,
etc) a little bit easier by saving you from having to trawl through long lists of
materials each time you place one.</p>
@ -2997,7 +3009,7 @@ materials, it returns you back to this screen. If you use this along with severa
enabled materials, you should be able to place complex constructions more conveniently.</p>
</div>
<div class="section" id="gui-liquids">
<h2><a class="toc-backref" href="#id141">gui/liquids</a></h2>
<h2><a class="toc-backref" href="#id143">gui/liquids</a></h2>
<p>To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.</p>
<img alt="images/liquids.png" src="images/liquids.png" />
<p>This script is a gui front-end to the liquids plugin and works similar to it,
@ -3017,7 +3029,7 @@ rivers power water wheels even when full and technically not flowing.</p>
<p>After setting up the desired operations using the described keys, use <tt class="docutils literal">Enter</tt> to apply them.</p>
</div>
<div class="section" id="gui-mechanisms">
<h2><a class="toc-backref" href="#id142">gui/mechanisms</a></h2>
<h2><a class="toc-backref" href="#id144">gui/mechanisms</a></h2>
<p>To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.</p>
<img alt="images/mechanisms.png" src="images/mechanisms.png" />
<p>Lists mechanisms connected to the building, and their links. Navigating the list centers
@ -3027,7 +3039,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="#id143">gui/rename</a></h2>
<h2><a class="toc-backref" href="#id145">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>
@ -3050,7 +3062,7 @@ their species string.</p>
unit profession change to Ctrl-Shift-T.</p>
</div>
<div class="section" id="gui-room-list">
<h2><a class="toc-backref" href="#id144">gui/room-list</a></h2>
<h2><a class="toc-backref" href="#id146">gui/room-list</a></h2>
<p>To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode,
either immediately or after opening the assign owner page.</p>
<img alt="images/room-list.png" src="images/room-list.png" />
@ -3058,7 +3070,7 @@ either immediately or after opening the assign owner page.</p>
list, and allows unassigning them.</p>
</div>
<div class="section" id="gui-choose-weapons">
<h2><a class="toc-backref" href="#id145">gui/choose-weapons</a></h2>
<h2><a class="toc-backref" href="#id147">gui/choose-weapons</a></h2>
<p>Bind to a key (the example config uses Ctrl-W), and activate in the Equip-&gt;View/Customize
page of the military screen.</p>
<p>Depending on the cursor location, it rewrites all 'individual choice weapon' entries
@ -3069,7 +3081,7 @@ only that entry, and does it even if it is not 'individual choice'.</p>
and may lead to inappropriate weapons being selected.</p>
</div>
<div class="section" id="gui-guide-path">
<h2><a class="toc-backref" href="#id146">gui/guide-path</a></h2>
<h2><a class="toc-backref" href="#id148">gui/guide-path</a></h2>
<p>Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with
the cursor over a Guide order.</p>
<img alt="images/guide-path.png" src="images/guide-path.png" />
@ -3077,7 +3089,7 @@ the cursor over a Guide order.</p>
computes it when the order is executed for the first time.</p>
</div>
<div class="section" id="gui-workshop-job">
<h2><a class="toc-backref" href="#id147">gui/workshop-job</a></h2>
<h2><a class="toc-backref" href="#id149">gui/workshop-job</a></h2>
<p>Bind to a key (the example config uses Alt-A), and activate with a job selected in
a workshop in the 'q' mode.</p>
<img alt="images/workshop-job.png" src="images/workshop-job.png" />
@ -3113,7 +3125,7 @@ and then try to change the input item type, now it won't let you select <em>plan
you have to unset the material first.</p>
</div>
<div class="section" id="gui-workflow">
<h2><a class="toc-backref" href="#id148">gui/workflow</a></h2>
<h2><a class="toc-backref" href="#id150">gui/workflow</a></h2>
<p>Bind to a key (the example config uses Alt-W), and activate with a job selected
in a workshop in the 'q' mode.</p>
<img alt="images/workflow.png" src="images/workflow.png" />
@ -3160,7 +3172,7 @@ the current stock value. The bright green dashed line is the target
limit (maximum) and the dark green line is that minus the gap (minimum).</p>
</div>
<div class="section" id="gui-assign-rack">
<h2><a class="toc-backref" href="#id149">gui/assign-rack</a></h2>
<h2><a class="toc-backref" href="#id151">gui/assign-rack</a></h2>
<p>Bind to a key (the example config uses P), and activate when viewing a weapon
rack in the 'q' mode.</p>
<img alt="images/assign-rack.png" src="images/assign-rack.png" />
@ -3184,7 +3196,7 @@ the intended user. In order to aid in the choice, it shows the number
of currently assigned racks for every valid squad.</p>
</div>
<div class="section" id="gui-advfort">
<h2><a class="toc-backref" href="#id150">gui/advfort</a></h2>
<h2><a class="toc-backref" href="#id152">gui/advfort</a></h2>
<p>This script allows to perform jobs in adventure mode. For more complete help
press '?' while script is running. It's most confortable to use this as a
keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:</p>
@ -3196,15 +3208,30 @@ implies -a</li>
<li>job - selects that job (e.g. Dig or FellTree)</li>
</ul>
</div>
<div class="section" id="gui-companion-order">
<h2><a class="toc-backref" href="#id153">gui/companion-order</a></h2>
<p>A script to issue orders for companions. Select companions with lower case chars, issue orders with upper
case. Must be in look or talk mode to issue command on tile.</p>
<ul class="simple">
<li>move - orders selected companions to move to location. If companions are following they will move no more than 3 tiles from you.</li>
<li>equip - try to equip items on the ground.</li>
<li>pick-up - try to take items into hand (also wield)</li>
<li>unequip - remove and drop equipment</li>
<li>unwield - drop held items</li>
<li>wait - temporarely remove from party</li>
<li>follow - rejoin the party after &quot;wait&quot;</li>
<li>leave - remove from party (can be rejoined by talking)</li>
</ul>
</div>
<div class="section" id="gui-gm-editor">
<h2><a class="toc-backref" href="#id151">gui/gm-editor</a></h2>
<h2><a class="toc-backref" href="#id154">gui/gm-editor</a></h2>
<p>There are three ways to open this editor:</p>
<ul class="simple">
<li>using gui/gm-editor command/keybinding - opens editor on what is selected
or viewed (e.g. unit/item description screen)</li>
<li>using gui/gm-editor &lt;lua command&gt; - executes lua command and opens editor on
it's results (e.g. gui/gm-editor &quot;df.global.world.items.all&quot; shows all items)</li>
<li>using gui/gm-edito dialog - shows an in game dialog to input lua command. Works
<li>using gui/gm-editor dialog - shows an in game dialog to input lua command. Works
the same as version above.</li>
</ul>
<p>This editor allows to change and modify almost anything in df. Press '?' for an
@ -3212,7 +3239,7 @@ in-game help.</p>
</div>
</div>
<div class="section" id="behavior-mods">
<h1><a class="toc-backref" href="#id152">Behavior Mods</a></h1>
<h1><a class="toc-backref" href="#id155">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>
@ -3223,20 +3250,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="#id153">Siege Engine</a></h2>
<h2><a class="toc-backref" href="#id156">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="#id154">Rationale</a></h3>
<h3><a class="toc-backref" href="#id157">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="#id155">Configuration UI</a></h3>
<h3><a class="toc-backref" href="#id158">Configuration UI</a></h3>
<p>The configuration front-end to the plugin is implemented by the gui/siege-engine
script. Bind it to a key (the example config uses Alt-A) and activate after selecting
a siege engine in 'q' mode.</p>
@ -3259,7 +3286,7 @@ menu.</p>
</div>
</div>
<div class="section" id="power-meter">
<h2><a class="toc-backref" href="#id156">Power Meter</a></h2>
<h2><a class="toc-backref" href="#id159">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
@ -3270,11 +3297,11 @@ in the build menu.</p>
configuration page, but configures parameters relevant to the modded power meter building.</p>
</div>
<div class="section" id="steam-engine">
<h2><a class="toc-backref" href="#id157">Steam Engine</a></h2>
<h2><a class="toc-backref" href="#id160">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="#id158">Rationale</a></h3>
<h3><a class="toc-backref" href="#id161">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
@ -3285,7 +3312,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="#id159">Construction</a></h3>
<h3><a class="toc-backref" href="#id162">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>
@ -3309,7 +3336,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="#id160">Operation</a></h3>
<h3><a class="toc-backref" href="#id163">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
@ -3340,7 +3367,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="#id161">Explosions</a></h3>
<h3><a class="toc-backref" href="#id164">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
@ -3349,7 +3376,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="#id162">Save files</a></h3>
<h3><a class="toc-backref" href="#id165">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
@ -3360,7 +3387,7 @@ being generated.</p>
</div>
</div>
<div class="section" id="add-spatter">
<h2><a class="toc-backref" href="#id163">Add Spatter</a></h2>
<h2><a class="toc-backref" href="#id166">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

@ -16,12 +16,12 @@ access DF memory and allow for easier development of new tools.
==============
Getting DFHack
==============
The project is currently hosted on github_, for both source and
binaries at http://github.com/peterix/dfhack
The project is currently hosted on github_
at http://github.com/peterix/dfhack
.. _github: http://www.github.com/
Releases can be downloaded from here: https://github.com/peterix/dfhack/downloads
Releases can be downloaded from here: http://dethware.org/dfhack/download
All new releases are announced in the bay12 thread: http://tinyurl.com/dfhack-ng
@ -489,6 +489,16 @@ Options:
:show X: Marks the selected map feature as discovered.
:hide X: Marks the selected map feature as undiscovered.
infiniteSky
-----------
Automatically allocates new z-levels of sky at the top of the map as you build up, or on request allocates many levels all at once.
Examples:
``infiniteSky n``
Raise the sky by n z-levels.
``infiniteSky enable/disable``
Enables/disables monitoring of constructions. If you build anything in the second to highest z-level, it will allocate one more sky level. This is so you can continue to build stairs upward.
liquids
-------
Allows adding magma, water and obsidian to the game. It replaces the normal
@ -761,6 +771,112 @@ showmood
--------
Shows all items needed for the currently active strange mood.
Mod Interaction
===============
This section describes plugins that interact with information in the raw files to add new features that cannot be achieved by only changing raw files.
autoSyndrome
------------
This plugin replaces "boiling rock" syndromes. Without this plugin, it is possible to add a syndrome to a unit by making the unit perform a custom reaction. First, add the syndrome to a rock which boils at room temperature. Make sure that the syndrome is spread by inhaling. Then, add a custom reaction which creates that rock. When the reaction is performed, the rock will be created, then boil. Hopefully, the dwarf will inhale the gas and become afflicted with the syndrome. This has disadvantages.
1. The creating unit might not inhale the gas. This makes it difficult to balance gameplay, as it is hard to measure this probability.
2. A different unit might inhale the gas. Pets or children might inhale the gas, which may be undesired.
To fix this problem, you can use autoSyndrome. The plugin monitors when custom reactions are completed within dwarf mode. If certain conditions are met, then the syndrome is immediately applied. The conditions are described below in priority order. If multiple products are created by the reaction, each one is considered independently in order. If a rock has multiple syndromes, each one is considered independently. If the conditions are all met, then the appropriate target will be instantly afficted with the appropriate syndrome, and the syndrome will behave just like any other.
1. The recently completed reaction must be a custom reaction, not a built-in one.
2. The product must be an inorganic boulder. Its boiling temperature is ignored.
3. The syndrome must have ``[SYN_CLASS:\AUTO_SYNDROME]``.
4. If the syndrome has ``[SYN_CLASS:\ALLOW_MULTIPLE_TARGETS]`` then an unbounded number of units can be targetted by the syndrome. If absent, at most one will be affected, and the worker will be considered first.
5. If the syndrome has ``[SYN_CLASS:\ALLOW_NONWORKER_TARGETS]`` then units that are in the building might be targetted. If absent, only the worker will be targetted. Even if present, the worker will be considered first.
6. If the syndrome has ``[SYN_CLASS:\PRESERVE_ROCK]`` then the stone or stones created will not be destroyed. If absent, they will be. Leaving this out ensures that gasses from boiling rocks will not sidestep the plugin, affecting nearby units using existing gameplay mechanics (because said gasses will never get a chance to be created).
7. If there are no ``SYN_IMMUNE_CREATURE``, ``SYN_AFFECTED_CREATURE``, ``SYN_IMMUNE_CLASS``, or ``SYN_AFFECTED_CLASS`` then any creature can be targetted, if it meets the above restrictions.
8. If the target creature is specified as ``SYN_IMMUNE_CREATURE`` in the syndrome tags, then it will not be affected.
9. If it is specified as ``SYN_AFFECTED_CREATURE`` then it will be affected.
10. If it has ``SYN_IMMUNE_CLASS`` it will not be affected.
11. It it has ``SYN_AFFECTED_CLASS`` it will be affected.
Note that tags like ``[SYN_INHALED]`` are ignored.
The plugin will work for transformations, but doesn't seem to properly apply CE_BLEEDING, for example. Further testing is required.
If the reaction is run twice, by default, a second instance of the syndrome is added. This behavior can be customized. With ``[SYN_CLASS:\RESET_POLICY DoNothing]``, units already afflicted with the syndrome will not be considered for syndrome application. With ``[SYN_CLASS:\RESET_POLICY ResetDuration]`` the existing syndrome timer is reset. With ``[SYN_CLASS:\RESET_POLICY AddDuration]`` the duration of the longest effect in the syndrome is added to the remaining duration of the existing syndrome. The tag ``[SYN_CLASS:\RESET_POLICY NewInstance]`` re-establishes the default behavior. If more than one such tag is present, the last one takes priority.
It is also possible to directly trigger dfhack plugins and scripts using autoSyndrome. If a syndrome has ``[SYN_CLASS:\COMMAND]`` then all following ``SYN_CLASS`` tags will be used to create a console command. The command will behave exactly as if the user had typed it in to the dfhack console. For example
``[SYN_CLASS:\COMMAND]``
``[SYN_CLASS:prospect]``
``[SYN_CLASS:all]``
would run the command "prospect all" whenever the given rock is created. The ``\AUTO_SYNDROME`` tag IS required for commands to execute. Note that since all ``SYN_CLASS`` tags after the ``\COMMAND`` tag are interpreted as part of the command, tags like ``\WORKER_ONLY`` must be placed before ``\COMMAND``, or not at all in order to work.
There are also certain "special" arguments that can be passed.
1. ``\LOCATION``: pass the x, y, and z coordinates of the work tile of the building which completed the job as separate arguments.
2. ``\WORKER_ID``: pass the unit id of the unit that finished the job as an argument.
3. ``\REACTION_INDEX``: pass the id of the completed reaction as an argument.
A note on spaces: when a plugin command executes in dfhack, it always has a list of arguments. Arguments are strings which tell the plugin what the user wants it to do. When the user types in a command, arguments will be separated by whitespace. However, if autoSyndrome is given a tag like ``[SYN_CLASS:123 abcde]`` after a ``[SYN_CLASS:\COMMAND]`` tag, this will still be treated as ONE argument. This may or may not cause problems, depending on the command in question. To be safe, never include spaces in as an argument to a command.
For example, suppose a reaction creates a rock which has a syndrome with the ``SYN_CLASS`` tags ``\AUTO_SYNDROME``, ``\COMMAND``, ``printArgs``, ``id_comes_next``, ``\WORKER_ID``, ``location_comes_next``, ``\LOCATION`` in that order. Suppose the reaction is done at ``(35,96,112)`` by unit number 15. This would be equivalent to typing ``printArgs id_comes_next 15 location_comes_next 35 96 112`` into the DFHack console and pressing enter.
Other syndrome classes that occur before ``\COMMAND`` (or in absence of any ``\COMMAND`` synclass) are ignored.
It is not currently possible to execute more than one command per syndrome. Instead, use multiple syndromes to achieve the same effect. Note that it is possible to have multiple syndromes on the same stone.
Again, note that plugins AND scripts can be executed this way, and arguments will be passed according to the same rules.
outsideOnly
-----------
This plugin makes it so that buildings whose names begin with ``OUTSIDE_ONLY`` cannot be built inside. If the player attempts to do so, the building will automatically be deconstructed.
syndromeTrigger
---------------
This plugin allows DFHack commands to be executed whenever a unit becomes afflicted with a syndrome. This can happen due to a boiling rock, an interaction, autoSyndrome, etc. Regardless of the cause, if the appropriate ``SYN_CLASS`` tags are present, the command will execute.
The syntax is very similar to autoSyndrome. If the syndrome has the ``\COMMAND`` tag, every subsequent ``SYN_CLASS`` tag will be used to create a console command. The following tags are "special":
1. ``\LOCATION``: this will be replaced by three arguments, one for each coordinate of the location of the unit.
2. ``\UNIT_ID``: this will be replaced by the identifier of the unit afllicted with the syndrome.
3. ``\SYNDROME_ID``: this will be replaced by the identifier of the syndrome in question.
If there is a ``[SYN_CLASS:\AUTO_SYNDROME]`` tag, then the command, if any, will NOT be executed by syndromeTrigger, because it should already have been executed by autoSyndrome.
True Transformation
...................
The syndromeTrigger plugin also allows true, permanent transformations. In vanilla DF, if syndrome A transforms dwarves into goblins permanently, and syndrome B transforms goblins into dragons permanently, then syndrome B would NOT properly transform goblins that had been transformed from dwarves. True transformations can be achieved with this plugin.
True transformations work differently. First, the unit transforms into a temporary, distinct, intermediate form. While transformed, this plugin overwrites their "original" unit type with the desired type. When the transformation wears off, they will turn "back" into the new unit type. Once truly transformed, units will function as if they had always been the new unit type. Equipment may be dropped on transformation, but relationships and experience should be maintained.
Suppose you want to transform dwarves into goblins. First, make a syndrome that turns dwarves into ducks for 1 tick (start:0:end:1). It should work with ``END:1``, but if it doesn't, try ``END:5``. You MUST use ``START:0``. Setting the end time very high will make the intermediate form take longer, and should have no other influence on the behavior of this plugin. The intermediate form must NOT be the same as the original form, and it must NOT be the same as the final form, or the game will crash. Add the following tags:
``[SYN_CLASS:\PERMANENT]``
``[SYN_CLASS:GOBLIN]``
``[SYN_CLASS:MALE]``
Note that you must use the "official" (usually allcaps) name of the target creature/caste, not necessarily the name used in game. For example, you would use ``BIRD_DUCK``, ``MALE``, instead of ``drake``.
It is perfectly fine to use syndromeTrigger along with autoSyndrome. This means that you can, for example, trigger a true transformation using a reaction. It is also possible to trigger a true transformation using an interaction, or another plugin that adds syndromes, so long as that other plugin does not interfere with the tags required for this one to work properly.
Designations
============
@ -947,6 +1063,14 @@ Example:
'alltraffic N' - Set traffic to 'normal' for all tiles.
restrictliquid
--------------
Restrict traffic on all visible tiles with liquid.
restrictice
-----------
Restrict traffic on all tiles on top of visible ice.
getplants
---------
This tool allows plant gathering and tree cutting by RAW ID. Specify the types
@ -1438,7 +1562,6 @@ Maintain 10-100 locally-made crafts of exceptional quality.
workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
Fortress activity management
============================
@ -1713,6 +1836,8 @@ also tries to have dwarves specialize in specific skills.
Warning: autolabor will override any manual changes you make to labors
while it is enabled.
To prevent particular dwarves from being managed by autolabor, put them in any burrow.
For detailed usage information, see 'help autolabor'.
@ -1847,7 +1972,7 @@ use in your farming plots.
With a seed type, the script will grow 100 of these seeds, ready to be
harvested. You can change the number with a 2nd argument.
For exemple, to grow 40 plump helmet spawn:
For example, to grow 40 plump helmet spawn:
::
growcrops plump 40
@ -1872,8 +1997,8 @@ Internals: the thoughts are set to be very old, so that the game remove them
quickly after you unpause.
slayrace
========
exterminate
===========
Kills any unit of a given race.
With no argument, lists the available races and count eligible targets.
@ -1890,42 +2015,58 @@ such as vampires, it also sets animal.vanish_countdown to 2.
An alternate mode is selected by adding a 2nd argument to the command,
``magma``. In this case, a column of 7/7 magma is generated on top of the
targets until they die (Warning: do not call on magma-safe creatures. Also,
using this mode for birds is not recommanded.)
using this mode on birds is not recommanded.)
Will target any unit on a revealed tile of the map, including ambushers.
Will target any unit on a revealed tile of the map, including ambushers,
but ignore caged/chained creatures.
Ex::
slayrace gob
exterminate gob
To kill a single creature, select the unit with the 'v' cursor and::
slayrace him
exterminate him
To purify all elves on the map with fire (may have side-effects)::
slayrace elve magma
exterminate elve magma
magmasource
===========
Create an infinite magma source on a tile.
source
======
Create an infinite magma or water source or drain on a tile.
This script registers a map tile as a magma source, and every 12 game ticks
that tile receives 1 new unit of flowing magma.
This script registers a map tile as a liquid source, and every 12 game ticks
that tile receives or remove 1 new unit of flow based on the configuration.
Place the game cursor where you want to create the source (must be a
flow-passable tile, and not too high in the sky) and call::
magmasource here
source add [magma|water] [0-7]
The number argument is the target liquid level (0 = drain, 7 = source).
To add more than 1 unit everytime, call the command again on the same spot.
To delete one source, place the cursor over its tile and use ``delete``.
To remove all existing sources, call ``source clear``.
The ``list`` argument shows all existing sources.
To add more than 1 unit everytime, call the command again.
Ex::
To delete one source, place the cursor over its tile and use ``delete-here``.
To remove all placed sources, call ``magmasource stop``.
source add water - water source
source add magma 7 - magma source
source add water 0 - water drain
With no argument, this command shows an help message and list existing sources.
masspit
=======
Designate all creatures in cages on top of a pit/pond activity zone for pitting.
Works best with an animal stockpile on top of the zone.
Works with a zone number as argument (eg ``Activity Zone #6`` -> ``masspit 6``)
or with the game cursor on top of the area.
digfort
=======
@ -2049,7 +2190,7 @@ Note that the script does not enforce anything, and will let you create
boulders of toad blood and stuff like that.
However the ``list`` mode will only show 'normal' materials.
Exemples::
Examples::
create-items boulders COAL_BITUMINOUS 12
create-items plant tail_pig
@ -2058,6 +2199,20 @@ Exemples::
create-items bar CREATURE:CAT:SOAP
create-items bar adamantine
locate-ore
==========
Scan the map for metal ores.
Finds and designate for digging one tile of a specific metal ore.
Only works for native metal ores, does not handle reaction stuff (eg STEEL).
When invoked with the ``list`` argument, lists metal ores available on the map.
Examples::
locate-ore list
locate-ore hematite
locate-ore iron
soundsense-season
=================
@ -2069,6 +2224,15 @@ This script registers a hook that prints the appropriate string
to gamelog.txt on every map load to fix this. For best results
call the script from ``dfhack.init``.
multicmd
========
Run multiple dfhack commands. The argument is split around the
character ; and all parts are run sequencially as independent
dfhack commands. Useful for hotkeys.
Example::
multicmd locate-ore iron ; digv
=======================
In-game interface tools
=======================

@ -42,6 +42,13 @@ keybinding add Ctrl-Shift-B "adv-bodyswap force"
# Context-specific bindings #
#############################
# Stocks plugin
keybinding add Ctrl-Shift-Z@dwarfmode/Default "stocks show"
# Workflow
keybinding add Ctrl-W@dwarfmode/QueryBuilding/Some "gui/workflow"
keybinding add Ctrl-I "gui/workflow status"
# q->stockpile; p - copy & paste stockpiles
keybinding add Alt-P copystock
@ -93,6 +100,9 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job
keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
keybinding add Alt-W@overallstatus "gui/workflow status"
# autobutcher front-end
keybinding add Shift-B@pet/List/Unit "gui/autobutcher"
# assign weapon racks to squads so that they can be used
keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack
@ -137,6 +147,9 @@ tweak military-color-assigned
# remove inverse dependency of squad training speed on unit list size and use more sparring
tweak military-training
# enable autoSyndrome
autoSyndrome enable
###########
# Scripts #
###########
@ -149,23 +162,21 @@ fix/cloth-stockpile enable
#######################################################
# Apply binary patches at runtime #
# #
# Commented out by default; enable the ones you want. #
#######################################################
# Bug 5994 - items teleported when removing a construction
#binpatch apply deconstruct-teleport
#binpatch apply deconstruct-heapfall
binpatch apply deconstruct-teleport
binpatch apply deconstruct-heapfall
# Bug 4406 - hospital overstocking on all items
#binpatch apply hospital-overstocking
binpatch apply hospital-overstocking
# Bug 808 - custom reactions completely using up all of their reagents
#binpatch apply custom-reagent-size
binpatch apply custom-reagent-size
# Bug 4530 - marksdwarves not training when quiver full of combat-only ammo
#binpatch apply training-ammo
binpatch apply training-ammo
# Bug 1445 - weapon racks broken, armor stand capacity too low
#binpatch apply weaponrack-unassign
#binpatch apply armorstand-capacity
binpatch apply weaponrack-unassign
binpatch apply armorstand-capacity

@ -125,6 +125,7 @@ include/modules/Translation.h
include/modules/Vermin.h
include/modules/World.h
include/modules/Graphic.h
include/modules/Once.h
)
SET( MODULE_SOURCES
@ -147,6 +148,7 @@ modules/Vermin.cpp
modules/World.cpp
modules/Graphic.cpp
modules/Windows.cpp
modules/Once.cpp
)
IF(WIN32)

@ -65,7 +65,7 @@ DFhackCExport int egg_prerender(void)
}
// hook - called for each SDL event, returns 0 when the event has been consumed. 1 otherwise
DFhackCExport int egg_sdl_event(SDL_Event* event)
DFhackCExport int egg_sdl_event(SDL::Event* event)
{
// if the event is valid, intercept
if( event != 0 )

@ -49,10 +49,10 @@ namespace DFHack
{
namespace Job {
// Duplicate the job structure. It is not linked into any DF lists.
DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepWorkerData=false);
DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepEverything=false);
// Delete a cloned structure.
DFHACK_EXPORT void deleteJobStruct(df::job *job);
DFHACK_EXPORT void deleteJobStruct(df::job *job, bool keptEverything=false);
DFHACK_EXPORT void printItemDetails(color_ostream &out, df::job_item *item, int idx);
DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job);

@ -0,0 +1,11 @@
#pragma once
#include "Export.h"
#include <string>
namespace DFHack {
namespace Once {
DFHACK_EXPORT bool alreadyDone(std::string);
DFHACK_EXPORT bool doOnce(std::string);
}
}

@ -635,7 +635,7 @@ bool Buildings::containsTile(df::building *bld, df::coord2d tile, bool room)
}
else
{
if (tile.x < bld->x1 || tile.x > bld->x2 || tile.y < bld->y1 || tile.y >= bld->y2)
if (tile.x < bld->x1 || tile.x > bld->x2 || tile.y < bld->y1 || tile.y > bld->y2)
return false;
}

@ -8,6 +8,9 @@
#include "df/building.h"
#include "df/construction.h"
#include "df/general_ref.h"
#include "df/general_ref_type.h"
#include "df/general_ref_unit_workerst.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/job.h"
@ -32,13 +35,13 @@ using namespace EventManager;
**/
//map<uint32_t, vector<DFHack::EventManager::EventHandler> > tickQueue;
multimap<uint32_t, EventHandler> tickQueue;
static multimap<uint32_t, EventHandler> tickQueue;
//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever
multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
uint32_t eventLastTick[EventType::EVENT_MAX];
static multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
static uint32_t eventLastTick[EventType::EVENT_MAX];
const uint32_t ticksPerYear = 403200;
static const uint32_t ticksPerYear = 403200;
void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) {
handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler));
@ -56,6 +59,7 @@ void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plug
if ( absolute ) {
tick = 0;
}
handler.freq = 1; //to make manageEvents work more nicely
tickQueue.insert(pair<uint32_t, EventHandler>(tick+(uint32_t)when, handler));
handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,handler));
@ -111,6 +115,19 @@ static void manageConstructionEvent(color_ostream& out);
static void manageSyndromeEvent(color_ostream& out);
static void manageInvasionEvent(color_ostream& out);
static void (*eventManager[])(color_ostream&) = {
manageTickEvent,
manageJobInitiatedEvent,
manageJobCompletedEvent,
manageUnitDeathEvent,
manageItemCreationEvent,
manageBuildingEvent,
manageConstructionEvent,
manageSyndromeEvent,
manageInvasionEvent,
};
//tick event
static uint32_t lastTick = 0;
@ -150,7 +167,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event
lastTick = 0;
lastJobId = -1;
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
Job::deleteJobStruct((*i).second, true);
}
prevJobs.clear();
tickQueue.clear();
@ -186,59 +203,32 @@ void DFHack::EventManager::manageEvents(color_ostream& out) {
if ( !gameLoaded ) {
return;
}
CoreSuspender suspender;
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
if ( tick <= lastTick )
return;
lastTick = tick;
/*if ( tick - lastTick > 1 ) {
out.print("EventManager missed tick: %d, %d, (%d)\n", lastTick, tick, tick - lastTick);
}*/
int32_t eventFrequency[EventType::EVENT_MAX];
for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) {
int32_t min = 1000000000;
if ( handlers[a].empty() )
continue;
int32_t eventFrequency = 1000000000;
for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) {
EventHandler bob = (*b).second;
if ( bob.freq < min )
min = bob.freq;
if ( bob.freq < eventFrequency )
eventFrequency = bob.freq;
}
eventFrequency[a] = min;
if ( tick - eventLastTick[a] < eventFrequency )
continue;
eventManager[a](out);
eventLastTick[a] = tick;
}
manageTickEvent(out);
if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) {
manageJobInitiatedEvent(out);
eventLastTick[EventType::JOB_INITIATED] = tick;
}
if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) {
manageJobCompletedEvent(out);
eventLastTick[EventType::JOB_COMPLETED] = tick;
}
if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) {
manageUnitDeathEvent(out);
eventLastTick[EventType::UNIT_DEATH] = tick;
}
if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) {
manageItemCreationEvent(out);
eventLastTick[EventType::ITEM_CREATED] = tick;
}
if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) {
manageBuildingEvent(out);
eventLastTick[EventType::BUILDING] = tick;
}
if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) {
manageConstructionEvent(out);
eventLastTick[EventType::CONSTRUCTION] = tick;
}
if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) {
manageSyndromeEvent(out);
eventLastTick[EventType::SYNDROME] = tick;
}
if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) {
manageInvasionEvent(out);
eventLastTick[EventType::INVASION] = tick;
}
return;
lastTick = tick;
}
static void manageTickEvent(color_ostream& out) {
@ -255,9 +245,6 @@ static void manageTickEvent(color_ostream& out) {
}
static void manageJobInitiatedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_INITIATED].empty() )
return;
if ( lastJobId == -1 ) {
lastJobId = *df::global::job_next_id - 1;
return;
@ -281,10 +268,23 @@ static void manageJobInitiatedEvent(color_ostream& out) {
lastJobId = *df::global::job_next_id - 1;
}
static void manageJobCompletedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_COMPLETED].empty() ) {
return;
//helper function for manageJobCompletedEvent
static int32_t getWorkerID(df::job* job) {
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
continue;
return ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id;
}
return -1;
}
/*
TODO: consider checking item creation / experience gain just in case
*/
static void manageJobCompletedEvent(color_ostream& out) {
uint32_t tick0 = eventLastTick[EventType::JOB_COMPLETED];
uint32_t tick1 = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
map<int32_t, df::job*> nowJobs;
@ -293,12 +293,97 @@ static void manageJobCompletedEvent(color_ostream& out) {
continue;
nowJobs[link->item->id] = link->item;
}
#if 0
//testing info on job initiation/completion
//newly allocated jobs
for ( auto j = nowJobs.begin(); j != nowJobs.end(); j++ ) {
if ( prevJobs.find((*j).first) != prevJobs.end() )
continue;
df::job& job1 = *(*j).second;
out.print("new job\n"
" location : 0x%X\n"
" id : %d\n"
" type : %d %s\n"
" working : %d\n"
" completion_timer : %d\n"
" workerID : %d\n"
" time : %d -> %d\n"
"\n", job1.list_link->item, job1.id, job1.job_type, ENUM_ATTR(job_type, caption, job1.job_type), job1.flags.bits.working, job1.completion_timer, getWorkerID(&job1), tick0, tick1);
}
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
if ( nowJobs.find((*i).first) != nowJobs.end() )
df::job& job0 = *(*i).second;
auto j = nowJobs.find((*i).first);
if ( j == nowJobs.end() ) {
out.print("job deallocated\n"
" location : 0x%X\n"
" id : %d\n"
" type : %d %s\n"
" working : %d\n"
" completion_timer : %d\n"
" workerID : %d\n"
" time : %d -> %d\n"
,job0.list_link == NULL ? 0 : job0.list_link->item, job0.id, job0.job_type, ENUM_ATTR(job_type, caption, job0.job_type), job0.flags.bits.working, job0.completion_timer, getWorkerID(&job0), tick0, tick1);
continue;
//recently finished or cancelled job!
}
df::job& job1 = *(*j).second;
if ( job0.flags.bits.working == job1.flags.bits.working &&
(job0.completion_timer == job1.completion_timer || (job1.completion_timer > 0 && job0.completion_timer-1 == job1.completion_timer)) &&
getWorkerID(&job0) == getWorkerID(&job1) )
continue;
out.print("job change\n"
" location : 0x%X -> 0x%X\n"
" id : %d -> %d\n"
" type : %d -> %d\n"
" type : %s -> %s\n"
" working : %d -> %d\n"
" completion timer : %d -> %d\n"
" workerID : %d -> %d\n"
" time : %d -> %d\n"
"\n",
job0.list_link->item, job1.list_link->item,
job0.id, job1.id,
job0.job_type, job1.job_type,
ENUM_ATTR(job_type, caption, job0.job_type), ENUM_ATTR(job_type, caption, job1.job_type),
job0.flags.bits.working, job1.flags.bits.working,
job0.completion_timer, job1.completion_timer,
getWorkerID(&job0), getWorkerID(&job1),
tick0, tick1
);
}
#endif
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
//if it happened within a tick, must have been cancelled by the user or a plugin: not completed
if ( tick1 <= tick0 )
continue;
if ( nowJobs.find((*i).first) != nowJobs.end() ) {
//could have just finished if it's a repeat job
df::job& job0 = *(*i).second;
if ( !job0.flags.bits.repeat )
continue;
df::job& job1 = *nowJobs[(*i).first];
if ( job0.completion_timer != 0 )
continue;
if ( job1.completion_timer != -1 )
continue;
//still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
(*j).second.eventHandler(out, (void*)&job0);
}
continue;
}
//recently finished or cancelled job
df::job& job0 = *(*i).second;
if ( job0.flags.bits.repeat || job0.completion_timer != 0 )
continue;
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
(*j).second.eventHandler(out, (void*)(*i).second);
}
@ -306,7 +391,7 @@ static void manageJobCompletedEvent(color_ostream& out) {
//erase old jobs, copy over possibly altered jobs
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
Job::deleteJobStruct((*i).second, true);
}
prevJobs.clear();
@ -320,18 +405,9 @@ static void manageJobCompletedEvent(color_ostream& out) {
df::job* newJob = Job::cloneJobStruct((*j).second, true);
prevJobs[newJob->id] = newJob;
}
/*//get rid of old pointers to deallocated jobs
for ( size_t a = 0; a < toDelete.size(); a++ ) {
prevJobs.erase(a);
}*/
}
static void manageUnitDeathEvent(color_ostream& out) {
if ( handlers[EventType::UNIT_DEATH].empty() ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[a];
@ -351,10 +427,6 @@ static void manageUnitDeathEvent(color_ostream& out) {
}
static void manageItemCreationEvent(color_ostream& out) {
if ( handlers[EventType::ITEM_CREATED].empty() ) {
return;
}
if ( nextItem >= *df::global::item_next_id ) {
return;
}
@ -387,9 +459,6 @@ static void manageBuildingEvent(color_ostream& out) {
* TODO: could be faster
* consider looking at jobs: building creation / destruction
**/
if ( handlers[EventType::BUILDING].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) {
@ -431,9 +500,6 @@ static void manageBuildingEvent(color_ostream& out) {
}
static void manageConstructionEvent(color_ostream& out) {
if ( handlers[EventType::CONSTRUCTION].empty() )
return;
unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end());
multimap<Plugin*,EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end());
@ -462,9 +528,6 @@ static void manageConstructionEvent(color_ostream& out) {
}
static void manageSyndromeEvent(color_ostream& out) {
if ( handlers[EventType::SYNDROME].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) {
df::unit* unit = *a;
@ -486,9 +549,6 @@ static void manageSyndromeEvent(color_ostream& out) {
}
static void manageInvasionEvent(color_ostream& out) {
if ( handlers[EventType::INVASION].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end());
if ( df::global::ui->invasions.next_id <= nextInvasion )

@ -55,53 +55,68 @@ using namespace std;
using namespace DFHack;
using namespace df::enums;
df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepWorkerData)
df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepEverything)
{
CHECK_NULL_POINTER(job);
df::job *pnew = new df::job(*job);
// Clean out transient fields
pnew->flags.whole = 0;
pnew->flags.bits.repeat = job->flags.bits.repeat;
pnew->flags.bits.suspend = job->flags.bits.suspend;
if ( !keepEverything ) {
// Clean out transient fields
pnew->flags.whole = 0;
pnew->flags.bits.repeat = job->flags.bits.repeat;
pnew->flags.bits.suspend = job->flags.bits.suspend;
pnew->completion_timer = -1;
}
pnew->list_link = NULL;
pnew->completion_timer = -1;
pnew->items.clear();
pnew->specific_refs.clear();
// Clone refs
for (int i = pnew->general_refs.size()-1; i >= 0; i--)
{
df::general_ref *ref = pnew->general_refs[i];
if (!keepWorkerData && virtual_cast<df::general_ref_unit_workerst>(ref))
vector_erase_at(pnew->general_refs, i);
else
pnew->general_refs[i] = ref->clone();
//pnew->items.clear();
//pnew->specific_refs.clear();
pnew->general_refs.clear();
//pnew->job_items.clear();
if ( keepEverything ) {
for ( int a = 0; a < pnew->items.size(); a++ )
pnew->items[a] = new df::job_item_ref(*pnew->items[a]);
for ( int a = 0; a < pnew->specific_refs.size(); a++ )
pnew->specific_refs[a] = new df::specific_ref(*pnew->specific_refs[a]);
} else {
pnew->items.clear();
pnew->specific_refs.clear();
}
// Clone items
for (int i = pnew->job_items.size()-1; i >= 0; i--)
pnew->job_items[i] = new df::job_item(*pnew->job_items[i]);
for ( int a = 0; a < pnew->job_items.size(); a++ )
pnew->job_items[a] = new df::job_item(*pnew->job_items[a]);
for ( int a = 0; a < job->general_refs.size(); a++ )
if ( keepEverything || job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
pnew->general_refs.push_back(job->general_refs[a]->clone());
return pnew;
}
void DFHack::Job::deleteJobStruct(df::job *job)
void DFHack::Job::deleteJobStruct(df::job *job, bool keptEverything)
{
if (!job)
return;
// Only allow free-floating job structs
assert(!job->list_link && job->items.empty() && job->specific_refs.empty());
for (int i = job->general_refs.size()-1; i >= 0; i--)
delete job->general_refs[i];
for (int i = job->job_items.size()-1; i >= 0; i--)
delete job->job_items[i];
if ( !keptEverything )
assert(!job->list_link && job->items.empty() && job->specific_refs.empty());
else
assert(!job->list_link);
if ( keptEverything ) {
for ( int a = 0; a < job->items.size(); a++ )
delete job->items[a];
for ( int a = 0; a < job->specific_refs.size(); a++ )
delete job->specific_refs[a];
}
for ( int a = 0; a < job->job_items.size(); a++ )
delete job->job_items[a];
for ( int a = 0; a < job->general_refs.size(); a++ )
delete job->general_refs[a];
delete job;
}

@ -0,0 +1,16 @@
#include "modules/Once.h"
#include <unordered_set>
using namespace std;
static unordered_set<string> thingsDone;
bool DFHack::Once::alreadyDone(string bob) {
return thingsDone.find(bob) != thingsDone.end();
}
bool DFHack::Once::doOnce(string bob) {
return thingsDone.insert(bob).second;
}

@ -1 +1 @@
Subproject commit 4d2afc3a0bcebdb17415dc2827b44fd35986a368
Subproject commit 20ecaa0393df1ea111861d67c789aaaa56a37c58

@ -36,7 +36,7 @@ old_tty_settings=$(stty -g)
# Now run
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
case "$1" in
-g | --gdb)

@ -3,6 +3,6 @@
DF_DIR=$(dirname "$0")
cd "${DF_DIR}"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
exec hack/dfhack-run "$@"

@ -1,7 +1,7 @@
#!/bin/sh
DF_DIR=$(dirname "$0")
cd "${DF_DIR}"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
export SDL_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch.
#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
./libs/Dwarf_Fortress $* # Go, go, go! :)

@ -0,0 +1,8 @@
#!/bin/sh
DF_DIR=$(dirname "$0")
cd "${DF_DIR}"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"
./isoworld/isoworld "$@"

@ -1,11 +1,7 @@
INCLUDE(Plugins.cmake)
# Dfusion plugin
IF(UNIX)
OPTION(BUILD_DFUSION "Build DFusion." OFF)
ELSE()
OPTION(BUILD_DFUSION "Build DFusion." ON)
ENDIF()
OPTION(BUILD_DFUSION "Build DFusion." ON)
if(BUILD_DFUSION)
add_subdirectory (Dfusion)
endif()
@ -15,6 +11,20 @@ if(BUILD_STONESENSE)
add_subdirectory (stonesense)
endif()
OPTION(BUILD_ISOWORLD "Build isoworld (needs a checkout first)." OFF)
if(BUILD_ISOWORLD)
add_subdirectory (isoworld)
IF(UNIX)
if (APPLE)
#TODO: add an OSX runner script
else()
# On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/runisoworld
DESTINATION .)
endif()
ENDIF()
endif()
OPTION(BUILD_DEV_PLUGINS "Build developer plugins." OFF)
if(BUILD_DEV_PLUGINS)
add_subdirectory (devel)
@ -65,6 +75,7 @@ ADD_CUSTOM_COMMAND(
${PROJECT_PROTOS}
DEPENDS protoc-bin ${PROJECT_PROTOS}
)
add_custom_target(generate_proto DEPENDS ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS})
SET_SOURCE_FILES_PROPERTIES( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE )
@ -112,7 +123,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(tweak tweak.cpp)
DFHACK_PLUGIN(feature feature.cpp)
DFHACK_PLUGIN(lair lair.cpp)
DFHACK_PLUGIN(zone zone.cpp)
DFHACK_PLUGIN(zone zone.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(catsplosion catsplosion.cpp)
DFHACK_PLUGIN(regrass regrass.cpp)
DFHACK_PLUGIN(forceequip forceequip.cpp)
@ -134,10 +145,19 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(workNow workNow.cpp)
#DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread)
DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp)
DFHACK_PLUGIN(trueTransformation trueTransformation.cpp)
DFHACK_PLUGIN(syndromeTrigger syndromeTrigger.cpp)
DFHACK_PLUGIN(infiniteSky infiniteSky.cpp)
DFHACK_PLUGIN(digSmart digSmart.cpp)
DFHACK_PLUGIN(createitem createitem.cpp)
DFHACK_PLUGIN(outsideOnly outsideOnly.cpp)
DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote)
DFHACK_PLUGIN(buildingplan buildingplan.cpp)
DFHACK_PLUGIN(resume resume.cpp)
DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp)
DFHACK_PLUGIN(mousequery mousequery.cpp)
DFHACK_PLUGIN(autotrade autotrade.cpp)
DFHACK_PLUGIN(stocks stocks.cpp)
DFHACK_PLUGIN(treefarm treefarm.cpp)
endif()

@ -65,14 +65,22 @@ MACRO(DFHACK_PLUGIN)
)
CAR(PLUGIN_NAME ${PLUGIN_DEFAULT_ARGS})
CDR(PLUGIN_SOURCES ${PLUGIN_DEFAULT_ARGS})
SET(PLUGIN_PROTOCPP)
FOREACH(pbuf ${PLUGIN_PROTOBUFS})
SET(PLUGIN_SOURCES ${PLUGIN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc)
SET(PLUGIN_PROTOCPP ${PLUGIN_PROTOCPP} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc)
ENDFOREACH()
# Tell CMake the source won't be available until build time.
SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTOCPP} PROPERTIES GENERATED 1)
ADD_LIBRARY(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES})
IDE_FOLDER(${PLUGIN_NAME} "Plugins")
# Make sure the source is generated before the executable builds.
ADD_DEPENDENCIES(${PLUGIN_NAME} generate_proto)
LIST(LENGTH PLUGIN_PROTOBUFS NUM_PROTO)
IF(NUM_PROTO)
TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack protobuf-lite ${PLUGIN_LINK_LIBRARIES})

@ -1,15 +1,22 @@
#include "PluginManager.h"
#include "Export.h"
#include "DataDefs.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h"
#include "modules/Job.h"
#include "modules/Maps.h"
#include "modules/Once.h"
#include "modules/World.h"
#include "df/building.h"
#include "df/caste_raw.h"
#include "df/creature_interaction_effect.h"
#include "df/creature_raw.h"
#include "df/general_ref.h"
#include "df/general_ref_building_holderst.h"
#include "df/general_ref_type.h"
#include "df/general_ref_unit_workerst.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/item_boulderst.h"
@ -23,10 +30,6 @@
#include "df/unit_syndrome.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/general_ref.h"
#include "df/general_ref_building_holderst.h"
#include "df/general_ref_type.h"
#include "df/general_ref_unit_workerst.h"
#include <string>
#include <vector>
@ -36,75 +39,17 @@
using namespace std;
using namespace DFHack;
/*
Example usage:
//////////////////////////////////////////////
//In file inorganic_duck.txt
inorganic_stone_duck
[OBJECT:INORGANIC]
[INORGANIC:DUCK_ROCK]
[USE_MATERIAL_TEMPLATE:STONE_TEMPLATE]
[STATE_NAME_ADJ:ALL_SOLID:drakium][DISPLAY_COLOR:0:7:0][TILE:'.']
[IS_STONE]
[SOLID_DENSITY:1][MELTING_POINT:25000]
[BOILING_POINT:9999] //This is the critical line: boiling point must be <= 10000
[SYNDROME]
[SYN_NAME:Chronic Duck Syndrome]
[CE_BODY_TRANSFORMATION:PROB:100:START:0]
[CE:CREATURE:BIRD_DUCK:MALE] //even though we don't have SYN_INHALED, the plugin will add it
///////////////////////////////////////////////
//In file building_duck.txt
building_duck
[OBJECT:BUILDING]
[BUILDING_WORKSHOP:DUCK_WORKSHOP]
[NAME:Duck Workshop]
[NAME_COLOR:7:0:1]
[DIM:1:1]
[WORK_LOCATION:1:1]
[BLOCK:1:0:0:0]
[TILE:0:1:236]
[COLOR:0:1:0:0:1]
[TILE:1:1:' ']
[COLOR:1:1:0:0:0]
[TILE:2:1:8]
[COLOR:2:1:0:0:1]
[TILE:3:1:8]
[COLOR:3:2:0:4:1]
[BUILD_ITEM:1:NONE:NONE:NONE:NONE]
[BUILDMAT]
[WORTHLESS_STONE_ONLY]
[CAN_USE_ARTIFACT]
///////////////////////////////////////////////
//In file reaction_duck.txt
reaction_duck
static bool enabled = false;
[OBJECT:REACTION]
[REACTION:DUCKIFICATION]
[NAME:become a duck]
[BUILDING:DUCK_WORKSHOP:NONE]
[PRODUCT:100:100:STONE:NO_SUBTYPE:STONE:DUCK_ROCK]
//////////////////////////////////////////////
//Add the following lines to your entity in entity_default.txt (or wherever it is)
[PERMITTED_BUILDING:DUCK_WORKSHOP]
[PERMITTED_REACTION:DUCKIFICATION]
//////////////////////////////////////////////
Next, start a new fort in a new world, build a duck workshop, then have someone become a duck.
*/
bool enabled = true;
namespace ResetPolicy {
typedef enum {DoNothing, ResetDuration, AddDuration, NewInstance} ResetPolicy;
}
DFHACK_PLUGIN("autoSyndrome");
command_result autoSyndrome(color_ostream& out, vector<string>& parameters);
void processJob(color_ostream& out, void* jobPtr);
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome);
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome, ResetPolicy::ResetPolicy policy);
DFhackCExport command_result plugin_init(color_ostream& out, vector<PluginCommand> &commands) {
commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false,
@ -114,20 +59,11 @@ DFhackCExport command_result plugin_init(color_ostream& out, vector<PluginComman
" autoSyndrome disable //disable\n"
" autoSyndrome enable //enable\n"
"\n"
"autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n"
"\n"
"Requirements:\n"
" 1) The job must be a custom reaction.\n"
" 2) The job must produce a stone of some inorganic material.\n"
" 3) The stone must have a boiling temperature less than or equal to 9000.\n"
"\n"
"When these conditions are met, the unit that completed the job will immediately become afflicted with all applicable syndromes associated with the inorganic material of the stone, or stones. It should correctly check for whether the creature or caste is affected or immune, and it should also correctly account for affected and immune creature classes.\n"
"Multiple syndromes per stone, or multiple boiling rocks produced with the same reaction should work fine.\n"
"autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the unit that finished that job the syndrome specified in the raw files. See Readme.rst for full details.\n"
));
EventManager::EventHandler handle(processJob, 5);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
//EventManager::EventHandler handle(processJob, 5);
//EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
return CR_OK;
}
@ -162,17 +98,15 @@ command_result autoSyndrome(color_ostream& out, vector<string>& parameters) {
if ( enabled == wasEnabled )
return CR_OK;
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome");
EventManager::unregisterAll(plugin_self);
if ( enabled ) {
EventManager::EventHandler handle(processJob, 5);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me);
} else {
EventManager::unregisterAll(me);
EventManager::EventHandler handle(processJob, 0);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
}
return CR_OK;
}
bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df::unit* unit) {
bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df::unit* unit, ResetPolicy::ResetPolicy policy) {
df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race];
df::caste_raw* caste = creature->caste[unit->caste];
std::string& creature_name = creature->creature_id;
@ -211,7 +145,9 @@ bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df
}
if ( syndrome->syn_affected_creature.size() != syndrome->syn_affected_caste.size() ) {
out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__);
if ( DFHack::Once::doOnce("autoSyndrome: different affected creature/caste sizes.") ) {
out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__);
}
return false;
}
for ( size_t c = 0; c < syndrome->syn_affected_creature.size(); c++ ) {
@ -235,15 +171,17 @@ bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df
if ( !applies ) {
return false;
}
if ( giveSyndrome(out, workerId, syndrome) < 0 )
if ( giveSyndrome(out, workerId, syndrome, policy) < 0 )
return false;
return true;
}
void processJob(color_ostream& out, void* jobPtr) {
CoreSuspender suspender;
df::job* job = (df::job*)jobPtr;
if ( job == NULL ) {
out.print("Error %s line %d: null job.\n", __FILE__, __LINE__);
if ( DFHack::Once::doOnce("autoSyndrome_processJob_null job") )
out.print("Error %s line %d: null job.\n", __FILE__, __LINE__);
return;
}
if ( job->completion_timer > 0 )
@ -261,7 +199,8 @@ void processJob(color_ostream& out, void* jobPtr) {
break;
}
if ( reaction == NULL ) {
out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() );
if ( DFHack::Once::doOnce("autoSyndrome processJob couldNotFind") )
out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() );
return;
}
@ -270,48 +209,52 @@ void processJob(color_ostream& out, void* jobPtr) {
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
continue;
if ( workerId != -1 ) {
out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__);
if ( DFHack::Once::doOnce("autoSyndrome processJob two workers same job") )
out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__);
}
workerId = ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id;
if (workerId == -1) {
out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__);
if ( DFHack::Once::doOnce("autoSyndrome processJob invalid worker") )
out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__);
continue;
}
}
int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId);
if ( workerIndex < 0 ) {
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId);
df::unit* worker = df::unit::find(workerId);
if ( worker == NULL ) {
//out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__);
//this probably means that it finished before EventManager could get a copy of the job while the job was running
//TODO: consider printing a warning once
return;
}
df::unit* worker = df::global::world->units.all[workerIndex];
//find the building that made it
int32_t buildingId = -1;
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER )
continue;
if ( buildingId != -1 ) {
out.print("%s, line %d: Found two buildings for the same job.\n", __FILE__, __LINE__);
if ( DFHack::Once::doOnce("autoSyndrome processJob two buildings same job") )
out.print("%s, line %d: Found two buildings for the same job.\n", __FILE__, __LINE__);
}
buildingId = ((df::general_ref_building_holderst*)job->general_refs[a])->building_id;
if (buildingId == -1) {
out.print("%s, line %d: invalid building.\n", __FILE__, __LINE__);
if ( DFHack::Once::doOnce("autoSyndrome processJob invalid building") )
out.print("%s, line %d: invalid building.\n", __FILE__, __LINE__);
continue;
}
}
df::building* building;
{
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, buildingId);
if ( index == -1 ) {
df::building* building = df::building::find(buildingId);
if ( building == NULL ) {
if ( DFHack::Once::doOnce("autoSyndrome processJob couldn't find building") )
out.print("%s, line %d: error: couldn't find building %d.\n", __FILE__, __LINE__, buildingId);
return;
}
building = df::global::world->buildings.all[index];
return;
}
//find all of the products it makes. Look for a stone with a low boiling point.
bool appliedSomething = false;
//find all of the products it makes. Look for a stone.
for ( size_t a = 0; a < reaction->products.size(); a++ ) {
bool appliedSomething = false;
df::reaction_product_type type = reaction->products[a]->getType();
//out.print("type = %d\n", (int32_t)type);
if ( type != df::enums::reaction_product_type::item )
@ -320,40 +263,59 @@ void processJob(color_ostream& out, void* jobPtr) {
//out.print("item_type = %d\n", (int32_t)bob->item_type);
if ( bob->item_type != df::enums::item_type::BOULDER )
continue;
if ( bob->mat_index < 0 )
continue;
//for now don't worry about subtype
//must be a boiling rock syndrome
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index];
if ( inorganic->material.heat.boiling_point > 9000 ) {
continue;
}
//maybe add each syndrome to the guy who did the job, or someone in the building, and maybe execute a command
for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) {
//add each syndrome to the guy who did the job
df::syndrome* syndrome = inorganic->material.syndrome[b];
bool workerOnly = false;
bool workerOnly = true;
bool allowMultipleTargets = false;
bool foundCommand = false;
bool destroyRock = true;
bool foundAutoSyndrome = false;
ResetPolicy::ResetPolicy policy = ResetPolicy::NewInstance;
string commandStr;
vector<string> args;
for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) {
std::string* clazz = syndrome->syn_class[c];
std::string& clazz = *syndrome->syn_class[c];
//special syn_classes
if ( clazz == "\\AUTO_SYNDROME" ) {
foundAutoSyndrome = true;
continue;
} else if ( clazz == "\\ALLOW_NONWORKER_TARGETS" ) {
workerOnly = false;
continue;
} else if ( clazz == "\\ALLOW_MULTIPLE_TARGETS" ) {
allowMultipleTargets = true;
continue;
} else if ( clazz == "\\PRESERVE_ROCK" ) {
destroyRock = false;
continue;
} else if ( clazz == "\\RESET_POLICY DoNothing" ) {
policy = ResetPolicy::DoNothing;
continue;
} else if ( clazz == "\\RESET_POLICY ResetDuration" ) {
policy = ResetPolicy::ResetDuration;
continue;
} else if ( clazz == "\\RESET_POLICY AddDuration" ) {
policy = ResetPolicy::AddDuration;
continue;
} else if ( clazz == "\\RESET_POLICY NewInstance" ) {
policy = ResetPolicy::NewInstance;
continue;
}
//special arguments for a DFHack console command
if ( foundCommand ) {
if ( commandStr == "" ) {
if ( *clazz == "\\WORKER_ONLY" ) {
workerOnly = true;
} else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) {
allowMultipleTargets = true;
} else if ( *clazz == "\\PRESERVE_ROCK" ) {
destroyRock = false;
}
else {
commandStr = *clazz;
}
commandStr = clazz;
} else {
stringstream bob;
if ( *clazz == "\\LOCATION" ) {
if ( clazz == "\\LOCATION" ) {
bob << job->pos.x;
args.push_back(bob.str());
bob.str("");
@ -368,24 +330,28 @@ void processJob(color_ostream& out, void* jobPtr) {
args.push_back(bob.str());
bob.str("");
bob.clear();
} else if ( *clazz == "\\WORKER_ID" ) {
} else if ( clazz == "\\WORKER_ID" ) {
bob << workerId;
args.push_back(bob.str());
} else if ( *clazz == "\\REACTION_INDEX" ) {
} else if ( clazz == "\\REACTION_INDEX" ) {
bob << reaction->index;
args.push_back(bob.str());
} else {
args.push_back(*clazz);
args.push_back(clazz);
}
}
} else if ( *clazz == "\\COMMAND" ) {
} else if ( clazz == "\\COMMAND" ) {
foundCommand = true;
}
}
if ( !foundAutoSyndrome ) {
continue;
}
if ( commandStr != "" ) {
Core::getInstance().runCommand(out, commandStr, args);
}
if ( destroyRock ) {
//find the rock and kill it before it can boil and cause problems and ugliness
for ( size_t c = 0; c < df::global::world->items.all.size(); c++ ) {
@ -403,35 +369,34 @@ void processJob(color_ostream& out, void* jobPtr) {
if ( boulder->mat_index != bob->mat_index )
continue;
boulder->flags.bits.garbage_collect = true;
boulder->flags.bits.forbid = true;
boulder->flags.bits.hidden = true;
boulder->flags.bits.forbid = true;
boulder->flags.bits.garbage_collect = true;
}
}
//only one syndrome per reaction will be applied, unless multiples are allowed.
if ( appliedSomething && !allowMultipleTargets )
continue;
if ( maybeApply(out, syndrome, workerId, worker) ) {
if ( maybeApply(out, syndrome, workerId, worker, policy) ) {
appliedSomething = true;
}
if ( workerOnly )
continue;
if ( appliedSomething && !allowMultipleTargets )
continue;
//now try applying it to everybody inside the building
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[a];
if ( unit == worker )
continue;
continue; //we already tried giving it to him, so no doubling up
if ( unit->pos.z != building->z )
continue;
if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 )
continue;
if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 )
continue;
if ( maybeApply(out, syndrome, unit->id, unit) ) {
if ( maybeApply(out, syndrome, unit->id, unit, policy) ) {
appliedSomething = true;
if ( !allowMultipleTargets )
break;
@ -446,27 +411,67 @@ void processJob(color_ostream& out, void* jobPtr) {
/*
* Heavily based on https://gist.github.com/4061959/
**/
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome) {
int32_t index = df::unit::binsearch_index(df::global::world->units.all, workerId);
if ( index < 0 ) {
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId);
int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome, ResetPolicy::ResetPolicy policy) {
df::unit* unit = df::unit::find(workerId);
if ( !unit ) {
if ( DFHack::Once::doOnce("autoSyndrome giveSyndrome couldn't find unit") )
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId);
return -1;
}
df::unit* unit = df::global::world->units.all[index];
if ( policy != ResetPolicy::NewInstance ) {
//figure out if already there
for ( size_t a = 0; a < unit->syndromes.active.size(); a++ ) {
df::unit_syndrome* unitSyndrome = unit->syndromes.active[a];
if ( unitSyndrome->type != syndrome->id )
continue;
int32_t most = 0;
switch(policy) {
case ResetPolicy::DoNothing:
return -1;
case ResetPolicy::ResetDuration:
for ( size_t b = 0; b < unitSyndrome->symptoms.size(); b++ ) {
unitSyndrome->symptoms[b]->ticks = 0; //might cause crashes with transformations
}
unitSyndrome->ticks = 0;
break;
case ResetPolicy::AddDuration:
if ( unitSyndrome->symptoms.size() != syndrome->ce.size() ) {
if ( DFHack::Once::doOnce("autoSyndrome giveSyndrome incorrect symptom count") )
out.print("%s, line %d. Incorrect symptom count %d != %d\n", __FILE__, __LINE__, unitSyndrome->symptoms.size(), syndrome->ce.size());
break;
}
for ( size_t b = 0; b < unitSyndrome->symptoms.size(); b++ ) {
if ( syndrome->ce[b]->end == -1 )
continue;
unitSyndrome->symptoms[b]->ticks -= syndrome->ce[b]->end;
if ( syndrome->ce[b]->end > most )
most = syndrome->ce[b]->end;
}
unitSyndrome->ticks -= most;
break;
default:
if ( DFHack::Once::doOnce("autoSyndrome giveSyndrome invalid reset policy") )
out.print("%s, line %d: invalid reset policy %d.\n", __FILE__, __LINE__, policy);
return -1;
}
return 0;
}
}
df::unit_syndrome* unitSyndrome = new df::unit_syndrome();
unitSyndrome->type = syndrome->id;
unitSyndrome->year = 0;
unitSyndrome->year_time = 0;
unitSyndrome->ticks = 1;
unitSyndrome->unk1 = 1;
unitSyndrome->flags = 0; //typecast
unitSyndrome->year = DFHack::World::ReadCurrentYear();
unitSyndrome->year_time = DFHack::World::ReadCurrentTick();
unitSyndrome->ticks = 0;
unitSyndrome->unk1 = 0;
unitSyndrome->flags = 0; //TODO: typecast?
for ( size_t a = 0; a < syndrome->ce.size(); a++ ) {
df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms();
symptom->unk1 = 0;
symptom->unk2 = 0;
symptom->ticks = 1;
symptom->ticks = 0;
symptom->flags = 2; //TODO: ???
unitSyndrome->symptoms.push_back(symptom);
}

@ -679,6 +679,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" also tries to have dwarves specialize in specific skills.\n"
" Warning: autolabor will override any manual changes you make to labors\n"
" while it is enabled.\n"
" To prevent particular dwarves from being managed by autolabor, put them\n"
" in any burrow.\n"
"Examples:\n"
" autolabor MINE 2\n"
" Keep at least 2 dwarves with mining enabled.\n"

File diff suppressed because it is too large Load Diff

@ -0,0 +1,619 @@
#include "uicommon.h"
#include "modules/Gui.h"
#include "df/world.h"
#include "df/world_raws.h"
#include "df/building_def.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/building_stockpilest.h"
#include "modules/Items.h"
#include "df/building_tradedepotst.h"
#include "df/general_ref_building_holderst.h"
#include "df/job.h"
#include "df/job_item_ref.h"
#include "modules/Job.h"
#include "df/ui.h"
#include "df/caravan_state.h"
#include "df/mandate.h"
#include "modules/Maps.h"
#include "modules/World.h"
using df::global::world;
using df::global::cursor;
using df::global::ui;
using df::building_stockpilest;
DFHACK_PLUGIN("autotrade");
#define PLUGIN_VERSION 0.2
/*
* Stockpile Access
*/
static building_stockpilest *get_selected_stockpile()
{
if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) ||
ui->main.mode != ui_sidebar_mode::QueryBuilding)
{
return nullptr;
}
return virtual_cast<building_stockpilest>(world->selected_building);
}
static bool can_trade()
{
if (df::global::ui->caravans.size() == 0)
return false;
for (auto it = df::global::ui->caravans.begin(); it != df::global::ui->caravans.end(); it++)
{
auto caravan = *it;
auto trade_state = caravan->trade_state;
auto time_remaining = caravan->time_remaining;
if ((trade_state != 1 && trade_state != 2) || time_remaining == 0)
return false;
}
return true;
}
class StockpileInfo {
public:
StockpileInfo(df::building_stockpilest *sp_) : sp(sp_)
{
readBuilding();
}
StockpileInfo(PersistentDataItem &config)
{
this->config = config;
id = config.ival(1);
}
bool inStockpile(df::item *i)
{
df::item *container = Items::getContainer(i);
if (container)
return inStockpile(container);
if (i->pos.z != z) return false;
if (i->pos.x < x1 || i->pos.x >= x2 ||
i->pos.y < y1 || i->pos.y >= y2) return false;
int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width;
return sp->room.extents[e] == 1;
}
bool isValid()
{
auto found = df::building::find(id);
return found && found == sp && found->getType() == building_type::Stockpile;
}
bool load()
{
auto found = df::building::find(id);
if (!found || found->getType() != building_type::Stockpile)
return false;
sp = virtual_cast<df::building_stockpilest>(found);
if (!sp)
return false;
readBuilding();
return true;
}
int32_t getId()
{
return id;
}
bool matches(df::building_stockpilest* sp)
{
return this->sp == sp;
}
void save()
{
config = DFHack::World::AddPersistentData("autotrade/stockpiles");
config.ival(1) = id;
}
void remove()
{
DFHack::World::DeletePersistentData(config);
}
private:
PersistentDataItem config;
df::building_stockpilest* sp;
int x1, x2, y1, y2, z;
int32_t id;
void readBuilding()
{
id = sp->id;
z = sp->z;
x1 = sp->room.x;
x2 = sp->room.x + sp->room.width;
y1 = sp->room.y;
y2 = sp->room.y + sp->room.height;
}
};
/*
* Depot Access
*/
class TradeDepotInfo
{
public:
TradeDepotInfo() : depot(0)
{
}
bool findDepot()
{
if (isValid())
return true;
reset();
for(auto bld_it = world->buildings.all.begin(); bld_it != world->buildings.all.end(); bld_it++)
{
auto bld = *bld_it;
if (!isUsableDepot(bld))
continue;
depot = bld;
id = depot->id;
break;
}
return depot;
}
bool assignItem(df::item *item)
{
auto href = df::allocate<df::general_ref_building_holderst>();
if (!href)
return false;
auto job = new df::job();
df::coord tpos(depot->centerx, depot->centery, depot->z);
job->pos = tpos;
job->job_type = job_type::BringItemToDepot;
// 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 = id;
depot->jobs.push_back(job);
job->general_refs.push_back(href);
// add to job list
Job::linkIntoWorld(job);
return true;
}
void reset()
{
depot = 0;
}
private:
int32_t id;
df::building *depot;
bool isUsableDepot(df::building* bld)
{
if (bld->getType() != building_type::TradeDepot)
return false;
if (bld->getBuildStage() < bld->getMaxBuildStage())
return false;
if (bld->jobs.size() == 1 && bld->jobs[0]->job_type == job_type::DestroyBuilding)
return false;
return true;
}
bool isValid()
{
if (!depot)
return false;
auto found = df::building::find(id);
return found && found == depot && isUsableDepot(found);
}
};
static TradeDepotInfo depot_info;
/*
* Item Manipulation
*/
static bool check_mandates(df::item *item)
{
for (auto it = world->mandates.begin(); it != world->mandates.end(); it++)
{
auto mandate = *it;
if (mandate->mode != 0)
continue;
if (item->getType() != mandate->item_type ||
(mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype))
continue;
if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type)
continue;
if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index)
continue;
return false;
}
return true;
}
static bool is_valid_item(df::item *item)
{
for (size_t i = 0; i < item->general_refs.size(); i++)
{
df::general_ref *ref = item->general_refs[i];
switch (ref->getType())
{
case general_ref_type::CONTAINED_IN_ITEM:
return false;
case general_ref_type::UNIT_HOLDER:
return false;
case general_ref_type::BUILDING_HOLDER:
return false;
default:
break;
}
}
for (size_t i = 0; i < item->specific_refs.size(); i++)
{
df::specific_ref *ref = item->specific_refs[i];
if (ref->type == specific_ref_type::JOB)
{
// Ignore any items assigned to a job
return false;
}
}
if (!check_mandates(item))
return false;
return true;
}
static void mark_all_in_stockpiles(vector<StockpileInfo> &stockpiles, bool announce)
{
if (!depot_info.findDepot())
{
if (announce)
Gui::showAnnouncement("Cannot trade, no valid depot available", COLOR_RED, true);
return;
}
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;
bad_flags.whole = 0;
#define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact);
F(spider_web); F(owned); F(in_job);
#undef F
size_t marked_count = 0;
size_t error_count = 0;
for (size_t i = 0; i < items.size(); i++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
if (!is_valid_item(item))
continue;
for (auto it = stockpiles.begin(); it != stockpiles.end(); it++)
{
if (!it->inStockpile(item))
continue;
// In case of container, check contained items for mandates
bool mandates_ok = true;
vector<df::item*> contained_items;
Items::getContainedItems(item, &contained_items);
for (auto cit = contained_items.begin(); cit != contained_items.end(); cit++)
{
if (!check_mandates(*cit))
{
mandates_ok = false;
break;
}
}
if (!mandates_ok)
continue;
if (depot_info.assignItem(item))
{
++marked_count;
}
else
{
if (++error_count < 5)
{
Gui::showZoomAnnouncement(df::announcement_type::CANCEL_JOB, item->pos,
"Cannot trade item from stockpile " + int_to_string(it->getId()), COLOR_RED, true);
}
}
}
}
if (marked_count)
Gui::showAnnouncement("Marked " + int_to_string(marked_count) + " items for trade", COLOR_GREEN, false);
else if (announce)
Gui::showAnnouncement("No more items to mark", COLOR_RED, true);
if (error_count >= 5)
{
Gui::showAnnouncement(int_to_string(error_count) + " items were not marked", COLOR_RED, true);
}
}
/*
* Stockpile Monitoring
*/
class StockpileMonitor
{
public:
bool isMonitored(df::building_stockpilest *sp)
{
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
{
if (it->matches(sp))
return true;
}
return false;
}
void add(df::building_stockpilest *sp)
{
auto pile = StockpileInfo(sp);
if (pile.isValid())
{
monitored_stockpiles.push_back(StockpileInfo(sp));
monitored_stockpiles.back().save();
}
}
void remove(df::building_stockpilest *sp)
{
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++)
{
if (it->matches(sp))
{
it->remove();
monitored_stockpiles.erase(it);
break;
}
}
}
void doCycle()
{
if (!can_trade())
return;
for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end();)
{
if (!it->isValid())
{
it = monitored_stockpiles.erase(it);
continue;
}
++it;
}
mark_all_in_stockpiles(monitored_stockpiles, false);
}
void reset()
{
monitored_stockpiles.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "autotrade/stockpiles");
for (auto i = items.begin(); i != items.end(); i++)
{
auto pile = StockpileInfo(*i);
if (pile.load())
monitored_stockpiles.push_back(StockpileInfo(pile));
else
pile.remove();
}
}
private:
vector<StockpileInfo> monitored_stockpiles;
};
static StockpileMonitor monitor;
#define DELTA_TICKS 600
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
if(!Maps::IsValid())
return CR_OK;
static decltype(world->frame_counter) last_frame_count = 0;
if (DFHack::World::ReadPauseState())
return CR_OK;
if (world->frame_counter - last_frame_count < DELTA_TICKS)
return CR_OK;
last_frame_count = world->frame_counter;
monitor.doCycle();
return CR_OK;
}
/*
* Interface
*/
struct trade_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
bool handleInput(set<df::interface_key> *input)
{
building_stockpilest *sp = get_selected_stockpile();
if (!sp)
return false;
if (input->count(interface_key::CUSTOM_M))
{
if (!can_trade())
return false;
vector<StockpileInfo> wrapper;
wrapper.push_back(StockpileInfo(sp));
mark_all_in_stockpiles(wrapper, true);
return true;
}
else if (input->count(interface_key::CUSTOM_U))
{
if (monitor.isMonitored(sp))
monitor.remove(sp);
else
monitor.add(sp);
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (!handleInput(input))
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
building_stockpilest *sp = get_selected_stockpile();
if (!sp)
return;
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
int y = 23;
if (can_trade())
OutputHotkeyString(x, y, "Mark all for trade", "m", true, left_margin);
OutputToggleString(x, y, "Auto trade", "u", monitor.isMonitored(sp), true, left_margin);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, render);
static command_result autotrade_cmd(color_ostream &out, vector <string> & parameters)
{
if (!parameters.empty())
{
if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v')
{
out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl;
}
}
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event)
{
case DFHack::SC_MAP_LOADED:
depot_info.reset();
monitor.reset();
break;
case DFHack::SC_MAP_UNLOADED:
break;
default:
break;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps || !INTERPOSE_HOOK(trade_hook, feed).apply() || !INTERPOSE_HOOK(trade_hook, render).apply())
out.printerr("Could not insert autotrade hooks!\n");
commands.push_back(
PluginCommand(
"autotrade", "Automatically send items in marked stockpiles to trade depot, when trading is possible.",
autotrade_cmd, false, ""));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}

File diff suppressed because it is too large Load Diff

@ -46,18 +46,23 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool glove2 = false)
bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_item = false)
{
vector<df::item *> out_items;
vector<df::reaction_reagent *> in_reag;
vector<df::item *> in_items;
bool is_gloves = (prod->item_type == df::item_type::GLOVES);
bool is_shoes = (prod->item_type == df::item_type::SHOES);
prod->produce(unit, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE,
df::historical_entity::find(unit->civ_id),
((*gametype == df::game_type::DWARF_MAIN) || (*gametype == df::game_type::DWARF_RECLAIM)) ? df::world_site::find(ui->site_id) : NULL);
if (!out_items.size())
return false;
// if we asked to make shoes and we got twice as many as we asked, then we're okay
// otherwise, make a second set because shoes are normally made in pairs
if (is_shoes && out_items.size() == prod->count * 2)
is_shoes = false;
for (size_t i = 0; i < out_items.size(); i++)
{
out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z);
@ -67,10 +72,10 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool glove2 =
if (out_items[i]->getGloveHandedness() > 0)
is_gloves = false;
else
out_items[i]->setGloveHandedness(glove2 ? 2 : 1);
out_items[i]->setGloveHandedness(second_item ? 2 : 1);
}
}
if (is_gloves && !glove2)
if ((is_gloves || is_shoes) && !second_item)
return makeItem(prod, unit, true);
return true;
@ -248,7 +253,9 @@ command_result df_createitem (color_ostream &out, vector <string> & parameters)
break;
}
if (!makeItem(prod, unit))
bool result = makeItem(prod, unit);
delete prod;
if (!result)
{
out.printerr("Failed to create item!\n");
return CR_FAILURE;

@ -21,6 +21,7 @@ DFHACK_PLUGIN(vshook vshook.cpp)
DFHACK_PLUGIN(autolabor2 autolabor2.cpp)
DFHACK_PLUGIN(eventExample eventExample.cpp)
DFHACK_PLUGIN(printArgs printArgs.cpp)
DFHACK_PLUGIN(onceExample onceExample.cpp)
IF(UNIX)
DFHACK_PLUGIN(ref-index ref-index.cpp)
ENDIF()

@ -2158,15 +2158,6 @@ public:
}
}
FOR_ENUM_ITEMS(unit_labor, l)
{
if (l == df::unit_labor::NONE)
continue;
if (labor_infos[l].idle_dwarfs == 0 && labor_infos[l].busy_dwarfs > 0 &&
(labor_infos[l].maximum_dwarfs() == 0 || labor_needed[l] < labor_infos[l].maximum_dwarfs()))
pq.push(make_pair(std::min(labor_infos[l].time_since_last_assigned()/12, 25), l));
}
if (print_debug)
out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size());

@ -7,6 +7,7 @@
#include "DataDefs.h"
#include "df/item.h"
#include "df/job.h"
#include "df/world.h"
#include <vector>
@ -34,11 +35,11 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginC
}
command_result eventExample(color_ostream& out, vector<string>& parameters) {
EventManager::EventHandler initiateHandler(jobInitiated, 10);
EventManager::EventHandler completeHandler(jobCompleted, 5);
EventManager::EventHandler initiateHandler(jobInitiated, 1);
EventManager::EventHandler completeHandler(jobCompleted, 0);
EventManager::EventHandler timeHandler(timePassed, 1);
EventManager::EventHandler deathHandler(unitDeath, 500);
EventManager::EventHandler itemHandler(itemCreate, 1000);
EventManager::EventHandler itemHandler(itemCreate, 1);
EventManager::EventHandler buildingHandler(building, 500);
EventManager::EventHandler constructionHandler(construction, 100);
EventManager::EventHandler syndromeHandler(syndrome, 1);
@ -62,8 +63,17 @@ command_result eventExample(color_ostream& out, vector<string>& parameters) {
return CR_OK;
}
void jobInitiated(color_ostream& out, void* job) {
out.print("Job initiated! 0x%X\n", job);
//static int timerCount=0;
//static int timerDenom=0;
void jobInitiated(color_ostream& out, void* job_) {
out.print("Job initiated! 0x%X\n", job_);
/*
df::job* job = (df::job*)job_;
out.print(" completion_timer = %d\n", job->completion_timer);
if ( job->completion_timer != -1 ) timerCount++;
timerDenom++;
out.print(" frac = %d / %d\n", timerCount, timerDenom);
*/
}
void jobCompleted(color_ostream& out, void* job) {

@ -0,0 +1,35 @@
#include "Core.h"
#include "Console.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Once.h"
using namespace DFHack;
using namespace df::enums;
command_result onceExample (color_ostream &out, std::vector <std::string> & parameters);
DFHACK_PLUGIN("onceExample");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"onceExample", "Test the doOnce command.",
onceExample, false,
" This command tests the doOnce command..\n"
));
return CR_OK;
}
command_result onceExample (color_ostream &out, std::vector <std::string> & parameters)
{
out.print("Already done = %d.\n", DFHack::Once::alreadyDone("onceExample_1"));
if ( DFHack::Once::doOnce("onceExample_1") ) {
out.print("Printing this message once!\n");
}
return CR_OK;
}

@ -55,7 +55,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" Also follows the stone between z-levels with stairs, like 'digl x' would.\n"
));
commands.push_back(PluginCommand("digexp","Select or designate an exploratory pattern. Use 'digexp ?' for help.",digexp));
commands.push_back(PluginCommand("digcircle","Dig designate a circle (filled or hollow) with given radius.",digcircle));
commands.push_back(PluginCommand("digcircle","Dig designate a circle (filled or hollow) with given diameter.",digcircle));
//commands.push_back(PluginCommand("digauto","Mark a tile for continuous digging.",autodig));
commands.push_back(PluginCommand("digtype", "Dig all veins of a given type.", digtype,Gui::cursor_hotkey,
"For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated.\n"
@ -120,7 +120,7 @@ bool dig (MapExtras::MapCache & MCache,
if(tileMaterial(tt) == tiletype_material::CONSTRUCTION && !des.bits.hidden)
return false;
df::tiletype_shape ts = tileShape(tt);
if (ts == tiletype_shape::EMPTY)
if (ts == tiletype_shape::EMPTY && !des.bits.hidden)
return false;
if(!des.bits.hidden)
{
@ -293,7 +293,7 @@ command_result digcircle (color_ostream &out, vector <string> & parameters)
"\n"
"After you have set the options, the command called with no options\n"
"repeats with the last selected parameters:\n"
"'digcircle filled 3' = Dig a filled circle with radius = 3.\n"
"'digcircle filled 3' = Dig a filled circle with diameter = 3.\n"
"'digcircle' = Do it again.\n"
);
return CR_OK;

File diff suppressed because it is too large Load Diff

@ -49,7 +49,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (!Units::isCitizen(unit))
continue;
if (enable_fastdwarf)
if (enable_fastdwarf && !enable_teledwarf )
{
if (unit->counters.job_counter > 0)
unit->counters.job_counter = 0;
@ -59,8 +59,11 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (enable_teledwarf) do
{
// don't do anything if the dwarf isn't going anywhere
if (!unit->path.dest.isValid())
if (!unit->pos.isValid() || !unit->path.dest.isValid() || unit->pos == unit->path.dest) {
if ( enable_fastdwarf && unit->counters.job_counter > 0 )
unit->counters.job_counter = 0;
break;
}
// skip dwarves that are dragging creatures or being dragged
if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1))

@ -23,6 +23,8 @@ typedef void (*checkTile)(DFCoord, MapExtras::MapCache &);
//Forward Declarations for Commands
command_result filltraffic(color_ostream &out, std::vector<std::string> & params);
command_result alltraffic(color_ostream &out, std::vector<std::string> & params);
command_result restrictLiquid(color_ostream &out, std::vector<std::string> & params);
command_result restrictIce(color_ostream &out, std::vector<std::string> & params);
//Forward Declarations for Utility Functions
command_result setAllMatching(color_ostream &out, checkTile checkProc,
@ -34,6 +36,9 @@ void allNormal(DFCoord coord, MapExtras::MapCache & map);
void allLow(DFCoord coord, MapExtras::MapCache & map);
void allRestricted(DFCoord coord, MapExtras::MapCache & map);
void restrictLiquidProc(DFCoord coord, MapExtras::MapCache &map);
void restrictIceProc(DFCoord coord, MapExtras::MapCache &map);
DFHACK_PLUGIN("filltraffic");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
@ -66,6 +71,14 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" L: Low Traffic\n"
" R: Restricted Traffic\n"
));
commands.push_back(PluginCommand(
"restrictliquids","Restrict on every visible square with liquid",
restrictLiquid, false, ""
));
commands.push_back(PluginCommand(
"restrictice","Restrict traffic on squares above visible ice",
restrictIce, false, ""
));
return CR_OK;
}
@ -265,6 +278,16 @@ command_result alltraffic(color_ostream &out, std::vector<std::string> & params)
return setAllMatching(out, proc);
}
command_result restrictLiquid(color_ostream &out, std::vector<std::string> & params)
{
return setAllMatching(out, restrictLiquidProc);
}
command_result restrictIce(color_ostream &out, std::vector<std::string> & params)
{
return setAllMatching(out, restrictIceProc);
}
//Helper function for writing new functions that check every tile on the map.
//newTraffic is the traffic designation to set.
//check takes a coordinate and the map cache as arguments, and returns true if the criteria is met.
@ -356,3 +379,33 @@ void allRestricted(DFCoord coord, MapExtras::MapCache &map)
des.bits.traffic = tile_traffic::Restricted;
map.setDesignationAt(coord, des);
}
//Restrict traffic if tile is visible and liquid is present.
void restrictLiquidProc(DFCoord coord, MapExtras::MapCache &map)
{
df::tile_designation des = map.designationAt(coord);
if ((des.bits.hidden == 0) && (des.bits.flow_size != 0))
{
des.bits.traffic = tile_traffic::Restricted;
map.setDesignationAt(coord, des);
}
}
//Restrict traffice if tile is above visible ice wall.
void restrictIceProc(DFCoord coord, MapExtras::MapCache &map)
{
//There is no ice below the bottom of the map.
if (coord.z == 0)
return;
DFCoord tile_below = DFCoord(coord.x, coord.y, coord.z - 1);
df::tiletype tt = map.tiletypeAt(tile_below);
df::tile_designation des = map.designationAt(tile_below);
if ((des.bits.hidden == 0) && (tileMaterial(tt) == tiletype_material::FROZEN_LIQUID))
{
des = map.designationAt(coord);
des.bits.traffic = tile_traffic::Restricted;
map.setDesignationAt(coord, des);
}
}

@ -0,0 +1 @@
Subproject commit aa3b1bd51f269c07b3235392fd7ed21fe9171f3f

@ -0,0 +1,366 @@
// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D
// some headers required for a plugin. Nothing special, just the basics.
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
// DF data structure definition headers
#include "DataDefs.h"
#include "df/world.h"
#include "df/map_block.h"
#include "df/builtin_mats.h"
#include "df/tile_designation.h"
//DFhack specific headers
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "modules/Materials.h"
//Needed for writing the protobuff stuff to a file.
#include <fstream>
#include <string>
#include <iomanip>
#include "isoworldremote.pb.h"
#include "RemoteServer.h"
using namespace DFHack;
using namespace df::enums;
using namespace isoworldremote;
// Here go all the command declarations...
// mostly to allow having the mandatory stuff on top of the file and commands on the bottom
command_result isoWorldRemote (color_ostream &out, std::vector <std::string> & parameters);
static command_result GetEmbarkTile(color_ostream &stream, const TileRequest *in, EmbarkTile *out);
static command_result GetEmbarkInfo(color_ostream &stream, const MapRequest *in, MapReply *out);
static command_result GetRawNames(color_ostream &stream, const MapRequest *in, RawNames *out);
bool gather_embark_tile_layer(int EmbX, int EmbY, int EmbZ, EmbarkTileLayer * tile, MapExtras::MapCache * MP);
bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCache * MP);
// A plugin must be able to return its name and version.
// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case
DFHACK_PLUGIN("isoworldremote");
// Mandatory init function. If you have some global state, create it here.
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
//// Fill the command list with your commands.
//commands.push_back(PluginCommand(
// "isoworldremote", "Dump north-west embark tile to text file for debug purposes.",
// isoWorldRemote, false, /* true means that the command can't be used from non-interactive user interface */
// // Extended help string. Used by CR_WRONG_USAGE and the help command:
// " This command does nothing at all.\n"
// "Example:\n"
// " isoworldremote\n"
// " Does nothing.\n"
//));
return CR_OK;
}
DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
{
RPCService *svc = new RPCService();
svc->addFunction("GetEmbarkTile", GetEmbarkTile);
svc->addFunction("GetEmbarkInfo", GetEmbarkInfo);
svc->addFunction("GetRawNames", GetRawNames);
return svc;
}
// This is called right before the plugin library is removed from memory.
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
// You *MUST* kill all threads you created before this returns.
// If everything fails, just return CR_FAILURE. Your plugin will be
// in a zombie state, but things won't crash.
return CR_OK;
}
// Called to notify the plugin about important state changes.
// Invoked with DF suspended, and always before the matching plugin_onupdate.
// More event codes may be added in the future.
/*
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_GAME_LOADED:
// initialize from the world just loaded
break;
case SC_GAME_UNLOADED:
// cleanup
break;
default:
break;
}
return CR_OK;
}
*/
// Whatever you put here will be done in each game step. Don't abuse it.
// It's optional, so you can just comment it out like this if you don't need it.
/*
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
// whetever. You don't need to suspend DF execution here.
return CR_OK;
}
*/
//// A command! It sits around and looks pretty. And it's nice and friendly.
//command_result isoWorldRemote (color_ostream &out, std::vector <std::string> & parameters)
//{
// // It's nice to print a help message you get invalid options
// // from the user instead of just acting strange.
// // This can be achieved by adding the extended help string to the
// // PluginCommand registration as show above, and then returning
// // CR_WRONG_USAGE from the function. The same string will also
// // be used by 'help your-command'.
// if (!parameters.empty())
// return CR_WRONG_USAGE;
// // Commands are called from threads other than the DF one.
// // Suspend this thread until DF has time for us. If you
// // use CoreSuspender, it'll automatically resume DF when
// // execution leaves the current scope.
// CoreSuspender suspend;
// // Actually do something here. Yay.
// out.print("Doing a test...\n");
// MapExtras::MapCache MC;
// EmbarkTile test_tile;
// if(!gather_embark_tile(0,0, &test_tile, &MC))
// return CR_FAILURE;
// //test-write the file to check it.
// std::ofstream output_file("tile.p", std::ios_base::binary);
// output_file << test_tile.SerializeAsString();
// output_file.close();
//
// //load it again to verify.
// std::ifstream input_file("tile.p", std::ios_base::binary);
// std::string input_string( (std::istreambuf_iterator<char>(input_file) ),
// (std::istreambuf_iterator<char>() ) );
// EmbarkTile verify_tile;
// verify_tile.ParseFromString(input_string);
// //write contents to text file.
// std::ofstream debug_text("tile.txt", std::ios_base::trunc);
// debug_text << "world coords:" << verify_tile.world_x()<< "," << verify_tile.world_y()<< "," << verify_tile.world_z() << std::endl;
// for(int i = 0; i < verify_tile.tile_layer_size(); i++) {
// debug_text << "layer: " << i << std::endl;
// for(int j = 0; j < 48; j++) {
// debug_text << " ";
// for(int k = 0; k < 48; k++) {
// debug_text << verify_tile.tile_layer(i).mat_type_table(j*48+k) << ",";
// }
// debug_text << " ";
// for(int k = 0; k < 48; k++) {
// debug_text << std::setw(3) << verify_tile.tile_layer(i).mat_subtype_table(j*48+k) << ",";
// }
// debug_text << std::endl;
// }
// debug_text << std::endl;
// }
// // Give control back to DF.
// return CR_OK;
//}
static command_result GetEmbarkTile(color_ostream &stream, const TileRequest *in, EmbarkTile *out)
{
MapExtras::MapCache MC;
gather_embark_tile(in->want_x() * 3, in->want_y() * 3, out, &MC);
MC.trash();
return CR_OK;
}
static command_result GetEmbarkInfo(color_ostream &stream, const MapRequest *in, MapReply *out)
{
if(!Core::getInstance().isWorldLoaded()) {
out->set_available(false);
return CR_OK;
}
if(!Core::getInstance().isMapLoaded()) {
out->set_available(false);
return CR_OK;
}
if(!df::global::gamemode) {
out->set_available(false);
return CR_OK;
}
if((*df::global::gamemode != game_mode::ADVENTURE) && (*df::global::gamemode != game_mode::DWARF)) {
out->set_available(false);
return CR_OK;
}
if(!DFHack::Maps::IsValid()) {
out->set_available(false);
return CR_OK;
}
if(!in->has_save_folder()) { //probably should send the stuff anyway, but nah.
out->set_available(false);
return CR_OK;
}
if(!(in->save_folder() == df::global::world->cur_savegame.save_dir)) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them.
out->set_available(false);
return CR_OK;
}
out->set_available(true);
out->set_current_year(*df::global::cur_year);
out->set_current_season(*df::global::cur_season);
out->set_region_x(df::global::world->map.region_x);
out->set_region_y(df::global::world->map.region_y);
out->set_region_size_x(df::global::world->map.x_count_block / 3);
out->set_region_size_y(df::global::world->map.y_count_block / 3);
return CR_OK;
}
int coord_to_index_48(int x, int y) {
return y*48+x;
}
bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCache * MP) {
tile->set_is_valid(false);
tile->set_world_x(df::global::world->map.region_x + (EmbX/3));
tile->set_world_y(df::global::world->map.region_y + (EmbY/3));
tile->set_world_z(df::global::world->map.region_z + 1); //adding one because floors get shifted one downwards.
tile->set_current_year(*df::global::cur_year);
tile->set_current_season(*df::global::cur_season);
int num_valid_layers = 0;
for(int z = 0; z < MP->maxZ(); z++)
{
EmbarkTileLayer * tile_layer = tile->add_tile_layer();
num_valid_layers += gather_embark_tile_layer(EmbX, EmbY, z, tile_layer, MP);
}
if(num_valid_layers > 0)
tile->set_is_valid(true);
return 1;
}
bool gather_embark_tile_layer(int EmbX, int EmbY, int EmbZ, EmbarkTileLayer * tile, MapExtras::MapCache * MP)
{
for(int i = tile->mat_type_table_size(); i < 2304; i++) { //This is needed so we have a full array to work with, otherwise the size isn't updated correctly.
tile->add_mat_type_table(AIR);
tile->add_mat_subtype_table(0);
}
int num_valid_blocks = 0;
for(int yy = 0; yy < 3; yy++) {
for(int xx = 0; xx < 3; xx++) {
DFCoord current_coord, upper_coord;
current_coord.x = EmbX+xx;
current_coord.y = EmbY+yy;
current_coord.z = EmbZ;
upper_coord = current_coord;
upper_coord.z += 1;
MapExtras::Block * b = MP->BlockAt(current_coord);
MapExtras::Block * b_upper = MP->BlockAt(upper_coord);
if(b && b->getRaw()) {
for(int block_y=0; block_y<16; block_y++) {
for(int block_x=0; block_x<16; block_x++) {
df::coord2d block_coord;
block_coord.x = block_x;
block_coord.y = block_y;
df::tiletype tile_type = b->tiletypeAt(block_coord);
df::tiletype upper_tile = df::tiletype::Void;
if(b_upper && b_upper->getRaw()) {
upper_tile = b_upper->tiletypeAt(block_coord);
}
df::tile_designation designation = b->DesignationAt(block_coord);
DFHack::t_matpair actual_mat;
if(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor && (tileMaterial(tile_type) != tiletype_material::FROZEN_LIQUID) && (tileMaterial(tile_type) != tiletype_material::BROOK)) { //if the upper tile is a floor, use that material instead. Unless it's ice.
actual_mat = b_upper->staticMaterialAt(block_coord);
}
else {
actual_mat = b->staticMaterialAt(block_coord);
}
if(((tileMaterial(tile_type) == tiletype_material::FROZEN_LIQUID) || (tileMaterial(tile_type) == tiletype_material::BROOK)) && (tileShapeBasic(tileShape(tile_type)) == tiletype_shape_basic::Floor)) {
tile_type = tiletype::OpenSpace;
}
unsigned int array_index = coord_to_index_48(xx*16+block_x, yy*16+block_y);
//make a new fake material at the given index
if(tileMaterial(tile_type) == tiletype_material::FROZEN_LIQUID && !((tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor) && (tileMaterial(upper_tile) != tiletype_material::FROZEN_LIQUID))) { //Ice.
tile->set_mat_type_table(array_index, BasicMaterial::LIQUID); //Ice is totally a liquid, shut up.
tile->set_mat_subtype_table(array_index, LiquidType::ICE);
num_valid_blocks++;
}
else if(designation.bits.flow_size && (tileShapeBasic(tileShape(upper_tile)) != tiletype_shape_basic::Floor)) { //Contains either water or lava.
tile->set_mat_type_table(array_index, BasicMaterial::LIQUID);
if(designation.bits.liquid_type) //Magma
tile->set_mat_subtype_table(array_index, LiquidType::MAGMA);
else //water
tile->set_mat_subtype_table(array_index, LiquidType::WATER);
num_valid_blocks++;
}
else if(((tileShapeBasic(tileShape(tile_type)) != tiletype_shape_basic::Open) ||
(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor)) &&
((tileShapeBasic(tileShape(tile_type)) != tiletype_shape_basic::Floor) ||
(tileShapeBasic(tileShape(upper_tile)) == tiletype_shape_basic::Floor))) { //if the upper tile is a floor, we don't skip, otherwise we do.
if(actual_mat.mat_type == builtin_mats::INORGANIC) { //inorganic
tile->set_mat_type_table(array_index, BasicMaterial::INORGANIC);
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
}
else if(actual_mat.mat_type == 419) { //Growing plants
tile->set_mat_type_table(array_index, BasicMaterial::PLANT);
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
}
else if(actual_mat.mat_type >= 420) { //Wooden constructions. Different from growing plants.
tile->set_mat_type_table(array_index, BasicMaterial::WOOD);
tile->set_mat_subtype_table(array_index, actual_mat.mat_index);
}
else { //Unknown and unsupported stuff. Will just be drawn as grey.
tile->set_mat_type_table(array_index, BasicMaterial::OTHER);
tile->set_mat_subtype_table(array_index, actual_mat.mat_type);
}
num_valid_blocks++;
}
else {
tile->set_mat_type_table(array_index, BasicMaterial::AIR);
}
}
}
}
}
}
return (num_valid_blocks >0);
}
static command_result GetRawNames(color_ostream &stream, const MapRequest *in, RawNames *out){
if(!Core::getInstance().isWorldLoaded()) {
out->set_available(false);
return CR_OK;
}
if(!Core::getInstance().isMapLoaded()) {
out->set_available(false);
return CR_OK;
}
if(!df::global::gamemode) {
out->set_available(false);
return CR_OK;
}
if((*df::global::gamemode != game_mode::ADVENTURE) && (*df::global::gamemode != game_mode::DWARF)) {
out->set_available(false);
return CR_OK;
}
if(!DFHack::Maps::IsValid()) {
out->set_available(false);
return CR_OK;
}
if(!in->has_save_folder()) { //probably should send the stuff anyway, but nah.
out->set_available(false);
return CR_OK;
}
if(!(in->save_folder() == df::global::world->cur_savegame.save_dir)) { //isoworld has a different map loaded, don't bother trying to load tiles for it, we don't have them.
out->set_available(false);
return CR_OK;
}
out->set_available(true);
for(int i = 0; i < df::global::world->raws.inorganics.size(); i++){
out->add_inorganic(df::global::world->raws.inorganics[i]->id);
}
for(int i = 0; i < df::global::world->raws.plants.all.size(); i++){
out->add_organic(df::global::world->raws.plants.all[i]->id);
}
return CR_OK;
}

@ -0,0 +1,12 @@
local _ENV = mkmodule('plugins.zone')
--[[
Native functions:
* autobutcher_isEnabled()
* autowatch_isEnabled()
--]]
return _ENV

@ -0,0 +1,263 @@
#include <sstream>
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <VTableInterpose.h>
#include "DataDefs.h"
#include "df/building.h"
#include "df/enabler.h"
#include "df/item.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/world.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
using std::set;
using std::string;
using std::ostringstream;
using namespace DFHack;
using namespace df::enums;
using df::global::enabler;
using df::global::gps;
using df::global::world;
using df::global::ui;
static int32_t last_x, last_y, last_z;
static size_t max_list_size = 100000; // Avoid iterating over huge lists
struct mousequery_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
void send_key(const df::interface_key &key)
{
set<df::interface_key> tmp;
tmp.insert(key);
//INTERPOSE_NEXT(feed)(&tmp);
this->feed(&tmp);
}
df::interface_key get_default_query_mode(const int32_t &x, const int32_t &y, const int32_t &z)
{
bool fallback_to_building_query = false;
// Check for unit under cursor
size_t count = world->units.all.size();
if (count <= max_list_size)
{
for(size_t i = 0; i < count; i++)
{
df::unit *unit = world->units.all[i];
if(unit->pos.x == x && unit->pos.y == y && unit->pos.z == z)
return df::interface_key::D_VIEWUNIT;
}
}
else
{
fallback_to_building_query = true;
}
// Check for building under cursor
count = world->buildings.all.size();
if (count <= max_list_size)
{
for(size_t i = 0; i < count; i++)
{
df::building *bld = world->buildings.all[i];
if (z == bld->z &&
x >= bld->x1 && x <= bld->x2 &&
y >= bld->y1 && y <= bld->y2)
{
df::building_type type = bld->getType();
if (type == building_type::Stockpile)
{
fallback_to_building_query = true;
break; // Check for items in stockpile first
}
// For containers use item view, fir everything else, query view
return (type == building_type::Box || type == building_type::Cabinet ||
type == building_type::Weaponrack || type == building_type::Armorstand)
? df::interface_key::D_BUILDITEM : df::interface_key::D_BUILDJOB;
}
}
}
else
{
fallback_to_building_query = true;
}
// Check for items under cursor
count = world->items.all.size();
if (count <= max_list_size)
{
for(size_t i = 0; i < count; i++)
{
df::item *item = world->items.all[i];
if (z == item->pos.z && x == item->pos.x && y == item->pos.y &&
!item->flags.bits.in_building && !item->flags.bits.hidden &&
!item->flags.bits.in_job && !item->flags.bits.in_chest &&
!item->flags.bits.in_inventory)
{
return df::interface_key::D_LOOK;
}
}
}
else
{
fallback_to_building_query = true;
}
return (fallback_to_building_query) ? df::interface_key::D_BUILDJOB : df::interface_key::D_LOOK;
}
bool handle_mouse(const set<df::interface_key> *input)
{
int32_t cx, cy, vz;
if (enabler->tracking_on)
{
if (enabler->mouse_lbut)
{
int32_t mx, my;
if (Gui::getMousePos(mx, my))
{
int32_t vx, vy;
if (Gui::getViewCoords(vx, vy, vz))
{
cx = vx + mx - 1;
cy = vy + my - 1;
using namespace df::enums::ui_sidebar_mode;
df::interface_key key = interface_key::NONE;
bool cursor_still_here = (last_x == cx && last_y == cy && last_z == vz);
switch(ui->main.mode)
{
case QueryBuilding:
if (cursor_still_here)
key = df::interface_key::D_BUILDITEM;
break;
case BuildingItems:
if (cursor_still_here)
key = df::interface_key::D_VIEWUNIT;
break;
case ViewUnits:
if (cursor_still_here)
key = df::interface_key::D_LOOK;
break;
case LookAround:
if (cursor_still_here)
key = df::interface_key::D_BUILDJOB;
break;
case Default:
break;
default:
return false;
}
enabler->mouse_lbut = 0;
// Can't check limits earlier as we must be sure we are in query or default mode we can clear the button flag
// Otherwise the feed gets stuck in a loop
uint8_t menu_width, area_map_width;
Gui::getMenuWidth(menu_width, area_map_width);
int32_t w = gps->dimx;
if (menu_width == 1) w -= 57; //Menu is open doubly wide
else if (menu_width == 2 && area_map_width == 3) w -= 33; //Just the menu is open
else if (menu_width == 2 && area_map_width == 2) w -= 26; //Just the area map is open
if (mx < 1 || mx > w || my < 1 || my > gps->dimy - 2)
return false;
while (ui->main.mode != Default)
{
send_key(df::interface_key::LEAVESCREEN);
}
if (key == interface_key::NONE)
key = get_default_query_mode(cx, cy, vz);
send_key(key);
// Force UI refresh
Gui::setCursorCoords(cx, cy, vz);
send_key(interface_key::CURSOR_DOWN_Z);
send_key(interface_key::CURSOR_UP_Z);
last_x = cx;
last_y = cy;
last_z = vz;
return true;
}
}
}
else if (enabler->mouse_rbut)
{
// Escape out of query mode
using namespace df::enums::ui_sidebar_mode;
if (ui->main.mode == QueryBuilding || ui->main.mode == BuildingItems ||
ui->main.mode == ViewUnits || ui->main.mode == LookAround)
{
while (ui->main.mode != Default)
{
enabler->mouse_rbut = 0;
send_key(df::interface_key::LEAVESCREEN);
}
}
}
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (!handle_mouse(input))
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(mousequery_hook, feed);
DFHACK_PLUGIN("mousequery");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps || !INTERPOSE_HOOK(mousequery_hook, feed).apply())
out.printerr("Could not insert mousequery hooks!\n");
last_x = last_y = last_z = -1;
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
last_x = last_y = last_z = -1;
break;
default:
break;
}
return CR_OK;
}

@ -0,0 +1,63 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Buildings.h"
#include "modules/EventManager.h"
#include "modules/Maps.h"
#include "df/coord.h"
#include "df/building.h"
#include "df/building_def.h"
#include "df/map_block.h"
#include "df/tile_designation.h"
#include <string>
using namespace DFHack;
using namespace std;
DFHACK_PLUGIN("outsideOnly");
void buildingCreated(color_ostream& out, void* data);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
EventManager::EventHandler handler(buildingCreated,1);
EventManager::registerListener(EventManager::EventType::BUILDING, handler, plugin_self);
return CR_OK;
}
// This is called right before the plugin library is removed from memory.
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
void buildingCreated(color_ostream& out, void* data) {
int32_t id = (int32_t)data;
df::building* building = df::building::find(id);
if ( building == NULL )
return;
if ( building->getCustomType() < 0 )
return;
string prefix("OUTSIDE_ONLY");
df::building_def* def = df::global::world->raws.buildings.all[building->getCustomType()];
if (def->code.compare(0, prefix.size(), prefix)) {
return;
}
//now, just check if it was created inside, and if so, scuttle it
df::coord pos(building->centerx,building->centery,building->z);
df::tile_designation* des = Maps::getTileDesignation(pos);
if ( des->bits.outside )
return;
Buildings::deconstruct(building);
}

@ -0,0 +1,59 @@
package isoworldremote;
//Describes a very basic material structure for the map embark
option optimize_for = LITE_RUNTIME;
enum BasicMaterial {
AIR = 0;
OTHER = 1;
INORGANIC = 2;
LIQUID = 3;
PLANT = 4;
WOOD = 5;
};
enum LiquidType {
ICE = 0;
WATER = 1;
MAGMA = 2;
}
message EmbarkTileLayer {
repeated BasicMaterial mat_type_table = 4 [packed=true];
repeated int32 mat_subtype_table = 5 [packed=true];
}
message EmbarkTile {
required int32 world_x = 1;
required int32 world_y = 2;
required sint32 world_z = 3;
repeated EmbarkTileLayer tile_layer = 4;
optional int32 current_year = 5;
optional int32 current_season = 6;
optional bool is_valid = 7;
}
message TileRequest {
optional int32 want_x = 1;
optional int32 want_y = 2;
}
message MapRequest {
optional string save_folder = 1;
}
message MapReply {
required bool available = 1;
optional int32 region_x = 2;
optional int32 region_y = 3;
optional int32 region_size_x = 4;
optional int32 region_size_y = 5;
optional int32 current_year = 6;
optional int32 current_season = 7;
}
message RawNames {
required bool available = 1;
repeated string inorganic = 2;
repeated string organic = 3;
}

@ -0,0 +1,315 @@
#include <string>
#include <vector>
#include <algorithm>
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <VTableInterpose.h>
// DF data structure definition headers
#include "DataDefs.h"
#include "MiscUtils.h"
#include "Types.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/world.h"
#include "df/building_constructionst.h"
#include "df/building.h"
#include "df/job.h"
#include "df/job_item.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/World.h"
using std::map;
using std::string;
using std::vector;
using namespace DFHack;
using namespace df::enums;
using df::global::gps;
using df::global::ui;
using df::global::world;
DFHACK_PLUGIN("resume");
#define PLUGIN_VERSION 0.2
#ifndef HAVE_NULLPTR
#define nullptr 0L
#endif
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
template <class T, typename Fn>
static void for_each_(vector<T> &v, Fn func)
{
for_each(v.begin(), v.end(), func);
}
template <class T, class V, typename Fn>
static void transform_(vector<T> &src, vector<V> &dst, Fn func)
{
transform(src.begin(), src.end(), back_inserter(dst), func);
}
void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0)
{
Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
if (newline)
{
++y;
x = left_margin;
}
else
x += text.length();
}
df::job *get_suspended_job(df::building *bld)
{
if (bld->getBuildStage() != 0)
return nullptr;
if (bld->jobs.size() == 0)
return nullptr;
auto job = bld->jobs[0];
if (job->flags.bits.suspend)
return job;
return nullptr;
}
struct SuspendedBuilding
{
df::building *bld;
df::coord pos;
bool was_resumed;
bool is_planned;
SuspendedBuilding(df::building *bld_) : bld(bld_), was_resumed(false), is_planned(false)
{
pos = df::coord(bld->centerx, bld->centery, bld->z);
}
bool isValid()
{
return bld && Buildings::findAtTile(pos) == bld && get_suspended_job(bld);
}
};
static bool enabled = false;
static bool buildings_scanned = false;
static vector<SuspendedBuilding> suspended_buildings, resumed_buildings;
void scan_for_suspended_buildings()
{
if (buildings_scanned)
return;
for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++)
{
auto bld = *b;
auto job = get_suspended_job(bld);
if (job)
{
SuspendedBuilding sb(bld);
sb.is_planned = job->job_items.size() == 1 && job->job_items[0]->item_type == item_type::NONE;
auto it = find_if(resumed_buildings.begin(), resumed_buildings.end(),
[&] (SuspendedBuilding &rsb) { return rsb.bld == bld; });
sb.was_resumed = it != resumed_buildings.end();
suspended_buildings.push_back(sb);
}
}
buildings_scanned = true;
}
void show_suspended_buildings()
{
int32_t vx, vy, vz;
if (!Gui::getViewCoords(vx, vy, vz))
return;
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = vx + dims.map_x2;
int bottom_margin = vy + dims.y2;
for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end();)
{
if (!sb->isValid())
{
sb = suspended_buildings.erase(sb);
continue;
}
if (sb->bld->z == vz && sb->bld->centerx >= vx && sb->bld->centerx <= left_margin &&
sb->bld->centery >= vy && sb->bld->centery <= bottom_margin)
{
int x = sb->bld->centerx - vx + 1;
int y = sb->bld->centery - vy + 1;
auto color = COLOR_YELLOW;
if (sb->is_planned)
color = COLOR_GREEN;
else if (sb->was_resumed)
color = COLOR_RED;
OutputString(color, x, y, "X");
}
sb++;
}
}
void clear_scanned()
{
buildings_scanned = false;
suspended_buildings.clear();
}
void resume_suspended_buildings(color_ostream &out)
{
out << "Resuming all buildings." << endl;
for (auto isb = resumed_buildings.begin(); isb != resumed_buildings.end();)
{
if (isb->isValid())
{
isb++;
continue;
}
isb = resumed_buildings.erase(isb);
}
scan_for_suspended_buildings();
for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end(); sb++)
{
if (sb->is_planned)
continue;
resumed_buildings.push_back(*sb);
sb->bld->jobs[0]->flags.bits.suspend = false;
}
clear_scanned();
out << resumed_buildings.size() << " buildings resumed" << endl;
}
//START Viewscreen Hook
struct resume_hook : public df::viewscreen_dwarfmodest
{
//START UI Methods
typedef df::viewscreen_dwarfmodest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (enabled && DFHack::World::ReadPauseState() && ui->main.mode == ui_sidebar_mode::Default)
{
scan_for_suspended_buildings();
show_suspended_buildings();
}
else
{
clear_scanned();
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(resume_hook, render);
static command_result resume_cmd(color_ostream &out, vector <string> & parameters)
{
bool show_help = false;
if (parameters.empty())
{
show_help = true;
}
else
{
auto cmd = parameters[0][0];
if (cmd == 'v')
{
out << "Resume" << endl << "Version: " << PLUGIN_VERSION << endl;
}
else if (cmd == 's')
{
enabled = true;
out << "Overlay enabled" << endl;
}
else if (cmd == 'h')
{
enabled = false;
out << "Overlay disabled" << endl;
}
else if (cmd == 'a')
{
resume_suspended_buildings(out);
}
else
{
show_help = true;
}
}
if (show_help)
return CR_WRONG_USAGE;
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps || !INTERPOSE_HOOK(resume_hook, render).apply())
out.printerr("Could not insert resume hooks!\n");
commands.push_back(
PluginCommand(
"resume", "A plugin to help display and resume suspended constructions conveniently",
resume_cmd, false,
"resume show\n"
" Show overlay when paused:\n"
" Yellow: Suspended construction\n"
" Red: Suspended after resume attempt, possibly stuck\n"
" Green: Planned building waiting for materials\n"
"resume hide\n"
" Hide overlay\n"
"resume all\n"
" Resume all suspended building constructions\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
suspended_buildings.clear();
resumed_buildings.clear();
break;
default:
break;
}
return CR_OK;
}

@ -54,6 +54,19 @@ The help string displayed in dfhack 'ls' command is the first line of the
script, if it is a comment (ie starts with '# ').
Calling dfhack commands
-----------------------
The ruby plugin allows the calling of arbitrary dfhack commands, as if typed
directly on the dfhack prompt.
However due to locks and stuff, the dfhack command is delayed until the current
ruby command is finished, so it is restricted to interactive uses.
It is possible to call the method many times, this will queue dfhack commands
to be run in order.
df.dfhack_run "reveal"
Ruby helper functions
---------------------
@ -107,7 +120,7 @@ eg 'gob' for 'GOBLIN' or 'coal' for 'COAL_BITUMINOUS', hence the name.
df.building_construct(bld, item_list)
Allocates a new building in DF memory, define its position / dimensions, and
create a dwarf job to construct it from the given list of items.
See buildings.rb/buildbed for an exemple.
See buildings.rb/buildbed for an example.
df.each_tree(material) { |t| }
Iterates over every tree of the given material (eg 'maple').
@ -137,13 +150,17 @@ To stop being called, use:
The same mechanism is available for 'onstatechange', but the
SC_BEGIN_UNLOAD event is not propagated to the ruby handler.
Available states:
:WORLD_LOADED, :WORLD_UNLOADED, :MAP_LOADED, :MAP_UNLOADED,
:VIEWSCREEN_CHANGED, :CORE_INITIALIZED, :PAUSED, :UNPAUSED
C++ object manipulation
-----------------------
The ruby classes defined in ruby-autogen.rb are accessors to the underlying
df C++ objects in-memory. To allocate a new C++ object for use in DF, use the
RubyClass.cpp_new method (see buildings.rb for exemples), works for Compounds
RubyClass.cpp_new method (see buildings.rb for examples), works for Compounds
only.
A special Compound DFHack::StlString is available for allocating a single c++
stl::string, so that you can call vmethods that take a string pointer argument
@ -186,10 +203,10 @@ Pointer fields are automatically dereferenced ; so a vector of pointer to
Units will yield Units directly. NULL pointers yield the 'nil' value.
Exemples
Examples
--------
For more complex exemples, check the dfhack/scripts/*.rb files.
For more complex examples, check the dfhack/scripts/*.rb files.
Show info on the currently selected unit ('v' or 'k' DF menu)
p df.unit_find.flags1
@ -240,7 +257,7 @@ which differ between Windows and Linux. Linux and Macosx are the same, as they
both use gcc).
It is stored inside the build directory (eg build/plugins/ruby/ruby-autogen.rb)
For exemple,
For example,
<ld:global-type ld:meta="struct-type" type-name="unit">
<ld:field type-name="language_name" name="name" ld:meta="global"/>
<ld:field name="custom_profession" ld:meta="primitive" ld:subtype="stl-string"/>

@ -48,6 +48,8 @@ module DFHack
raise "invalid building type #{type.inspect}" if not cls
bld = cls.cpp_new
bld.race = ui.race_id
subtype = ConstructionType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Construction
subtype = SiegeengineType.int(subtype) if subtype.kind_of?(::Symbol) and type == :SiegeEngine
subtype = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop
subtype = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace
subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone

@ -149,7 +149,21 @@ module DFHack
def vein
# last vein wins
all_veins.reverse.find { |v|
(v.tile_bitmask.bits[@dy] & (1 << @dx)) > 0
v.tile_bitmask.bits[@dy][@dx] > 0
}
end
# return the first BlockBurrow this tile is in (nil if none)
def burrow
mapblock.block_burrows.find { |b|
b.tile_bitmask.bits[@dy][@dx] > 0
}
end
# return the array of BlockBurrow this tile is in
def all_burrows
mapblock.block_burrows.find_all { |b|
b.tile_bitmask.bits[@dy][@dx] > 0
}
end

@ -122,7 +122,8 @@ module DFHack
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
end
def _cpp_delete
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_delete }
# cannot call delete on compound members (would call free on member address)
#_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_delete }
DFHack.free(@_memaddr)
@_memaddr = nil # turn future segfaults in harmless ruby exceptions
end
@ -642,7 +643,7 @@ module DFHack
@_tg = tg
end
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every
# possible struct size, like for StlVector. Just ignore it for now, deque are rare enough.
# possible struct size, like for StlVector. Just ignore it for now, deques are rare enough.
def inspect ; "#<StlDeque>" ; end
end
@ -676,7 +677,7 @@ module DFHack
def inspect
out = "#<DfFlagarray"
each_with_index { |e, idx|
out << " #{_indexenum.sym(idx)}" if e
out << " #{_indexenum ? _indexenum.sym(idx) : idx}" if e
}
out << '>'
end

@ -41,6 +41,7 @@ static tthread::thread *r_thread;
static int onupdate_active;
static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1;
static color_ostream_proxy *console_proxy;
static std::vector<std::string> *dfhack_run_queue;
DFHACK_PLUGIN("ruby")
@ -63,6 +64,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
// lock this before anything, and release when everything is done
m_mutex = new tthread::mutex();
// list of dfhack commands to run when the current ruby run is done (once locks are released)
dfhack_run_queue = new std::vector<std::string>;
r_type = RB_INIT;
// create the dedicated ruby thread
@ -84,6 +88,10 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
"Ruby interpreter. Eval() a ruby string.",
df_rubyeval));
commands.push_back(PluginCommand("rb",
"Ruby interpreter. Eval() a ruby string (alias for rb_eval).",
df_rubyeval));
return CR_OK;
}
@ -111,6 +119,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
// we can release m_mutex, other users will check r_thread
m_mutex->unlock();
delete m_mutex;
delete dfhack_run_queue;
// dlclose libruby
df_unloadruby();
@ -152,6 +161,8 @@ static command_result do_plugin_eval_ruby(color_ostream &out, const char *comman
// send a single ruby line to be evaluated by the ruby thread
DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command)
{
command_result ret;
// if dlopen failed
if (!r_thread)
return CR_FAILURE;
@ -160,14 +171,24 @@ DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *c
// debug only!
// run ruby commands without locking the main thread
// useful when the game is frozen after a segfault
return do_plugin_eval_ruby(out, command+7);
ret = do_plugin_eval_ruby(out, command+7);
} else {
// wrap all ruby code inside a suspend block
// if we dont do that and rely on ruby code doing it, we'll deadlock in
// onupdate
CoreSuspender suspend;
return do_plugin_eval_ruby(out, command);
ret = do_plugin_eval_ruby(out, command);
}
// if any dfhack command is queued for run, do it now
while (!dfhack_run_queue->empty()) {
std::string cmd = dfhack_run_queue->at(0);
// delete before running the command, which may be ruby and cause infinite loops
dfhack_run_queue->erase(dfhack_run_queue->begin());
Core::getInstance().runCommand(out, cmd);
}
return ret;
}
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
@ -550,18 +571,10 @@ static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr)
// run a dfhack command, as if typed from the dfhack console
static VALUE rb_dfhack_run(VALUE self, VALUE cmd)
{
if (!r_console) // XXX
return Qnil;
std::string s;
int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0));
s.assign(rb_string_value_ptr(&cmd), strlen);
// allow the target command to suspend
// FIXME
//CoreSuspendClaimer suspend(true);
Core::getInstance().runCommand(*r_console, s);
dfhack_run_queue->push_back(s);
return Qtrue;
}
@ -1045,7 +1058,7 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1);
rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
//rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1);
rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1);
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1);
rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1);
rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);

@ -422,12 +422,17 @@ protected:
virtual bool can_init(S *screen)
{
auto list = getLayerList(screen);
if (!list->active)
if (!is_list_valid(screen) || !list->active)
return false;
return true;
}
virtual bool is_list_valid(S*)
{
return true;
}
virtual void do_search()
{
search_generic<S,T>::do_search();
@ -444,8 +449,12 @@ protected:
virtual void clear_search()
{
search_generic<S,T>::clear_search();
auto list = getLayerList(this->viewscreen);
list->num_entries = this->get_primary_list()->size();
if (is_list_valid(this->viewscreen))
{
auto list = getLayerList(this->viewscreen);
list->num_entries = this->get_primary_list()->size();
}
}
private:
@ -1208,12 +1217,14 @@ public:
return 'q';
}
bool can_init(df::viewscreen_layer_militaryst *screen)
// When not on the positions page, this list is used for something
// else entirely, so screwing with it seriously breaks stuff.
bool is_list_valid(df::viewscreen_layer_militaryst *screen)
{
if (screen->page != df::viewscreen_layer_militaryst::Positions)
return false;
return military_search_base::can_init(screen);
return true;
}
vector<df::unit *> *get_primary_list()

@ -0,0 +1,40 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
//#include "df/world.h"
using namespace DFHack;
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters);
DFHACK_PLUGIN("skeleton2");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"skeleton2",
"shortHelpString",
skeleton2,
false, //allow non-interactive use
"longHelpString"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
command_result skeleton2 (color_ostream &out, std::vector <std::string> & parameters)
{
if (!parameters.empty())
return CR_WRONG_USAGE;
CoreSuspender suspend;
out.print("blah");
return CR_OK;
}

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit 83e2b8a3754e2feb0d5bf106d2b43ff71ff63935
Subproject commit 0d41614ff3dae9245e786ad667b0e463fe0dea3e

@ -0,0 +1,198 @@
#include "Core.h"
#include "Console.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h"
#include "modules/Once.h"
#include "df/caste_raw.h"
#include "df/creature_raw.h"
#include "df/syndrome.h"
#include "df/unit.h"
#include "df/unit_syndrome.h"
#include "df/world.h"
#include <cstdlib>
using namespace DFHack;
using namespace std;
static bool enabled = false;
DFHACK_PLUGIN("syndromeTrigger");
void syndromeHandler(color_ostream& out, void* ptr);
command_result syndromeTrigger(color_ostream& out, vector<string>& parameters);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand("syndromeTrigger", "Run commands and enable true transformations, configured by the raw files.\n", &syndromeTrigger, false,
"syndromeTrigger:\n"
" syndromeTrigger 0 //disable\n"
" syndromeTrigger 1 //enable\n"
" syndromeTrigger disable //disable\n"
" syndromeTrigger enable //enable\n"
"\n"
"See Readme.rst for details.\n"
));
return CR_OK;
}
command_result syndromeTrigger(color_ostream& out, vector<string>& parameters) {
if ( parameters.size() > 1 )
return CR_WRONG_USAGE;
bool wasEnabled = enabled;
if ( parameters.size() == 1 ) {
if ( parameters[0] == "enable" ) {
enabled = true;
} else if ( parameters[0] == "disable" ) {
enabled = false;
} else {
int32_t a = atoi(parameters[0].c_str());
if ( a < 0 || a > 1 )
return CR_WRONG_USAGE;
enabled = (bool)a;
}
}
out.print("syndromeTrigger is %s\n", enabled ? "enabled" : "disabled");
if ( enabled == wasEnabled )
return CR_OK;
EventManager::unregisterAll(plugin_self);
if ( enabled ) {
EventManager::EventHandler handle(syndromeHandler, 1);
EventManager::registerListener(EventManager::EventType::SYNDROME, handle, plugin_self);
}
return CR_OK;
}
void syndromeHandler(color_ostream& out, void* ptr) {
EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr;
if ( !ptr ) {
if ( DFHack::Once::doOnce("syndromeTrigger_null data") ) {
out.print("%s, %d: null pointer from EventManager.\n", __FILE__, __LINE__);
}
return;
}
//out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex);
df::unit* unit = df::unit::find(data->unitId);
if (!unit) {
if ( DFHack::Once::doOnce("syndromeTrigger_no find unit" ) )
out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__);
return;
}
df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex];
//out.print(" syndrome type %d\n", unit_syndrome->type);
df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type];
bool foundPermanent = false;
bool foundCommand = false;
bool foundAutoSyndrome = false;
string commandStr;
vector<string> args;
int32_t raceId = -1;
df::creature_raw* creatureRaw = NULL;
int32_t casteId = -1;
for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) {
std::string& clazz = *syndrome->syn_class[a];
//out.print(" clazz %d = %s\n", a, clazz.c_str());
if ( foundCommand ) {
if ( commandStr == "" ) {
commandStr = clazz;
continue;
}
stringstream bob;
if ( clazz == "\\LOCATION" ) {
bob << unit->pos.x;
args.push_back(bob.str());
bob.str("");
bob.clear();
bob << unit->pos.y;
args.push_back(bob.str());
bob.str("");
bob.clear();
bob << unit->pos.z;
args.push_back(bob.str());
bob.str("");
bob.clear();
} else if ( clazz == "\\UNIT_ID" ) {
bob << unit->id;
args.push_back(bob.str());
bob.str("");
bob.clear();
} else if ( clazz == "\\SYNDROME_ID" ) {
bob << unit_syndrome->type;
args.push_back(bob.str());
bob.str("");
bob.clear();
} else {
args.push_back(clazz);
}
continue;
}
if ( clazz == "\\AUTO_SYNDROME" ) {
foundAutoSyndrome = true;
continue;
}
if ( clazz == "\\COMMAND" ) {
foundCommand = true;
continue;
}
if ( clazz == "\\PERMANENT" ) {
foundPermanent = true;
continue;
}
if ( !foundPermanent )
continue;
if ( raceId == -1 ) {
//find the race with the name
string& name = *syndrome->syn_class[a];
for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) {
df::creature_raw* creature = df::global::world->raws.creatures.all[b];
if ( creature->creature_id != name )
continue;
raceId = b;
creatureRaw = creature;
break;
}
continue;
}
if ( raceId != -1 && casteId == -1 ) {
string& name = *syndrome->syn_class[a];
for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) {
df::caste_raw* caste = creatureRaw->caste[b];
if ( caste->caste_id != name )
continue;
casteId = b;
break;
}
continue;
}
}
if ( !foundAutoSyndrome && commandStr != "" ) {
Core::getInstance().runCommand(out, commandStr, args);
}
if ( !foundPermanent || raceId == -1 || casteId == -1 )
return;
unit->enemy.normal_race = raceId;
unit->enemy.normal_caste = casteId;
//that's it!
}

@ -0,0 +1,145 @@
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h"
#include "modules/Once.h"
#include "df/block_burrow.h"
#include "df/block_burrow_link.h"
#include "df/burrow.h"
#include "df/map_block.h"
#include "df/tile_bitmask.h"
#include "df/tile_dig_designation.h"
#include "df/tiletype.h"
#include "df/tiletype_shape.h"
#include "df/world.h"
//#include "df/world.h"
using namespace DFHack;
void checkFarms(color_ostream& out, void* ptr);
command_result treefarm (color_ostream &out, std::vector <std::string> & parameters);
EventManager::EventHandler handler(&checkFarms, -1);
int32_t frequency = 1200*30;
DFHACK_PLUGIN("treefarm");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"treefarm",
"automatically manages special burrows and regularly schedules tree chopping and digging when appropriate",
treefarm,
false, //allow non-interactive use
"treefarm\n"
" enables treefarm monitoring, starting next frame\n"
"treefarm n\n"
" enables treefarm monitoring, starting next frame\n"
" sets monitoring interval to n frames\n"
" if n is less than one, disables monitoring\n"
"\n"
"Every time the plugin runs, it checks for burrows with a name containing the string \"treefarm\". For each such burrow, it checks every tile in it for fully-grown trees and for diggable walls. For each fully-grown tree it finds, it designates the tree to be chopped, and for each natural wall it finds, it designates the wall to be dug.\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
void checkFarms(color_ostream& out, void* ptr) {
EventManager::unregisterAll(plugin_self);
EventManager::registerTick(handler, frequency, plugin_self);
CoreSuspender suspend;
df::world* world = df::global::world;
df::ui* ui = df::global::ui;
int32_t xOffset = world->map.region_x*3;
int32_t yOffset = world->map.region_y*3;
int32_t zOffset = world->map.region_z;
//for each burrow named treefarm or obsidianfarm, check if you can dig/chop any obsidian/trees
for ( size_t a = 0; a < df::burrow::get_vector().size(); a++ ) {
df::burrow* burrow = df::burrow::get_vector()[a];
if ( !burrow || burrow->name.find("treefarm") == std::string::npos )
continue;
if ( burrow->block_x.size() != burrow->block_y.size() || burrow->block_x.size() != burrow->block_z.size() )
continue;
for ( size_t b = 0; b < burrow->block_x.size(); b++ ) {
int32_t x=burrow->block_x[b] - xOffset;
int32_t y=burrow->block_y[b] - yOffset;
int32_t z=burrow->block_z[b] - zOffset;
df::map_block* block = world->map.block_index[x][y][z];
if ( !block )
continue;
df::block_burrow_link* link = &block->block_burrows;
df::tile_bitmask mask;
for ( ; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
if ( link->item->id == burrow->id ) {
mask = link->item->tile_bitmask;
break;
}
}
if ( link == NULL )
continue;
for ( int32_t x = 0; x < 16; x++ ) {
for ( int32_t y = 0; y < 16; y++ ) {
if ( !mask.getassignment(x,y) )
continue;
df::tiletype type = block->tiletype[x][y];
df::tiletype_shape shape = ENUM_ATTR(tiletype, shape, type);
if ( !block->designation[x][y].bits.hidden &&
shape != df::enums::tiletype_shape::WALL &&
shape != df::enums::tiletype_shape::TREE )
continue;
if ( shape != df::enums::tiletype_shape::TREE ) {
if ( x == 0 && (block->map_pos.x/16) == 0 )
continue;
if ( y == 0 && (block->map_pos.y/16) == 0 )
continue;
if ( x == 15 && (block->map_pos.x/16) == world->map.x_count_block-1 )
continue;
if ( y == 15 && (block->map_pos.y/16) == world->map.y_count_block-1 )
continue;
}
block->designation[x][y].bits.dig = df::enums::tile_dig_designation::Default;
}
}
}
}
}
command_result treefarm (color_ostream &out, std::vector <std::string> & parameters)
{
EventManager::unregisterAll(plugin_self);
if ( parameters.size() > 1 )
return CR_WRONG_USAGE;
if ( parameters.size() == 1 ) {
int32_t i = atoi(parameters[0].c_str());
if ( i < 1 ) {
out.print("treefarm disabled\n");
return CR_OK;
}
frequency = i;
}
EventManager::registerTick(handler, 1, plugin_self);
out.print("treefarm enabled with update frequency %d ticks\n", frequency);
return CR_OK;
}

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

@ -702,7 +702,7 @@ static bool can_spar(df::unit *unit) {
return unit->counters2.exhaustion <= 2000 && // actually 4000, but leave a gap
(unit->status2.limbs_grasp_count > 0 || unit->status2.limbs_grasp_max == 0) &&
(!unit->health || (unit->health->flags.whole&0x7FF) == 0) &&
(!unit->job.current_job || unit->job.current_job != job_type::Rest);
(!unit->job.current_job || unit->job.current_job->job_type != job_type::Rest);
}
static bool has_spar_inventory(df::unit *unit, df::job_skill skill)

@ -0,0 +1,580 @@
#include <algorithm>
#include <map>
#include <string>
#include <set>
#include "Core.h"
#include "MiscUtils.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <VTableInterpose.h>
#include "modules/Screen.h"
#include "df/enabler.h"
using std::string;
using std::vector;
using std::map;
using std::ostringstream;
using std::set;
using namespace DFHack;
using namespace df::enums;
using df::global::enabler;
using df::global::gps;
#ifndef HAVE_NULLPTR
#define nullptr 0L
#endif
#define COLOR_TITLE COLOR_BLUE
#define COLOR_UNSELECTED COLOR_GREY
#define COLOR_SELECTED COLOR_WHITE
#define COLOR_HIGHLIGHTED COLOR_GREEN
template <class T, typename Fn>
static void for_each_(vector<T> &v, Fn func)
{
for_each(v.begin(), v.end(), func);
}
template <class T, class V, typename Fn>
static void for_each_(map<T, V> &v, Fn func)
{
for_each(v.begin(), v.end(), func);
}
template <class T, class V, typename Fn>
static void transform_(vector<T> &src, vector<V> &dst, Fn func)
{
transform(src.begin(), src.end(), back_inserter(dst), func);
}
typedef int8_t UIColor;
void OutputString(UIColor color, int &x, int &y, const std::string &text,
bool newline = false, int left_margin = 0, const UIColor bg_color = 0)
{
Screen::paintString(Screen::Pen(' ', color, bg_color), x, y, text);
if (newline)
{
++y;
x = left_margin;
}
else
x += text.length();
}
void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false,
int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN)
{
OutputString(hotkey_color, x, y, hotkey);
string display(": ");
display.append(text);
OutputString(text_color, x, y, display, newline, left_margin);
}
void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false,
int left_margin = 0, int8_t hotkey_color = COLOR_LIGHTGREEN)
{
OutputString(hotkey_color, x, y, hotkey);
OutputString(COLOR_WHITE, x, y, ": ");
OutputString((state) ? COLOR_WHITE : COLOR_GREY, x, y, text, newline, left_margin);
}
void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE)
{
OutputHotkeyString(x, y, text, hotkey);
OutputString(COLOR_WHITE, x, y, ": ");
if (state)
OutputString(COLOR_GREEN, x, y, "Enabled", newline, left_margin);
else
OutputString(COLOR_GREY, x, y, "Disabled", newline, left_margin);
}
const int ascii_to_enum_offset = interface_key::STRING_A048 - '0';
inline string int_to_string(const int n)
{
return static_cast<ostringstream*>( &(ostringstream() << n) )->str();
}
static void set_to_limit(int &value, const int maximum, const int min = 0)
{
if (value < min)
value = min;
else if (value > maximum)
value = maximum;
}
inline void paint_text(const UIColor color, const int &x, const int &y, const std::string &text, const UIColor background = 0)
{
Screen::paintString(Screen::Pen(' ', color, background), x, y, text);
}
static string pad_string(string text, const int size, const bool front = true, const bool trim = false)
{
if (text.length() > size)
{
if (trim && size > 10)
{
text = text.substr(0, size-3);
text.append("...");
}
return text;
}
string aligned(size - text.length(), ' ');
if (front)
{
aligned.append(text);
return aligned;
}
else
{
text.append(aligned);
return text;
}
}
/*
* List classes
*/
template <typename T>
class ListEntry
{
public:
T elem;
string text, keywords;
bool selected;
ListEntry(const string text, const T elem, const string keywords = "") :
elem(elem), text(text), selected(false), keywords(keywords)
{
}
};
template <typename T>
class ListColumn
{
public:
int highlighted_index;
int display_start_offset;
unsigned short text_clip_at;
int32_t bottom_margin, search_margin, left_margin;
bool multiselect;
bool allow_null;
bool auto_select;
bool force_sort;
bool allow_search;
bool feed_changed_highlight;
ListColumn()
{
bottom_margin = 3;
clear();
left_margin = 2;
search_margin = 63;
highlighted_index = 0;
text_clip_at = 0;
multiselect = false;
allow_null = true;
auto_select = false;
force_sort = false;
allow_search = true;
feed_changed_highlight = false;
}
void clear()
{
list.clear();
display_list.clear();
display_start_offset = 0;
max_item_width = title.length();
resize();
}
void resize()
{
display_max_rows = gps->dimy - 4 - bottom_margin;
}
void add(ListEntry<T> &entry)
{
list.push_back(entry);
if (entry.text.length() > max_item_width)
max_item_width = entry.text.length();
}
void add(const string &text, const T &elem)
{
list.push_back(ListEntry<T>(text, elem));
if (text.length() > max_item_width)
max_item_width = text.length();
}
int fixWidth()
{
if (text_clip_at > 0 && max_item_width > text_clip_at)
max_item_width = text_clip_at;
for (auto it = list.begin(); it != list.end(); it++)
{
it->text = pad_string(it->text, max_item_width, false);
}
return left_margin + max_item_width;
}
virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {}
void display(const bool is_selected_column) const
{
int32_t y = 2;
paint_text(COLOR_TITLE, left_margin, y, title);
int last_index_able_to_display = display_start_offset + display_max_rows;
for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++)
{
++y;
UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : COLOR_UNSELECTED;
UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK;
string item_label = display_list[i]->text;
if (text_clip_at > 0 && item_label.length() > text_clip_at)
item_label.resize(text_clip_at);
paint_text(fg_color, left_margin, y, item_label, bg_color);
int x = left_margin + display_list[i]->text.length() + 1;
display_extras(display_list[i]->elem, x, y);
}
if (is_selected_column && allow_search)
{
y = gps->dimy - 3;
int32_t x = search_margin;
OutputHotkeyString(x, y, "Search" ,"S");
OutputString(COLOR_WHITE, x, y, ": ");
OutputString(COLOR_WHITE, x, y, search_string);
OutputString(COLOR_LIGHTGREEN, x, y, "_");
}
}
void filterDisplay()
{
ListEntry<T> *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL;
display_list.clear();
search_string = toLower(search_string);
vector<string> search_tokens;
if (!search_string.empty())
split_string(&search_tokens, search_string, " ");
for (size_t i = 0; i < list.size(); i++)
{
ListEntry<T> *entry = &list[i];
bool include_item = true;
if (!search_string.empty())
{
string item_string = toLower(list[i].text);
for (auto si = search_tokens.begin(); si != search_tokens.end(); si++)
{
if (!si->empty() && item_string.find(*si) == string::npos &&
list[i].keywords.find(*si) == string::npos)
{
include_item = false;
break;
}
}
}
if (include_item)
{
display_list.push_back(entry);
if (entry == prev_selected)
highlighted_index = display_list.size() - 1;
}
else if (auto_select)
{
entry->selected = false;
}
}
changeHighlight(0);
feed_changed_highlight = true;
}
void selectDefaultEntry()
{
for (size_t i = 0; i < display_list.size(); i++)
{
if (display_list[i]->selected)
{
highlighted_index = i;
break;
}
}
}
void validateHighlight()
{
set_to_limit(highlighted_index, display_list.size() - 1);
if (highlighted_index < display_start_offset)
display_start_offset = highlighted_index;
else if (highlighted_index >= display_start_offset + display_max_rows)
display_start_offset = highlighted_index - display_max_rows + 1;
if (auto_select || (!allow_null && list.size() == 1))
display_list[highlighted_index]->selected = true;
feed_changed_highlight = true;
}
void changeHighlight(const int highlight_change, const int offset_shift = 0)
{
if (!initHighlightChange())
return;
highlighted_index += highlight_change + offset_shift * display_max_rows;
display_start_offset += offset_shift * display_max_rows;
set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows));
validateHighlight();
}
void setHighlight(const int index)
{
if (!initHighlightChange())
return;
highlighted_index = index;
validateHighlight();
}
bool initHighlightChange()
{
if (display_list.size() == 0)
return false;
if (auto_select && !multiselect)
{
for (auto it = list.begin(); it != list.end(); it++)
{
it->selected = false;
}
}
return true;
}
void toggleHighlighted()
{
if (auto_select)
return;
ListEntry<T> *entry = display_list[highlighted_index];
if (!multiselect || !allow_null)
{
int selected_count = 0;
for (size_t i = 0; i < list.size(); i++)
{
if (!multiselect && !entry->selected)
list[i].selected = false;
if (!allow_null && list[i].selected)
selected_count++;
}
if (!allow_null && entry->selected && selected_count == 1)
return;
}
entry->selected = !entry->selected;
}
vector<T> getSelectedElems(bool only_one = false)
{
vector<T> results;
for (auto it = list.begin(); it != list.end(); it++)
{
if ((*it).selected)
{
results.push_back(it->elem);
if (only_one)
break;
}
}
return results;
}
T getFirstSelectedElem()
{
vector<T> results = getSelectedElems(true);
if (results.size() == 0)
return nullptr;
else
return results[0];
}
void clearSelection()
{
for_each_(list, [] (ListEntry<T> &e) { e.selected = false; });
}
void selectItem(const T elem)
{
int i = 0;
for (; i < display_list.size(); i++)
{
if (display_list[i]->elem == elem)
{
setHighlight(i);
break;
}
}
}
void clearSearch()
{
search_string.clear();
filterDisplay();
}
size_t getDisplayListSize()
{
return display_list.size();
}
vector<ListEntry<T>*> &getDisplayList()
{
return display_list;
}
size_t getBaseListSize()
{
return list.size();
}
bool feed(set<df::interface_key> *input)
{
feed_changed_highlight = false;
if (input->count(interface_key::CURSOR_UP))
{
changeHighlight(-1);
}
else if (input->count(interface_key::CURSOR_DOWN))
{
changeHighlight(1);
}
else if (input->count(interface_key::STANDARDSCROLL_PAGEUP))
{
changeHighlight(0, -1);
}
else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN))
{
changeHighlight(0, 1);
}
else if (input->count(interface_key::SELECT) && !auto_select)
{
toggleHighlighted();
}
else if (input->count(interface_key::CUSTOM_SHIFT_S))
{
clearSearch();
}
else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut)
{
return setHighlightByMouse();
}
else if (allow_search)
{
// Search query typing mode always on
df::interface_key last_token = *input->rbegin();
if ((last_token >= interface_key::STRING_A096 && last_token <= interface_key::STRING_A123) ||
last_token == interface_key::STRING_A032)
{
// Standard character
search_string += last_token - ascii_to_enum_offset;
filterDisplay();
}
else if (last_token == interface_key::STRING_A000)
{
// Backspace
if (search_string.length() > 0)
{
search_string.erase(search_string.length()-1);
filterDisplay();
}
}
else
{
return false;
}
return true;
}
else
{
return false;
}
return true;
}
bool setHighlightByMouse()
{
if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 &&
gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width)
{
int new_index = display_start_offset + gps->mouse_y - 3;
if (new_index < display_list.size())
setHighlight(new_index);
enabler->mouse_lbut = enabler->mouse_rbut = 0;
return true;
}
return false;
}
void sort()
{
if (force_sort || list.size() < 100)
std::sort(list.begin(), list.end(),
[] (ListEntry<T> const& a, ListEntry<T> const& b) { return a.text.compare(b.text) < 0; });
filterDisplay();
}
void setTitle(const string t)
{
title = t;
if (title.length() > max_item_width)
max_item_width = title.length();
}
size_t getDisplayedListSize()
{
return display_list.size();
}
private:
vector<ListEntry<T>> list;
vector<ListEntry<T>*> display_list;
string search_string;
string title;
int display_max_rows;
int max_item_width;
};

@ -24,7 +24,7 @@ void jobCompletedHandler(color_ostream& out, void* ptr);
EventManager::EventHandler handler(jobCompletedHandler,1);
DFhackCExport command_result plugin_init(color_ostream& out, std::vector<PluginCommand> &commands) {
commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs whever they finish one, or every time you pause", workNow, false, "When workNow is active, every time the game pauses, DF will make dwarves perform any appropriate available jobs. This includes when you one step through the game using the pause menu. When workNow is in mode 2, it will make dwarves look for jobs every time a job completes (or is cancelled).\n"
commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs whever they finish one, or every time you pause", workNow, false, "When workNow is active, every time the game pauses, DF will make dwarves perform any appropriate available jobs. This includes when you one step through the game using the pause menu. When workNow is in mode 2, it will make dwarves look for jobs every time a job completes.\n"
"workNow\n"
" print workNow status\n"
"workNow 0\n"
@ -54,7 +54,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK;
*df::global::process_jobs = true;
*df::global::process_dig = true;
*df::global::process_dig = true;
return CR_OK;
}

File diff suppressed because it is too large Load Diff

@ -1,165 +1,176 @@
class AutoFarm
def initialize
@thresholds = Hash.new(50)
@lastcounts = Hash.new(0)
end
def setthreshold(id, v)
if df.world.raws.plants.all.find { |r| r.id == id }
@thresholds[id] = v.to_i
else
puts "No plant with id #{id}"
end
end
def setdefault(v)
@thresholds.default = v.to_i
end
def is_plantable (plant)
has_seed = plant.flags[:SEED]
season = df.cur_season
harvest = df.cur_season_tick + plant.growdur * 10
will_finish = harvest < 10080
can_plant = has_seed && plant.flags[season]
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
can_plant
end
def find_plantable_plants
plantable = {}
counts = Hash.new(0)
df.world.items.other[:SEEDS].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact)
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
end
}
counts.keys.each { |i|
if df.ui.tasks.known_plants[i]
plant = df.world.raws.plants.all[i]
if is_plantable(plant)
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
end
end
}
return plantable
end
def set_farms( plants, farms)
return if farms.length == 0
if plants.length == 0
plants = [-1]
end
season = df.cur_season
idx = 0
farms.each { |f|
f.plant_id[season] = plants[idx]
idx = (idx + 1) % plants.length
}
end
def process
return false unless @running
plantable = find_plantable_plants
counts = Hash.new(0)
df.world.items.other[:PLANT].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact && plantable.has_key?(i.mat_index))
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
end
}
plants_s = []
plants_u = []
@lastcounts.clear
plantable.each_key { |k|
plant = df.world.raws.plants.all[k]
if (counts[k] < @thresholds[plant.id])
plants_s.push(k) if plantable[k] == :Surface
plants_u.push(k) if plantable[k] == :Underground
end
@lastcounts[plant.id] = counts[k]
}
farms_s = []
farms_u = []
df.world.buildings.other[:FARM_PLOT].each { |f|
if (f.flags.exists)
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
farms_s.push(f) unless underground
farms_u.push(f) if underground
end
}
set_farms(plants_s, farms_s)
set_farms(plants_u, farms_u)
end
def start
@onupdate = df.onupdate_register('autofarm', 100) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
stat = @running ? "Running." : "Stopped."
@thresholds.each { |k,v|
stat += "\n#{k} limit #{v} current #{@lastcounts[k]}"
}
stat += "\nDefault: #{@thresholds.default}"
stat
end
end
$AutoFarm = AutoFarm.new unless $AutoFarm
case $script_args[0]
when 'start'
$AutoFarm.start
when 'end', 'stop'
$AutoFarm.stop
when 'default'
$AutoFarm.setdefault($script_args[1])
when 'threshold'
t = $script_args[1]
$script_args[2..-1].each {|i|
$AutoFarm.setthreshold(i, t)
}
when 'delete'
$AutoFarm.stop
$AutoFarm = nil
else
if $AutoFarm
puts $AutoFarm.status
else
puts "AI not started"
end
end
class AutoFarm
def initialize
@thresholds = Hash.new(50)
@lastcounts = Hash.new(0)
end
def setthreshold(id, v)
list = df.world.raws.plants.all.find_all { |plt| plt.flags[:SEED] }.map { |plt| plt.id }
if tok = df.match_rawname(id, list)
@thresholds[tok] = v.to_i
else
puts "No plant with id #{id}, try one of " +
list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.sort.join(' ')
end
end
def setdefault(v)
@thresholds.default = v.to_i
end
def is_plantable(plant)
has_seed = plant.flags[:SEED]
season = df.cur_season
harvest = df.cur_season_tick + plant.growdur * 10
will_finish = harvest < 10080
can_plant = has_seed && plant.flags[season]
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
can_plant
end
def find_plantable_plants
plantable = {}
counts = Hash.new(0)
df.world.items.other[:SEEDS].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact)
counts[i.mat_index] += i.stack_size
end
}
counts.keys.each { |i|
if df.ui.tasks.known_plants[i]
plant = df.world.raws.plants.all[i]
if is_plantable(plant)
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
end
end
}
return plantable
end
def set_farms(plants, farms)
return if farms.length == 0
if plants.length == 0
plants = [-1]
end
season = df.cur_season
farms.each_with_index { |f, idx|
f.plant_id[season] = plants[idx % plants.length]
}
end
def process
plantable = find_plantable_plants
@lastcounts = Hash.new(0)
df.world.items.other[:PLANT].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact && plantable.has_key?(i.mat_index))
id = df.world.raws.plants.all[i.mat_index].id
@lastcounts[id] += i.stack_size
end
}
return unless @running
plants_s = []
plants_u = []
plantable.each_key { |k|
plant = df.world.raws.plants.all[k]
if (@lastcounts[plant.id] < @thresholds[plant.id])
plants_s.push(k) if plantable[k] == :Surface
plants_u.push(k) if plantable[k] == :Underground
end
}
farms_s = []
farms_u = []
df.world.buildings.other[:FARM_PLOT].each { |f|
if (f.flags.exists)
underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean
farms_s.push(f) unless underground
farms_u.push(f) if underground
end
}
set_farms(plants_s, farms_s)
set_farms(plants_u, farms_u)
end
def start
return if @running
@onupdate = df.onupdate_register('autofarm', 1200) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
stat = @running ? "Running." : "Stopped."
@lastcounts.each { |k,v|
stat << "\n#{k} limit #{@thresholds.fetch(k, 'default')} current #{v}"
}
@thresholds.each { |k,v|
stat << "\n#{k} limit #{v} current 0" unless @lastcounts.has_key?(k)
}
stat << "\nDefault: #{@thresholds.default}"
stat
end
end
$AutoFarm ||= AutoFarm.new
case $script_args[0]
when 'start', 'enable'
$AutoFarm.start
puts $AutoFarm.status
when 'end', 'stop', 'disable'
$AutoFarm.stop
puts 'Stopped.'
when 'default'
$AutoFarm.setdefault($script_args[1])
when 'threshold'
t = $script_args[1]
$script_args[2..-1].each {|i|
$AutoFarm.setthreshold(i, t)
}
when 'delete'
$AutoFarm.stop
$AutoFarm = nil
when 'help', '?'
puts <<EOS
Automatically handle crop selection in farm plots based on current plant stocks.
Selects a crop for planting if current stock is below a threshold.
Selected crops are dispatched on all farmplots.
Usage:
autofarm start
autofarm default 30
autofarm threshold 150 helmet_plump tail_pig
EOS
else
$AutoFarm.process
puts $AutoFarm.status
end

@ -1,58 +1,58 @@
class AutoUnsuspend
def initialize
end
def process
return false unless @running
joblist = df.world.job_list.next
count = 0
while joblist
job = joblist.item
joblist = joblist.next
if job.job_type == :ConstructBuilding
if (job.flags.suspend)
item = job.items[0].item
job.flags.suspend = false
count += 1
end
end
end
puts "Unsuspended #{count} job(s)." unless count == 0
end
def start
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
@running ? 'Running.' : 'Stopped.'
end
end
case $script_args[0]
when 'start'
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
$AutoUnsuspend.start
when 'end', 'stop'
$AutoUnsuspend.stop
else
if $AutoUnsuspend
puts $AutoUnsuspend.status
else
puts 'Not loaded.'
end
end
class AutoUnsuspend
def initialize
end
def process
return false unless @running
joblist = df.world.job_list.next
count = 0
while joblist
job = joblist.item
joblist = joblist.next
if job.job_type == :ConstructBuilding
if (job.flags.suspend)
item = job.items[0].item
job.flags.suspend = false
count += 1
end
end
end
puts "Unsuspended #{count} job(s)." unless count == 0
end
def start
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
@running ? 'Running.' : 'Stopped.'
end
end
case $script_args[0]
when 'start'
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
$AutoUnsuspend.start
when 'end', 'stop'
$AutoUnsuspend.stop
else
if $AutoUnsuspend
puts $AutoUnsuspend.status
else
puts 'Not loaded.'
end
end

@ -1,161 +1,177 @@
# create arbitrary items under cursor
category = $script_args[0] || 'help'
mat_raw = $script_args[1] || 'list'
count = $script_args[2]
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs']) || 'help'
if category == 'help'
puts <<EOS
Create items under the cursor.
Usage:
create [category] [raws token] [number]
Item categories:
bars, boulders, plants, logs, web
Raw token:
either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
(the missing part is autocompleted depending on the item category)
use 'list' to show all possibilities
Exemples:
create boulders hematite 30
create bars CREATURE_MAT:CAT:SOAP 10
create web cave_giant
create plants list
EOS
throw :script_finished
elsif mat_raw == 'list'
# allowed with no cursor
elsif df.cursor.x == -30000
puts "Please place the game cursor somewhere"
throw :script_finished
elsif !(maptile = df.map_tile_at(df.cursor))
puts "Error: unallocated map block !"
throw :script_finished
elsif !maptile.shape_passablehigh
puts "Error: impassible tile !"
throw :script_finished
end
def match_list(tok, list)
if tok != 'list'
tok = df.match_rawname(tok, list)
if not tok
puts "Invalid raws token, use one in:"
tok = 'list'
end
end
if tok == 'list'
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
throw :script_finished
end
tok
end
case category
when 'bars'
# create metal bar, eg createbar INORGANIC:IRON
cls = DFHack::ItemBarst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
customize = lambda { |item|
item.dimension = 150
item.subtype = -1
}
when 'boulders'
cls = DFHack::ItemBoulderst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_STONE]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
when 'plants'
cls = DFHack::ItemPlantst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
puts mat_raw
end
when 'logs'
cls = DFHack::ItemWoodst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'WOOD' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
puts mat_raw
end
when 'webs'
cls = DFHack::ItemThreadst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.creatures.all.find_all { |cre|
cre.material.find { |mat| mat.id == 'SILK' }
}.map { |cre| cre.creature_id }
mat_raw = match_list(mat_raw, list)
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
puts mat_raw
end
count ||= 1
customize = lambda { |item|
item.flags.spider_web = true
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
}
end
mat = df.decode_mat mat_raw
count ||= 20
count.to_i.times {
item = cls.cpp_new
item.id = df.item_next_id
item.stack_size = 1
item.mat_type = mat.mat_type
item.mat_index = mat.mat_index
customize[item] if customize
df.item_next_id += 1
item.categorize(true)
df.world.items.all << item
item.pos = df.cursor
item.flags.on_ground = true
df.map_tile_at.mapblock.items << item.id
df.map_tile_at.occupancy.item = true
}
# move game view, so that the ui menu updates
df.curview.feed_keys(:CURSOR_UP_Z)
df.curview.feed_keys(:CURSOR_DOWN_Z)
# create first necessity items under cursor
category = $script_args[0] || 'help'
mat_raw = $script_args[1] || 'list'
count = $script_args[2]
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help'
if category == 'help'
puts <<EOS
Create first necessity items under the cursor.
Usage:
create-items [category] [raws token] [number]
Item categories:
bars, boulders, plants, logs, webs, anvils
Raw token:
Either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
(the missing part is autocompleted depending on the item category)
Use 'list' to show all possibilities
Exemples:
create-items boulders hematite 30
create-items bars CREATURE_MAT:CAT:SOAP 10
create-items web cave_giant
create-items plants list
EOS
throw :script_finished
elsif mat_raw == 'list'
# allowed with no cursor
elsif df.cursor.x == -30000
puts "Please place the game cursor somewhere"
throw :script_finished
elsif !(maptile = df.map_tile_at(df.cursor))
puts "Error: unallocated map block !"
throw :script_finished
elsif !maptile.shape_passablehigh
puts "Error: impassible tile !"
throw :script_finished
end
def match_list(tok, list)
if tok != 'list'
tok = df.match_rawname(tok, list)
if not tok
puts "Invalid raws token, use one in:"
tok = 'list'
end
end
if tok == 'list'
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
throw :script_finished
end
tok
end
case category
when 'bars'
# create metal bar, eg createbar INORGANIC:IRON
cls = DFHack::ItemBarst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
customize = lambda { |item|
item.dimension = 150
item.subtype = -1
}
when 'boulders'
cls = DFHack::ItemBoulderst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_STONE]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
when 'plants'
cls = DFHack::ItemPlantst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'STRUCTURAL' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
puts mat_raw
end
when 'logs'
cls = DFHack::ItemWoodst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'WOOD' }
}.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
puts mat_raw
end
when 'webs'
cls = DFHack::ItemThreadst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.creatures.all.find_all { |cre|
cre.material.find { |mat| mat.id == 'SILK' }
}.map { |cre| cre.creature_id }
mat_raw = match_list(mat_raw, list)
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
puts mat_raw
end
count ||= 1
customize = lambda { |item|
item.flags.spider_web = true
item.dimension = 15000 # XXX may depend on creature (this is for GCS)
}
when 'anvils'
cls = DFHack::ItemAnvilst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
}.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw
end
count ||= 1
end
mat = df.decode_mat mat_raw
count ||= 20
count.to_i.times {
item = cls.cpp_new
item.id = df.item_next_id
item.stack_size = 1
item.mat_type = mat.mat_type
item.mat_index = mat.mat_index
customize[item] if customize
df.item_next_id += 1
item.categorize(true)
df.world.items.all << item
item.pos = df.cursor
item.flags.on_ground = true
df.map_tile_at.mapblock.items << item.id
df.map_tile_at.occupancy.item = true
}
# move game view, so that the ui menu updates
if df.cursor.z > 5
df.curview.feed_keys(:CURSOR_DOWN_Z)
df.curview.feed_keys(:CURSOR_UP_Z)
else
df.curview.feed_keys(:CURSOR_UP_Z)
df.curview.feed_keys(:CURSOR_DOWN_Z)
end

@ -1,67 +1,67 @@
# show death cause of a creature
def display_death_event(e)
str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}"
str << " (cause: #{e.death_cause.to_s.downcase}),"
str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1
str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON
str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON
puts str.chomp(',') + '.'
end
def display_death_unit(u)
death_info = u.counters.death_tg
killer = death_info.killer_tg if death_info
str = "The #{u.race_tg.name[0]}"
str << " #{u.name}" if u.name.has_name
str << " died"
str << " in year #{death_info.event_year}" if death_info
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
puts str.chomp(',') + '.'
end
item = df.item_find(:selected)
unit = df.unit_find(:selected)
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
end
if item and item.kind_of?(DFHack::ItemBodyComponent)
hf = item.hist_figure_id
elsif unit
hf = unit.hist_figure_id
end
if not hf
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
elsif hf == -1
if unit ||= item.unit_tg
display_death_unit(unit)
else
puts "Not a historical figure, cannot death find info"
end
else
histfig = df.world.history.figures.binsearch(hf)
unit = histfig ? df.unit_find(histfig.unit_id) : nil
if unit and not unit.flags1.dead and not unit.flags3.ghostly
puts "#{unit.name} is not dead yet !"
else
events = df.world.history.events
(0...events.length).reverse_each { |i|
e = events[i]
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
display_death_event(e)
break
end
}
end
end
# show death cause of a creature
def display_death_event(e)
str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}"
str << " (cause: #{e.death_cause.to_s.downcase}),"
str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1
str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON
str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON
puts str.chomp(',') + '.'
end
def display_death_unit(u)
death_info = u.counters.death_tg
killer = death_info.killer_tg if death_info
str = "The #{u.race_tg.name[0]}"
str << " #{u.name}" if u.name.has_name
str << " died"
str << " in year #{death_info.event_year}" if death_info
str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1
str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
puts str.chomp(',') + '.'
end
item = df.item_find(:selected)
unit = df.unit_find(:selected)
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
end
if item and item.kind_of?(DFHack::ItemBodyComponent)
hf = item.hist_figure_id
elsif unit
hf = unit.hist_figure_id
end
if not hf
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
elsif hf == -1
if unit ||= item.unit_tg
display_death_unit(unit)
else
puts "Not a historical figure, cannot death find info"
end
else
histfig = df.world.history.figures.binsearch(hf)
unit = histfig ? df.unit_find(histfig.unit_id) : nil
if unit and not unit.flags1.dead and not unit.flags3.ghostly
puts "#{unit.name} is not dead yet !"
else
events = df.world.history.events
(0...events.length).reverse_each { |i|
e = events[i]
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
display_death_event(e)
break
end
}
end
end

@ -0,0 +1,10 @@
# list indexes in world.item.other[] where current selected item appears
tg = df.item_find
raise 'select an item' if not tg
o = df.world.items.other
# discard ANY/BAD
o._indexenum::ENUM.sort.transpose[1][1..-2].each { |k|
puts k if o[k].find { |i| i == tg }
}

@ -0,0 +1,3 @@
# unforbid all items
df.world.items.all.each { |i| i.flags.forbid = false }

@ -1,38 +1,38 @@
# designate an area for digging according to a plan in csv format
raise "usage: digfort <plan filename>" if not $script_args[0]
planfile = File.read($script_args[0])
if df.cursor.x == -30000
raise "place the game cursor to the top-left corner of the design"
end
tiles = planfile.lines.map { |l|
l.sub(/#.*/, '').split(';').map { |t| t.strip }
}
x = x0 = df.cursor.x
y = df.cursor.y
z = df.cursor.z
tiles.each { |line|
next if line.empty? or line == ['']
line.each { |tile|
t = df.map_tile_at(x, y, z)
s = t.shape_basic
case tile
when 'd'; t.dig(:Default) if s == :Wall
when 'u'; t.dig(:UpStair) if s == :Wall
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
when 'i'; t.dig(:UpDownStair) if s == :Wall
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
when 'r'; t.dig(:Ramp) if s == :Wall
when 'x'; t.dig(:No)
end
x += 1
}
x = x0
y += 1
}
puts 'done'
# designate an area for digging according to a plan in csv format
raise "usage: digfort <plan filename>" if not $script_args[0]
planfile = File.read($script_args[0])
if df.cursor.x == -30000
raise "place the game cursor to the top-left corner of the design"
end
tiles = planfile.lines.map { |l|
l.sub(/#.*/, '').split(';').map { |t| t.strip }
}
x = x0 = df.cursor.x
y = df.cursor.y
z = df.cursor.z
tiles.each { |line|
next if line.empty? or line == ['']
line.each { |tile|
t = df.map_tile_at(x, y, z)
s = t.shape_basic
case tile
when 'd'; t.dig(:Default) if s == :Wall
when 'u'; t.dig(:UpStair) if s == :Wall
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
when 'i'; t.dig(:UpDownStair) if s == :Wall
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
when 'r'; t.dig(:Ramp) if s == :Wall
when 'x'; t.dig(:No)
end
x += 1
}
x = x0
y += 1
}
puts 'done'

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

@ -1,93 +1,135 @@
# slay all creatures of a given race
# race = name of the race to eradicate, use 'him' to target only the selected creature
# use 'undead' to target all undeads
race = $script_args[0]
# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death
magma = ($script_args[1] == 'magma')
checkunit = lambda { |u|
(u.body.blood_count != 0 or u.body.blood_max == 0) and
not u.flags1.dead and
not u.flags1.caged and not u.flags1.chained and
#not u.flags1.hidden_in_ambush and
not df.map_designation_at(u).hidden
}
slayit = lambda { |u|
if not magma
# just make them drop dead
u.body.blood_count = 0
# some races dont mind having no blood, ensure they are still taken care of.
u.animal.vanish_countdown = 2
else
# it's getting hot around here
# !!WARNING!! do not call on a magma-safe creature
ouh = df.onupdate_register("slayrace ensure #{u.id}", 1) {
if u.flags1.dead
df.onupdate_unregister(ouh)
else
x, y, z = u.pos.x, u.pos.y, u.pos.z
z += 1 while tile = df.map_tile_at(x, y, z+1) and
tile.shape_passableflow and tile.shape_passablelow
df.map_tile_at(x, y, z).spawn_magma(7)
end
}
end
}
all_races = Hash.new(0)
df.world.units.active.map { |u|
if checkunit[u]
if (u.enemy.undead or
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
u.curse.rem_tags1.OPPOSED_TO_LIFE))
all_races['Undead'] += 1
else
all_races[u.race_tg.creature_id] += 1
end
end
}
case race
when nil
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
when 'him'
if him = df.unit_find
slayit[him]
else
puts "Select a target ingame"
end
when /^undead/i
count = 0
df.world.units.active.each { |u|
if (u.enemy.undead or
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
u.curse.rem_tags1.OPPOSED_TO_LIFE)) and
checkunit[u]
slayit[u]
count += 1
end
}
puts "slain #{count} undeads"
else
raw_race = df.match_rawname(race, all_races.keys)
raise 'invalid race' if not raw_race
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race }
count = 0
df.world.units.active.each { |u|
if u.race == race_nr and checkunit[u]
slayit[u]
count += 1
end
}
puts "slain #{count} #{raw_race}"
end
# exterminate creatures
# race = name of the race to eradicate, use 'him' to target only the selected creature
# use 'undead' to target all undeads
race = $script_args[0]
# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death
# if it is 'butcher' mark all units for butchering (wont work with hostiles)
kill_by = $script_args[1]
case kill_by
when 'magma'
slain = 'burning'
when 'slaughter', 'butcher'
slain = 'marked for butcher'
when nil
slain = 'slain'
else
race = 'help'
end
checkunit = lambda { |u|
(u.body.blood_count != 0 or u.body.blood_max == 0) and
not u.flags1.dead and
not u.flags1.caged and not u.flags1.chained and
#not u.flags1.hidden_in_ambush and
not df.map_designation_at(u).hidden
}
slayit = lambda { |u|
case kill_by
when 'magma'
# it's getting hot around here
# !!WARNING!! do not call on a magma-safe creature
ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) {
if u.flags1.dead
df.onupdate_unregister(ouh)
else
x, y, z = u.pos.x, u.pos.y, u.pos.z
z += 1 while tile = df.map_tile_at(x, y, z+1) and
tile.shape_passableflow and tile.shape_passablelow
df.map_tile_at(x, y, z).spawn_magma(7)
end
}
when 'butcher', 'slaughter'
# mark for slaughter at butcher's shop
u.flags2.slaughter = true
else
# just make them drop dead
u.body.blood_count = 0
# some races dont mind having no blood, ensure they are still taken care of.
u.animal.vanish_countdown = 2
end
}
all_races = Hash.new(0)
df.world.units.active.map { |u|
if checkunit[u]
if (u.enemy.undead or
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
u.curse.rem_tags1.OPPOSED_TO_LIFE))
all_races['Undead'] += 1
else
all_races[u.race_tg.creature_id] += 1
end
end
}
case race
when nil
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
when 'help', '?'
puts <<EOS
Kills all creatures of a given race.
With no argument, lists possible targets with their head count.
With the special argument 'him' or 'her', kill only the currently selected creature.
With the special argument 'undead', kill all undead creatures/thralls.
The targets will bleed out on the next game tick, or if they are immune to that, will vanish in a puff of smoke.
The special final argument 'magma' will make magma rain on the targets instead.
The special final argument 'butcher' will mark the targets for butchering instead.
Ex: exterminate gob
exterminate elve magma
exterminate him
exterminate pig butcher
EOS
when 'him', 'her', 'it', 'that'
if him = df.unit_find
case him.race_tg.caste[him.caste].gender
when 0; puts 'its a she !' if race != 'her'
when 1; puts 'its a he !' if race != 'him'
else; puts 'its an it !' if race != 'it' and race != 'that'
end
slayit[him]
else
puts "Select a target ingame"
end
when /^undead/i
count = 0
df.world.units.active.each { |u|
if (u.enemy.undead or
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
u.curse.rem_tags1.OPPOSED_TO_LIFE)) and
checkunit[u]
slayit[u]
count += 1
end
}
puts "#{slain} #{count} undeads"
else
raw_race = df.match_rawname(race, all_races.keys)
if not raw_race
puts "Invalid race, use one of #{all_races.keys.sort.join(' ')}"
throw :script_finished
end
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race }
count = 0
df.world.units.active.each { |u|
if u.race == race_nr and checkunit[u]
slayit[u]
count += 1
end
}
puts "#{slain} #{count} #{raw_race}"
end

@ -1,64 +1,64 @@
# script to fix loyalty cascade, when you order your militia to kill friendly units
def fixunit(unit)
return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id
links = unit.hist_figure_tg.entity_links
fixed = false
# check if the unit is a civ renegade
if i1 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
l.entity_id == df.ui.civ_id
} and i2 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
l.entity_id == df.ui.civ_id
}
fixed = true
i1, i2 = i2, i1 if i1 > i2
links.delete_at i2
links.delete_at i1
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100)
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again"
end
# check if the unit is a group renegade
if i1 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
l.entity_id == df.ui.group_id
} and i2 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
l.entity_id == df.ui.group_id
}
fixed = true
i1, i2 = i2, i1 if i1 > i2
links.delete_at i2
links.delete_at i1
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100)
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again"
end
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed)
if fixed and unit.enemy.enemy_status_slot != -1
i = unit.enemy.enemy_status_slot
unit.enemy.enemy_status_slot = -1
cache = df.world.enemy_status_cache
cache.slot_used[i] = false
cache.rel_map[i].map! { -1 }
cache.rel_map.each { |a| a[i] = -1 }
cache.next_slot = i if cache.next_slot > i
end
# return true if we actually fixed the unit
fixed
end
count = 0
df.unit_citizens.each { |u|
count += 1 if fixunit(u)
}
if count > 0
puts "loyalty cascade fixed (#{count} dwarves)"
else
puts "no loyalty cascade found"
end
# script to fix loyalty cascade, when you order your militia to kill friendly units
def fixunit(unit)
return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id
links = unit.hist_figure_tg.entity_links
fixed = false
# check if the unit is a civ renegade
if i1 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
l.entity_id == df.ui.civ_id
} and i2 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
l.entity_id == df.ui.civ_id
}
fixed = true
i1, i2 = i2, i1 if i1 > i2
links.delete_at i2
links.delete_at i1
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100)
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again"
end
# check if the unit is a group renegade
if i1 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
l.entity_id == df.ui.group_id
} and i2 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
l.entity_id == df.ui.group_id
}
fixed = true
i1, i2 = i2, i1 if i1 > i2
links.delete_at i2
links.delete_at i1
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100)
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again"
end
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed)
if fixed and unit.enemy.enemy_status_slot != -1
i = unit.enemy.enemy_status_slot
unit.enemy.enemy_status_slot = -1
cache = df.world.enemy_status_cache
cache.slot_used[i] = false
cache.rel_map[i].map! { -1 }
cache.rel_map.each { |a| a[i] = -1 }
cache.next_slot = i if cache.next_slot > i
end
# return true if we actually fixed the unit
fixed
end
count = 0
df.unit_citizens.each { |u|
count += 1 if fixunit(u)
}
if count > 0
puts "loyalty cascade fixed (#{count} dwarves)"
else
puts "no loyalty cascade found"
end

@ -1,20 +1,25 @@
# fix doors that are frozen in 'open' state
# door is stuck in open state if the map occupancy flag incorrectly indicates
# that an unit is present (and creatures will prone to pass through)
count = 0
df.world.buildings.all.each { |bld|
# for all doors
next if bld._rtti_classname != :building_doorst
# check if it is open
next if bld.close_timer == 0
# check if occupancy is set
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
next if not occ.unit
# check if an unit is present
next if df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z }
count += 1
occ.unit = false
}
puts "unstuck #{count} doors"
# fix doors that are frozen in 'open' state
# this may happen after people mess with the game by (incorrectly) teleporting units or items
# a door may stick open if the map occupancy flags are wrong
count = 0
df.world.buildings.all.each { |bld|
# for all doors
next if bld._rtti_classname != :building_doorst
# check if it is open
next if bld.close_timer == 0
# check if occupancy is set
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
if (occ.unit or occ.unit_grounded) and not
# check if an unit is present
df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z }
count += 1
occ.unit = occ.unit_grounded = false
end
if occ.item and not df.world.items.all.find { |i| i.pos.x == bld.x1 and i.pos.y == bld.y1 and i.pos.z == bld.z }
count += 1
occ.item = false
end
}
puts "unstuck #{count} doors"

@ -1,49 +1,49 @@
# grow crops in farm plots. ex: growcrops helmet_plump 20
material = $script_args[0]
count_max = $script_args[1].to_i
count_max = 100 if count_max == 0
# cache information from the raws
@raws_plant_name ||= {}
@raws_plant_growdur ||= {}
if @raws_plant_name.empty?
df.world.raws.plants.all.each_with_index { |p, idx|
@raws_plant_name[idx] = p.id
@raws_plant_growdur[idx] = p.growdur
}
end
inventory = Hash.new(0)
df.world.items.other[:SEEDS].each { |seed|
next if not seed.flags.in_building
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
inventory[seed.mat_index] += 1
}
if !material or material == 'help' or material == 'list'
# show a list of available crop types
inventory.sort_by { |mat, c| c }.each { |mat, c|
name = df.world.raws.plants.all[mat].id
puts " #{name} #{c}"
}
else
mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] })
unless wantmat = @raws_plant_name.index(mat)
raise "invalid plant material #{material}"
end
count = 0
df.world.items.other[:SEEDS].each { |seed|
next if seed.mat_index != wantmat
next if not seed.flags.in_building
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
count += 1
}
puts "Grown #{count} #{mat}"
end
# grow crops in farm plots. ex: growcrops helmet_plump 20
material = $script_args[0]
count_max = $script_args[1].to_i
count_max = 100 if count_max == 0
# cache information from the raws
@raws_plant_name ||= {}
@raws_plant_growdur ||= {}
if @raws_plant_name.empty?
df.world.raws.plants.all.each_with_index { |p, idx|
@raws_plant_name[idx] = p.id
@raws_plant_growdur[idx] = p.growdur
}
end
inventory = Hash.new(0)
df.world.items.other[:SEEDS].each { |seed|
next if not seed.flags.in_building
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
inventory[seed.mat_index] += 1
}
if !material or material == 'help' or material == 'list'
# show a list of available crop types
inventory.sort_by { |mat, c| c }.each { |mat, c|
name = df.world.raws.plants.all[mat].id
puts " #{name} #{c}"
}
else
mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] })
unless wantmat = @raws_plant_name.index(mat)
raise "invalid plant material #{material}"
end
count = 0
df.world.items.other[:SEEDS].each { |seed|
next if seed.mat_index != wantmat
next if not seed.flags.in_building
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
count += 1
}
puts "Grown #{count} #{mat}"
end

@ -186,15 +186,27 @@ function SetPatientRef(args)
end
end
end
function SetCarveDir(args)
local job=args.job
local pos=args.pos
local from_pos=args.from_pos
local dirs={up=18,down=19,right=20,left=21}
if pos.x>from_pos.x then
job.item_category[dirs.right]=true
elseif pos.x<from_pos.x then
job.item_category[dirs.left]=true
elseif pos.y>from_pos.y then
job.item_category[dirs.up]=true
elseif pos.y<from_pos.y then
job.item_category[dirs.down]=true
end
end
function MakePredicateWieldsItem(item_skill)
local pred=function(args)
local inv=args.unit.inventory
for k,v in pairs(inv) do
if v.mode==1 and df.item_weaponst:is_instance(v.item) then
if v.item.subtype.skill_melee==item_skill and args.unit.body.weapon_bp==v.body_part_id then
return true
end
if v.mode==1 and v.item:getMeleeSkill()==item_skill and args.unit.body.weapon_bp==v.body_part_id then
return true
end
end
return false,"Correct tool not equiped"
@ -819,9 +831,10 @@ end
actions={
{"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}},
{"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}},
{"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMaterial,SameSquare}},
--{"CarveTrack" ,df.job_type.CarveTrack}, -- does not work??
{"DetailWall" ,df.job_type.DetailWall,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall,IsHardMaterial}},
{"DetailFloor" ,df.job_type.DetailFloor,{MakePredicateWieldsItem(df.job_skill.MINING),IsFloor,IsHardMaterial,SameSquare}},
{"CarveTrack" ,df.job_type.CarveTrack,{MakePredicateWieldsItem(df.job_skill.MINING),IsFloor,IsHardMaterial}
,{SetCarveDir}},
{"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}},
{"CarveUpwardStaircase" ,df.job_type.CarveUpwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}},
{"CarveDownwardStaircase",df.job_type.CarveDownwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING)}},

@ -0,0 +1,656 @@
-- A GUI front-end for the autobutcher plugin.
local gui = require 'gui'
local utils = require 'utils'
local widgets = require 'gui.widgets'
local dlg = require 'gui.dialogs'
local plugin = require 'plugins.zone'
WatchList = defclass(WatchList, gui.FramedScreen)
WatchList.ATTRS {
frame_title = 'Autobutcher Watchlist',
frame_inset = 0, -- cover full DF window
frame_background = COLOR_BLACK,
frame_style = gui.BOUNDARY_FRAME,
}
-- width of the race name column in the UI
local racewidth = 25
function nextAutowatchState()
if(plugin.autowatch_isEnabled()) then
return 'Stop '
end
return 'Start'
end
function nextAutobutcherState()
if(plugin.autobutcher_isEnabled()) then
return 'Stop '
end
return 'Start'
end
function getSleepTimer()
return plugin.autobutcher_getSleep()
end
function setSleepTimer(ticks)
plugin.autobutcher_setSleep(ticks)
end
function WatchList:init(args)
local colwidth = 7
self:addviews{
widgets.Panel{
frame = { l = 0, r = 0 },
frame_inset = 1,
subviews = {
widgets.Label{
frame = { l = 0, t = 0 },
text_pen = COLOR_CYAN,
text = {
{ text = 'Race', width = racewidth }, ' ',
{ text = 'female', width = colwidth }, ' ',
{ text = ' male', width = colwidth }, ' ',
{ text = 'Female', width = colwidth }, ' ',
{ text = ' Male', width = colwidth }, ' ',
{ text = 'watch? ' },
{ text = ' butchering' },
NEWLINE,
{ text = '', width = racewidth }, ' ',
{ text = ' kids', width = colwidth }, ' ',
{ text = ' kids', width = colwidth }, ' ',
{ text = 'adults', width = colwidth }, ' ',
{ text = 'adults', width = colwidth }, ' ',
{ text = ' ' },
{ text = ' ordered' },
}
},
widgets.List{
view_id = 'list',
frame = { t = 3, b = 5 },
not_found_label = 'Watchlist is empty.',
edit_pen = COLOR_LIGHTCYAN,
text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK },
cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN },
--on_select = self:callback('onSelectEntry'),
},
widgets.Label{
view_id = 'bottom_ui',
frame = { b = 0, h = 1 },
text = 'filled by updateBottom()'
}
}
},
}
self:initListChoices()
self:updateBottom()
end
-- change the viewmode for stock data displayed in left section of columns
local viewmodes = { 'total stock', 'protected stock', 'butcherable', 'butchering ordered' }
local viewmode = 1
function WatchList:onToggleView()
if viewmode < #viewmodes then
viewmode = viewmode + 1
else
viewmode = 1
end
self:initListChoices()
self:updateBottom()
end
-- update the bottom part of the UI (after sleep timer changed etc)
function WatchList:updateBottom()
self.subviews.bottom_ui:setText(
{
{ key = 'CUSTOM_SHIFT_V', text = ': View in colums shows: '..viewmodes[viewmode]..' / target max',
on_activate = self:callback('onToggleView') }, NEWLINE,
{ key = 'CUSTOM_F', text = ': f kids',
on_activate = self:callback('onEditFK') }, ', ',
{ key = 'CUSTOM_M', text = ': m kids',
on_activate = self:callback('onEditMK') }, ', ',
{ key = 'CUSTOM_SHIFT_F', text = ': f adults',
on_activate = self:callback('onEditFA') }, ', ',
{ key = 'CUSTOM_SHIFT_M', text = ': m adults',
on_activate = self:callback('onEditMA') }, '. ',
{ key = 'CUSTOM_W', text = ': Toggle watch',
on_activate = self:callback('onToggleWatching') }, '. ',
{ key = 'CUSTOM_X', text = ': Delete',
on_activate = self:callback('onDeleteEntry') }, '. ', NEWLINE,
--{ key = 'CUSTOM_A', text = ': Add race',
-- on_activate = self:callback('onAddRace') }, ', ',
{ key = 'CUSTOM_SHIFT_R', text = ': Set whole row',
on_activate = self:callback('onSetRow') }, '. ',
{ key = 'CUSTOM_B', text = ': Remove butcher orders',
on_activate = self:callback('onUnbutcherRace') }, '. ',
{ key = 'CUSTOM_SHIFT_B', text = ': Butcher race',
on_activate = self:callback('onButcherRace') }, '. ', NEWLINE,
{ key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher',
on_activate = self:callback('onToggleAutobutcher') }, '. ',
{ key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch',
on_activate = self:callback('onToggleAutowatch') }, '. ',
{ key = 'CUSTOM_SHIFT_S', text = ': Sleep ('..getSleepTimer()..' ticks)',
on_activate = self:callback('onEditSleepTimer') }, '. ',
})
end
function stringify(number)
-- cap displayed number to 3 digits
-- after population of 50 per race is reached pets stop breeding anyways
-- so probably this could safely be reduced to 99
local max = 999
if number > max then number = max end
return tostring(number)
end
function WatchList:initListChoices()
local choices = {}
-- first two rows are for "edit all races" and "edit new races"
local settings = plugin.autobutcher_getSettings()
local fk = stringify(settings.fk)
local fa = stringify(settings.fa)
local mk = stringify(settings.mk)
local ma = stringify(settings.ma)
local watched = ''
local colwidth = 7
table.insert (choices, {
text = {
{ text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = watched, width = 6, rjustify = true }
}
})
table.insert (choices, {
text = {
{ text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ',
{ text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ',
{ text = watched, width = 6, rjustify = true }
}
})
local watchlist = plugin.autobutcher_getWatchList()
for i,entry in ipairs(watchlist) do
fk = stringify(entry.fk)
fa = stringify(entry.fa)
mk = stringify(entry.mk)
ma = stringify(entry.ma)
if viewmode == 1 then
fkc = stringify(entry.fk_total)
fac = stringify(entry.fa_total)
mkc = stringify(entry.mk_total)
mac = stringify(entry.ma_total)
end
if viewmode == 2 then
fkc = stringify(entry.fk_protected)
fac = stringify(entry.fa_protected)
mkc = stringify(entry.mk_protected)
mac = stringify(entry.ma_protected)
end
if viewmode == 3 then
fkc = stringify(entry.fk_butcherable)
fac = stringify(entry.fa_butcherable)
mkc = stringify(entry.mk_butcherable)
mac = stringify(entry.ma_butcherable)
end
if viewmode == 4 then
fkc = stringify(entry.fk_butcherflag)
fac = stringify(entry.fa_butcherflag)
mkc = stringify(entry.mk_butcherflag)
mac = stringify(entry.ma_butcherflag)
end
local butcher_ordered = entry.fk_butcherflag + entry.fa_butcherflag + entry.mk_butcherflag + entry.ma_butcherflag
local bo = ' '
if butcher_ordered > 0 then bo = stringify(butcher_ordered) end
local watched = 'no'
if entry.watched then watched = 'yes' end
local racestr = entry.name
-- highlight entries where the target quota can't be met because too many are protected
bad_pen = COLOR_LIGHTRED
good_pen = NONE -- this is stupid, but it works. sue me
fk_pen = good_pen
fa_pen = good_pen
mk_pen = good_pen
ma_pen = good_pen
if entry.fk_protected > entry.fk then fk_pen = bad_pen end
if entry.fa_protected > entry.fa then fa_pen = bad_pen end
if entry.mk_protected > entry.mk then mk_pen = bad_pen end
if entry.ma_protected > entry.ma then ma_pen = bad_pen end
table.insert (choices, {
text = {
{ text = racestr, width = racewidth, pad_char = ' ' }, --' ',
{ text = fkc, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = fk, width = 3, rjustify = false, pad_char = ' ', pen = fk_pen }, ' ',
{ text = mkc, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = mk, width = 3, rjustify = false, pad_char = ' ', pen = mk_pen }, ' ',
{ text = fac, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = fa, width = 3, rjustify = false, pad_char = ' ', pen = fa_pen }, ' ',
{ text = mac, width = 3, rjustify = true, pad_char = ' ' }, '/',
{ text = ma, width = 3, rjustify = false, pad_char = ' ', pen = ma_pen }, ' ',
{ text = watched, width = 6, rjustify = true, pad_char = ' ' }, ' ',
{ text = bo, width = 8, rjustify = true, pad_char = ' ' }
},
obj = entry,
})
end
local list = self.subviews.list
list:setChoices(choices)
end
function WatchList:onInput(keys)
if keys.LEAVESCREEN then
self:dismiss()
else
WatchList.super.onInput(self, keys)
end
end
-- check the user input for target population values
function WatchList:checkUserInput(count, text)
if count == nil then
dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED)
return false
end
if count < 0 then
dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED)
return false
end
return true
end
-- check the user input for sleep timer
function WatchList:checkUserInputSleep(count, text)
if count == nil then
dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED)
return false
end
if count < 1000 then
dlg.showMessage('Invalid Number',
'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!',
COLOR_LIGHTRED)
return false
end
return true
end
function WatchList:onEditFK()
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of female kids:',
COLOR_WHITE,
' '..fk,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
fk = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditMK()
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of male kids:',
COLOR_WHITE,
' '..mk,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
mk = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditFA()
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of female adults:',
COLOR_WHITE,
' '..fa,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
fa = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditMA()
local selidx,selobj = self.subviews.list:getSelected()
local settings = plugin.autobutcher_getSettings()
local fk = settings.fk
local mk = settings.mk
local fa = settings.fa
local ma = settings.ma
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
if selidx > 2 then
local entry = selobj.obj
fk = entry.fk
mk = entry.mk
fa = entry.fa
ma = entry.ma
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Race: '..race,
'Enter desired maximum of male adults:',
COLOR_WHITE,
' '..ma,
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
ma = count
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onEditSleepTimer()
local sleep = getSleepTimer()
dlg.showInputPrompt(
'Edit Sleep Timer',
'Enter new sleep timer in ticks:'..NEWLINE..'(1 ingame day equals 1200 ticks)',
COLOR_WHITE,
' '..sleep,
function(text)
local count = tonumber(text)
if self:checkUserInputSleep(count, text) then
sleep = count
setSleepTimer(sleep)
self:updateBottom()
end
end
)
end
function WatchList:onToggleWatching()
local selidx,selobj = self.subviews.list:getSelected()
if selidx > 2 then
local entry = selobj.obj
plugin.autobutcher_setWatchListRace(entry.id, entry.fk, entry.mk, entry.fa, entry.ma, not entry.watched)
end
self:initListChoices()
end
function WatchList:onDeleteEntry()
local selidx,selobj = self.subviews.list:getSelected()
if(selidx < 3 or selobj == nil) then
return
end
dlg.showYesNoPrompt(
'Delete from Watchlist',
'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)',
COLOR_YELLOW,
function()
plugin.autobutcher_removeFromWatchList(selobj.obj.id)
self:initListChoices()
end
)
end
function WatchList:onAddRace()
print('onAddRace - not implemented yet')
end
function WatchList:onUnbutcherRace()
local selidx,selobj = self.subviews.list:getSelected()
if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end
if selidx > 2 then
local entry = selobj.obj
local race = entry.name
plugin.autobutcher_unbutcherRace(entry.id)
self:initListChoices()
self:updateBottom()
end
end
function WatchList:onButcherRace()
local selidx,selobj = self.subviews.list:getSelected()
if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end
if selidx > 2 then
local entry = selobj.obj
local race = entry.name
plugin.autobutcher_butcherRace(entry.id)
self:initListChoices()
self:updateBottom()
end
end
-- set whole row (fk, mk, fa, ma) to one value
function WatchList:onSetRow()
local selidx,selobj = self.subviews.list:getSelected()
local race = 'ALL RACES PLUS NEW'
local id = -1
local watched = false
if selidx == 2 then
race = 'ONLY NEW RACES'
end
local watchindex = selidx - 3
if selidx > 2 then
local entry = selobj.obj
race = entry.name
id = entry.id
watched = entry.watched
end
dlg.showInputPrompt(
'Set whole row for '..race,
'Enter desired maximum for all subtypes:',
COLOR_WHITE,
' ',
function(text)
local count = tonumber(text)
if self:checkUserInput(count, text) then
if selidx == 1 then
plugin.autobutcher_setDefaultTargetAll( count, count, count, count )
end
if selidx == 2 then
plugin.autobutcher_setDefaultTargetNew( count, count, count, count )
end
if selidx > 2 then
plugin.autobutcher_setWatchListRace(id, count, count, count, count, watched)
end
self:initListChoices()
end
end
)
end
function WatchList:onToggleAutobutcher()
if(plugin.autobutcher_isEnabled()) then
plugin.autobutcher_setEnabled(false)
plugin.autobutcher_sortWatchList()
else
plugin.autobutcher_setEnabled(true)
end
self:initListChoices()
self:updateBottom()
end
function WatchList:onToggleAutowatch()
if(plugin.autowatch_isEnabled()) then
plugin.autowatch_setEnabled(false)
else
plugin.autowatch_setEnabled(true)
end
self:initListChoices()
self:updateBottom()
end
if not dfhack.isMapLoaded() then
qerror('Map is not loaded.')
end
if string.match(dfhack.gui.getCurFocus(), '^dfhack/lua') then
qerror("This script must not be called while other lua gui stuff is running.")
end
-- maybe this is too strict, there is not really a reason why it can only be called from the status screen
-- (other than the hotkey might overlap with other scripts)
if (not string.match(dfhack.gui.getCurFocus(), '^overallstatus') and not string.match(dfhack.gui.getCurFocus(), '^pet/List/Unit')) then
qerror("This script must either be called from the overall status screen or the animal list screen.")
end
local screen = WatchList{ }
screen:show()

@ -1,120 +1,120 @@
# control your levers from the dfhack console
def lever_pull_job(bld)
ref = DFHack::GeneralRefBuildingHolderst.cpp_new
ref.building_id = bld.id
job = DFHack::Job.cpp_new
job.job_type = :PullLever
job.pos = [bld.centerx, bld.centery, bld.z]
job.general_refs << ref
bld.jobs << job
df.job_link job
puts lever_descr(bld)
end
def lever_pull_cheat(bld)
bld.linked_mechanisms.each { |i|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r|
r.building_tg.setTriggerState(bld.state)
}
}
bld.state = (bld.state == 0 ? 1 : 0)
puts lever_descr(bld)
end
def lever_descr(bld, idx=nil)
ret = []
# lever description
descr = ''
descr << "#{idx}: " if idx
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
bld.jobs.each { |j|
if j.job_type == :PullLever
flags = ''
flags << ', repeat' if j.flags.repeat
flags << ', suspended' if j.flags.suspend
descr << " (pull order#{flags})"
end
}
bld.linked_mechanisms.map { |i|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst)
}.flatten.each { |r|
# linked building description
tg = r.building_tg
state = ''
if tg.respond_to?(:gate_flags)
state << (tg.gate_flags.closed ? 'closed' : 'opened')
state << ", closing (#{tg.timer})" if tg.gate_flags.closing
state << ", opening (#{tg.timer})" if tg.gate_flags.opening
end
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
# indent other links
descr = descr.gsub(/./, ' ')
}
ret << descr if ret.empty?
ret
end
def lever_list
@lever_list = []
df.world.buildings.other[:TRAP].find_all { |bld|
bld.trap_type == :Lever
}.sort_by { |bld| bld.id }.each { |bld|
puts lever_descr(bld, @lever_list.length)
@lever_list << bld.id
}
end
case $script_args[0]
when 'pull'
cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
id = $script_args[1].to_i
id = @lever_list[id] || id
bld = df.building_find(id)
raise 'invalid lever id' if not bld
if cheat
lever_pull_cheat(bld)
else
lever_pull_job(bld)
end
when 'list'
lever_list
when /^\d+$/
id = $script_args[0].to_i
id = @lever_list[id] || id
bld = df.building_find(id)
raise 'invalid lever id' if not bld
puts lever_descr(bld)
else
puts <<EOS
Lever control from the dfhack console
Usage:
lever list
shows the list of levers in the fortress, with their id and links
lever pull 42
order the dwarves to pull lever 42
lever pull 42 --cheat
magically pull lever 42 immediately
EOS
end
# control your levers from the dfhack console
def lever_pull_job(bld)
ref = DFHack::GeneralRefBuildingHolderst.cpp_new
ref.building_id = bld.id
job = DFHack::Job.cpp_new
job.job_type = :PullLever
job.pos = [bld.centerx, bld.centery, bld.z]
job.general_refs << ref
bld.jobs << job
df.job_link job
puts lever_descr(bld)
end
def lever_pull_cheat(bld)
bld.linked_mechanisms.each { |i|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r|
r.building_tg.setTriggerState(bld.state)
}
}
bld.state = (bld.state == 0 ? 1 : 0)
puts lever_descr(bld)
end
def lever_descr(bld, idx=nil)
ret = []
# lever description
descr = ''
descr << "#{idx}: " if idx
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
bld.jobs.each { |j|
if j.job_type == :PullLever
flags = ''
flags << ', repeat' if j.flags.repeat
flags << ', suspended' if j.flags.suspend
descr << " (pull order#{flags})"
end
}
bld.linked_mechanisms.map { |i|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst)
}.flatten.each { |r|
# linked building description
tg = r.building_tg
state = ''
if tg.respond_to?(:gate_flags)
state << (tg.gate_flags.closed ? 'closed' : 'opened')
state << ", closing (#{tg.timer})" if tg.gate_flags.closing
state << ", opening (#{tg.timer})" if tg.gate_flags.opening
end
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
# indent other links
descr = descr.gsub(/./, ' ')
}
ret << descr if ret.empty?
ret
end
def lever_list
@lever_list = []
df.world.buildings.other[:TRAP].find_all { |bld|
bld.trap_type == :Lever
}.sort_by { |bld| bld.id }.each { |bld|
puts lever_descr(bld, @lever_list.length)
@lever_list << bld.id
}
end
case $script_args[0]
when 'pull'
cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
id = $script_args[1].to_i
id = @lever_list[id] || id
bld = df.building_find(id)
raise 'invalid lever id' if not bld
if cheat
lever_pull_cheat(bld)
else
lever_pull_job(bld)
end
when 'list'
lever_list
when /^\d+$/
id = $script_args[0].to_i
id = @lever_list[id] || id
bld = df.building_find(id)
raise 'invalid lever id' if not bld
puts lever_descr(bld)
else
puts <<EOS
Lever control from the dfhack console
Usage:
lever list
shows the list of levers in the fortress, with their id and links
lever pull 42
order the dwarves to pull lever 42
lever pull 42 --cheat
magically pull lever 42 immediately
EOS
end

@ -0,0 +1,84 @@
# scan the map for ore veins
target_ore = $script_args[0]
def find_all_ore_veins
puts 'scanning map...'
$ore_veins = {}
seen_mat = {}
df.each_map_block { |block|
block.block_events.grep(DFHack::BlockSquareEventMineralst).each { |vein|
mat_index = vein.inorganic_mat
if not seen_mat[mat_index] or $ore_veins[mat_index]
seen_mat[mat_index] = true
if df.world.raws.inorganics[mat_index].flags[:METAL_ORE]
$ore_veins[mat_index] ||= []
$ore_veins[mat_index] << [block.map_pos.x, block.map_pos.y, block.map_pos.z]
end
end
}
}
df.onstatechange_register_once { |st|
if st == :MAP_LOADED
$ore_veins = nil # invalidate veins cache
true
end
}
$ore_veins
end
$ore_veins ||= find_all_ore_veins
if not target_ore or target_ore == 'help'
puts <<EOS
Scan the map to find one random tile of unmined ore.
It will center the game view on that tile and mark it for digging.
Only works with metal ores.
Usage:
locate_ore list list all existing vein materials (including mined ones)
locate_ore hematite find one tile of unmined hematite ore
locate_ore iron find one tile of unmined ore you can smelt into iron
EOS
elsif target_ore and mats = $ore_veins.keys.find_all { |k|
ino = df.world.raws.inorganics[k]
ino.id =~ /#{target_ore}/i or ino.metal_ore.mat_index.find { |m|
df.world.raws.inorganics[m].id =~ /#{target_ore}/i
}
} and not mats.empty?
pos = nil
dxs = (0..15).sort_by { rand }
dys = (0..15).sort_by { rand }
if found_mat = mats.sort_by { rand }.find { |mat|
$ore_veins[mat].sort_by { rand }.find { |bx, by, bz|
dys.find { |dy|
dxs.find { |dx|
tile = df.map_tile_at(bx+dx, by+dy, bz)
if tile.tilemat == :MINERAL and tile.designation.dig == :No and tile.shape == :WALL and
tile.mat_index_vein == mat and
# ignore map borders
bx+dx > 0 and bx+dx < df.world.map.x_count-1 and by+dy > 0 and by+dy < df.world.map.y_count-1
pos = [bx+dx, by+dy, bz]
end
}
}
}
}
df.center_viewscreen(*pos)
df.map_tile_at(*pos).dig
puts "Here is some #{df.world.raws.inorganics[found_mat].id}"
else
puts "Cannot find unmined #{mats.map { |mat| df.world.raws.inorganics[mat].id }.join(', ')}"
end
else
puts "Available ores:", $ore_veins.sort_by { |mat, pos| pos.length }.map { |mat, pos|
ore = df.world.raws.inorganics[mat]
metals = ore.metal_ore.mat_index.map { |m| df.world.raws.inorganics[m] }
' ' + ore.id.downcase + ' (' + metals.map { |m| m.id.downcase }.join(', ') + ')'
}
end

@ -1,56 +0,0 @@
# create an infinite magma source at the cursor
$magma_sources ||= []
case $script_args[0]
when 'here'
$magma_onupdate ||= df.onupdate_register('magmasource', 12) {
# called every 12 game ticks (100x a dwarf day)
if $magma_sources.empty?
df.onupdate_unregister($magma_onupdate)
$magma_onupdate = nil
end
$magma_sources.each { |x, y, z|
if tile = df.map_tile_at(x, y, z) and tile.shape_passableflow
des = tile.designation
tile.spawn_magma(des.flow_size + 1) if des.flow_size < 7
end
}
}
if df.cursor.x != -30000
if tile = df.map_tile_at(df.cursor)
if tile.shape_passableflow
$magma_sources << [df.cursor.x, df.cursor.y, df.cursor.z]
else
puts "Impassable tile: I'm afraid I can't do that, Dave"
end
else
puts "Unallocated map block - build something here first"
end
else
puts "Please put the game cursor where you want a magma source"
end
when 'delete-here'
$magma_sources.delete [df.cursor.x, df.cursor.y, df.cursor.z]
when 'stop'
$magma_sources.clear
else
puts <<EOS
Creates a new infinite magma source at the cursor.
Arguments:
here - create a new source at the current cursor position
(call multiple times for higher flow)
delete-here - delete the source under the cursor
stop - delete all created magma sources
EOS
if $magma_sources.first
puts '', 'Current magma sources:', $magma_sources.map { |s| " #{s.inspect}" }
end
end

@ -0,0 +1,40 @@
# pit all caged creatures in a zone
case $script_args[0]
when '?', 'help'
puts <<EOS
Run this script with the cursor on top of a pit/pond activity zone, or with a zone identifier as argument.
It will mark all caged creatures on tiles covered by the zone to be dumped.
Works best with an animal stockpile on top of the pit/pond zone.
EOS
throw :script_finished
when /(\d+)/
nr = $1.to_i
bld = df.world.buildings.other[:ACTIVITY_ZONE].find { |zone| zone.zone_num == nr }
else
bld = df.world.buildings.other[:ACTIVITY_ZONE].find { |zone|
zone.zone_flags.pit_pond and zone.z == df.cursor.z and
zone.x1 <= df.cursor.x and zone.x2 >= df.cursor.x and zone.y1 <= df.cursor.y and zone.y2 >= df.cursor.y
}
end
if not bld
puts "Please select a pit/pond zone"
throw :script_finished
end
found = 0
df.world.items.other[:CAGE].each { |cg|
next if not cg.flags.on_ground
next if cg.pos.z != bld.z or cg.pos.x < bld.x1 or cg.pos.x > bld.x2 or cg.pos.y < bld.y1 or cg.pos.y > bld.y2
next if not uref = cg.general_refs.grep(DFHack::GeneralRefContainsUnitst).first
found += 1
u = uref.unit_tg
puts "Pitting #{u.race_tg.name[0]} #{u.id} #{u.name}"
u.general_refs << DFHack::GeneralRefBuildingCivzoneAssignedst.cpp_new(:building_id => bld.id)
bld.assigned_creature << u.id
}
puts "No creature available for pitting" if found == 0

@ -0,0 +1,4 @@
# run many dfhack commands separated by ;
# ex: multicmd locate-ore IRON ; digv ; digcircle 16
$script_args.join(' ').split(/\s*;\s*/).each { |cmd| df.dfhack_run cmd }

@ -1,43 +1,43 @@
# remove bad thoughts for the selected unit or the whole fort
dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n')
$script_args << 'all' if dry_run and $script_args.empty?
seenbad = Hash.new(0)
clear_mind = lambda { |u|
u.status.recent_events.each { |e|
next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-'
seenbad[e.type] += 1
e.age = 0x1000_0000 unless dry_run
}
}
summary = lambda {
seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt|
puts " #{thought} #{cnt}"
}
count = seenbad.values.inject(0) { |sum, cnt| sum+cnt }
puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run
}
case $script_args[0]
when 'him'
if u = df.unit_find
clear_mind[u]
summary[]
else
puts 'Please select a dwarf ingame'
end
when 'all'
df.unit_citizens.each { |uu|
clear_mind[uu]
}
summary[]
else
puts "Usage: removebadthoughts [--dry-run] <him|all>"
end
# remove bad thoughts for the selected unit or the whole fort
dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n')
$script_args << 'all' if dry_run and $script_args.empty?
seenbad = Hash.new(0)
clear_mind = lambda { |u|
u.status.recent_events.each { |e|
next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-'
seenbad[e.type] += 1
e.age = 0x1000_0000 unless dry_run
}
}
summary = lambda {
seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt|
puts " #{thought} #{cnt}"
}
count = seenbad.values.inject(0) { |sum, cnt| sum+cnt }
puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run
}
case $script_args[0]
when 'him'
if u = df.unit_find
clear_mind[u]
summary[]
else
puts 'Please select a dwarf ingame'
end
when 'all'
df.unit_citizens.each { |uu|
clear_mind[uu]
}
summary[]
else
puts "Usage: removebadthoughts [--dry-run] <him|all>"
end

@ -1,6 +1,7 @@
-- On map load writes the current season to gamelog.txt
local seasons = {
[-1] = 'Nothing', -- worldgen
[0] = 'Spring',
[1] = 'Summer',
[2] = 'Autumn',

@ -0,0 +1,83 @@
# create an infinite magma/water source/drain at the cursor
$sources ||= []
cur_source = {
:liquid => 'water',
:amount => 7,
:pos => [df.cursor.x, df.cursor.y, df.cursor.z]
}
cmd = 'help'
$script_args.each { |a|
case a.downcase
when 'water', 'magma'
cur_source[:liquid] = a.downcase
when /^\d+$/
cur_source[:amount] = a.to_i
when 'add', 'del', 'delete', 'clear', 'help', 'list'
cmd = a.downcase
else
puts "source: unhandled argument #{a}"
end
}
case cmd
when 'add'
$sources_onupdate ||= df.onupdate_register('sources', 12) {
# called every 12 game ticks (100x a dwarf day)
$sources.each { |s|
if tile = df.map_tile_at(*s[:pos]) and tile.shape_passableflow
# XXX does not check current liquid_type
des = tile.designation
cur = des.flow_size
if cur != s[:amount]
tile.spawn_liquid((cur > s[:amount] ? cur-1 : cur+1), s[:liquid] == 'magma')
end
end
}
if $sources.empty?
df.onupdate_unregister($sources_onupdate)
$sources_onupdate = nil
end
}
if cur_source[:pos][0] >= 0
if tile = df.map_tile_at(*cur_source[:pos])
if tile.shape_passableflow
$sources << cur_source
else
puts "Impassable tile: I'm afraid I can't do that, Dave"
end
else
puts "Unallocated map block - build something here first"
end
else
puts "Please put the game cursor where you want a source"
end
when 'del', 'delete'
$sources.delete_if { |s| s[:pos] == cur_source[:pos] }
when 'clear'
$sources.clear
when 'list'
puts "Source list:", $sources.map { |s|
" #{s[:pos].inspect} #{s[:liquid]} #{s[:amount]}"
}
puts "Current cursor pos: #{[df.cursor.x, df.cursor.y, df.cursor.z].inspect}" if df.cursor.x >= 0
else
puts <<EOS
Creates a new infinite liquid source at the cursor.
Examples:
source add water - create a water source under cursor
source add water 0 - create a water drain
source add magma 5 - create a magma source, up to 5/7 deep
source delete - delete source under cursor
source clear - remove all sources
source list
EOS
end

@ -1,204 +1,204 @@
# mark stuff inside of cages for dumping.
def plural(nr, name)
# '1 cage' / '4 cages'
"#{nr} #{name}#{'s' if nr > 1}"
end
def cage_dump_items(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsItemst)
next if ref.item_tg.flags.dump
count += 1
ref.item_tg.flags.dump = true
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_armor(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
ref.unit_tg.inventory.each { |it|
next if it.mode != :Worn
next if it.item.flags.dump
count += 1
it.item.flags.dump = true
}
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_weapons(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
ref.unit_tg.inventory.each { |it|
next if it.mode != :Weapon
next if it.item.flags.dump
count += 1
it.item.flags.dump = true
}
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_all(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
case ref
when DFHack::GeneralRefContainsItemst
next if ref.item_tg.flags.dump
count += 1
ref.item_tg.flags.dump = true
when DFHack::GeneralRefContainsUnitst
ref.unit_tg.inventory.each { |it|
next if it.item.flags.dump
count += 1
it.item.flags.dump = true
}
end
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_list(list)
count_total = Hash.new(0)
empty_cages = 0
list.each { |cage|
count = Hash.new(0)
cage.general_refs.each { |ref|
case ref
when DFHack::GeneralRefContainsItemst
count[ref.item_tg._rtti_classname] += 1
when DFHack::GeneralRefContainsUnitst
ref.unit_tg.inventory.each { |it|
count[it.item._rtti_classname] += 1
}
# TODO vermin ?
else
puts "unhandled ref #{ref.inspect}" if $DEBUG
end
}
type = case cage
when DFHack::ItemCagest; 'Cage'
when DFHack::ItemAnimaltrapst; 'Animal trap'
else cage._rtti_classname
end
if count.empty?
empty_cages += 1
else
puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
end
count.each { |k, v| count_total[k] += v }
}
if list.length > 2
puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
puts "with #{plural(empty_cages, 'empty cage')}"
end
end
# handle magic script arguments
here_only = $script_args.delete 'here'
if here_only
it = df.item_find
list = [it]
if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) }
end
if list.empty?
puts 'Please select a cage'
throw :script_finished
end
elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
list = []
ids.each { |id|
$script_args.delete id
if not it = df.item_find(id.to_i)
puts "Invalid item id #{id}"
elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
puts "Item ##{id} is not a cage"
list << it
else
list << it
end
}
if list.empty?
puts 'Please use a valid cage id'
throw :script_finished
end
else
list = df.world.items.other[:ANY_CAGE_OR_TRAP]
end
# act
case $script_args[0]
when /^it/i
cage_dump_items(list)
when /^arm/i
cage_dump_armor(list)
when /^wea/i
cage_dump_weapons(list)
when 'all'
cage_dump_all(list)
when 'list'
cage_dump_list(list)
else
puts <<EOS
Marks items inside all cages for dumping.
Add 'here' to dump stuff only for selected cage.
Add a cage id to dump stuff for this cage only.
See 'autodump' to actually dump stuff.
Usage:
stripcaged items
dump items directly in cages (eg seeds after training)
stripcaged [armor|weapons] here
dump armor or weapons of caged creatures in selected cage
stripcaged all 28 29
dump every item in cage id 28 and 29, along with every item worn by creatures in there too
stripcaged list
show content of the cages
EOS
end
# mark stuff inside of cages for dumping.
def plural(nr, name)
# '1 cage' / '4 cages'
"#{nr} #{name}#{'s' if nr > 1}"
end
def cage_dump_items(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsItemst)
next if ref.item_tg.flags.dump
count += 1
ref.item_tg.flags.dump = true
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_armor(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
ref.unit_tg.inventory.each { |it|
next if it.mode != :Worn
next if it.item.flags.dump
count += 1
it.item.flags.dump = true
}
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_weapons(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
ref.unit_tg.inventory.each { |it|
next if it.mode != :Weapon
next if it.item.flags.dump
count += 1
it.item.flags.dump = true
}
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_all(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
case ref
when DFHack::GeneralRefContainsItemst
next if ref.item_tg.flags.dump
count += 1
ref.item_tg.flags.dump = true
when DFHack::GeneralRefContainsUnitst
ref.unit_tg.inventory.each { |it|
next if it.item.flags.dump
count += 1
it.item.flags.dump = true
}
end
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_list(list)
count_total = Hash.new(0)
empty_cages = 0
list.each { |cage|
count = Hash.new(0)
cage.general_refs.each { |ref|
case ref
when DFHack::GeneralRefContainsItemst
count[ref.item_tg._rtti_classname] += 1
when DFHack::GeneralRefContainsUnitst
ref.unit_tg.inventory.each { |it|
count[it.item._rtti_classname] += 1
}
# TODO vermin ?
else
puts "unhandled ref #{ref.inspect}" if $DEBUG
end
}
type = case cage
when DFHack::ItemCagest; 'Cage'
when DFHack::ItemAnimaltrapst; 'Animal trap'
else cage._rtti_classname
end
if count.empty?
empty_cages += 1
else
puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
end
count.each { |k, v| count_total[k] += v }
}
if list.length > 2
puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
puts "with #{plural(empty_cages, 'empty cage')}"
end
end
# handle magic script arguments
here_only = $script_args.delete 'here'
if here_only
it = df.item_find
list = [it]
if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) }
end
if list.empty?
puts 'Please select a cage'
throw :script_finished
end
elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
list = []
ids.each { |id|
$script_args.delete id
if not it = df.item_find(id.to_i)
puts "Invalid item id #{id}"
elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
puts "Item ##{id} is not a cage"
list << it
else
list << it
end
}
if list.empty?
puts 'Please use a valid cage id'
throw :script_finished
end
else
list = df.world.items.other[:ANY_CAGE_OR_TRAP]
end
# act
case $script_args[0]
when /^it/i
cage_dump_items(list)
when /^arm/i
cage_dump_armor(list)
when /^wea/i
cage_dump_weapons(list)
when 'all'
cage_dump_all(list)
when 'list'
cage_dump_list(list)
else
puts <<EOS
Marks items inside all cages for dumping.
Add 'here' to dump stuff only for selected cage.
Add a cage id to dump stuff for this cage only.
See 'autodump' to actually dump stuff.
Usage:
stripcaged items
dump items directly in cages (eg seeds after training)
stripcaged [armor|weapons] here
dump armor or weapons of caged creatures in selected cage
stripcaged all 28 29
dump every item in cage id 28 and 29, along with every item worn by creatures in there too
stripcaged list
show content of the cages
EOS
end

@ -1,61 +1,61 @@
# give super-dwarven speed to an unit
$superdwarf_onupdate ||= nil
$superdwarf_ids ||= []
case $script_args[0]
when 'add'
if u = df.unit_find
$superdwarf_ids |= [u.id]
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) {
if $superdwarf_ids.empty?
df.onupdate_unregister($superdwarf_onupdate)
$superdwarf_onupdate = nil
else
$superdwarf_ids.each { |id|
if u = df.unit_find(id) and not u.flags1.dead
# faster walk/work
if u.counters.job_counter > 0
u.counters.job_counter = 0
end
# no sleep
if u.counters2.sleepiness_timer > 10000
u.counters2.sleepiness_timer = 1
end
# no break
if b = u.status.misc_traits.find { |t| t.id == :OnBreak }
b.value = 500_000
end
else
$superdwarf_ids.delete id
end
}
end
}
else
puts "Select a creature using 'v'"
end
when 'del'
if u = df.unit_find
$superdwarf_ids.delete u.id
else
puts "Select a creature using 'v'"
end
when 'clear'
$superdwarf_ids.clear
when 'list'
puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name }
else
puts "Usage:",
" - superdwarf add: give superspeed to currently selected creature",
" - superdwarf del: remove superspeed to current creature",
" - superdwarf clear: remove all superpowers",
" - superdwarf list: list super-dwarves"
end
# give super-dwarven speed to an unit
$superdwarf_onupdate ||= nil
$superdwarf_ids ||= []
case $script_args[0]
when 'add'
if u = df.unit_find
$superdwarf_ids |= [u.id]
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) {
if $superdwarf_ids.empty?
df.onupdate_unregister($superdwarf_onupdate)
$superdwarf_onupdate = nil
else
$superdwarf_ids.each { |id|
if u = df.unit_find(id) and not u.flags1.dead
# faster walk/work
if u.counters.job_counter > 0
u.counters.job_counter = 0
end
# no sleep
if u.counters2.sleepiness_timer > 10000
u.counters2.sleepiness_timer = 1
end
# no break
if b = u.status.misc_traits.find { |t| t.id == :OnBreak }
b.value = 500_000
end
else
$superdwarf_ids.delete id
end
}
end
}
else
puts "Select a creature using 'v'"
end
when 'del'
if u = df.unit_find
$superdwarf_ids.delete u.id
else
puts "Select a creature using 'v'"
end
when 'clear'
$superdwarf_ids.clear
when 'list'
puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name }
else
puts "Usage:",
" - superdwarf add: give superspeed to currently selected creature",
" - superdwarf del: remove superspeed to current creature",
" - superdwarf clear: remove all superpowers",
" - superdwarf list: list super-dwarves"
end

@ -1,17 +1,17 @@
joblist = df.world.job_list.next
count = 0
while joblist
job = joblist.item
joblist = joblist.next
if job.job_type == :ConstructBuilding
if (job.flags.suspend && job.items && job.items[0])
item = job.items[0].item
job.flags.suspend = false
count += 1
end
end
end
puts "Unsuspended #{count} job(s)."
joblist = df.world.job_list.next
count = 0
while joblist
job = joblist.item
joblist = joblist.next
if job.job_type == :ConstructBuilding
if (job.flags.suspend && job.items && job.items[0])
item = job.items[0].item
job.flags.suspend = false
count += 1
end
end
end
puts "Unsuspended #{count} job(s)."