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"] [submodule "plugins/stonesense"]
path = plugins/stonesense path = plugins/stonesense
url = git://github.com/peterix/stonesense.git url = git://github.com/peterix/stonesense.git
[submodule "plugins/isoworld"]
path = plugins/isoworld
url = git://github.com/peterix/isoworld.git
[submodule "plugins/df2mc"] [submodule "plugins/df2mc"]
path = plugins/df2mc path = plugins/df2mc
url = git://github.com/peterix/DF2MC.git url = git://github.com/peterix/DF2MC.git

@ -109,7 +109,8 @@ IF(UNIX)
SET(CMAKE_C_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic") SET(CMAKE_C_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic")
ELSEIF(MSVC) ELSEIF(MSVC)
# for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion # 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() ENDIF()
# use shared libraries for protobuf # use shared libraries for protobuf

@ -3,13 +3,13 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.9.1: http://docutils.sourceforge.net/" /> <meta name="generator" content="Docutils 0.10: http://docutils.sourceforge.net/" />
<title>Building DFHACK</title> <title>Building DFHACK</title>
<style type="text/css"> <style type="text/css">
/* /*
:Author: David Goodger (goodger@python.org) :Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7434 2012-05-11 21:06:27Z milde $ :Id: $Id: html4css1.css 7514 2012-09-14 14:27:12Z milde $
:Copyright: This stylesheet has been placed in the public domain. :Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils. Default cascading style sheet for the HTML output of Docutils.
@ -77,7 +77,7 @@ div.tip p.admonition-title {
div.attention p.admonition-title, div.caution p.admonition-title, div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title { div.warning p.admonition-title, .code .error {
color: red ; color: red ;
font-weight: bold ; font-weight: bold ;
font-family: sans-serif } font-family: sans-serif }
@ -253,13 +253,14 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ; margin-left: 2em ;
margin-right: 2em } margin-right: 2em }
pre.code .ln { /* line numbers */ pre.code .ln { color: grey; } /* line numbers */
color: grey; pre.code, code { background-color: #eeeeee }
} pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
.code { pre.code .literal.string, code .literal.string { color: #0C5404 }
background-color: #eeeeee pre.code .name.builtin, code .name.builtin { color: #352B84 }
} pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier { span.classifier {
font-family: sans-serif ; font-family: sans-serif ;
@ -334,21 +335,24 @@ ul.auto-toc {
<li><a class="reference internal" href="#build" id="id7">Build</a></li> <li><a class="reference internal" href="#build" id="id7">Build</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#mac-os-x" id="id8">Mac OS X</a></li> <li><a class="reference internal" href="#mac-os-x" id="id8">Mac OS X</a><ul>
<li><a class="reference internal" href="#windows" id="id9">Windows</a><ul> <li><a class="reference internal" href="#snow-leopard-changes" id="id9">Snow Leopard Changes</a></li>
<li><a class="reference internal" href="#id1" id="id10">How to get the code</a></li> </ul>
<li><a class="reference internal" href="#id2" id="id11">Dependencies</a></li> </li>
<li><a class="reference internal" href="#id3" id="id12">Build</a></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> </ul>
</li> </li>
<li><a class="reference internal" href="#build-types" id="id13">Build types</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="id14">Using the library as a developer</a><ul> <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="id15">DF data structure definitions</a></li> <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="id16">Remote access interface</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="id17">Contributing to DFHack</a><ul> <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="id18">Coding style</a></li> <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="id19">How to get new code into DFHack</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="id20">Memory research</a></li> <li><a class="reference internal" href="#memory-research" id="id21">Memory research</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -406,6 +410,7 @@ program.</p>
</div> </div>
<div class="section" id="mac-os-x"> <div class="section" id="mac-os-x">
<h1><a class="toc-backref" href="#id8">Mac OS X</a></h1> <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"> <ol class="arabic">
<li><p class="first">Download and unpack a copy of the latest DF</p> <li><p class="first">Download and unpack a copy of the latest DF</p>
</li> </li>
@ -458,12 +463,34 @@ make install
</pre> </pre>
</li> </li>
</ol> </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>
<div class="section" id="windows"> <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> <p>On Windows, DFHack replaces the SDL library distributed with DF.</p>
<div class="section" id="id1"> <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. <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> You will need some sort of Windows port of git, or a GUI. Some examples:</p>
<blockquote> <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> <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>
<div class="section" id="id2"> <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 <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> 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 <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> <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>
<div class="section" id="id3"> <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>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. <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> 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> </div>
<div class="section" id="build-types"> <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 <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> variable: <tt class="docutils literal">CMAKE_BUILD_TYPE</tt></p>
<pre class="literal-block"> <pre class="literal-block">
@ -535,7 +562,7 @@ cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE
'RelWithDebInfo'. 'Debug' is not available on Windows.</p> 'RelWithDebInfo'. 'Debug' is not available on Windows.</p>
</div> </div>
<div class="section" id="using-the-library-as-a-developer"> <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. <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 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> 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 <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> the dfhack repository is welcome and the right thing to do :)</p>
<div class="section" id="df-data-structure-definitions"> <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>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>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> <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>
<div class="section" id="remote-access-interface"> <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>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>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> <p>Protocol client implementations exist for Java and C#.</p>
</div> </div>
<div class="section" id="contributing-to-dfhack"> <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> <p>Several things should be kept in mind when contributing to DFHack.</p>
<div class="section" id="coding-style"> <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 <p>DFhack uses ANSI formatting and four spaces as indentation. Line
endings are UNIX. The files use UTF-8 encoding. Code not following this 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 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> I'll make <em>you</em> fix it ;)</p>
</div> </div>
<div class="section" id="how-to-get-new-code-into-dfhack"> <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 <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 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> 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> fixing.</p>
</div> </div>
<div class="section" id="memory-research"> <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. <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 In general, you'll need a good memory viewer and optionally something
to look at machine code without getting crazy :)</p> 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 You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui
program. 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 Mac OS X
======== ========

37
NEWS

@ -1,7 +1,38 @@
DFHack future 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 DFHack v0.34.11-r3
Internals: Internals:
@ -10,9 +41,11 @@ DFHack v0.34.11-r3
- Maps::canStepBetween: returns whether you can walk between two tiles in one step. - Maps::canStepBetween: returns whether you can walk between two tiles in one step.
- EventManager: monitors various in game events centrally so that individual plugins - EventManager: monitors various in game events centrally so that individual plugins
don't have to monitor the same things redundantly. don't have to monitor the same things redundantly.
- Now works with OSX 10.6.8
Notable bugfixes: Notable bugfixes:
- autobutcher can be re-enabled again after being stopped. - autobutcher can be re-enabled again after being stopped.
- stopped Dwarf Manipulator from unmasking vampires. - stopped Dwarf Manipulator from unmasking vampires.
- Stonesense is now fixed on OSX
Misc improvements: Misc improvements:
- fastdwarf: new mode using debug flags, and some internal consistency fixes. - fastdwarf: new mode using debug flags, and some internal consistency fixes.
- added a small stand-alone utility for applying and removing binary patches. - 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="#digtype" id="id68">digtype</a></li>
<li><a class="reference internal" href="#filltraffic" id="id69">filltraffic</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="#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> </ul>
</li> </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="#cleanup-and-garbage-disposal" id="id74">Cleanup and garbage disposal</a><ul>
<li><a class="reference internal" href="#clean" id="id73">clean</a></li> <li><a class="reference internal" href="#clean" id="id75">clean</a></li>
<li><a class="reference internal" href="#spotclean" id="id74">spotclean</a></li> <li><a class="reference internal" href="#spotclean" id="id76">spotclean</a></li>
<li><a class="reference internal" href="#autodump" id="id75">autodump</a></li> <li><a class="reference internal" href="#autodump" id="id77">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-here" id="id78">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="#autodump-destroy-item" id="id79">autodump-destroy-item</a></li>
<li><a class="reference internal" href="#cleanowned" id="id78">cleanowned</a></li> <li><a class="reference internal" href="#cleanowned" id="id80">cleanowned</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#bugfixes" id="id79">Bugfixes</a><ul> <li><a class="reference internal" href="#bugfixes" id="id81">Bugfixes</a><ul>
<li><a class="reference internal" href="#drybuckets" id="id80">drybuckets</a></li> <li><a class="reference internal" href="#drybuckets" id="id82">drybuckets</a></li>
<li><a class="reference internal" href="#fixdiplomats" id="id81">fixdiplomats</a></li> <li><a class="reference internal" href="#fixdiplomats" id="id83">fixdiplomats</a></li>
<li><a class="reference internal" href="#fixmerchants" id="id82">fixmerchants</a></li> <li><a class="reference internal" href="#fixmerchants" id="id84">fixmerchants</a></li>
<li><a class="reference internal" href="#fixveins" id="id83">fixveins</a></li> <li><a class="reference internal" href="#fixveins" id="id85">fixveins</a></li>
<li><a class="reference internal" href="#tweak" id="id84">tweak</a></li> <li><a class="reference internal" href="#tweak" id="id86">tweak</a></li>
<li><a class="reference internal" href="#fix-armory" id="id85">fix-armory</a></li> <li><a class="reference internal" href="#fix-armory" id="id87">fix-armory</a></li>
</ul> </ul>
</li> </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="#mode-switch-and-reclaim" id="id88">Mode switch and reclaim</a><ul>
<li><a class="reference internal" href="#lair" id="id87">lair</a></li> <li><a class="reference internal" href="#lair" id="id89">lair</a></li>
<li><a class="reference internal" href="#mode" id="id88">mode</a></li> <li><a class="reference internal" href="#mode" id="id90">mode</a></li>
</ul> </ul>
</li> </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="#visualizer-and-data-export" id="id91">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="#ssense-stonesense" id="id92">ssense / stonesense</a></li>
<li><a class="reference internal" href="#mapexport" id="id91">mapexport</a></li> <li><a class="reference internal" href="#mapexport" id="id93">mapexport</a></li>
<li><a class="reference internal" href="#dwarfexport" id="id92">dwarfexport</a></li> <li><a class="reference internal" href="#dwarfexport" id="id94">dwarfexport</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#job-management" id="id93">Job management</a><ul> <li><a class="reference internal" href="#job-management" id="id95">Job management</a><ul>
<li><a class="reference internal" href="#job" id="id94">job</a></li> <li><a class="reference internal" href="#job" id="id96">job</a></li>
<li><a class="reference internal" href="#job-material" id="id95">job-material</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="id96">job-duplicate</a></li> <li><a class="reference internal" href="#job-duplicate" id="id98">job-duplicate</a></li>
<li><a class="reference internal" href="#workflow" id="id97">workflow</a><ul> <li><a class="reference internal" href="#workflow" id="id99">workflow</a><ul>
<li><a class="reference internal" href="#function" id="id98">Function</a></li> <li><a class="reference internal" href="#function" id="id100">Function</a></li>
<li><a class="reference internal" href="#constraint-format" id="id99">Constraint format</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="id100">Constraint examples</a></li> <li><a class="reference internal" href="#constraint-examples" id="id102">Constraint examples</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#fortress-activity-management" id="id101">Fortress activity management</a><ul> <li><a class="reference internal" href="#fortress-activity-management" id="id103">Fortress activity management</a><ul>
<li><a class="reference internal" href="#seedwatch" id="id102">seedwatch</a></li> <li><a class="reference internal" href="#seedwatch" id="id104">seedwatch</a></li>
<li><a class="reference internal" href="#zone" id="id103">zone</a><ul> <li><a class="reference internal" href="#zone" id="id105">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-single-units" id="id106">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="#usage-with-filters" id="id107">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="#mass-renaming" id="id108">Mass-renaming</a></li>
<li><a class="reference internal" href="#cage-zones" id="id107">Cage zones</a></li> <li><a class="reference internal" href="#cage-zones" id="id109">Cage zones</a></li>
<li><a class="reference internal" href="#examples" id="id108">Examples</a></li> <li><a class="reference internal" href="#examples" id="id110">Examples</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#autonestbox" id="id109">autonestbox</a></li> <li><a class="reference internal" href="#autonestbox" id="id111">autonestbox</a></li>
<li><a class="reference internal" href="#autobutcher" id="id110">autobutcher</a></li> <li><a class="reference internal" href="#autobutcher" id="id112">autobutcher</a></li>
<li><a class="reference internal" href="#autolabor" id="id111">autolabor</a></li> <li><a class="reference internal" href="#autolabor" id="id113">autolabor</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#other" id="id112">Other</a><ul> <li><a class="reference internal" href="#other" id="id114">Other</a><ul>
<li><a class="reference internal" href="#catsplosion" id="id113">catsplosion</a></li> <li><a class="reference internal" href="#catsplosion" id="id115">catsplosion</a></li>
<li><a class="reference internal" href="#dfusion" id="id114">dfusion</a></li> <li><a class="reference internal" href="#dfusion" id="id116">dfusion</a></li>
<li><a class="reference internal" href="#misery" id="id115">misery</a></li> <li><a class="reference internal" href="#misery" id="id117">misery</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#scripts" id="id116">Scripts</a><ul> <li><a class="reference internal" href="#scripts" id="id118">Scripts</a><ul>
<li><a class="reference internal" href="#fix" id="id117">fix/*</a></li> <li><a class="reference internal" href="#fix" id="id119">fix/*</a></li>
<li><a class="reference internal" href="#gui" id="id118">gui/*</a></li> <li><a class="reference internal" href="#gui" id="id120">gui/*</a></li>
<li><a class="reference internal" href="#binpatch" id="id119">binpatch</a></li> <li><a class="reference internal" href="#binpatch" id="id121">binpatch</a></li>
<li><a class="reference internal" href="#quicksave" id="id120">quicksave</a></li> <li><a class="reference internal" href="#quicksave" id="id122">quicksave</a></li>
<li><a class="reference internal" href="#setfps" id="id121">setfps</a></li> <li><a class="reference internal" href="#setfps" id="id123">setfps</a></li>
<li><a class="reference internal" href="#siren" id="id122">siren</a></li> <li><a class="reference internal" href="#siren" id="id124">siren</a></li>
<li><a class="reference internal" href="#growcrops" id="id123">growcrops</a></li> <li><a class="reference internal" href="#growcrops" id="id125">growcrops</a></li>
<li><a class="reference internal" href="#removebadthoughts" id="id124">removebadthoughts</a></li> <li><a class="reference internal" href="#removebadthoughts" id="id126">removebadthoughts</a></li>
<li><a class="reference internal" href="#slayrace" id="id125">slayrace</a></li> <li><a class="reference internal" href="#slayrace" id="id127">slayrace</a></li>
<li><a class="reference internal" href="#magmasource" id="id126">magmasource</a></li> <li><a class="reference internal" href="#magmasource" id="id128">magmasource</a></li>
<li><a class="reference internal" href="#digfort" id="id127">digfort</a></li> <li><a class="reference internal" href="#digfort" id="id129">digfort</a></li>
<li><a class="reference internal" href="#superdwarf" id="id128">superdwarf</a></li> <li><a class="reference internal" href="#superdwarf" id="id130">superdwarf</a></li>
<li><a class="reference internal" href="#drainaquifer" id="id129">drainaquifer</a></li> <li><a class="reference internal" href="#drainaquifer" id="id131">drainaquifer</a></li>
<li><a class="reference internal" href="#deathcause" id="id130">deathcause</a></li> <li><a class="reference internal" href="#deathcause" id="id132">deathcause</a></li>
<li><a class="reference internal" href="#lua" id="id131">lua</a></li> <li><a class="reference internal" href="#lua" id="id133">lua</a></li>
<li><a class="reference internal" href="#embark" id="id132">embark</a></li> <li><a class="reference internal" href="#embark" id="id134">embark</a></li>
<li><a class="reference internal" href="#lever" id="id133">lever</a></li> <li><a class="reference internal" href="#lever" id="id135">lever</a></li>
<li><a class="reference internal" href="#stripcaged" id="id134">stripcaged</a></li> <li><a class="reference internal" href="#stripcaged" id="id136">stripcaged</a></li>
<li><a class="reference internal" href="#create-items" id="id135">create-items</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="id136">soundsense-season</a></li> <li><a class="reference internal" href="#soundsense-season" id="id138">soundsense-season</a></li>
</ul> </ul>
</li> </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="#in-game-interface-tools" id="id139">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="#dwarf-manipulator" id="id140">Dwarf Manipulator</a></li>
<li><a class="reference internal" href="#search" id="id139">Search</a></li> <li><a class="reference internal" href="#search" id="id141">Search</a></li>
<li><a class="reference internal" href="#automaterial" id="id140">AutoMaterial</a></li> <li><a class="reference internal" href="#automaterial" id="id142">AutoMaterial</a></li>
<li><a class="reference internal" href="#gui-liquids" id="id141">gui/liquids</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="id142">gui/mechanisms</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="id143">gui/rename</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="id144">gui/room-list</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="id145">gui/choose-weapons</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="id146">gui/guide-path</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="id147">gui/workshop-job</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="id148">gui/workflow</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="id149">gui/assign-rack</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="id150">gui/advfort</a></li> <li><a class="reference internal" href="#gui-advfort" id="id152">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="#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> </ul>
</li> </li>
<li><a class="reference internal" href="#behavior-mods" id="id152">Behavior Mods</a><ul> <li><a class="reference internal" href="#behavior-mods" id="id155">Behavior Mods</a><ul>
<li><a class="reference internal" href="#siege-engine" id="id153">Siege Engine</a><ul> <li><a class="reference internal" href="#siege-engine" id="id156">Siege Engine</a><ul>
<li><a class="reference internal" href="#rationale" id="id154">Rationale</a></li> <li><a class="reference internal" href="#rationale" id="id157">Rationale</a></li>
<li><a class="reference internal" href="#configuration-ui" id="id155">Configuration UI</a></li> <li><a class="reference internal" href="#configuration-ui" id="id158">Configuration UI</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#power-meter" id="id156">Power Meter</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="id157">Steam Engine</a><ul> <li><a class="reference internal" href="#steam-engine" id="id160">Steam Engine</a><ul>
<li><a class="reference internal" href="#id1" id="id158">Rationale</a></li> <li><a class="reference internal" href="#id1" id="id161">Rationale</a></li>
<li><a class="reference internal" href="#construction" id="id159">Construction</a></li> <li><a class="reference internal" href="#construction" id="id162">Construction</a></li>
<li><a class="reference internal" href="#operation" id="id160">Operation</a></li> <li><a class="reference internal" href="#operation" id="id163">Operation</a></li>
<li><a class="reference internal" href="#explosions" id="id161">Explosions</a></li> <li><a class="reference internal" href="#explosions" id="id164">Explosions</a></li>
<li><a class="reference internal" href="#save-files" id="id162">Save files</a></li> <li><a class="reference internal" href="#save-files" id="id165">Save files</a></li>
</ul> </ul>
</li> </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> </ul>
</li> </li>
</ul> </ul>
@ -554,9 +557,9 @@ access DF memory and allow for easier development of new tools.</p>
</div> </div>
<div class="section" id="getting-dfhack"> <div class="section" id="getting-dfhack">
<h1><a class="toc-backref" href="#id3">Getting DFHack</a></h1> <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 <p>The project is currently hosted on <a class="reference external" href="http://www.github.com/">github</a>
binaries at <a class="reference external" href="http://github.com/peterix/dfhack">http://github.com/peterix/dfhack</a></p> 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>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> <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>
<div class="section" id="compatibility"> <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> <blockquote>
'alltraffic N' - Set traffic to 'normal' for all tiles.</blockquote> 'alltraffic N' - Set traffic to 'normal' for all tiles.</blockquote>
</div> </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"> <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 <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 of trees to cut down and/or shrubs to gather by their plant names, separated
by spaces.</p> by spaces.</p>
@ -1696,9 +1707,9 @@ all valid plant IDs will be listed.</p>
</div> </div>
</div> </div>
<div class="section" id="cleanup-and-garbage-disposal"> <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"> <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 <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 creatures. In an old fortress, this can significantly reduce FPS lag. It can
also spoil your !!FUN!!, so think before you use it.</p> 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> </blockquote>
</div> </div>
<div class="section" id="spotclean"> <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 <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> if you want to keep that bloody entrance 'clean map' would clean up.</p>
</div> </div>
<div class="section" id="autodump"> <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. <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, 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. 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> </blockquote>
</div> </div>
<div class="section" id="autodump-destroy-here"> <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 <p>Destroy items marked for dumping under cursor. Identical to autodump
destroy-here, but intended for use as keybinding.</p> destroy-here, but intended for use as keybinding.</p>
</div> </div>
<div class="section" id="autodump-destroy-item"> <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 <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> a container. If called again before the game is resumed, cancels destroy.</p>
</div> </div>
<div class="section" id="cleanowned"> <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 <p>Confiscates items owned by dwarfs. By default, owned food on the floor
and rotten items are confistacted and dumped.</p> and rotten items are confistacted and dumped.</p>
<p>Options:</p> <p>Options:</p>
@ -1808,13 +1819,13 @@ worn items with 'X' damage and above.</dd>
</div> </div>
</div> </div>
<div class="section" id="bugfixes"> <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"> <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> <p>This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye.</p>
</div> </div>
<div class="section" id="fixdiplomats"> <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 <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 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 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> your raws accordingly.</p>
</div> </div>
<div class="section" id="fixmerchants"> <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, <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 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> and earlier) in case you haven't already modified your raws accordingly.</p>
</div> </div>
<div class="section" id="fixveins"> <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. <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 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> accidentally placed a construction on top of a valuable mineral floor.</p>
</div> </div>
<div class="section" id="tweak"> <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>Contains various tweaks for minor bugs.</p>
<p>One-shot subcommands:</p> <p>One-shot subcommands:</p>
<table class="docutils field-list" frame="void" rules="none"> <table class="docutils field-list" frame="void" rules="none">
@ -1941,7 +1952,7 @@ the units spar more.</p>
</table> </table>
</div> </div>
<div class="section" id="fix-armory"> <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>Enables a fix for storage of squad equipment in barracks.</p>
<p>Specifically, it prevents your haulers from moving squad equipment <p>Specifically, it prevents your haulers from moving squad equipment
to stockpiles, and instead queues jobs to store it on weapon racks, 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> </div>
<div class="section" id="mode-switch-and-reclaim"> <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"> <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 <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> 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 <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> </blockquote>
</div> </div>
<div class="section" id="mode"> <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. <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 Not all combinations are good for every situation and most of them will
produce undesirable results. There are a few good ones though.</p> 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> </div>
<div class="section" id="visualizer-and-data-export"> <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"> <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 <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 graphics acceleration and at least a dual core CPU (otherwise it will slow
down DF).</p> 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> <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>
<div class="section" id="mapexport"> <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 <p>Export the current loaded map as a file. This will be eventually usable
with visualizers.</p> with visualizers.</p>
</div> </div>
<div class="section" id="dwarfexport"> <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> <p>Export dwarves to RuneSmith-compatible XML.</p>
</div> </div>
</div> </div>
<div class="section" id="job-management"> <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"> <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> <p>Command for general job query and manipulation.</p>
<dl class="docutils"> <dl class="docutils">
<dt>Options:</dt> <dt>Options:</dt>
@ -2083,7 +2094,7 @@ in a workshop, or the unit/jobs screen.</dd>
</dl> </dl>
</div> </div>
<div class="section" id="job-material"> <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>Alter the material of the selected job.</p>
<p>Invoked as:</p> <p>Invoked as:</p>
<pre class="literal-block"> <pre class="literal-block">
@ -2101,7 +2112,7 @@ over the first available choice with the matching material.</li>
</blockquote> </blockquote>
</div> </div>
<div class="section" id="job-duplicate"> <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"> <dl class="docutils">
<dt>Duplicate the selected job in a workshop:</dt> <dt>Duplicate the selected job in a workshop:</dt>
<dd><ul class="first last simple"> <dd><ul class="first last simple">
@ -2112,7 +2123,7 @@ instantly duplicates the job.</li>
</dl> </dl>
</div> </div>
<div class="section" id="workflow"> <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>Manage control of repeat jobs.</p>
<p>Usage:</p> <p>Usage:</p>
<blockquote> <blockquote>
@ -2144,7 +2155,7 @@ this list can be copied to a file, and then reloaded using the
</dl> </dl>
</blockquote> </blockquote>
<div class="section" id="function"> <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. <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 If they do disappear due to any cause, they are immediately re-added to their
workshop and suspended.</p> workshop and suspended.</p>
@ -2157,7 +2168,7 @@ the frequency of jobs being toggled.</p>
in the game UI.</p> in the game UI.</p>
</div> </div>
<div class="section" id="constraint-format"> <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> <p>The contstraint spec consists of 4 parts, separated with '/' characters:</p>
<pre class="literal-block"> <pre class="literal-block">
ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,&lt;quality&gt;] 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> </ul>
</div> </div>
<div class="section" id="constraint-examples"> <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> <p>Keep metal bolts within 900-1000, and wood/bone within 150-200.</p>
<pre class="literal-block"> <pre class="literal-block">
workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100 workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
@ -2235,15 +2246,15 @@ workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
</div> </div>
</div> </div>
<div class="section" id="fortress-activity-management"> <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"> <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 <p>Tool for turning cooking of seeds and plants on/off depending on how much you
have of them.</p> have of them.</p>
<p>See 'seedwatch help' for detailed description.</p> <p>See 'seedwatch help' for detailed description.</p>
</div> </div>
<div class="section" id="zone"> <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>Helps a bit with managing activity zones (pens, pastures and pits) and cages.</p>
<p>Options:</p> <p>Options:</p>
<blockquote> <blockquote>
@ -2342,7 +2353,7 @@ for war/hunt). Negatable.</td>
</table> </table>
</blockquote> </blockquote>
<div class="section" id="usage-with-single-units"> <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 <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 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 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> own dwarves, by the way.</p>
</div> </div>
<div class="section" id="usage-with-filters"> <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>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 <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. 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> <p>Most filters can be negated (e.g. 'not grazer' -&gt; race is not a grazer).</p>
</div> </div>
<div class="section" id="mass-renaming"> <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. <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 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 current default target zone. Combined with 'assign', 'all' or 'count' (and
further optional filters) it will rename units matching the filter conditions.</p> further optional filters) it will rename units matching the filter conditions.</p>
</div> </div>
<div class="section" id="cage-zones"> <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 <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 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 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> the usual filters.</p>
</div> </div>
<div class="section" id="examples"> <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"> <dl class="docutils">
<dt><tt class="docutils literal">zone assign all own ALPACA minage 3 maxage 10</tt></dt> <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 <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> </div>
<div class="section" id="autonestbox"> <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 <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 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 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> </blockquote>
</div> </div>
<div class="section" id="autobutcher"> <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 <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> 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 <p>Named units will be completely ignored (to protect specific animals from
@ -2550,28 +2561,29 @@ autobutcher.bat
</pre> </pre>
</div> </div>
<div class="section" id="autolabor"> <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>Automatically manage dwarf labors.</p>
<p>When enabled, autolabor periodically checks your dwarves and enables or <p>When enabled, autolabor periodically checks your dwarves and enables or
disables labors. It tries to keep as many dwarves as possible busy but disables labors. It tries to keep as many dwarves as possible busy but
also tries to have dwarves specialize in specific skills.</p> also tries to have dwarves specialize in specific skills.</p>
<div class="note"> <div class="note">
<p class="first admonition-title">Note</p> <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> while it is enabled.</p>
<p class="last">To prevent particular dwarves from being managed by autolabor, put them in any burrow.</p>
</div> </div>
<p>For detailed usage information, see 'help autolabor'.</p> <p>For detailed usage information, see 'help autolabor'.</p>
</div> </div>
</div> </div>
<div class="section" id="other"> <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"> <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 <p>Makes cats just <em>multiply</em>. It is not a good idea to run this more than once or
twice.</p> twice.</p>
</div> </div>
<div class="section" id="dfusion"> <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"> <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> <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"> <dd><table class="first last docutils field-list" frame="void" rules="none">
@ -2596,7 +2608,7 @@ twice.</p>
</div> </div>
</div> </div>
<div class="section" id="misery"> <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>When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).</p>
<p>Usage:</p> <p>Usage:</p>
<table class="docutils field-list" frame="void" rules="none"> <table class="docutils field-list" frame="void" rules="none">
@ -2620,7 +2632,7 @@ twice.</p>
</div> </div>
</div> </div>
<div class="section" id="scripts"> <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 <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 execution as if they were native DFHack commands. They are listed at the end
of the 'ls' command output.</p> 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> scripts that are obscure, developer-oriented, or should be used as keybindings.</p>
<p>Some notable scripts:</p> <p>Some notable scripts:</p>
<div class="section" id="fix"> <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> <p>Scripts in this subdirectory fix various bugs and issues, some of them obscure.</p>
<ul> <ul>
<li><p class="first">fix/dead-units</p> <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> </ul>
</div> </div>
<div class="section" id="gui"> <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 <p>Scripts that implement dialogs inserted into the main game window are put in this
directory.</p> directory.</p>
</div> </div>
<div class="section" id="binpatch"> <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> <p>Checks, applies or removes binary patches directly in memory at runtime:</p>
<pre class="literal-block"> <pre class="literal-block">
binpatch check/apply/remove &lt;patchname&gt; 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> the version appropriate for the currently loaded executable.</p>
</div> </div>
<div class="section" id="quicksave"> <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 <p>If called in dwarf mode, makes DF immediately auto-save the game by setting a flag
normally used in seasonal auto-save.</p> normally used in seasonal auto-save.</p>
</div> </div>
<div class="section" id="setfps"> <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 <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> combat in slow motion or something :)</p>
</div> </div>
<div class="section" id="siren"> <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, <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 or in the burrows given as arguments. In return, adds bad thoughts about
noise, tiredness and lack of protection. Also, the units with interrupted 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> emergencies, e.g. when a siege appears, and all your military is partying.</p>
</div> </div>
<div class="section" id="growcrops"> <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>Instantly grow seeds inside farming plots.</p>
<p>With no argument, this command list the various seed types currently in <p>With no argument, this command list the various seed types currently in
use in your farming plots. use in your farming plots.
@ -2706,7 +2718,7 @@ growcrops plump 40
</pre> </pre>
</div> </div>
<div class="section" id="removebadthoughts"> <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 <p>This script remove negative thoughts from your dwarves. Very useful against
tantrum spirals.</p> tantrum spirals.</p>
<p>The script can target a single creature, when used with the <tt class="docutils literal">him</tt> argument, <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> quickly after you unpause.</p>
</div> </div>
<div class="section" id="slayrace"> <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>Kills any unit of a given race.</p>
<p>With no argument, lists the available races and count eligible targets.</p> <p>With no argument, lists the available races and count eligible targets.</p>
<p>With the special argument <tt class="docutils literal">him</tt>, targets only the selected creature.</p> <p>With the special argument <tt class="docutils literal">him</tt>, targets only the selected creature.</p>
@ -2748,7 +2760,7 @@ slayrace elve magma
</pre> </pre>
</div> </div>
<div class="section" id="magmasource"> <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>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 <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> 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> <p>With no argument, this command shows an help message and list existing sources.</p>
</div> </div>
<div class="section" id="digfort"> <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>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. <p>This script, inspired from quickfort, can designate an area for digging.
Your plan should be stored in a .csv file like this:</p> 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> <p>The script takes the plan filename, starting from the root df folder.</p>
</div> </div>
<div class="section" id="superdwarf"> <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>Similar to fastdwarf, per-creature.</p>
<p>To make any creature superfast, target it ingame using 'v' and:</p> <p>To make any creature superfast, target it ingame using 'v' and:</p>
<pre class="literal-block"> <pre class="literal-block">
@ -2791,17 +2803,17 @@ superdwarf add
<p>This plugin also shortens the 'sleeping' and 'on break' periods of targets.</p> <p>This plugin also shortens the 'sleeping' and 'on break' periods of targets.</p>
</div> </div>
<div class="section" id="drainaquifer"> <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> <p>Remove all 'aquifer' tag from the map blocks. Irreversible.</p>
</div> </div>
<div class="section" id="deathcause"> <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 <p>Focus a body part ingame, and this script will display the cause of death of
the creature. the creature.
Also works when selecting units from the 'u'nitlist viewscreen.</p> Also works when selecting units from the 'u'nitlist viewscreen.</p>
</div> </div>
<div class="section" id="lua"> <div class="section" id="lua">
<h2><a class="toc-backref" href="#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> <p>There are the following ways to invoke this command:</p>
<ol class="arabic"> <ol class="arabic">
<li><p class="first"><tt class="docutils literal">lua</tt> (without any parameters)</p> <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> </ol>
</div> </div>
<div class="section" id="embark"> <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> <p>Allows to embark anywhere. Currently windows only.</p>
</div> </div>
<div class="section" id="lever"> <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>Allow manipulation of in-game levers from the dfhack console.</p>
<p>Can list levers, including state and links, with:</p> <p>Can list levers, including state and links, with:</p>
<pre class="literal-block"> <pre class="literal-block">
@ -2838,7 +2850,7 @@ lever pull 42 --now
</pre> </pre>
</div> </div>
<div class="section" id="stripcaged"> <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 <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> 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 <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> </pre>
</div> </div>
<div class="section" id="create-items"> <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>Spawn arbitrary items under the cursor.</p>
<p>The first argument gives the item category, the second gives the material, <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> and the optionnal third gives the number of items to create (defaults to 20).</p>
@ -2878,7 +2890,7 @@ create-items bar adamantine
</pre> </pre>
</div> </div>
<div class="section" id="soundsense-season"> <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 <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 current season when a savegame is loaded and has to play random
season music until a season switch occurs.</p> 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> </div>
<div class="section" id="in-game-interface-tools"> <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 <p>These tools work by displaying dialogs or overlays in the game window, and
are mostly implemented by lua scripts.</p> are mostly implemented by lua scripts.</p>
<div class="note"> <div class="note">
@ -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> guideline because it arguably just fixes small usability bugs in the game UI.</p>
</div> </div>
<div class="section" id="dwarf-manipulator"> <div class="section" id="dwarf-manipulator">
<h2><a class="toc-backref" href="#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 <p>Implemented by the manipulator plugin. To activate, open the unit screen and
press 'l'.</p> press 'l'.</p>
<img alt="images/manipulator.png" src="images/manipulator.png" /> <img alt="images/manipulator.png" src="images/manipulator.png" />
@ -2940,7 +2952,7 @@ cursor onto that cell instead of toggling it.</li>
directly to the main dwarf mode screen.</p> directly to the main dwarf mode screen.</p>
</div> </div>
<div class="section" id="search"> <div class="section" id="search">
<h2><a class="toc-backref" href="#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, <p>The search plugin adds search to the Stocks, Animals, Trading, Stockpile,
Noble (assignment candidates), Military (position candidates), Burrows Noble (assignment candidates), Military (position candidates), Burrows
(unit list), Rooms, Announcements, Job List and Unit List screens.</p> (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> using Permit Fats again while the list is filtered.</p>
</div> </div>
<div class="section" id="automaterial"> <div class="section" id="automaterial">
<h2><a class="toc-backref" href="#id140">AutoMaterial</a></h2> <h2><a class="toc-backref" href="#id142">AutoMaterial</a></h2>
<p>The automaterial plugin makes building constructions (walls, floors, fortifications, <p>The automaterial plugin makes building constructions (walls, floors, fortifications,
etc) a little bit easier by saving you from having to trawl through long lists of etc) a little bit easier by saving you from having to trawl through long lists of
materials each time you place one.</p> materials each time you place one.</p>
@ -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> enabled materials, you should be able to place complex constructions more conveniently.</p>
</div> </div>
<div class="section" id="gui-liquids"> <div class="section" id="gui-liquids">
<h2><a class="toc-backref" href="#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> <p>To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.</p>
<img alt="images/liquids.png" src="images/liquids.png" /> <img alt="images/liquids.png" src="images/liquids.png" />
<p>This script is a gui front-end to the liquids plugin and works similar to it, <p>This script is a gui front-end to the liquids plugin and works similar to it,
@ -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> <p>After setting up the desired operations using the described keys, use <tt class="docutils literal">Enter</tt> to apply them.</p>
</div> </div>
<div class="section" id="gui-mechanisms"> <div class="section" id="gui-mechanisms">
<h2><a class="toc-backref" href="#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> <p>To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.</p>
<img alt="images/mechanisms.png" src="images/mechanisms.png" /> <img alt="images/mechanisms.png" src="images/mechanisms.png" />
<p>Lists mechanisms connected to the building, and their links. Navigating the list centers <p>Lists mechanisms connected to the building, and their links. Navigating the list centers
@ -3027,7 +3039,7 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter
re-entering the mechanisms ui.</p> re-entering the mechanisms ui.</p>
</div> </div>
<div class="section" id="gui-rename"> <div class="section" id="gui-rename">
<h2><a class="toc-backref" href="#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 <p>Backed by the rename plugin, this script allows entering the desired name
via a simple dialog in the game ui.</p> via a simple dialog in the game ui.</p>
<ul> <ul>
@ -3050,7 +3062,7 @@ their species string.</p>
unit profession change to Ctrl-Shift-T.</p> unit profession change to Ctrl-Shift-T.</p>
</div> </div>
<div class="section" id="gui-room-list"> <div class="section" id="gui-room-list">
<h2><a class="toc-backref" href="#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, <p>To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode,
either immediately or after opening the assign owner page.</p> either immediately or after opening the assign owner page.</p>
<img alt="images/room-list.png" src="images/room-list.png" /> <img alt="images/room-list.png" src="images/room-list.png" />
@ -3058,7 +3070,7 @@ either immediately or after opening the assign owner page.</p>
list, and allows unassigning them.</p> list, and allows unassigning them.</p>
</div> </div>
<div class="section" id="gui-choose-weapons"> <div class="section" id="gui-choose-weapons">
<h2><a class="toc-backref" href="#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 <p>Bind to a key (the example config uses Ctrl-W), and activate in the Equip-&gt;View/Customize
page of the military screen.</p> page of the military screen.</p>
<p>Depending on the cursor location, it rewrites all 'individual choice weapon' entries <p>Depending on the cursor location, it rewrites all 'individual choice weapon' entries
@ -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> and may lead to inappropriate weapons being selected.</p>
</div> </div>
<div class="section" id="gui-guide-path"> <div class="section" id="gui-guide-path">
<h2><a class="toc-backref" href="#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 <p>Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with
the cursor over a Guide order.</p> the cursor over a Guide order.</p>
<img alt="images/guide-path.png" src="images/guide-path.png" /> <img alt="images/guide-path.png" src="images/guide-path.png" />
@ -3077,7 +3089,7 @@ the cursor over a Guide order.</p>
computes it when the order is executed for the first time.</p> computes it when the order is executed for the first time.</p>
</div> </div>
<div class="section" id="gui-workshop-job"> <div class="section" id="gui-workshop-job">
<h2><a class="toc-backref" href="#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 <p>Bind to a key (the example config uses Alt-A), and activate with a job selected in
a workshop in the 'q' mode.</p> a workshop in the 'q' mode.</p>
<img alt="images/workshop-job.png" src="images/workshop-job.png" /> <img alt="images/workshop-job.png" src="images/workshop-job.png" />
@ -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> you have to unset the material first.</p>
</div> </div>
<div class="section" id="gui-workflow"> <div class="section" id="gui-workflow">
<h2><a class="toc-backref" href="#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 <p>Bind to a key (the example config uses Alt-W), and activate with a job selected
in a workshop in the 'q' mode.</p> in a workshop in the 'q' mode.</p>
<img alt="images/workflow.png" src="images/workflow.png" /> <img alt="images/workflow.png" src="images/workflow.png" />
@ -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> limit (maximum) and the dark green line is that minus the gap (minimum).</p>
</div> </div>
<div class="section" id="gui-assign-rack"> <div class="section" id="gui-assign-rack">
<h2><a class="toc-backref" href="#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 <p>Bind to a key (the example config uses P), and activate when viewing a weapon
rack in the 'q' mode.</p> rack in the 'q' mode.</p>
<img alt="images/assign-rack.png" src="images/assign-rack.png" /> <img alt="images/assign-rack.png" src="images/assign-rack.png" />
@ -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> of currently assigned racks for every valid squad.</p>
</div> </div>
<div class="section" id="gui-advfort"> <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 <p>This script allows to perform jobs in adventure mode. For more complete help
press '?' while script is running. It's most confortable to use this as a press '?' while script is running. It's most confortable to use this as a
keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:</p> keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:</p>
@ -3196,15 +3208,30 @@ implies -a</li>
<li>job - selects that job (e.g. Dig or FellTree)</li> <li>job - selects that job (e.g. Dig or FellTree)</li>
</ul> </ul>
</div> </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"> <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> <p>There are three ways to open this editor:</p>
<ul class="simple"> <ul class="simple">
<li>using gui/gm-editor command/keybinding - opens editor on what is selected <li>using gui/gm-editor command/keybinding - opens editor on what is selected
or viewed (e.g. unit/item description screen)</li> 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 <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> 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> the same as version above.</li>
</ul> </ul>
<p>This editor allows to change and modify almost anything in df. Press '?' for an <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> </div>
<div class="section" id="behavior-mods"> <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 <p>These plugins, when activated via configuration UI or by detecting certain
structures in RAWs, modify the game engine behavior concerning the target structures in RAWs, modify the game engine behavior concerning the target
objects to add features not otherwise present.</p> objects to add features not otherwise present.</p>
@ -3223,20 +3250,20 @@ technical challenge, and do not represent any long-term plans to produce more
similar modifications of the game.</p> similar modifications of the game.</p>
</div> </div>
<div class="section" id="siege-engine"> <div class="section" id="siege-engine">
<h2><a class="toc-backref" href="#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 <p>The siege-engine plugin enables siege engines to be linked to stockpiles, and
aimed at an arbitrary rectangular area across Z levels, instead of the original aimed at an arbitrary rectangular area across Z levels, instead of the original
four directions. Also, catapults can be ordered to load arbitrary objects, not four directions. Also, catapults can be ordered to load arbitrary objects, not
just stones.</p> just stones.</p>
<div class="section" id="rationale"> <div class="section" id="rationale">
<h3><a class="toc-backref" href="#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 <p>Siege engines are a very interesting feature, but sadly almost useless in the current state
because they haven't been updated since 2D and can only aim in four directions. This is an because they haven't been updated since 2D and can only aim in four directions. This is an
attempt to bring them more up to date until Toady has time to work on it. Actual improvements, attempt to bring them more up to date until Toady has time to work on it. Actual improvements,
e.g. like making siegers bring their own, are something only Toady can do.</p> e.g. like making siegers bring their own, are something only Toady can do.</p>
</div> </div>
<div class="section" id="configuration-ui"> <div class="section" id="configuration-ui">
<h3><a class="toc-backref" href="#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 <p>The configuration front-end to the plugin is implemented by the gui/siege-engine
script. Bind it to a key (the example config uses Alt-A) and activate after selecting script. Bind it to a key (the example config uses Alt-A) and activate after selecting
a siege engine in 'q' mode.</p> a siege engine in 'q' mode.</p>
@ -3259,7 +3286,7 @@ menu.</p>
</div> </div>
</div> </div>
<div class="section" id="power-meter"> <div class="section" id="power-meter">
<h2><a class="toc-backref" href="#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 <p>The power-meter plugin implements a modified pressure plate that detects power being
supplied to gear boxes built in the four adjacent N/S/W/E tiles.</p> supplied to gear boxes built in the four adjacent N/S/W/E tiles.</p>
<p>The configuration front-end is implemented by the gui/power-meter script. Bind it to a <p>The configuration front-end is implemented by the gui/power-meter script. Bind it to a
@ -3270,11 +3297,11 @@ in the build menu.</p>
configuration page, but configures parameters relevant to the modded power meter building.</p> configuration page, but configures parameters relevant to the modded power meter building.</p>
</div> </div>
<div class="section" id="steam-engine"> <div class="section" id="steam-engine">
<h2><a class="toc-backref" href="#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 <p>The steam-engine plugin detects custom workshops with STEAM_ENGINE in
their token, and turns them into real steam engines.</p> their token, and turns them into real steam engines.</p>
<div class="section" id="id1"> <div class="section" id="id1">
<h3><a class="toc-backref" href="#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 <p>The vanilla game contains only water wheels and windmills as sources of
power, but windmills give relatively little power, and water wheels require power, but windmills give relatively little power, and water wheels require
flowing water, which must either be a real river and thus immovable and flowing water, which must either be a real river and thus immovable and
@ -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> in a new way with some glue code and a bit of custom logic.</p>
</div> </div>
<div class="section" id="construction"> <div class="section" id="construction">
<h3><a class="toc-backref" href="#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 <p>The workshop needs water as its input, which it takes via a
passable floor tile below it, like usual magma workshops do. passable floor tile below it, like usual magma workshops do.
The magma version also needs magma.</p> The magma version also needs magma.</p>
@ -3309,7 +3336,7 @@ short axles that can be built later than both of the engines.</p>
</div> </div>
</div> </div>
<div class="section" id="operation"> <div class="section" id="operation">
<h3><a class="toc-backref" href="#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 <p>In order to operate the engine, queue the Stoke Boiler job (optionally
on repeat). A furnace operator will come, possibly bringing a bar of fuel, on repeat). A furnace operator will come, possibly bringing a bar of fuel,
and perform it. As a result, a &quot;boiling water&quot; item will appear and perform it. As a result, a &quot;boiling water&quot; item will appear
@ -3340,7 +3367,7 @@ decrease it by further 4%, and also decrease the whole steam
use rate by 10%.</p> use rate by 10%.</p>
</div> </div>
<div class="section" id="explosions"> <div class="section" id="explosions">
<h3><a class="toc-backref" href="#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 <p>The engine must be constructed using barrel, pipe and piston
from fire-safe, or in the magma version magma-safe metals.</p> from fire-safe, or in the magma version magma-safe metals.</p>
<p>During operation weak parts get gradually worn out, and <p>During operation weak parts get gradually worn out, and
@ -3349,7 +3376,7 @@ toppled during operation by a building destroyer, or a
tantruming dwarf.</p> tantruming dwarf.</p>
</div> </div>
<div class="section" id="save-files"> <div class="section" id="save-files">
<h3><a class="toc-backref" href="#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 <p>It should be safe to load and view engine-using fortresses
from a DF version without DFHack installed, except that in such from a DF version without DFHack installed, except that in such
case the engines won't work. However actually making modifications case the engines won't work. However actually making modifications
@ -3360,7 +3387,7 @@ being generated.</p>
</div> </div>
</div> </div>
<div class="section" id="add-spatter"> <div class="section" id="add-spatter">
<h2><a class="toc-backref" href="#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> <p>This plugin makes reactions with names starting with <tt class="docutils literal">SPATTER_ADD_</tt>
produce contaminants on the items instead of improvements. The produced produce contaminants on the items instead of improvements. The produced
contaminants are immune to being washed away by water or destroyed by contaminants are immune to being washed away by water or destroyed by

@ -16,12 +16,12 @@ access DF memory and allow for easier development of new tools.
============== ==============
Getting DFHack Getting DFHack
============== ==============
The project is currently hosted on github_, for both source and The project is currently hosted on github_
binaries at http://github.com/peterix/dfhack at http://github.com/peterix/dfhack
.. _github: http://www.github.com/ .. _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 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. :show X: Marks the selected map feature as discovered.
:hide X: Marks the selected map feature as undiscovered. :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 liquids
------- -------
Allows adding magma, water and obsidian to the game. It replaces the normal 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. 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 Designations
============ ============
@ -947,6 +1063,14 @@ Example:
'alltraffic N' - Set traffic to 'normal' for all tiles. '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 getplants
--------- ---------
This tool allows plant gathering and tree cutting by RAW ID. Specify the types 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 workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
Fortress activity management 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 Warning: autolabor will override any manual changes you make to labors
while it is enabled. 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'. 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 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. 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 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. quickly after you unpause.
slayrace exterminate
======== ===========
Kills any unit of a given race. Kills any unit of a given race.
With no argument, lists the available races and count eligible targets. 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, 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 ``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, 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:: Ex::
slayrace gob exterminate gob
To kill a single creature, select the unit with the 'v' cursor and:: 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):: To purify all elves on the map with fire (may have side-effects)::
slayrace elve magma exterminate elve magma
magmasource source
=========== ======
Create an infinite magma source on a tile. 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 This script registers a map tile as a liquid source, and every 12 game ticks
that tile receives 1 new unit of flowing magma. 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 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:: 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``. source add water - water source
To remove all placed sources, call ``magmasource stop``. 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 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. boulders of toad blood and stuff like that.
However the ``list`` mode will only show 'normal' materials. However the ``list`` mode will only show 'normal' materials.
Exemples:: Examples::
create-items boulders COAL_BITUMINOUS 12 create-items boulders COAL_BITUMINOUS 12
create-items plant tail_pig create-items plant tail_pig
@ -2058,6 +2199,20 @@ Exemples::
create-items bar CREATURE:CAT:SOAP create-items bar CREATURE:CAT:SOAP
create-items bar adamantine 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 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 to gamelog.txt on every map load to fix this. For best results
call the script from ``dfhack.init``. 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 In-game interface tools
======================= =======================

@ -42,6 +42,13 @@ keybinding add Ctrl-Shift-B "adv-bodyswap force"
# Context-specific bindings # # 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 # q->stockpile; p - copy & paste stockpiles
keybinding add Alt-P copystock 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@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
keybinding add Alt-W@overallstatus "gui/workflow status" 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 # assign weapon racks to squads so that they can be used
keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack 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 # remove inverse dependency of squad training speed on unit list size and use more sparring
tweak military-training tweak military-training
# enable autoSyndrome
autoSyndrome enable
########### ###########
# Scripts # # Scripts #
########### ###########
@ -149,23 +162,21 @@ fix/cloth-stockpile enable
####################################################### #######################################################
# Apply binary patches at runtime # # Apply binary patches at runtime #
# #
# Commented out by default; enable the ones you want. #
####################################################### #######################################################
# Bug 5994 - items teleported when removing a construction # Bug 5994 - items teleported when removing a construction
#binpatch apply deconstruct-teleport binpatch apply deconstruct-teleport
#binpatch apply deconstruct-heapfall binpatch apply deconstruct-heapfall
# Bug 4406 - hospital overstocking on all items # 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 # 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 # 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 # Bug 1445 - weapon racks broken, armor stand capacity too low
#binpatch apply weaponrack-unassign binpatch apply weaponrack-unassign
#binpatch apply armorstand-capacity binpatch apply armorstand-capacity

@ -125,6 +125,7 @@ include/modules/Translation.h
include/modules/Vermin.h include/modules/Vermin.h
include/modules/World.h include/modules/World.h
include/modules/Graphic.h include/modules/Graphic.h
include/modules/Once.h
) )
SET( MODULE_SOURCES SET( MODULE_SOURCES
@ -147,6 +148,7 @@ modules/Vermin.cpp
modules/World.cpp modules/World.cpp
modules/Graphic.cpp modules/Graphic.cpp
modules/Windows.cpp modules/Windows.cpp
modules/Once.cpp
) )
IF(WIN32) 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 // 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 the event is valid, intercept
if( event != 0 ) if( event != 0 )

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

@ -8,6 +8,9 @@
#include "df/building.h" #include "df/building.h"
#include "df/construction.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/global_objects.h"
#include "df/item.h" #include "df/item.h"
#include "df/job.h" #include "df/job.h"
@ -32,13 +35,13 @@ using namespace EventManager;
**/ **/
//map<uint32_t, vector<DFHack::EventManager::EventHandler> > tickQueue; //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 //TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever
multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX]; static multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
uint32_t eventLastTick[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) { void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) {
handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler)); handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler));
@ -56,6 +59,7 @@ void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plug
if ( absolute ) { if ( absolute ) {
tick = 0; tick = 0;
} }
handler.freq = 1; //to make manageEvents work more nicely
tickQueue.insert(pair<uint32_t, EventHandler>(tick+(uint32_t)when, handler)); tickQueue.insert(pair<uint32_t, EventHandler>(tick+(uint32_t)when, handler));
handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,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 manageSyndromeEvent(color_ostream& out);
static void manageInvasionEvent(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 //tick event
static uint32_t lastTick = 0; static uint32_t lastTick = 0;
@ -150,7 +167,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event
lastTick = 0; lastTick = 0;
lastJobId = -1; lastJobId = -1;
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second); Job::deleteJobStruct((*i).second, true);
} }
prevJobs.clear(); prevJobs.clear();
tickQueue.clear(); tickQueue.clear();
@ -186,59 +203,32 @@ void DFHack::EventManager::manageEvents(color_ostream& out) {
if ( !gameLoaded ) { if ( !gameLoaded ) {
return; return;
} }
CoreSuspender suspender;
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick(); + DFHack::World::ReadCurrentTick();
/*if ( tick - lastTick > 1 ) {
if ( tick <= lastTick ) out.print("EventManager missed tick: %d, %d, (%d)\n", lastTick, tick, tick - lastTick);
return; }*/
lastTick = tick;
int32_t eventFrequency[EventType::EVENT_MAX];
for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) { 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++ ) { for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) {
EventHandler bob = (*b).second; EventHandler bob = (*b).second;
if ( bob.freq < min ) if ( bob.freq < eventFrequency )
min = bob.freq; eventFrequency = bob.freq;
} }
eventFrequency[a] = min;
if ( tick - eventLastTick[a] < eventFrequency )
continue;
eventManager[a](out);
eventLastTick[a] = tick;
} }
manageTickEvent(out); lastTick = tick;
if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) {
manageJobInitiatedEvent(out);
eventLastTick[EventType::JOB_INITIATED] = tick;
}
if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) {
manageJobCompletedEvent(out);
eventLastTick[EventType::JOB_COMPLETED] = tick;
}
if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) {
manageUnitDeathEvent(out);
eventLastTick[EventType::UNIT_DEATH] = tick;
}
if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) {
manageItemCreationEvent(out);
eventLastTick[EventType::ITEM_CREATED] = tick;
}
if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) {
manageBuildingEvent(out);
eventLastTick[EventType::BUILDING] = tick;
}
if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) {
manageConstructionEvent(out);
eventLastTick[EventType::CONSTRUCTION] = tick;
}
if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) {
manageSyndromeEvent(out);
eventLastTick[EventType::SYNDROME] = tick;
}
if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) {
manageInvasionEvent(out);
eventLastTick[EventType::INVASION] = tick;
}
return;
} }
static void manageTickEvent(color_ostream& out) { static void manageTickEvent(color_ostream& out) {
@ -255,9 +245,6 @@ static void manageTickEvent(color_ostream& out) {
} }
static void manageJobInitiatedEvent(color_ostream& out) { static void manageJobInitiatedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_INITIATED].empty() )
return;
if ( lastJobId == -1 ) { if ( lastJobId == -1 ) {
lastJobId = *df::global::job_next_id - 1; lastJobId = *df::global::job_next_id - 1;
return; return;
@ -281,10 +268,23 @@ static void manageJobInitiatedEvent(color_ostream& out) {
lastJobId = *df::global::job_next_id - 1; lastJobId = *df::global::job_next_id - 1;
} }
static void manageJobCompletedEvent(color_ostream& out) { //helper function for manageJobCompletedEvent
if ( handlers[EventType::JOB_COMPLETED].empty() ) { static int32_t getWorkerID(df::job* job) {
return; 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()); multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
map<int32_t, df::job*> nowJobs; map<int32_t, df::job*> nowJobs;
@ -293,12 +293,97 @@ static void manageJobCompletedEvent(color_ostream& out) {
continue; continue;
nowJobs[link->item->id] = link->item; 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++ ) { 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; 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++ ) { for ( auto j = copy.begin(); j != copy.end(); j++ ) {
(*j).second.eventHandler(out, (void*)(*i).second); (*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 //erase old jobs, copy over possibly altered jobs
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second); Job::deleteJobStruct((*i).second, true);
} }
prevJobs.clear(); prevJobs.clear();
@ -320,18 +405,9 @@ static void manageJobCompletedEvent(color_ostream& out) {
df::job* newJob = Job::cloneJobStruct((*j).second, true); df::job* newJob = Job::cloneJobStruct((*j).second, true);
prevJobs[newJob->id] = newJob; 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) { 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()); 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++ ) { for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[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) { static void manageItemCreationEvent(color_ostream& out) {
if ( handlers[EventType::ITEM_CREATED].empty() ) {
return;
}
if ( nextItem >= *df::global::item_next_id ) { if ( nextItem >= *df::global::item_next_id ) {
return; return;
} }
@ -387,9 +459,6 @@ static void manageBuildingEvent(color_ostream& out) {
* TODO: could be faster * TODO: could be faster
* consider looking at jobs: building creation / destruction * 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()); multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings //first alert people about new buildings
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) { 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) { 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()); 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()); 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) { static void manageSyndromeEvent(color_ostream& out) {
if ( handlers[EventType::SYNDROME].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end()); 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++ ) { for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) {
df::unit* unit = *a; df::unit* unit = *a;
@ -486,9 +549,6 @@ static void manageSyndromeEvent(color_ostream& out) {
} }
static void manageInvasionEvent(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()); multimap<Plugin*,EventHandler> copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end());
if ( df::global::ui->invasions.next_id <= nextInvasion ) if ( df::global::ui->invasions.next_id <= nextInvasion )

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

@ -3,6 +3,6 @@
DF_DIR=$(dirname "$0") DF_DIR=$(dirname "$0")
cd "${DF_DIR}" 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 "$@" exec hack/dfhack-run "$@"

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
DF_DIR=$(dirname "$0") DF_DIR=$(dirname "$0")
cd "${DF_DIR}" 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_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch.
#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing. #export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
./libs/Dwarf_Fortress $* # Go, go, go! :) ./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) INCLUDE(Plugins.cmake)
# Dfusion plugin # Dfusion plugin
IF(UNIX) OPTION(BUILD_DFUSION "Build DFusion." ON)
OPTION(BUILD_DFUSION "Build DFusion." OFF)
ELSE()
OPTION(BUILD_DFUSION "Build DFusion." ON)
ENDIF()
if(BUILD_DFUSION) if(BUILD_DFUSION)
add_subdirectory (Dfusion) add_subdirectory (Dfusion)
endif() endif()
@ -15,6 +11,20 @@ if(BUILD_STONESENSE)
add_subdirectory (stonesense) add_subdirectory (stonesense)
endif() 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) OPTION(BUILD_DEV_PLUGINS "Build developer plugins." OFF)
if(BUILD_DEV_PLUGINS) if(BUILD_DEV_PLUGINS)
add_subdirectory (devel) add_subdirectory (devel)
@ -65,6 +75,7 @@ ADD_CUSTOM_COMMAND(
${PROJECT_PROTOS} ${PROJECT_PROTOS}
DEPENDS protoc-bin ${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 ) 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(tweak tweak.cpp)
DFHACK_PLUGIN(feature feature.cpp) DFHACK_PLUGIN(feature feature.cpp)
DFHACK_PLUGIN(lair lair.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(catsplosion catsplosion.cpp)
DFHACK_PLUGIN(regrass regrass.cpp) DFHACK_PLUGIN(regrass regrass.cpp)
DFHACK_PLUGIN(forceequip forceequip.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp)
@ -134,10 +145,19 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(workNow workNow.cpp) DFHACK_PLUGIN(workNow workNow.cpp)
#DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread)
DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp) DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp)
DFHACK_PLUGIN(trueTransformation trueTransformation.cpp) DFHACK_PLUGIN(syndromeTrigger syndromeTrigger.cpp)
DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp)
DFHACK_PLUGIN(digSmart digSmart.cpp) DFHACK_PLUGIN(digSmart digSmart.cpp)
DFHACK_PLUGIN(createitem createitem.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() endif()

@ -65,14 +65,22 @@ MACRO(DFHACK_PLUGIN)
) )
CAR(PLUGIN_NAME ${PLUGIN_DEFAULT_ARGS}) CAR(PLUGIN_NAME ${PLUGIN_DEFAULT_ARGS})
CDR(PLUGIN_SOURCES ${PLUGIN_DEFAULT_ARGS}) CDR(PLUGIN_SOURCES ${PLUGIN_DEFAULT_ARGS})
SET(PLUGIN_PROTOCPP)
FOREACH(pbuf ${PLUGIN_PROTOBUFS}) FOREACH(pbuf ${PLUGIN_PROTOBUFS})
SET(PLUGIN_SOURCES ${PLUGIN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc) 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() 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}) ADD_LIBRARY(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES})
IDE_FOLDER(${PLUGIN_NAME} "Plugins") 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) LIST(LENGTH PLUGIN_PROTOBUFS NUM_PROTO)
IF(NUM_PROTO) IF(NUM_PROTO)
TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack protobuf-lite ${PLUGIN_LINK_LIBRARIES}) 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 "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h" #include "modules/EventManager.h"
#include "modules/Job.h" #include "modules/Job.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Once.h"
#include "modules/World.h"
#include "df/building.h" #include "df/building.h"
#include "df/caste_raw.h" #include "df/caste_raw.h"
#include "df/creature_interaction_effect.h"
#include "df/creature_raw.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/global_objects.h"
#include "df/item.h" #include "df/item.h"
#include "df/item_boulderst.h" #include "df/item_boulderst.h"
@ -23,10 +30,6 @@
#include "df/unit_syndrome.h" #include "df/unit_syndrome.h"
#include "df/ui.h" #include "df/ui.h"
#include "df/unit.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 <string>
#include <vector> #include <vector>
@ -36,75 +39,17 @@
using namespace std; using namespace std;
using namespace DFHack; using namespace DFHack;
/* static bool enabled = false;
Example usage:
//////////////////////////////////////////////
//In file inorganic_duck.txt
inorganic_stone_duck
[OBJECT:INORGANIC]
[INORGANIC:DUCK_ROCK]
[USE_MATERIAL_TEMPLATE:STONE_TEMPLATE]
[STATE_NAME_ADJ:ALL_SOLID:drakium][DISPLAY_COLOR:0:7:0][TILE:'.']
[IS_STONE]
[SOLID_DENSITY:1][MELTING_POINT:25000]
[BOILING_POINT:9999] //This is the critical line: boiling point must be <= 10000
[SYNDROME]
[SYN_NAME:Chronic Duck Syndrome]
[CE_BODY_TRANSFORMATION:PROB:100:START:0]
[CE:CREATURE:BIRD_DUCK:MALE] //even though we don't have SYN_INHALED, the plugin will add it
///////////////////////////////////////////////
//In file building_duck.txt
building_duck
[OBJECT:BUILDING]
[BUILDING_WORKSHOP:DUCK_WORKSHOP]
[NAME:Duck Workshop]
[NAME_COLOR:7:0:1]
[DIM:1:1]
[WORK_LOCATION:1:1]
[BLOCK:1:0:0:0]
[TILE:0:1:236]
[COLOR:0:1:0:0:1]
[TILE:1:1:' ']
[COLOR:1:1:0:0:0]
[TILE:2:1:8]
[COLOR:2:1:0:0:1]
[TILE:3:1:8]
[COLOR:3:2:0:4:1]
[BUILD_ITEM:1:NONE:NONE:NONE:NONE]
[BUILDMAT]
[WORTHLESS_STONE_ONLY]
[CAN_USE_ARTIFACT]
///////////////////////////////////////////////
//In file reaction_duck.txt
reaction_duck
[OBJECT:REACTION] namespace ResetPolicy {
typedef enum {DoNothing, ResetDuration, AddDuration, NewInstance} ResetPolicy;
[REACTION:DUCKIFICATION] }
[NAME:become a duck]
[BUILDING:DUCK_WORKSHOP:NONE]
[PRODUCT:100:100:STONE:NO_SUBTYPE:STONE:DUCK_ROCK]
//////////////////////////////////////////////
//Add the following lines to your entity in entity_default.txt (or wherever it is)
[PERMITTED_BUILDING:DUCK_WORKSHOP]
[PERMITTED_REACTION:DUCKIFICATION]
//////////////////////////////////////////////
Next, start a new fort in a new world, build a duck workshop, then have someone become a duck.
*/
bool enabled = true;
DFHACK_PLUGIN("autoSyndrome"); DFHACK_PLUGIN("autoSyndrome");
command_result autoSyndrome(color_ostream& out, vector<string>& parameters); command_result autoSyndrome(color_ostream& out, vector<string>& parameters);
void processJob(color_ostream& out, void* jobPtr); 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) { 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, 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 disable //disable\n"
" autoSyndrome enable //enable\n" " autoSyndrome enable //enable\n"
"\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" "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"
"\n"
"Requirements:\n"
" 1) The job must be a custom reaction.\n"
" 2) The job must produce a stone of some inorganic material.\n"
" 3) The stone must have a boiling temperature less than or equal to 9000.\n"
"\n"
"When these conditions are met, the unit that completed the job will immediately become afflicted with all applicable syndromes associated with the inorganic material of the stone, or stones. It should correctly check for whether the creature or caste is affected or immune, and it should also correctly account for affected and immune creature classes.\n"
"Multiple syndromes per stone, or multiple boiling rocks produced with the same reaction should work fine.\n"
)); ));
//EventManager::EventHandler handle(processJob, 5);
EventManager::EventHandler handle(processJob, 5); //EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
return CR_OK; return CR_OK;
} }
@ -162,17 +98,15 @@ command_result autoSyndrome(color_ostream& out, vector<string>& parameters) {
if ( enabled == wasEnabled ) if ( enabled == wasEnabled )
return CR_OK; return CR_OK;
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome"); EventManager::unregisterAll(plugin_self);
if ( enabled ) { if ( enabled ) {
EventManager::EventHandler handle(processJob, 5); EventManager::EventHandler handle(processJob, 0);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self);
} else {
EventManager::unregisterAll(me);
} }
return CR_OK; 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::creature_raw* creature = df::global::world->raws.creatures.all[unit->race];
df::caste_raw* caste = creature->caste[unit->caste]; df::caste_raw* caste = creature->caste[unit->caste];
std::string& creature_name = creature->creature_id; 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() ) { 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; return false;
} }
for ( size_t c = 0; c < syndrome->syn_affected_creature.size(); c++ ) { 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 ) { if ( !applies ) {
return false; return false;
} }
if ( giveSyndrome(out, workerId, syndrome) < 0 ) if ( giveSyndrome(out, workerId, syndrome, policy) < 0 )
return false; return false;
return true; return true;
} }
void processJob(color_ostream& out, void* jobPtr) { void processJob(color_ostream& out, void* jobPtr) {
CoreSuspender suspender;
df::job* job = (df::job*)jobPtr; df::job* job = (df::job*)jobPtr;
if ( job == NULL ) { 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; return;
} }
if ( job->completion_timer > 0 ) if ( job->completion_timer > 0 )
@ -261,7 +199,8 @@ void processJob(color_ostream& out, void* jobPtr) {
break; break;
} }
if ( reaction == NULL ) { 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; 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 ) if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
continue; continue;
if ( workerId != -1 ) { 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; workerId = ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id;
if (workerId == -1) { 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; continue;
} }
} }
int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId); df::unit* worker = df::unit::find(workerId);
if ( workerIndex < 0 ) { if ( worker == NULL ) {
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); //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; return;
} }
df::unit* worker = df::global::world->units.all[workerIndex];
//find the building that made it //find the building that made it
int32_t buildingId = -1; int32_t buildingId = -1;
for ( size_t a = 0; a < job->general_refs.size(); a++ ) { for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER ) if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER )
continue; continue;
if ( buildingId != -1 ) { 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; buildingId = ((df::general_ref_building_holderst*)job->general_refs[a])->building_id;
if (buildingId == -1) { 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; continue;
} }
} }
df::building* building;
{ df::building* building = df::building::find(buildingId);
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, buildingId); if ( building == NULL ) {
if ( index == -1 ) { 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); out.print("%s, line %d: error: couldn't find building %d.\n", __FILE__, __LINE__, buildingId);
return; return;
}
building = df::global::world->buildings.all[index];
} }
//find all of the products it makes. Look for a stone with a low boiling point. //find all of the products it makes. Look for a stone.
bool appliedSomething = false;
for ( size_t a = 0; a < reaction->products.size(); a++ ) { for ( size_t a = 0; a < reaction->products.size(); a++ ) {
bool appliedSomething = false;
df::reaction_product_type type = reaction->products[a]->getType(); df::reaction_product_type type = reaction->products[a]->getType();
//out.print("type = %d\n", (int32_t)type); //out.print("type = %d\n", (int32_t)type);
if ( type != df::enums::reaction_product_type::item ) 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); //out.print("item_type = %d\n", (int32_t)bob->item_type);
if ( bob->item_type != df::enums::item_type::BOULDER ) if ( bob->item_type != df::enums::item_type::BOULDER )
continue; continue;
if ( bob->mat_index < 0 )
continue;
//for now don't worry about subtype //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]; 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++ ) { 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]; df::syndrome* syndrome = inorganic->material.syndrome[b];
bool workerOnly = false; bool workerOnly = true;
bool allowMultipleTargets = false; bool allowMultipleTargets = false;
bool foundCommand = false; bool foundCommand = false;
bool destroyRock = true; bool destroyRock = true;
bool foundAutoSyndrome = false;
ResetPolicy::ResetPolicy policy = ResetPolicy::NewInstance;
string commandStr; string commandStr;
vector<string> args; vector<string> args;
for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) { 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 ( foundCommand ) {
if ( commandStr == "" ) { if ( commandStr == "" ) {
if ( *clazz == "\\WORKER_ONLY" ) { commandStr = clazz;
workerOnly = true;
} else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) {
allowMultipleTargets = true;
} else if ( *clazz == "\\PRESERVE_ROCK" ) {
destroyRock = false;
}
else {
commandStr = *clazz;
}
} else { } else {
stringstream bob; stringstream bob;
if ( *clazz == "\\LOCATION" ) { if ( clazz == "\\LOCATION" ) {
bob << job->pos.x; bob << job->pos.x;
args.push_back(bob.str()); args.push_back(bob.str());
bob.str(""); bob.str("");
@ -368,24 +330,28 @@ void processJob(color_ostream& out, void* jobPtr) {
args.push_back(bob.str()); args.push_back(bob.str());
bob.str(""); bob.str("");
bob.clear(); bob.clear();
} else if ( *clazz == "\\WORKER_ID" ) { } else if ( clazz == "\\WORKER_ID" ) {
bob << workerId; bob << workerId;
args.push_back(bob.str()); args.push_back(bob.str());
} else if ( *clazz == "\\REACTION_INDEX" ) { } else if ( clazz == "\\REACTION_INDEX" ) {
bob << reaction->index; bob << reaction->index;
args.push_back(bob.str()); args.push_back(bob.str());
} else { } else {
args.push_back(*clazz); args.push_back(clazz);
} }
} }
} else if ( *clazz == "\\COMMAND" ) { } else if ( clazz == "\\COMMAND" ) {
foundCommand = true; foundCommand = true;
} }
} }
if ( !foundAutoSyndrome ) {
continue;
}
if ( commandStr != "" ) { if ( commandStr != "" ) {
Core::getInstance().runCommand(out, commandStr, args); Core::getInstance().runCommand(out, commandStr, args);
} }
if ( destroyRock ) { if ( destroyRock ) {
//find the rock and kill it before it can boil and cause problems and ugliness //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++ ) { 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 ) if ( boulder->mat_index != bob->mat_index )
continue; continue;
boulder->flags.bits.garbage_collect = true;
boulder->flags.bits.forbid = true;
boulder->flags.bits.hidden = 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 ( maybeApply(out, syndrome, workerId, worker, policy) ) {
if ( appliedSomething && !allowMultipleTargets )
continue;
if ( maybeApply(out, syndrome, workerId, worker) ) {
appliedSomething = true; appliedSomething = true;
} }
if ( workerOnly ) if ( workerOnly )
continue; continue;
if ( appliedSomething && !allowMultipleTargets )
continue;
//now try applying it to everybody inside the building //now try applying it to everybody inside the building
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[a]; df::unit* unit = df::global::world->units.active[a];
if ( unit == worker ) if ( unit == worker )
continue; continue; //we already tried giving it to him, so no doubling up
if ( unit->pos.z != building->z ) if ( unit->pos.z != building->z )
continue; continue;
if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 ) if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 )
continue; continue;
if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 ) if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 )
continue; continue;
if ( maybeApply(out, syndrome, unit->id, unit) ) { if ( maybeApply(out, syndrome, unit->id, unit, policy) ) {
appliedSomething = true; appliedSomething = true;
if ( !allowMultipleTargets ) if ( !allowMultipleTargets )
break; break;
@ -446,27 +411,67 @@ void processJob(color_ostream& out, void* jobPtr) {
/* /*
* Heavily based on https://gist.github.com/4061959/ * Heavily based on https://gist.github.com/4061959/
**/ **/
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) {
int32_t index = df::unit::binsearch_index(df::global::world->units.all, workerId); df::unit* unit = df::unit::find(workerId);
if ( index < 0 ) { if ( !unit ) {
out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); 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; 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(); df::unit_syndrome* unitSyndrome = new df::unit_syndrome();
unitSyndrome->type = syndrome->id; unitSyndrome->type = syndrome->id;
unitSyndrome->year = 0; unitSyndrome->year = DFHack::World::ReadCurrentYear();
unitSyndrome->year_time = 0; unitSyndrome->year_time = DFHack::World::ReadCurrentTick();
unitSyndrome->ticks = 1; unitSyndrome->ticks = 0;
unitSyndrome->unk1 = 1; unitSyndrome->unk1 = 0;
unitSyndrome->flags = 0; //typecast unitSyndrome->flags = 0; //TODO: typecast?
for ( size_t a = 0; a < syndrome->ce.size(); a++ ) { for ( size_t a = 0; a < syndrome->ce.size(); a++ ) {
df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms(); df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms();
symptom->unk1 = 0; symptom->unk1 = 0;
symptom->unk2 = 0; symptom->unk2 = 0;
symptom->ticks = 1; symptom->ticks = 0;
symptom->flags = 2; //TODO: ??? symptom->flags = 2; //TODO: ???
unitSyndrome->symptoms.push_back(symptom); 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" " also tries to have dwarves specialize in specific skills.\n"
" Warning: autolabor will override any manual changes you make to labors\n" " Warning: autolabor will override any manual changes you make to labors\n"
" while it is enabled.\n" " while it is enabled.\n"
" To prevent particular dwarves from being managed by autolabor, put them\n"
" in any burrow.\n"
"Examples:\n" "Examples:\n"
" autolabor MINE 2\n" " autolabor MINE 2\n"
" Keep at least 2 dwarves with mining enabled.\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; 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::item *> out_items;
vector<df::reaction_reagent *> in_reag; vector<df::reaction_reagent *> in_reag;
vector<df::item *> in_items; vector<df::item *> in_items;
bool is_gloves = (prod->item_type == df::item_type::GLOVES); 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, prod->produce(unit, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE,
df::historical_entity::find(unit->civ_id), 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); ((*gametype == df::game_type::DWARF_MAIN) || (*gametype == df::game_type::DWARF_RECLAIM)) ? df::world_site::find(ui->site_id) : NULL);
if (!out_items.size()) if (!out_items.size())
return false; 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++) for (size_t i = 0; i < out_items.size(); i++)
{ {
out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z); 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) if (out_items[i]->getGloveHandedness() > 0)
is_gloves = false; is_gloves = false;
else 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 makeItem(prod, unit, true);
return true; return true;
@ -248,7 +253,9 @@ command_result df_createitem (color_ostream &out, vector <string> & parameters)
break; break;
} }
if (!makeItem(prod, unit)) bool result = makeItem(prod, unit);
delete prod;
if (!result)
{ {
out.printerr("Failed to create item!\n"); out.printerr("Failed to create item!\n");
return CR_FAILURE; return CR_FAILURE;

@ -21,6 +21,7 @@ DFHACK_PLUGIN(vshook vshook.cpp)
DFHACK_PLUGIN(autolabor2 autolabor2.cpp) DFHACK_PLUGIN(autolabor2 autolabor2.cpp)
DFHACK_PLUGIN(eventExample eventExample.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp)
DFHACK_PLUGIN(printArgs printArgs.cpp) DFHACK_PLUGIN(printArgs printArgs.cpp)
DFHACK_PLUGIN(onceExample onceExample.cpp)
IF(UNIX) IF(UNIX)
DFHACK_PLUGIN(ref-index ref-index.cpp) DFHACK_PLUGIN(ref-index ref-index.cpp)
ENDIF() 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) if (print_debug)
out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size()); out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size());

@ -7,6 +7,7 @@
#include "DataDefs.h" #include "DataDefs.h"
#include "df/item.h" #include "df/item.h"
#include "df/job.h"
#include "df/world.h" #include "df/world.h"
#include <vector> #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) { command_result eventExample(color_ostream& out, vector<string>& parameters) {
EventManager::EventHandler initiateHandler(jobInitiated, 10); EventManager::EventHandler initiateHandler(jobInitiated, 1);
EventManager::EventHandler completeHandler(jobCompleted, 5); EventManager::EventHandler completeHandler(jobCompleted, 0);
EventManager::EventHandler timeHandler(timePassed, 1); EventManager::EventHandler timeHandler(timePassed, 1);
EventManager::EventHandler deathHandler(unitDeath, 500); EventManager::EventHandler deathHandler(unitDeath, 500);
EventManager::EventHandler itemHandler(itemCreate, 1000); EventManager::EventHandler itemHandler(itemCreate, 1);
EventManager::EventHandler buildingHandler(building, 500); EventManager::EventHandler buildingHandler(building, 500);
EventManager::EventHandler constructionHandler(construction, 100); EventManager::EventHandler constructionHandler(construction, 100);
EventManager::EventHandler syndromeHandler(syndrome, 1); EventManager::EventHandler syndromeHandler(syndrome, 1);
@ -62,8 +63,17 @@ command_result eventExample(color_ostream& out, vector<string>& parameters) {
return CR_OK; return CR_OK;
} }
void jobInitiated(color_ostream& out, void* job) { //static int timerCount=0;
out.print("Job initiated! 0x%X\n", job); //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) { 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" " 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("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("digauto","Mark a tile for continuous digging.",autodig));
commands.push_back(PluginCommand("digtype", "Dig all veins of a given type.", digtype,Gui::cursor_hotkey, 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" "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) if(tileMaterial(tt) == tiletype_material::CONSTRUCTION && !des.bits.hidden)
return false; return false;
df::tiletype_shape ts = tileShape(tt); df::tiletype_shape ts = tileShape(tt);
if (ts == tiletype_shape::EMPTY) if (ts == tiletype_shape::EMPTY && !des.bits.hidden)
return false; return false;
if(!des.bits.hidden) if(!des.bits.hidden)
{ {
@ -293,7 +293,7 @@ command_result digcircle (color_ostream &out, vector <string> & parameters)
"\n" "\n"
"After you have set the options, the command called with no options\n" "After you have set the options, the command called with no options\n"
"repeats with the last selected parameters:\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" "'digcircle' = Do it again.\n"
); );
return CR_OK; 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)) if (!Units::isCitizen(unit))
continue; continue;
if (enable_fastdwarf) if (enable_fastdwarf && !enable_teledwarf )
{ {
if (unit->counters.job_counter > 0) if (unit->counters.job_counter > 0)
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 if (enable_teledwarf) do
{ {
// don't do anything if the dwarf isn't going anywhere // 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; break;
}
// skip dwarves that are dragging creatures or being dragged // skip dwarves that are dragging creatures or being dragged
if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1)) 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 //Forward Declarations for Commands
command_result filltraffic(color_ostream &out, std::vector<std::string> & params); command_result filltraffic(color_ostream &out, std::vector<std::string> & params);
command_result alltraffic(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 //Forward Declarations for Utility Functions
command_result setAllMatching(color_ostream &out, checkTile checkProc, 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 allLow(DFCoord coord, MapExtras::MapCache & map);
void allRestricted(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"); DFHACK_PLUGIN("filltraffic");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) 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" " L: Low Traffic\n"
" R: Restricted 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; return CR_OK;
} }
@ -265,6 +278,16 @@ command_result alltraffic(color_ostream &out, std::vector<std::string> & params)
return setAllMatching(out, proc); 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. //Helper function for writing new functions that check every tile on the map.
//newTraffic is the traffic designation to set. //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. //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; des.bits.traffic = tile_traffic::Restricted;
map.setDesignationAt(coord, des); 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 '# '). 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 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) df.building_construct(bld, item_list)
Allocates a new building in DF memory, define its position / dimensions, and 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. 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| } df.each_tree(material) { |t| }
Iterates over every tree of the given material (eg 'maple'). 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 The same mechanism is available for 'onstatechange', but the
SC_BEGIN_UNLOAD event is not propagated to the ruby handler. 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 C++ object manipulation
----------------------- -----------------------
The ruby classes defined in ruby-autogen.rb are accessors to the underlying 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 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. only.
A special Compound DFHack::StlString is available for allocating a single c++ 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 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. 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) Show info on the currently selected unit ('v' or 'k' DF menu)
p df.unit_find.flags1 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). both use gcc).
It is stored inside the build directory (eg build/plugins/ruby/ruby-autogen.rb) 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:global-type ld:meta="struct-type" type-name="unit">
<ld:field type-name="language_name" name="name" ld:meta="global"/> <ld:field type-name="language_name" name="name" ld:meta="global"/>
<ld:field name="custom_profession" ld:meta="primitive" ld:subtype="stl-string"/> <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 raise "invalid building type #{type.inspect}" if not cls
bld = cls.cpp_new bld = cls.cpp_new
bld.race = ui.race_id 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 = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop
subtype = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace subtype = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace
subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone

@ -149,7 +149,21 @@ module DFHack
def vein def vein
# last vein wins # last vein wins
all_veins.reverse.find { |v| 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 end

@ -122,7 +122,8 @@ module DFHack
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init } _fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
end end
def _cpp_delete 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) DFHack.free(@_memaddr)
@_memaddr = nil # turn future segfaults in harmless ruby exceptions @_memaddr = nil # turn future segfaults in harmless ruby exceptions
end end
@ -642,7 +643,7 @@ module DFHack
@_tg = tg @_tg = tg
end end
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every # 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 def inspect ; "#<StlDeque>" ; end
end end
@ -676,7 +677,7 @@ module DFHack
def inspect def inspect
out = "#<DfFlagarray" out = "#<DfFlagarray"
each_with_index { |e, idx| each_with_index { |e, idx|
out << " #{_indexenum.sym(idx)}" if e out << " #{_indexenum ? _indexenum.sym(idx) : idx}" if e
} }
out << '>' out << '>'
end end

@ -41,6 +41,7 @@ static tthread::thread *r_thread;
static int onupdate_active; static int onupdate_active;
static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1; static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1;
static color_ostream_proxy *console_proxy; static color_ostream_proxy *console_proxy;
static std::vector<std::string> *dfhack_run_queue;
DFHACK_PLUGIN("ruby") 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 // lock this before anything, and release when everything is done
m_mutex = new tthread::mutex(); 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; r_type = RB_INIT;
// create the dedicated ruby thread // 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.", "Ruby interpreter. Eval() a ruby string.",
df_rubyeval)); df_rubyeval));
commands.push_back(PluginCommand("rb",
"Ruby interpreter. Eval() a ruby string (alias for rb_eval).",
df_rubyeval));
return CR_OK; 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 // we can release m_mutex, other users will check r_thread
m_mutex->unlock(); m_mutex->unlock();
delete m_mutex; delete m_mutex;
delete dfhack_run_queue;
// dlclose libruby // dlclose libruby
df_unloadruby(); 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 // send a single ruby line to be evaluated by the ruby thread
DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command) DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command)
{ {
command_result ret;
// if dlopen failed // if dlopen failed
if (!r_thread) if (!r_thread)
return CR_FAILURE; return CR_FAILURE;
@ -160,14 +171,24 @@ DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *c
// debug only! // debug only!
// run ruby commands without locking the main thread // run ruby commands without locking the main thread
// useful when the game is frozen after a segfault // 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 { } else {
// wrap all ruby code inside a suspend block // wrap all ruby code inside a suspend block
// if we dont do that and rely on ruby code doing it, we'll deadlock in // if we dont do that and rely on ruby code doing it, we'll deadlock in
// onupdate // onupdate
CoreSuspender suspend; 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 ) 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 // run a dfhack command, as if typed from the dfhack console
static VALUE rb_dfhack_run(VALUE self, VALUE cmd) static VALUE rb_dfhack_run(VALUE self, VALUE cmd)
{ {
if (!r_console) // XXX
return Qnil;
std::string s; std::string s;
int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0)); int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0));
s.assign(rb_string_value_ptr(&cmd), strlen); s.assign(rb_string_value_ptr(&cmd), strlen);
dfhack_run_queue->push_back(s);
// allow the target command to suspend
// FIXME
//CoreSuspendClaimer suspend(true);
Core::getInstance().runCommand(*r_console, s);
return Qtrue; 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_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_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, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
//rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1); rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1);
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1); rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1);
rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1); rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1);
rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1); rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);

@ -422,12 +422,17 @@ protected:
virtual bool can_init(S *screen) virtual bool can_init(S *screen)
{ {
auto list = getLayerList(screen); auto list = getLayerList(screen);
if (!list->active) if (!is_list_valid(screen) || !list->active)
return false; return false;
return true; return true;
} }
virtual bool is_list_valid(S*)
{
return true;
}
virtual void do_search() virtual void do_search()
{ {
search_generic<S,T>::do_search(); search_generic<S,T>::do_search();
@ -444,8 +449,12 @@ protected:
virtual void clear_search() virtual void clear_search()
{ {
search_generic<S,T>::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: private:
@ -1208,12 +1217,14 @@ public:
return 'q'; 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) if (screen->page != df::viewscreen_layer_militaryst::Positions)
return false; return false;
return military_search_base::can_init(screen); return true;
} }
vector<df::unit *> *get_primary_list() 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 return unit->counters2.exhaustion <= 2000 && // actually 4000, but leave a gap
(unit->status2.limbs_grasp_count > 0 || unit->status2.limbs_grasp_max == 0) && (unit->status2.limbs_grasp_count > 0 || unit->status2.limbs_grasp_max == 0) &&
(!unit->health || (unit->health->flags.whole&0x7FF) == 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) 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); EventManager::EventHandler handler(jobCompletedHandler,1);
DFhackCExport command_result plugin_init(color_ostream& out, std::vector<PluginCommand> &commands) { DFhackCExport command_result plugin_init(color_ostream& out, std::vector<PluginCommand> &commands) {
commands.push_back(PluginCommand("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" "workNow\n"
" print workNow status\n" " print workNow status\n"
"workNow 0\n" "workNow 0\n"
@ -54,7 +54,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK; return CR_OK;
*df::global::process_jobs = true; *df::global::process_jobs = true;
*df::global::process_dig = true; *df::global::process_dig = true;
return CR_OK; return CR_OK;
} }

File diff suppressed because it is too large Load Diff

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

@ -1,161 +1,177 @@
# create arbitrary items under cursor # create first necessity items under cursor
category = $script_args[0] || 'help' category = $script_args[0] || 'help'
mat_raw = $script_args[1] || 'list' mat_raw = $script_args[1] || 'list'
count = $script_args[2] count = $script_args[2]
category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs']) || 'help' category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs', 'anvils']) || 'help'
if category == 'help' if category == 'help'
puts <<EOS puts <<EOS
Create items under the cursor. Create first necessity items under the cursor.
Usage: Usage:
create [category] [raws token] [number] create-items [category] [raws token] [number]
Item categories: Item categories:
bars, boulders, plants, logs, web bars, boulders, plants, logs, webs, anvils
Raw token: Raw token:
either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only Either a full token (PLANT_MAT:ADLER:WOOD) or the middle part only
(the missing part is autocompleted depending on the item category) (the missing part is autocompleted depending on the item category)
use 'list' to show all possibilities Use 'list' to show all possibilities
Exemples: Exemples:
create boulders hematite 30 create-items boulders hematite 30
create bars CREATURE_MAT:CAT:SOAP 10 create-items bars CREATURE_MAT:CAT:SOAP 10
create web cave_giant create-items web cave_giant
create plants list create-items plants list
EOS EOS
throw :script_finished throw :script_finished
elsif mat_raw == 'list' elsif mat_raw == 'list'
# allowed with no cursor # allowed with no cursor
elsif df.cursor.x == -30000 elsif df.cursor.x == -30000
puts "Please place the game cursor somewhere" puts "Please place the game cursor somewhere"
throw :script_finished throw :script_finished
elsif !(maptile = df.map_tile_at(df.cursor)) elsif !(maptile = df.map_tile_at(df.cursor))
puts "Error: unallocated map block !" puts "Error: unallocated map block !"
throw :script_finished throw :script_finished
elsif !maptile.shape_passablehigh elsif !maptile.shape_passablehigh
puts "Error: impassible tile !" puts "Error: impassible tile !"
throw :script_finished throw :script_finished
end end
def match_list(tok, list) def match_list(tok, list)
if tok != 'list' if tok != 'list'
tok = df.match_rawname(tok, list) tok = df.match_rawname(tok, list)
if not tok if not tok
puts "Invalid raws token, use one in:" puts "Invalid raws token, use one in:"
tok = 'list' tok = 'list'
end end
end end
if tok == 'list' if tok == 'list'
puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ') puts list.map { |w| w =~ /[^\w]/ ? w.inspect : w }.join(' ')
throw :script_finished throw :script_finished
end end
tok tok
end end
case category case category
when 'bars' when 'bars'
# create metal bar, eg createbar INORGANIC:IRON # create metal bar, eg createbar INORGANIC:IRON
cls = DFHack::ItemBarst cls = DFHack::ItemBarst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino| list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL] ino.material.flags[:IS_METAL]
}.map { |ino| ino.id } }.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}" mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw puts mat_raw
end end
customize = lambda { |item| customize = lambda { |item|
item.dimension = 150 item.dimension = 150
item.subtype = -1 item.subtype = -1
} }
when 'boulders' when 'boulders'
cls = DFHack::ItemBoulderst cls = DFHack::ItemBoulderst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.inorganics.find_all { |ino| list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_STONE] ino.material.flags[:IS_STONE]
}.map { |ino| ino.id } }.map { |ino| ino.id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "INORGANIC:#{mat_raw}" mat_raw = "INORGANIC:#{mat_raw}"
puts mat_raw puts mat_raw
end end
when 'plants' when 'plants'
cls = DFHack::ItemPlantst cls = DFHack::ItemPlantst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt| list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'STRUCTURAL' } plt.material.find { |mat| mat.id == 'STRUCTURAL' }
}.map { |plt| plt.id } }.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL" mat_raw = "PLANT_MAT:#{mat_raw}:STRUCTURAL"
puts mat_raw puts mat_raw
end end
when 'logs' when 'logs'
cls = DFHack::ItemWoodst cls = DFHack::ItemWoodst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.plants.all.find_all { |plt| list = df.world.raws.plants.all.find_all { |plt|
plt.material.find { |mat| mat.id == 'WOOD' } plt.material.find { |mat| mat.id == 'WOOD' }
}.map { |plt| plt.id } }.map { |plt| plt.id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "PLANT_MAT:#{mat_raw}:WOOD" mat_raw = "PLANT_MAT:#{mat_raw}:WOOD"
puts mat_raw puts mat_raw
end end
when 'webs' when 'webs'
cls = DFHack::ItemThreadst cls = DFHack::ItemThreadst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil) if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
list = df.world.raws.creatures.all.find_all { |cre| list = df.world.raws.creatures.all.find_all { |cre|
cre.material.find { |mat| mat.id == 'SILK' } cre.material.find { |mat| mat.id == 'SILK' }
}.map { |cre| cre.creature_id } }.map { |cre| cre.creature_id }
mat_raw = match_list(mat_raw, list) mat_raw = match_list(mat_raw, list)
mat_raw = "CREATURE_MAT:#{mat_raw}:SILK" mat_raw = "CREATURE_MAT:#{mat_raw}:SILK"
puts mat_raw puts mat_raw
end end
count ||= 1 count ||= 1
customize = lambda { |item| customize = lambda { |item|
item.flags.spider_web = true item.flags.spider_web = true
item.dimension = 15000 # XXX may depend on creature (this is for GCS) item.dimension = 15000 # XXX may depend on creature (this is for GCS)
} }
end when 'anvils'
cls = DFHack::ItemAnvilst
if mat_raw !~ /:/ and !(df.decode_mat(mat_raw) rescue nil)
mat = df.decode_mat mat_raw list = df.world.raws.inorganics.find_all { |ino|
ino.material.flags[:IS_METAL]
count ||= 20 }.map { |ino| ino.id }
count.to_i.times { mat_raw = match_list(mat_raw, list)
item = cls.cpp_new mat_raw = "INORGANIC:#{mat_raw}"
item.id = df.item_next_id puts mat_raw
item.stack_size = 1 end
item.mat_type = mat.mat_type count ||= 1
item.mat_index = mat.mat_index
end
customize[item] if customize
df.item_next_id += 1 mat = df.decode_mat mat_raw
item.categorize(true)
df.world.items.all << item count ||= 20
count.to_i.times {
item.pos = df.cursor item = cls.cpp_new
item.flags.on_ground = true item.id = df.item_next_id
df.map_tile_at.mapblock.items << item.id item.stack_size = 1
df.map_tile_at.occupancy.item = true item.mat_type = mat.mat_type
} item.mat_index = mat.mat_index
customize[item] if customize
# move game view, so that the ui menu updates
df.curview.feed_keys(:CURSOR_UP_Z) df.item_next_id += 1
df.curview.feed_keys(:CURSOR_DOWN_Z) 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 # show death cause of a creature
def display_death_event(e) 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 = "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 << " (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 << " 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 << " 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 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(',') + '.' puts str.chomp(',') + '.'
end end
def display_death_unit(u) def display_death_unit(u)
death_info = u.counters.death_tg death_info = u.counters.death_tg
killer = death_info.killer_tg if death_info killer = death_info.killer_tg if death_info
str = "The #{u.race_tg.name[0]}" str = "The #{u.race_tg.name[0]}"
str << " #{u.name}" if u.name.has_name str << " #{u.name}" if u.name.has_name
str << " died" str << " died"
str << " in year #{death_info.event_year}" if death_info 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 << " (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 str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer
puts str.chomp(',') + '.' puts str.chomp(',') + '.'
end end
item = df.item_find(:selected) item = df.item_find(:selected)
unit = df.unit_find(:selected) unit = df.unit_find(:selected)
if !item or !item.kind_of?(DFHack::ItemBodyComponent) if !item or !item.kind_of?(DFHack::ItemBodyComponent)
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) } item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
end end
if item and item.kind_of?(DFHack::ItemBodyComponent) if item and item.kind_of?(DFHack::ItemBodyComponent)
hf = item.hist_figure_id hf = item.hist_figure_id
elsif unit elsif unit
hf = unit.hist_figure_id hf = unit.hist_figure_id
end end
if not hf if not hf
puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen" puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
elsif hf == -1 elsif hf == -1
if unit ||= item.unit_tg if unit ||= item.unit_tg
display_death_unit(unit) display_death_unit(unit)
else else
puts "Not a historical figure, cannot death find info" puts "Not a historical figure, cannot death find info"
end end
else else
histfig = df.world.history.figures.binsearch(hf) histfig = df.world.history.figures.binsearch(hf)
unit = histfig ? df.unit_find(histfig.unit_id) : nil unit = histfig ? df.unit_find(histfig.unit_id) : nil
if unit and not unit.flags1.dead and not unit.flags3.ghostly if unit and not unit.flags1.dead and not unit.flags3.ghostly
puts "#{unit.name} is not dead yet !" puts "#{unit.name} is not dead yet !"
else else
events = df.world.history.events events = df.world.history.events
(0...events.length).reverse_each { |i| (0...events.length).reverse_each { |i|
e = events[i] e = events[i]
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
display_death_event(e) display_death_event(e)
break break
end end
} }
end 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 # designate an area for digging according to a plan in csv format
raise "usage: digfort <plan filename>" if not $script_args[0] raise "usage: digfort <plan filename>" if not $script_args[0]
planfile = File.read($script_args[0]) planfile = File.read($script_args[0])
if df.cursor.x == -30000 if df.cursor.x == -30000
raise "place the game cursor to the top-left corner of the design" raise "place the game cursor to the top-left corner of the design"
end end
tiles = planfile.lines.map { |l| tiles = planfile.lines.map { |l|
l.sub(/#.*/, '').split(';').map { |t| t.strip } l.sub(/#.*/, '').split(';').map { |t| t.strip }
} }
x = x0 = df.cursor.x x = x0 = df.cursor.x
y = df.cursor.y y = df.cursor.y
z = df.cursor.z z = df.cursor.z
tiles.each { |line| tiles.each { |line|
next if line.empty? or line == [''] next if line.empty? or line == ['']
line.each { |tile| line.each { |tile|
t = df.map_tile_at(x, y, z) t = df.map_tile_at(x, y, z)
s = t.shape_basic s = t.shape_basic
case tile case tile
when 'd'; t.dig(:Default) if s == :Wall when 'd'; t.dig(:Default) if s == :Wall
when 'u'; t.dig(:UpStair) if s == :Wall when 'u'; t.dig(:UpStair) if s == :Wall
when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor
when 'i'; t.dig(:UpDownStair) if s == :Wall when 'i'; t.dig(:UpDownStair) if s == :Wall
when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor
when 'r'; t.dig(:Ramp) if s == :Wall when 'r'; t.dig(:Ramp) if s == :Wall
when 'x'; t.dig(:No) when 'x'; t.dig(:No)
end end
x += 1 x += 1
} }
x = x0 x = x0
y += 1 y += 1
} }
puts 'done' puts 'done'

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

@ -1,93 +1,135 @@
# slay all creatures of a given race # exterminate creatures
# race = name of the race to eradicate, use 'him' to target only the selected creature # race = name of the race to eradicate, use 'him' to target only the selected creature
# use 'undead' to target all undeads # use 'undead' to target all undeads
race = $script_args[0] race = $script_args[0]
# if the 2nd parameter is 'magma', magma rain for the targets instead of instant death # if the 2nd parameter is 'magma', magma rain for the targets instead of instant death
magma = ($script_args[1] == 'magma') # if it is 'butcher' mark all units for butchering (wont work with hostiles)
kill_by = $script_args[1]
checkunit = lambda { |u|
(u.body.blood_count != 0 or u.body.blood_max == 0) and case kill_by
not u.flags1.dead and when 'magma'
not u.flags1.caged and not u.flags1.chained and slain = 'burning'
#not u.flags1.hidden_in_ambush and when 'slaughter', 'butcher'
not df.map_designation_at(u).hidden slain = 'marked for butcher'
} when nil
slain = 'slain'
slayit = lambda { |u| else
if not magma race = 'help'
# just make them drop dead end
u.body.blood_count = 0
# some races dont mind having no blood, ensure they are still taken care of. checkunit = lambda { |u|
u.animal.vanish_countdown = 2 (u.body.blood_count != 0 or u.body.blood_max == 0) and
else not u.flags1.dead and
# it's getting hot around here not u.flags1.caged and not u.flags1.chained and
# !!WARNING!! do not call on a magma-safe creature #not u.flags1.hidden_in_ambush and
ouh = df.onupdate_register("slayrace ensure #{u.id}", 1) { not df.map_designation_at(u).hidden
if u.flags1.dead }
df.onupdate_unregister(ouh)
else slayit = lambda { |u|
x, y, z = u.pos.x, u.pos.y, u.pos.z case kill_by
z += 1 while tile = df.map_tile_at(x, y, z+1) and when 'magma'
tile.shape_passableflow and tile.shape_passablelow # it's getting hot around here
df.map_tile_at(x, y, z).spawn_magma(7) # !!WARNING!! do not call on a magma-safe creature
end ouh = df.onupdate_register("exterminate ensure #{u.id}", 1) {
} if u.flags1.dead
end df.onupdate_unregister(ouh)
} else
x, y, z = u.pos.x, u.pos.y, u.pos.z
all_races = Hash.new(0) z += 1 while tile = df.map_tile_at(x, y, z+1) and
tile.shape_passableflow and tile.shape_passablelow
df.world.units.active.map { |u| df.map_tile_at(x, y, z).spawn_magma(7)
if checkunit[u] end
if (u.enemy.undead or }
(u.curse.add_tags1.OPPOSED_TO_LIFE and not when 'butcher', 'slaughter'
u.curse.rem_tags1.OPPOSED_TO_LIFE)) # mark for slaughter at butcher's shop
all_races['Undead'] += 1 u.flags2.slaughter = true
else else
all_races[u.race_tg.creature_id] += 1 # just make them drop dead
end u.body.blood_count = 0
end # some races dont mind having no blood, ensure they are still taken care of.
} u.animal.vanish_countdown = 2
end
case race }
when nil
all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" } all_races = Hash.new(0)
when 'him' df.world.units.active.map { |u|
if him = df.unit_find if checkunit[u]
slayit[him] if (u.enemy.undead or
else (u.curse.add_tags1.OPPOSED_TO_LIFE and not
puts "Select a target ingame" u.curse.rem_tags1.OPPOSED_TO_LIFE))
end all_races['Undead'] += 1
else
when /^undead/i all_races[u.race_tg.creature_id] += 1
count = 0 end
df.world.units.active.each { |u| end
if (u.enemy.undead or }
(u.curse.add_tags1.OPPOSED_TO_LIFE and not
u.curse.rem_tags1.OPPOSED_TO_LIFE)) and case race
checkunit[u] when nil
slayit[u] all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" }
count += 1
end when 'help', '?'
} puts <<EOS
puts "slain #{count} undeads" Kills all creatures of a given race.
With no argument, lists possible targets with their head count.
else With the special argument 'him' or 'her', kill only the currently selected creature.
raw_race = df.match_rawname(race, all_races.keys) With the special argument 'undead', kill all undead creatures/thralls.
raise 'invalid race' if not raw_race
The targets will bleed out on the next game tick, or if they are immune to that, will vanish in a puff of smoke.
race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race }
The special final argument 'magma' will make magma rain on the targets instead.
count = 0 The special final argument 'butcher' will mark the targets for butchering instead.
df.world.units.active.each { |u|
if u.race == race_nr and checkunit[u] Ex: exterminate gob
slayit[u] exterminate elve magma
count += 1 exterminate him
end exterminate pig butcher
} EOS
puts "slain #{count} #{raw_race}"
when 'him', 'her', 'it', 'that'
end 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 # script to fix loyalty cascade, when you order your militia to kill friendly units
def fixunit(unit) def fixunit(unit)
return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id
links = unit.hist_figure_tg.entity_links links = unit.hist_figure_tg.entity_links
fixed = false fixed = false
# check if the unit is a civ renegade # check if the unit is a civ renegade
if i1 = links.index { |l| if i1 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
l.entity_id == df.ui.civ_id l.entity_id == df.ui.civ_id
} and i2 = links.index { |l| } and i2 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
l.entity_id == df.ui.civ_id l.entity_id == df.ui.civ_id
} }
fixed = true fixed = true
i1, i2 = i2, i1 if i1 > i2 i1, i2 = i2, i1 if i1 > i2
links.delete_at i2 links.delete_at i2
links.delete_at i1 links.delete_at i1
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100) 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" df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again"
end end
# check if the unit is a group renegade # check if the unit is a group renegade
if i1 = links.index { |l| if i1 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
l.entity_id == df.ui.group_id l.entity_id == df.ui.group_id
} and i2 = links.index { |l| } and i2 = links.index { |l|
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
l.entity_id == df.ui.group_id l.entity_id == df.ui.group_id
} }
fixed = true fixed = true
i1, i2 = i2, i1 if i1 > i2 i1, i2 = i2, i1 if i1 > i2
links.delete_at i2 links.delete_at i2
links.delete_at i1 links.delete_at i1
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100) 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" df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again"
end end
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed) # 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 if fixed and unit.enemy.enemy_status_slot != -1
i = unit.enemy.enemy_status_slot i = unit.enemy.enemy_status_slot
unit.enemy.enemy_status_slot = -1 unit.enemy.enemy_status_slot = -1
cache = df.world.enemy_status_cache cache = df.world.enemy_status_cache
cache.slot_used[i] = false cache.slot_used[i] = false
cache.rel_map[i].map! { -1 } cache.rel_map[i].map! { -1 }
cache.rel_map.each { |a| a[i] = -1 } cache.rel_map.each { |a| a[i] = -1 }
cache.next_slot = i if cache.next_slot > i cache.next_slot = i if cache.next_slot > i
end end
# return true if we actually fixed the unit # return true if we actually fixed the unit
fixed fixed
end end
count = 0 count = 0
df.unit_citizens.each { |u| df.unit_citizens.each { |u|
count += 1 if fixunit(u) count += 1 if fixunit(u)
} }
if count > 0 if count > 0
puts "loyalty cascade fixed (#{count} dwarves)" puts "loyalty cascade fixed (#{count} dwarves)"
else else
puts "no loyalty cascade found" puts "no loyalty cascade found"
end end

@ -1,20 +1,25 @@
# fix doors that are frozen in 'open' state # fix doors that are frozen in 'open' state
# door is stuck in open state if the map occupancy flag incorrectly indicates # this may happen after people mess with the game by (incorrectly) teleporting units or items
# that an unit is present (and creatures will prone to pass through) # a door may stick open if the map occupancy flags are wrong
count = 0 count = 0
df.world.buildings.all.each { |bld| df.world.buildings.all.each { |bld|
# for all doors # for all doors
next if bld._rtti_classname != :building_doorst next if bld._rtti_classname != :building_doorst
# check if it is open # check if it is open
next if bld.close_timer == 0 next if bld.close_timer == 0
# check if occupancy is set # check if occupancy is set
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z) occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
next if not occ.unit if (occ.unit or occ.unit_grounded) and not
# check if an unit is present # 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 } 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 count += 1
occ.unit = false occ.unit = occ.unit_grounded = false
} end
puts "unstuck #{count} doors" 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 # grow crops in farm plots. ex: growcrops helmet_plump 20
material = $script_args[0] material = $script_args[0]
count_max = $script_args[1].to_i count_max = $script_args[1].to_i
count_max = 100 if count_max == 0 count_max = 100 if count_max == 0
# cache information from the raws # cache information from the raws
@raws_plant_name ||= {} @raws_plant_name ||= {}
@raws_plant_growdur ||= {} @raws_plant_growdur ||= {}
if @raws_plant_name.empty? if @raws_plant_name.empty?
df.world.raws.plants.all.each_with_index { |p, idx| df.world.raws.plants.all.each_with_index { |p, idx|
@raws_plant_name[idx] = p.id @raws_plant_name[idx] = p.id
@raws_plant_growdur[idx] = p.growdur @raws_plant_growdur[idx] = p.growdur
} }
end end
inventory = Hash.new(0) inventory = Hash.new(0)
df.world.items.other[:SEEDS].each { |seed| df.world.items.other[:SEEDS].each { |seed|
next if not seed.flags.in_building next if not seed.flags.in_building
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } 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] next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
inventory[seed.mat_index] += 1 inventory[seed.mat_index] += 1
} }
if !material or material == 'help' or material == 'list' if !material or material == 'help' or material == 'list'
# show a list of available crop types # show a list of available crop types
inventory.sort_by { |mat, c| c }.each { |mat, c| inventory.sort_by { |mat, c| c }.each { |mat, c|
name = df.world.raws.plants.all[mat].id name = df.world.raws.plants.all[mat].id
puts " #{name} #{c}" puts " #{name} #{c}"
} }
else else
mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] }) mat = df.match_rawname(material, inventory.keys.map { |k| @raws_plant_name[k] })
unless wantmat = @raws_plant_name.index(mat) unless wantmat = @raws_plant_name.index(mat)
raise "invalid plant material #{material}" raise "invalid plant material #{material}"
end end
count = 0 count = 0
df.world.items.other[:SEEDS].each { |seed| df.world.items.other[:SEEDS].each { |seed|
next if seed.mat_index != wantmat next if seed.mat_index != wantmat
next if not seed.flags.in_building next if not seed.flags.in_building
next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } 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] next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
seed.grow_counter = @raws_plant_growdur[seed.mat_index] seed.grow_counter = @raws_plant_growdur[seed.mat_index]
count += 1 count += 1
} }
puts "Grown #{count} #{mat}" puts "Grown #{count} #{mat}"
end end

@ -186,15 +186,27 @@ function SetPatientRef(args)
end end
end 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) function MakePredicateWieldsItem(item_skill)
local pred=function(args) local pred=function(args)
local inv=args.unit.inventory local inv=args.unit.inventory
for k,v in pairs(inv) do for k,v in pairs(inv) do
if v.mode==1 and df.item_weaponst:is_instance(v.item) then if v.mode==1 and v.item:getMeleeSkill()==item_skill and args.unit.body.weapon_bp==v.body_part_id then
if v.item.subtype.skill_melee==item_skill and args.unit.body.weapon_bp==v.body_part_id then return true
return true
end
end end
end end
return false,"Correct tool not equiped" return false,"Correct tool not equiped"
@ -819,9 +831,10 @@ end
actions={ actions={
{"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}}, {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}},
{"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}}, {"DetailWall" ,df.job_type.DetailWall,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall,IsHardMaterial}},
{"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMaterial,SameSquare}}, {"DetailFloor" ,df.job_type.DetailFloor,{MakePredicateWieldsItem(df.job_skill.MINING),IsFloor,IsHardMaterial,SameSquare}},
--{"CarveTrack" ,df.job_type.CarveTrack}, -- does not work?? {"CarveTrack" ,df.job_type.CarveTrack,{MakePredicateWieldsItem(df.job_skill.MINING),IsFloor,IsHardMaterial}
,{SetCarveDir}},
{"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, {"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}},
{"CarveUpwardStaircase" ,df.job_type.CarveUpwardStaircase,{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)}}, {"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 # control your levers from the dfhack console
def lever_pull_job(bld) def lever_pull_job(bld)
ref = DFHack::GeneralRefBuildingHolderst.cpp_new ref = DFHack::GeneralRefBuildingHolderst.cpp_new
ref.building_id = bld.id ref.building_id = bld.id
job = DFHack::Job.cpp_new job = DFHack::Job.cpp_new
job.job_type = :PullLever job.job_type = :PullLever
job.pos = [bld.centerx, bld.centery, bld.z] job.pos = [bld.centerx, bld.centery, bld.z]
job.general_refs << ref job.general_refs << ref
bld.jobs << job bld.jobs << job
df.job_link job df.job_link job
puts lever_descr(bld) puts lever_descr(bld)
end end
def lever_pull_cheat(bld) def lever_pull_cheat(bld)
bld.linked_mechanisms.each { |i| bld.linked_mechanisms.each { |i|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r| i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r|
r.building_tg.setTriggerState(bld.state) r.building_tg.setTriggerState(bld.state)
} }
} }
bld.state = (bld.state == 0 ? 1 : 0) bld.state = (bld.state == 0 ? 1 : 0)
puts lever_descr(bld) puts lever_descr(bld)
end end
def lever_descr(bld, idx=nil) def lever_descr(bld, idx=nil)
ret = [] ret = []
# lever description # lever description
descr = '' descr = ''
descr << "#{idx}: " if idx descr << "#{idx}: " if idx
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
bld.jobs.each { |j| bld.jobs.each { |j|
if j.job_type == :PullLever if j.job_type == :PullLever
flags = '' flags = ''
flags << ', repeat' if j.flags.repeat flags << ', repeat' if j.flags.repeat
flags << ', suspended' if j.flags.suspend flags << ', suspended' if j.flags.suspend
descr << " (pull order#{flags})" descr << " (pull order#{flags})"
end end
} }
bld.linked_mechanisms.map { |i| bld.linked_mechanisms.map { |i|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst) i.general_refs.grep(DFHack::GeneralRefBuildingHolderst)
}.flatten.each { |r| }.flatten.each { |r|
# linked building description # linked building description
tg = r.building_tg tg = r.building_tg
state = '' state = ''
if tg.respond_to?(:gate_flags) if tg.respond_to?(:gate_flags)
state << (tg.gate_flags.closed ? 'closed' : 'opened') state << (tg.gate_flags.closed ? 'closed' : 'opened')
state << ", closing (#{tg.timer})" if tg.gate_flags.closing state << ", closing (#{tg.timer})" if tg.gate_flags.closing
state << ", opening (#{tg.timer})" if tg.gate_flags.opening state << ", opening (#{tg.timer})" if tg.gate_flags.opening
end end
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}") ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
# indent other links # indent other links
descr = descr.gsub(/./, ' ') descr = descr.gsub(/./, ' ')
} }
ret << descr if ret.empty? ret << descr if ret.empty?
ret ret
end end
def lever_list def lever_list
@lever_list = [] @lever_list = []
df.world.buildings.other[:TRAP].find_all { |bld| df.world.buildings.other[:TRAP].find_all { |bld|
bld.trap_type == :Lever bld.trap_type == :Lever
}.sort_by { |bld| bld.id }.each { |bld| }.sort_by { |bld| bld.id }.each { |bld|
puts lever_descr(bld, @lever_list.length) puts lever_descr(bld, @lever_list.length)
@lever_list << bld.id @lever_list << bld.id
} }
end end
case $script_args[0] case $script_args[0]
when 'pull' when 'pull'
cheat = $script_args.delete('--cheat') || $script_args.delete('--now') cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
id = $script_args[1].to_i id = $script_args[1].to_i
id = @lever_list[id] || id id = @lever_list[id] || id
bld = df.building_find(id) bld = df.building_find(id)
raise 'invalid lever id' if not bld raise 'invalid lever id' if not bld
if cheat if cheat
lever_pull_cheat(bld) lever_pull_cheat(bld)
else else
lever_pull_job(bld) lever_pull_job(bld)
end end
when 'list' when 'list'
lever_list lever_list
when /^\d+$/ when /^\d+$/
id = $script_args[0].to_i id = $script_args[0].to_i
id = @lever_list[id] || id id = @lever_list[id] || id
bld = df.building_find(id) bld = df.building_find(id)
raise 'invalid lever id' if not bld raise 'invalid lever id' if not bld
puts lever_descr(bld) puts lever_descr(bld)
else else
puts <<EOS puts <<EOS
Lever control from the dfhack console Lever control from the dfhack console
Usage: Usage:
lever list lever list
shows the list of levers in the fortress, with their id and links shows the list of levers in the fortress, with their id and links
lever pull 42 lever pull 42
order the dwarves to pull lever 42 order the dwarves to pull lever 42
lever pull 42 --cheat lever pull 42 --cheat
magically pull lever 42 immediately magically pull lever 42 immediately
EOS EOS
end 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 # remove bad thoughts for the selected unit or the whole fort
dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n') dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n')
$script_args << 'all' if dry_run and $script_args.empty? $script_args << 'all' if dry_run and $script_args.empty?
seenbad = Hash.new(0) seenbad = Hash.new(0)
clear_mind = lambda { |u| clear_mind = lambda { |u|
u.status.recent_events.each { |e| u.status.recent_events.each { |e|
next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-' next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-'
seenbad[e.type] += 1 seenbad[e.type] += 1
e.age = 0x1000_0000 unless dry_run e.age = 0x1000_0000 unless dry_run
} }
} }
summary = lambda { summary = lambda {
seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt| seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt|
puts " #{thought} #{cnt}" puts " #{thought} #{cnt}"
} }
count = seenbad.values.inject(0) { |sum, cnt| sum+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 puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run
} }
case $script_args[0] case $script_args[0]
when 'him' when 'him'
if u = df.unit_find if u = df.unit_find
clear_mind[u] clear_mind[u]
summary[] summary[]
else else
puts 'Please select a dwarf ingame' puts 'Please select a dwarf ingame'
end end
when 'all' when 'all'
df.unit_citizens.each { |uu| df.unit_citizens.each { |uu|
clear_mind[uu] clear_mind[uu]
} }
summary[] summary[]
else else
puts "Usage: removebadthoughts [--dry-run] <him|all>" puts "Usage: removebadthoughts [--dry-run] <him|all>"
end end

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

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

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