Conflicts:
	library/xml
	plugins/CMakeLists.txt
develop
Japa 2013-11-19 04:15:04 +05:30
commit 8917892473
109 changed files with 9792 additions and 1902 deletions

6
.gitignore vendored

@ -58,5 +58,9 @@ build/CPack*Config.cmake
/cmakeall.bat
# vim swap files
# vim files
*.swp
.vimrc
# ctags file
tags

@ -59,7 +59,7 @@ endif()
# set up versioning.
set(DF_VERSION "0.34.11")
SET(DFHACK_RELEASE "r3" CACHE STRING "Current release revision.")
SET(DFHACK_RELEASE "r4" CACHE STRING "Current release revision.")
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")
@ -104,9 +104,9 @@ OPTION(BUILD_PLUGINS "Build the plugins." ON)
# enable C++11 features
IF(UNIX)
add_definitions(-DLINUX_BUILD)
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -Wall -Wno-unused-variable")
SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic -std=c++0x")
SET(CMAKE_C_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic")
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -g -Wall -Wno-unused-variable")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -m32 -march=i686 -mtune=generic -std=c++0x")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -m32 -march=i686 -mtune=generic")
ELSEIF(MSVC)
# for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP")

@ -3,13 +3,13 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.9.1: http://docutils.sourceforge.net/" />
<meta name="generator" content="Docutils 0.11: http://docutils.sourceforge.net/" />
<title>DFHack Lua API</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7434 2012-05-11 21:06:27Z milde $
:Id: $Id: html4css1.css 7614 2013-02-21 15:55:51Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
@ -77,7 +77,7 @@ div.tip p.admonition-title {
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title {
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
@ -253,13 +253,14 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { /* line numbers */
color: grey;
}
.code {
background-color: #eeeeee
}
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
@ -312,6 +313,21 @@ table.docutils th.field-name, table.docinfo th.docinfo-name {
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
@ -349,69 +365,71 @@ ul.auto-toc {
<li><a class="reference internal" href="#locking-and-finalization" id="id15">Locking and finalization</a></li>
<li><a class="reference internal" href="#persistent-configuration-storage" id="id16">Persistent configuration storage</a></li>
<li><a class="reference internal" href="#material-info-lookup" id="id17">Material info lookup</a></li>
<li><a class="reference internal" href="#random-number-generation" id="id18">Random number generation</a></li>
</ul>
</li>
<li><a class="reference internal" href="#c-function-wrappers" id="id18">C++ function wrappers</a><ul>
<li><a class="reference internal" href="#gui-module" id="id19">Gui module</a></li>
<li><a class="reference internal" href="#job-module" id="id20">Job module</a></li>
<li><a class="reference internal" href="#units-module" id="id21">Units module</a></li>
<li><a class="reference internal" href="#items-module" id="id22">Items module</a></li>
<li><a class="reference internal" href="#maps-module" id="id23">Maps module</a></li>
<li><a class="reference internal" href="#burrows-module" id="id24">Burrows module</a></li>
<li><a class="reference internal" href="#buildings-module" id="id25">Buildings module</a></li>
<li><a class="reference internal" href="#constructions-module" id="id26">Constructions module</a></li>
<li><a class="reference internal" href="#screen-api" id="id27">Screen API</a></li>
<li><a class="reference internal" href="#internal-api" id="id28">Internal API</a></li>
<li><a class="reference internal" href="#c-function-wrappers" id="id19">C++ function wrappers</a><ul>
<li><a class="reference internal" href="#gui-module" id="id20">Gui module</a></li>
<li><a class="reference internal" href="#job-module" id="id21">Job module</a></li>
<li><a class="reference internal" href="#units-module" id="id22">Units module</a></li>
<li><a class="reference internal" href="#items-module" id="id23">Items module</a></li>
<li><a class="reference internal" href="#maps-module" id="id24">Maps module</a></li>
<li><a class="reference internal" href="#burrows-module" id="id25">Burrows module</a></li>
<li><a class="reference internal" href="#buildings-module" id="id26">Buildings module</a></li>
<li><a class="reference internal" href="#constructions-module" id="id27">Constructions module</a></li>
<li><a class="reference internal" href="#screen-api" id="id28">Screen API</a></li>
<li><a class="reference internal" href="#internal-api" id="id29">Internal API</a></li>
</ul>
</li>
<li><a class="reference internal" href="#core-interpreter-context" id="id29">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id30">Event type</a></li>
<li><a class="reference internal" href="#core-interpreter-context" id="id30">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id31">Event type</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#lua-modules" id="id31">Lua Modules</a><ul>
<li><a class="reference internal" href="#global-environment" id="id32">Global environment</a></li>
<li><a class="reference internal" href="#utils" id="id33">utils</a></li>
<li><a class="reference internal" href="#dumper" id="id34">dumper</a></li>
<li><a class="reference internal" href="#class" id="id35">class</a></li>
<li><a class="reference internal" href="#lua-modules" id="id32">Lua Modules</a><ul>
<li><a class="reference internal" href="#global-environment" id="id33">Global environment</a></li>
<li><a class="reference internal" href="#utils" id="id34">utils</a></li>
<li><a class="reference internal" href="#dumper" id="id35">dumper</a></li>
<li><a class="reference internal" href="#class" id="id36">class</a></li>
</ul>
</li>
<li><a class="reference internal" href="#in-game-ui-library" id="id36">In-game UI Library</a><ul>
<li><a class="reference internal" href="#gui" id="id37">gui</a><ul>
<li><a class="reference internal" href="#misc" id="id38">Misc</a></li>
<li><a class="reference internal" href="#viewrect-class" id="id39">ViewRect class</a></li>
<li><a class="reference internal" href="#painter-class" id="id40">Painter class</a></li>
<li><a class="reference internal" href="#view-class" id="id41">View class</a></li>
<li><a class="reference internal" href="#screen-class" id="id42">Screen class</a></li>
<li><a class="reference internal" href="#framedscreen-class" id="id43">FramedScreen class</a></li>
<li><a class="reference internal" href="#in-game-ui-library" id="id37">In-game UI Library</a><ul>
<li><a class="reference internal" href="#gui" id="id38">gui</a><ul>
<li><a class="reference internal" href="#misc" id="id39">Misc</a></li>
<li><a class="reference internal" href="#viewrect-class" id="id40">ViewRect class</a></li>
<li><a class="reference internal" href="#painter-class" id="id41">Painter class</a></li>
<li><a class="reference internal" href="#view-class" id="id42">View class</a></li>
<li><a class="reference internal" href="#screen-class" id="id43">Screen class</a></li>
<li><a class="reference internal" href="#framedscreen-class" id="id44">FramedScreen class</a></li>
</ul>
</li>
<li><a class="reference internal" href="#gui-widgets" id="id44">gui.widgets</a><ul>
<li><a class="reference internal" href="#widget-class" id="id45">Widget class</a></li>
<li><a class="reference internal" href="#panel-class" id="id46">Panel class</a></li>
<li><a class="reference internal" href="#pages-class" id="id47">Pages class</a></li>
<li><a class="reference internal" href="#editfield-class" id="id48">EditField class</a></li>
<li><a class="reference internal" href="#label-class" id="id49">Label class</a></li>
<li><a class="reference internal" href="#list-class" id="id50">List class</a></li>
<li><a class="reference internal" href="#filteredlist-class" id="id51">FilteredList class</a></li>
<li><a class="reference internal" href="#gui-widgets" id="id45">gui.widgets</a><ul>
<li><a class="reference internal" href="#widget-class" id="id46">Widget class</a></li>
<li><a class="reference internal" href="#panel-class" id="id47">Panel class</a></li>
<li><a class="reference internal" href="#pages-class" id="id48">Pages class</a></li>
<li><a class="reference internal" href="#editfield-class" id="id49">EditField class</a></li>
<li><a class="reference internal" href="#label-class" id="id50">Label class</a></li>
<li><a class="reference internal" href="#list-class" id="id51">List class</a></li>
<li><a class="reference internal" href="#filteredlist-class" id="id52">FilteredList class</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#plugins" id="id52">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id53">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id54">sort</a></li>
<li><a class="reference internal" href="#eventful" id="id55">Eventful</a><ul>
<li><a class="reference internal" href="#list-of-events" id="id56">List of events</a></li>
<li><a class="reference internal" href="#functions" id="id57">Functions</a></li>
<li><a class="reference internal" href="#examples" id="id58">Examples</a></li>
<li><a class="reference internal" href="#plugins" id="id53">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id54">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id55">sort</a></li>
<li><a class="reference internal" href="#eventful" id="id56">Eventful</a><ul>
<li><a class="reference internal" href="#list-of-events" id="id57">List of events</a></li>
<li><a class="reference internal" href="#events-from-eventmanager" id="id58">Events from EventManager</a></li>
<li><a class="reference internal" href="#functions" id="id59">Functions</a></li>
<li><a class="reference internal" href="#examples" id="id60">Examples</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#scripts" id="id59">Scripts</a><ul>
<li><a class="reference internal" href="#save-init-script" id="id60">Save init script</a></li>
<li><a class="reference internal" href="#scripts" id="id61">Scripts</a><ul>
<li><a class="reference internal" href="#save-init-script" id="id62">Save init script</a></li>
</ul>
</li>
</ul>
@ -1023,9 +1041,52 @@ Accept dfhack_material_category auto-assign table.</p>
</li>
</ul>
</div>
<div class="section" id="random-number-generation">
<h3><a class="toc-backref" href="#id18">Random number generation</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.random.new([seed[,perturb_count]])</span></tt></p>
<p>Creates a new random number generator object. Without any
arguments, the object is initialized using current time.
Otherwise, the seed must be either a non-negative integer,
or a list of such integers. The second argument may specify
the number of additional randomization steps performed to
improve the initial state.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">rng:init([seed[,perturb_count]])</span></tt></p>
<p>Re-initializes an already existing random number generator object.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">rng:random([limit])</span></tt></p>
<p>Returns a random integer. If <tt class="docutils literal">limit</tt> is specified, the value
is in the range [0, limit); otherwise it uses the whole 32-bit
unsigned integer range.</p>
</li>
<li><p class="first"><tt class="docutils literal">rng:drandom()</tt></p>
<p>Returns a random floating-point number in the range [0,1).</p>
</li>
<li><p class="first"><tt class="docutils literal">rng:drandom0()</tt></p>
<p>Returns a random floating-point number in the range (0,1).</p>
</li>
<li><p class="first"><tt class="docutils literal">rng:drandom1()</tt></p>
<p>Returns a random floating-point number in the range [0,1].</p>
</li>
<li><p class="first"><tt class="docutils literal">rng:unitrandom()</tt></p>
<p>Returns a random floating-point number in the range [-1,1].</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">rng:unitvector([size])</span></tt></p>
<p>Returns multiple values that form a random vector of length 1,
uniformly distributed over the corresponding sphere surface.
The default size is 3.</p>
</li>
<li><p class="first"><tt class="docutils literal">fn = <span class="pre">rng:perlin([dim]);</span> <span class="pre">fn(x[,y[,z]])</span></tt></p>
<p>Returns a closure that computes a classical Perlin noise function
of dimension <em>dim</em>, initialized from this random generator.
Dimension may be 1, 2 or 3 (default).</p>
</li>
</ul>
</div>
</div>
<div class="section" id="c-function-wrappers">
<h2><a class="toc-backref" href="#id18">C++ function wrappers</a></h2>
<h2><a class="toc-backref" href="#id19">C++ function wrappers</a></h2>
<p>Thin wrappers around C++ functions, similar to the ones for virtual methods.
One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments
@ -1060,7 +1121,7 @@ can be omitted.</p>
</li>
</ul>
<div class="section" id="gui-module">
<h3><a class="toc-backref" href="#id19">Gui module</a></h3>
<h3><a class="toc-backref" href="#id20">Gui module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.getCurViewscreen([skip_dismissed])</span></tt></p>
<p>Returns the topmost viewscreen. If <tt class="docutils literal">skip_dismissed</tt> is <em>true</em>,
@ -1110,7 +1171,7 @@ above operations accordingly. If enabled, pauses and zooms to position.</p>
</ul>
</div>
<div class="section" id="job-module">
<h3><a class="toc-backref" href="#id20">Job module</a></h3>
<h3><a class="toc-backref" href="#id21">Job module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.job.cloneJobStruct(job)</tt></p>
<p>Creates a deep copy of the given job.</p>
@ -1160,7 +1221,7 @@ the flags in the job item.</p>
</ul>
</div>
<div class="section" id="units-module">
<h3><a class="toc-backref" href="#id21">Units module</a></h3>
<h3><a class="toc-backref" href="#id22">Units module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.units.getPosition(unit)</tt></p>
<p>Returns true <em>x,y,z</em> of the unit, or <em>nil</em> if invalid; may be not equal to unit.pos if caged.</p>
@ -1260,7 +1321,7 @@ or raws. The <tt class="docutils literal">ignore_noble</tt> boolean disables the
</ul>
</div>
<div class="section" id="items-module">
<h3><a class="toc-backref" href="#id22">Items module</a></h3>
<h3><a class="toc-backref" href="#id23">Items module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.items.getPosition(item)</tt></p>
<p>Returns true <em>x,y,z</em> of the item, or <em>nil</em> if invalid; may be not equal to item.pos if in inventory.</p>
@ -1324,7 +1385,7 @@ Returns <em>false</em> in case of error.</p>
</ul>
</div>
<div class="section" id="maps-module">
<h3><a class="toc-backref" href="#id23">Maps module</a></h3>
<h3><a class="toc-backref" href="#id24">Maps module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getSize()</tt></p>
<p>Returns map size in blocks: <em>x, y, z</em></p>
@ -1393,7 +1454,7 @@ burrows, or the presence of invaders.</p>
</ul>
</div>
<div class="section" id="burrows-module">
<h3><a class="toc-backref" href="#id24">Burrows module</a></h3>
<h3><a class="toc-backref" href="#id25">Burrows module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.burrows.findByName(name)</tt></p>
<p>Returns the burrow pointer or <em>nil</em>.</p>
@ -1428,7 +1489,7 @@ burrows, or the presence of invaders.</p>
</ul>
</div>
<div class="section" id="buildings-module">
<h3><a class="toc-backref" href="#id25">Buildings module</a></h3>
<h3><a class="toc-backref" href="#id26">Buildings module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getGeneralRef(building, type)</tt></p>
<p>Searches for a general_ref with the given type.</p>
@ -1578,7 +1639,7 @@ can be determined this way, <tt class="docutils literal">constructBuilding</tt>
</ul>
</div>
<div class="section" id="constructions-module">
<h3><a class="toc-backref" href="#id26">Constructions module</a></h3>
<h3><a class="toc-backref" href="#id27">Constructions module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.constructions.designateNew(pos,type,item_type,mat_index)</tt></p>
<p>Designates a new construction at given position. If there already is
@ -1594,7 +1655,7 @@ Returns <em>true, was_only_planned</em> if removed; or <em>false</em> if none fo
</ul>
</div>
<div class="section" id="screen-api">
<h3><a class="toc-backref" href="#id27">Screen API</a></h3>
<h3><a class="toc-backref" href="#id28">Screen API</a></h3>
<p>The screen module implements support for drawing to the tiled screen of the game.
Note that drawing only has any effect when done from callbacks, so it can only
be feasibly used in the core context.</p>
@ -1782,7 +1843,7 @@ options; if multiple interpretations exist, the table will contain multiple keys
</ul>
</div>
<div class="section" id="internal-api">
<h3><a class="toc-backref" href="#id28">Internal API</a></h3>
<h3><a class="toc-backref" href="#id29">Internal API</a></h3>
<p>These functions are intended for the use by dfhack developers,
and are only documented here for completeness:</p>
<ul>
@ -1847,7 +1908,7 @@ Returns: <em>found_index</em>, or <em>nil</em> if end reached.</p>
</div>
</div>
<div class="section" id="core-interpreter-context">
<h2><a class="toc-backref" href="#id29">Core interpreter context</a></h2>
<h2><a class="toc-backref" href="#id30">Core interpreter context</a></h2>
<p>While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.</p>
@ -1878,7 +1939,7 @@ Using <tt class="docutils literal">timeout_active(id,nil)</tt> cancels the timer
</li>
</ul>
<div class="section" id="event-type">
<h3><a class="toc-backref" href="#id30">Event type</a></h3>
<h3><a class="toc-backref" href="#id31">Event type</a></h3>
<p>An event is a native object transparently wrapping a lua table,
and implementing a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
@ -1910,7 +1971,7 @@ order using <tt class="docutils literal">dfhack.safecall</tt>.</p>
</div>
</div>
<div class="section" id="lua-modules">
<h1><a class="toc-backref" href="#id31">Lua Modules</a></h1>
<h1><a class="toc-backref" href="#id32">Lua Modules</a></h1>
<p>DFHack sets up the lua interpreter so that the built-in <tt class="docutils literal">require</tt>
function can be used to load shared lua code from hack/lua/.
The <tt class="docutils literal">dfhack</tt> namespace reference itself may be obtained via
@ -1939,7 +2000,7 @@ in this document.</p>
</li>
</ul>
<div class="section" id="global-environment">
<h2><a class="toc-backref" href="#id32">Global environment</a></h2>
<h2><a class="toc-backref" href="#id33">Global environment</a></h2>
<p>A number of variables and functions are provided in the base global
environment by the mandatory init file dfhack.lua:</p>
<ul>
@ -2002,7 +2063,7 @@ Returns <em>nil</em> if any of obj or indices is <em>nil</em>, or a numeric inde
</ul>
</div>
<div class="section" id="utils">
<h2><a class="toc-backref" href="#id33">utils</a></h2>
<h2><a class="toc-backref" href="#id34">utils</a></h2>
<ul>
<li><p class="first"><tt class="docutils literal">utils.compare(a,b)</tt></p>
<p>Comparator function; returns <em>-1</em> if a&lt;b, <em>1</em> if a&gt;b, <em>0</em> otherwise.</p>
@ -2148,7 +2209,7 @@ throws an error.</p>
</ul>
</div>
<div class="section" id="dumper">
<h2><a class="toc-backref" href="#id34">dumper</a></h2>
<h2><a class="toc-backref" href="#id35">dumper</a></h2>
<p>A third-party lua table dumper module from
<a class="reference external" href="http://lua-users.org/wiki/DataDumper">http://lua-users.org/wiki/DataDumper</a>. Defines one
function:</p>
@ -2161,7 +2222,7 @@ the other arguments see the original documentation link above.</p>
</ul>
</div>
<div class="section" id="class">
<h2><a class="toc-backref" href="#id35">class</a></h2>
<h2><a class="toc-backref" href="#id36">class</a></h2>
<p>Implements a trivial single-inheritance class system.</p>
<ul>
<li><p class="first"><tt class="docutils literal">Foo = defclass(Foo[, ParentClass])</tt></p>
@ -2252,7 +2313,7 @@ library itself uses them for constructors.</p>
</div>
</div>
<div class="section" id="in-game-ui-library">
<h1><a class="toc-backref" href="#id36">In-game UI Library</a></h1>
<h1><a class="toc-backref" href="#id37">In-game UI Library</a></h1>
<p>A number of lua modules with names starting with <tt class="docutils literal">gui</tt> are dedicated
to wrapping the natives of the <tt class="docutils literal">dfhack.screen</tt> module in a way that
is easy to use. This allows relatively easily and naturally creating
@ -2261,12 +2322,12 @@ dialogs that integrate in the main game UI window.</p>
things ranging from the basic <tt class="docutils literal">Painter</tt>, <tt class="docutils literal">View</tt> and <tt class="docutils literal">Screen</tt>
classes, to fully functional predefined dialogs.</p>
<div class="section" id="gui">
<h2><a class="toc-backref" href="#id37">gui</a></h2>
<h2><a class="toc-backref" href="#id38">gui</a></h2>
<p>This module defines the most important classes and functions for
implementing interfaces. This documents those of them that are
considered stable.</p>
<div class="section" id="misc">
<h3><a class="toc-backref" href="#id38">Misc</a></h3>
<h3><a class="toc-backref" href="#id39">Misc</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">USE_GRAPHICS</tt></p>
<p>Contains the value of <tt class="docutils literal">dfhack.screen.inGraphicsMode()</tt>, which cannot be
@ -2305,7 +2366,7 @@ msec. This is intended for rendering blinking interface objects.</p>
</ul>
</div>
<div class="section" id="viewrect-class">
<h3><a class="toc-backref" href="#id39">ViewRect class</a></h3>
<h3><a class="toc-backref" href="#id40">ViewRect class</a></h3>
<p>This class represents an on-screen rectangle with an associated independent
clip area rectangle. It is the base of the <tt class="docutils literal">Painter</tt> class, and is used by
<tt class="docutils literal">Views</tt> to track their client area.</p>
@ -2353,7 +2414,7 @@ it with the clip area of the original object.</p>
</ul>
</div>
<div class="section" id="painter-class">
<h3><a class="toc-backref" href="#id40">Painter class</a></h3>
<h3><a class="toc-backref" href="#id41">Painter class</a></h3>
<p>The painting natives in <tt class="docutils literal">dfhack.screen</tt> apply to the whole screen, are
completely stateless and don't implement clipping.</p>
<p>The Painter class inherits from ViewRect to provide clipping and local
@ -2424,7 +2485,7 @@ painter:pen(foo):seek(x,y):char(1):advance(1):string('bar')...
</pre>
</div>
<div class="section" id="view-class">
<h3><a class="toc-backref" href="#id41">View class</a></h3>
<h3><a class="toc-backref" href="#id42">View class</a></h3>
<p>This class is the common abstract base of both the stand-alone screens
and common widgets to be used inside them. It defines the basic layout,
rendering and event handling framework.</p>
@ -2548,7 +2609,7 @@ Returns <em>true</em> if any of the subviews handled the event.</p>
</ul>
</div>
<div class="section" id="screen-class">
<h3><a class="toc-backref" href="#id42">Screen class</a></h3>
<h3><a class="toc-backref" href="#id43">Screen class</a></h3>
<p>This is a View subclass intended for use as a stand-alone dialog or screen.
It adds the following methods:</p>
<ul>
@ -2602,7 +2663,7 @@ the screen is removed by any means here.</p>
</ul>
</div>
<div class="section" id="framedscreen-class">
<h3><a class="toc-backref" href="#id43">FramedScreen class</a></h3>
<h3><a class="toc-backref" href="#id44">FramedScreen class</a></h3>
<p>A Screen subclass that paints a visible frame around its body.
Most dialogs should inherit from this class.</p>
<p>A framed screen has the following attributes:</p>
@ -2640,10 +2701,10 @@ Most dialogs should inherit from this class.</p>
</div>
</div>
<div class="section" id="gui-widgets">
<h2><a class="toc-backref" href="#id44">gui.widgets</a></h2>
<h2><a class="toc-backref" href="#id45">gui.widgets</a></h2>
<p>This module implements some basic widgets based on the View infrastructure.</p>
<div class="section" id="widget-class">
<h3><a class="toc-backref" href="#id45">Widget class</a></h3>
<h3><a class="toc-backref" href="#id46">Widget class</a></h3>
<p>Base of all the widgets. Inherits from View and has the following attributes:</p>
<ul>
<li><p class="first"><tt class="docutils literal">frame = <span class="pre">{...}</span></tt></p>
@ -2709,7 +2770,7 @@ inset, or a table with the following fields:</p>
</ul>
</div>
<div class="section" id="panel-class">
<h3><a class="toc-backref" href="#id46">Panel class</a></h3>
<h3><a class="toc-backref" href="#id47">Panel class</a></h3>
<p>Inherits from Widget, and intended for grouping a number of subviews.</p>
<p>Has attributes:</p>
<ul>
@ -2722,7 +2783,7 @@ inset, or a table with the following fields:</p>
</ul>
</div>
<div class="section" id="pages-class">
<h3><a class="toc-backref" href="#id47">Pages class</a></h3>
<h3><a class="toc-backref" href="#id48">Pages class</a></h3>
<p>Subclass of Panel; keeps exactly one child visible.</p>
<ul>
<li><p class="first"><tt class="docutils literal">Pages{ <span class="pre">...,</span> selected = ... }</tt></p>
@ -2738,7 +2799,7 @@ It is permitted to use the subview object, or its <tt class="docutils literal">v
</ul>
</div>
<div class="section" id="editfield-class">
<h3><a class="toc-backref" href="#id48">EditField class</a></h3>
<h3><a class="toc-backref" href="#id49">EditField class</a></h3>
<p>Subclass of Widget; implements a simple edit field.</p>
<p>Attributes:</p>
<table class="docutils field-list" frame="void" rules="none">
@ -2760,7 +2821,7 @@ If it returns false, the character is ignored.</td>
</table>
</div>
<div class="section" id="label-class">
<h3><a class="toc-backref" href="#id49">Label class</a></h3>
<h3><a class="toc-backref" href="#id50">Label class</a></h3>
<p>This Widget subclass implements flowing semi-static text.</p>
<p>It has the following attributes:</p>
<table class="docutils field-list" frame="void" rules="none">
@ -2856,7 +2917,7 @@ this may be extended with mouse click support.</p>
</ul>
</div>
<div class="section" id="list-class">
<h3><a class="toc-backref" href="#id50">List class</a></h3>
<h3><a class="toc-backref" href="#id51">List class</a></h3>
<p>The List widget implements a simple list with paging.</p>
<p>It has the following attributes:</p>
<table class="docutils field-list" frame="void" rules="none">
@ -2942,7 +3003,7 @@ with the following fields:</p>
</ul>
</div>
<div class="section" id="filteredlist-class">
<h3><a class="toc-backref" href="#id51">FilteredList class</a></h3>
<h3><a class="toc-backref" href="#id52">FilteredList class</a></h3>
<p>This widget combines List, EditField and Label into a combo-box like
construction that allows filtering the list by subwords of its items.</p>
<p>In addition to passing through all attributes supported by List, it
@ -2995,14 +3056,14 @@ index <tt class="docutils literal">pos</tt> in the <em>unfiltered</em> list if p
</div>
</div>
<div class="section" id="plugins">
<h1><a class="toc-backref" href="#id52">Plugins</a></h1>
<h1><a class="toc-backref" href="#id53">Plugins</a></h1>
<p>DFHack plugins may export native functions and events
to lua contexts. They are automatically imported by
<tt class="docutils literal"><span class="pre">mkmodule('plugins.&lt;name&gt;')</span></tt>; this means that a lua
module file is still necessary for <tt class="docutils literal">require</tt> to read.</p>
<p>The following plugins have lua support.</p>
<div class="section" id="burrows">
<h2><a class="toc-backref" href="#id53">burrows</a></h2>
<h2><a class="toc-backref" href="#id54">burrows</a></h2>
<p>Implements extended burrow manipulations.</p>
<p>Events:</p>
<ul>
@ -3040,16 +3101,16 @@ set is the same as used by the command line.</p>
<p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p>
</div>
<div class="section" id="sort">
<h2><a class="toc-backref" href="#id54">sort</a></h2>
<h2><a class="toc-backref" href="#id55">sort</a></h2>
<p>Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.</p>
</div>
<div class="section" id="eventful">
<h2><a class="toc-backref" href="#id55">Eventful</a></h2>
<h2><a class="toc-backref" href="#id56">Eventful</a></h2>
<p>This plugin exports some events to lua thus allowing to run lua functions
on DF world events.</p>
<div class="section" id="list-of-events">
<h3><a class="toc-backref" href="#id56">List of events</a></h3>
<h3><a class="toc-backref" href="#id57">List of events</a></h3>
<ol class="arabic">
<li><p class="first"><tt class="docutils literal">onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native)</tt></p>
<p>Auto activates if detects reactions starting with <tt class="docutils literal">LUA_HOOK_</tt>. Is called when reaction finishes.</p>
@ -3078,8 +3139,41 @@ tweaking (e.g. adding custom reactions)</p>
</li>
</ol>
</div>
<div class="section" id="events-from-eventmanager">
<h3><a class="toc-backref" href="#id58">Events from EventManager</a></h3>
<p>These events are straight from EventManager module. Each of them first needs to be enabled. See functions for more info. If you register a listener before the game is loaded, be aware that no events will be triggered immediately after loading, so you might need to add another event listener for when the game first loads in some cases.</p>
<ol class="arabic">
<li><p class="first"><tt class="docutils literal">onBuildingCreatedDestroyed(building_id)</tt></p>
<p>Gets called when building is created or destroyed.</p>
</li>
<li><p class="first"><tt class="docutils literal">onConstructionCreatedDestroyed(building_id)</tt></p>
<p>Gets called when construction is created or destroyed.</p>
</li>
<li><p class="first"><tt class="docutils literal">onJobInitiated(job)</tt></p>
<p>Gets called when job is issued.</p>
</li>
<li><p class="first"><tt class="docutils literal">onJobCompleted(job)</tt></p>
<p>Gets called when job is finished. The job that is passed to this function is a copy. Requires a frequency of 0 in order to distinguish between workshop jobs that were cancelled by the user and workshop jobs that completed successfully.</p>
</li>
<li><p class="first"><tt class="docutils literal">onUnitDeath(unit_id)</tt></p>
<p>Gets called on unit death.</p>
</li>
<li><p class="first"><tt class="docutils literal">onItemCreated(item_id)</tt></p>
<p>Gets called when item is created (except due to traders, migrants, invaders and spider webs).</p>
</li>
<li><p class="first"><tt class="docutils literal">onSyndrome(unit_id,syndrome_index)</tt></p>
<p>Gets called when new syndrome appears on a unit.</p>
</li>
<li><p class="first"><tt class="docutils literal">onInvasion(invasion_id)</tt></p>
<p>Gets called when new invasion happens.</p>
</li>
<li><p class="first"><tt class="docutils literal">onInventoryChange(unit_id,item_id,old_equip,new_equip)</tt></p>
<p>Gets called when someone picks up an item, puts one down, or changes the way they are holding it. If an item is picked up, old_equip will be null. If an item is dropped, new_equip will be null. If an item is re-equipped in a new way, then neither will be null. You absolutely must NOT alter either old_equip or new_equip or you might break other plugins.</p>
</li>
</ol>
</div>
<div class="section" id="functions">
<h3><a class="toc-backref" href="#id57">Functions</a></h3>
<h3><a class="toc-backref" href="#id59">Functions</a></h3>
<ol class="arabic">
<li><p class="first"><tt class="docutils literal">registerReaction(reaction_name,callback)</tt></p>
<p>Simplified way of using onReactionComplete; the callback is function (same params as event).</p>
@ -3090,10 +3184,13 @@ tweaking (e.g. adding custom reactions)</p>
<li><p class="first"><tt class="docutils literal">addReactionToShop(reaction_name,shop_name)</tt></p>
<p>Add a custom reaction to the building.</p>
</li>
<li><p class="first"><tt class="docutils literal">enableEvent(evType,frequency)</tt></p>
<p>Enable event checking for EventManager events. For event types use <tt class="docutils literal">eventType</tt> table. Note that different types of events require different frequencies to be effective. The frequency is how many ticks EventManager will wait before checking if that type of event has happened. If multiple scripts or plugins use the same event type, the smallest frequency is the one that is used, so you might get events triggered more often than the frequency you use here.</p>
</li>
</ol>
</div>
<div class="section" id="examples">
<h3><a class="toc-backref" href="#id58">Examples</a></h3>
<h3><a class="toc-backref" href="#id60">Examples</a></h3>
<p>Spawn dragon breath on each item attempt to contaminate wound:</p>
<pre class="literal-block">
b=require &quot;plugins.eventful&quot;
@ -3130,7 +3227,7 @@ b.addReactionToShop(&quot;TAN_A_HIDE&quot;,&quot;LEATHERWORKS&quot;)
</div>
</div>
<div class="section" id="scripts">
<h1><a class="toc-backref" href="#id59">Scripts</a></h1>
<h1><a class="toc-backref" href="#id61">Scripts</a></h1>
<p>Any files with the .lua extension placed into hack/scripts/*
are automatically used by the DFHack core as commands. The
matching command name consists of the name of the file sans
@ -3161,7 +3258,7 @@ The <tt class="docutils literal">name</tt> argument should be the name stem, as
</ul>
<p>Note that this function lets errors propagate to the caller.</p>
<div class="section" id="save-init-script">
<h2><a class="toc-backref" href="#id60">Save init script</a></h2>
<h2><a class="toc-backref" href="#id62">Save init script</a></h2>
<p>If a save directory contains a file called <tt class="docutils literal">raw/init.lua</tt>, it is
automatically loaded and executed every time the save is loaded. It
can also define the following functions to be called by dfhack:</p>

@ -717,6 +717,57 @@ Functions:
Checks if the material matches job_material_category or job_item.
Accept dfhack_material_category auto-assign table.
Random number generation
------------------------
* ``dfhack.random.new([seed[,perturb_count]])``
Creates a new random number generator object. Without any
arguments, the object is initialized using current time.
Otherwise, the seed must be either a non-negative integer,
or a list of such integers. The second argument may specify
the number of additional randomization steps performed to
improve the initial state.
* ``rng:init([seed[,perturb_count]])``
Re-initializes an already existing random number generator object.
* ``rng:random([limit])``
Returns a random integer. If ``limit`` is specified, the value
is in the range [0, limit); otherwise it uses the whole 32-bit
unsigned integer range.
* ``rng:drandom()``
Returns a random floating-point number in the range [0,1).
* ``rng:drandom0()``
Returns a random floating-point number in the range (0,1).
* ``rng:drandom1()``
Returns a random floating-point number in the range [0,1].
* ``rng:unitrandom()``
Returns a random floating-point number in the range [-1,1].
* ``rng:unitvector([size])``
Returns multiple values that form a random vector of length 1,
uniformly distributed over the corresponding sphere surface.
The default size is 3.
* ``fn = rng:perlin([dim]); fn(x[,y[,z]])``
Returns a closure that computes a classical Perlin noise function
of dimension *dim*, initialized from this random generator.
Dimension may be 1, 2 or 3 (default).
C++ function wrappers
=====================
@ -3000,6 +3051,46 @@ List of events
Is called after calling (or not) native fillSidebarMenu(). Useful for job button
tweaking (e.g. adding custom reactions)
Events from EventManager
------------------------
These events are straight from EventManager module. Each of them first needs to be enabled. See functions for more info. If you register a listener before the game is loaded, be aware that no events will be triggered immediately after loading, so you might need to add another event listener for when the game first loads in some cases.
1. ``onBuildingCreatedDestroyed(building_id)``
Gets called when building is created or destroyed.
2. ``onConstructionCreatedDestroyed(building_id)``
Gets called when construction is created or destroyed.
3. ``onJobInitiated(job)``
Gets called when job is issued.
4. ``onJobCompleted(job)``
Gets called when job is finished. The job that is passed to this function is a copy. Requires a frequency of 0 in order to distinguish between workshop jobs that were cancelled by the user and workshop jobs that completed successfully.
5. ``onUnitDeath(unit_id)``
Gets called on unit death.
6. ``onItemCreated(item_id)``
Gets called when item is created (except due to traders, migrants, invaders and spider webs).
7. ``onSyndrome(unit_id,syndrome_index)``
Gets called when new syndrome appears on a unit.
8. ``onInvasion(invasion_id)``
Gets called when new invasion happens.
9. ``onInventoryChange(unit_id,item_id,old_equip,new_equip)``
Gets called when someone picks up an item, puts one down, or changes the way they are holding it. If an item is picked up, old_equip will be null. If an item is dropped, new_equip will be null. If an item is re-equipped in a new way, then neither will be null. You absolutely must NOT alter either old_equip or new_equip or you might break other plugins.
Functions
---------
@ -3015,6 +3106,10 @@ Functions
Add a custom reaction to the building.
4. ``enableEvent(evType,frequency)``
Enable event checking for EventManager events. For event types use ``eventType`` table. Note that different types of events require different frequencies to be effective. The frequency is how many ticks EventManager will wait before checking if that type of event has happened. If multiple scripts or plugins use the same event type, the smallest frequency is the one that is used, so you might get events triggered more often than the frequency you use here.
Examples
--------
Spawn dragon breath on each item attempt to contaminate wound::

44
NEWS

@ -1,27 +1,55 @@
DFHack future
- Is not yet known.
DFHack v0.34.11-r4
New commands:
- restrictliquid - Restrict traffic on every visible square with liquid.
- diggingInvaders - allows invaders to dig and/or deconstruct walls and buildings in order to get at your dwarves.
- digFlood - automatically dig out specified veins as they are revealed
- enable, disable - Built-in commands that can be used to enable/disable many plugins.
- restrictice - Restrict traffic on squares above visible ice.
- restrictliquid - Restrict traffic on every visible square with liquid.
- treefarm - automatically chop trees and dig obsidian
New scripts:
- masspit: designate caged creatures in a zone for pitting
- autobutcher: A GUI front-end for the autobutcher plugin.
- invasionNow: trigger an invasion, or many
- locate_ore: scan the map for unmined ore veins
- masspit: designate caged creatures in a zone for pitting
- multicmd: run a sequence of dfhack commands, separated by ';'
- autobutcher: A GUI front-end for the autobutcher plugin.
- startdwarf: change the number of dwarves for a new embark
- digmat: dig veins/layers tile by tile, as discovered
Misc improvements:
- 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
- core: fix SC_WORLD_(UN)LOADED event for arena mode
- exterminate: renamed from slayrace, add help message, add butcher mode
- autoSyndrome: disable by default
- fastdwarf: fixed bug involving fastdwarf and teledwarf being on at the same time
- magmasource: rename to 'source', allow water/magma sources/drains
- ruby: add df.dfhack_run "somecommand"
- magmasource: rename to source, allow water/magma sources/drains
- syndromeTrigger: replaces and extends trueTransformation. Can trigger things when syndromes are added for any reason.
- tiletypes: support changing tile material to arbitrary stone.
- workNow: can optionally look for jobs when jobs are completed
New tweaks:
- hive-crash: Prevent crash if bees die in a hive with ungathered products (bug 6368).
New plugins:
- 3dveins: Reshapes all veins on the map in a way that flows between Z levels. May be unstable. Backup before using.
- autotrade: Automatically send items in marked stockpiles to trade depot, when trading is possible.
- buildingplan: Place furniture before it's built
- 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.
- outsideOnly: make raw-specified buildings impossible to build inside
- resume: A plugin to help display and resume suspended constructions conveniently
- stocks: An improved stocks display screen.
Internals:
- Core: there is now a per-save dfhack.init file for when the save is loaded, and another for when it is unloaded
- EventManager: fixed job completion detection, fixed removal of TICK events, added EQUIPMENT_CHANGE event
- Lua API for a better random number generator and perlin noise functions.
- Once: easy way to make sure something happens once per run of DF, such as an error message
DFHack v0.34.11-r3
Internals:

File diff suppressed because it is too large Load Diff

@ -209,6 +209,25 @@ or is a prefix ending at a '/' boundary would be considered for execution, i.e.
for context ``foo/bar/baz``, possible matches are any of ``@foo/bar/baz``, ``@foo/bar``,
``@foo`` or none.
Enabling plugins
================
Many plugins can be in a distinct enabled or disabled state. Some of
them activate and deactivate automatically depending on the contents
of the world raws. Others store their state in world data. However a
number of them have to be enabled globally, and the init file is the
right place to do it.
Most of such plugins support the built-in ``enable`` and ``disable``
commands. Calling them at any time without arguments prints a list
of enabled and disabled plugins, and shows whether that can be changed
through the same commands.
To enable or disable plugins that support this, use their names as
arguments for the command::
enable manipulator search
========
Commands
@ -342,6 +361,24 @@ Usage:
Map modification
================
3dveins
-------
Removes all existing veins from the map and generates new ones using
3D Perlin noise, in order to produce a layout that smoothly flows between
Z levels. The vein distribution is based on the world seed, so running
the command for the second time should produce no change. It is best to
run it just once immediately after embark.
This command is intended as only a cosmetic change, so it takes
care to exactly preserve the mineral counts reported by ``prospect all``.
The amounts of different layer stone may slightly change in some cases
if vein mass shifts between Z layers.
This command is very unlikely to work on maps generated before version 0.34.08.
Note that there is no undo option other than restoring from backup.
changelayer
-----------
Changes material of the geology layer under cursor to the specified inorganic
@ -489,6 +526,16 @@ Options:
:show X: Marks the selected map feature as discovered.
:hide X: Marks the selected map feature as undiscovered.
infiniteSky
-----------
Automatically allocates new z-levels of sky at the top of the map as you build up, or on request allocates many levels all at once.
Examples:
``infiniteSky n``
Raise the sky by n z-levels.
``infiniteSky enable/disable``
Enables/disables monitoring of constructions. If you build anything in the second to highest z-level, it will allocate one more sky level. This is so you can continue to build stairs upward.
liquids
-------
Allows adding magma, water and obsidian to the game. It replaces the normal
@ -557,6 +604,33 @@ Or this:
This will hide previously revealed tiles (or show hidden with the 0 option).
More recently, the tool supports changing the base material of the tile to
an arbitrary stone from the raws, by creating new veins as required. Note
that this mode paints under ice and constructions, instead of overwriting
them. To enable, use:
::
paint stone MICROCLINE
This mode is incompatible with the regular ``material`` setting, so changing
it cancels the specific stone selection:
::
paint material ANY
Since different vein types have different drop rates, it is possible to choose
which one to use in painting:
::
paint veintype CLUSTER_SMALL
When the chosen type is ``CLUSTER`` (the default), the tool may automatically
choose to use layer stone or lava stone instead of veins if its material matches
the desired one.
Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword:
::
@ -761,6 +835,112 @@ showmood
--------
Shows all items needed for the currently active strange mood.
Mod Interaction
===============
This section describes plugins that interact with information in the raw files to add new features that cannot be achieved by only changing raw files.
autoSyndrome
------------
This plugin replaces "boiling rock" syndromes. Without this plugin, it is possible to add a syndrome to a unit by making the unit perform a custom reaction. First, add the syndrome to a rock which boils at room temperature. Make sure that the syndrome is spread by inhaling. Then, add a custom reaction which creates that rock. When the reaction is performed, the rock will be created, then boil. Hopefully, the dwarf will inhale the gas and become afflicted with the syndrome. This has disadvantages.
1. The creating unit might not inhale the gas. This makes it difficult to balance gameplay, as it is hard to measure this probability.
2. A different unit might inhale the gas. Pets or children might inhale the gas, which may be undesired.
To fix this problem, you can use autoSyndrome. The plugin monitors when custom reactions are completed within dwarf mode. If certain conditions are met, then the syndrome is immediately applied. The conditions are described below in priority order. If multiple products are created by the reaction, each one is considered independently in order. If a rock has multiple syndromes, each one is considered independently. If the conditions are all met, then the appropriate target will be instantly afficted with the appropriate syndrome, and the syndrome will behave just like any other.
1. The recently completed reaction must be a custom reaction, not a built-in one.
2. The product must be an inorganic boulder. Its boiling temperature is ignored.
3. The syndrome must have ``[SYN_CLASS:\AUTO_SYNDROME]``.
4. If the syndrome has ``[SYN_CLASS:\ALLOW_MULTIPLE_TARGETS]`` then an unbounded number of units can be targetted by the syndrome. If absent, at most one will be affected, and the worker will be considered first.
5. If the syndrome has ``[SYN_CLASS:\ALLOW_NONWORKER_TARGETS]`` then units that are in the building might be targetted. If absent, only the worker will be targetted. Even if present, the worker will be considered first.
6. If the syndrome has ``[SYN_CLASS:\PRESERVE_ROCK]`` then the stone or stones created will not be destroyed. If absent, they will be. Leaving this out ensures that gasses from boiling rocks will not sidestep the plugin, affecting nearby units using existing gameplay mechanics (because said gasses will never get a chance to be created).
7. If there are no ``SYN_IMMUNE_CREATURE``, ``SYN_AFFECTED_CREATURE``, ``SYN_IMMUNE_CLASS``, or ``SYN_AFFECTED_CLASS`` then any creature can be targetted, if it meets the above restrictions.
8. If the target creature is specified as ``SYN_IMMUNE_CREATURE`` in the syndrome tags, then it will not be affected.
9. If it is specified as ``SYN_AFFECTED_CREATURE`` then it will be affected.
10. If it has ``SYN_IMMUNE_CLASS`` it will not be affected.
11. It it has ``SYN_AFFECTED_CLASS`` it will be affected.
Note that tags like ``[SYN_INHALED]`` are ignored.
The plugin will work for transformations, but doesn't seem to properly apply CE_BLEEDING, for example. Further testing is required.
If the reaction is run twice, by default, a second instance of the syndrome is added. This behavior can be customized. With ``[SYN_CLASS:\RESET_POLICY DoNothing]``, units already afflicted with the syndrome will not be considered for syndrome application. With ``[SYN_CLASS:\RESET_POLICY ResetDuration]`` the existing syndrome timer is reset. With ``[SYN_CLASS:\RESET_POLICY AddDuration]`` the duration of the longest effect in the syndrome is added to the remaining duration of the existing syndrome. The tag ``[SYN_CLASS:\RESET_POLICY NewInstance]`` re-establishes the default behavior. If more than one such tag is present, the last one takes priority.
It is also possible to directly trigger dfhack plugins and scripts using autoSyndrome. If a syndrome has ``[SYN_CLASS:\COMMAND]`` then all following ``SYN_CLASS`` tags will be used to create a console command. The command will behave exactly as if the user had typed it in to the dfhack console. For example
``[SYN_CLASS:\COMMAND]``
``[SYN_CLASS:prospect]``
``[SYN_CLASS:all]``
would run the command "prospect all" whenever the given rock is created. The ``\AUTO_SYNDROME`` tag IS required for commands to execute. Note that since all ``SYN_CLASS`` tags after the ``\COMMAND`` tag are interpreted as part of the command, tags like ``\WORKER_ONLY`` must be placed before ``\COMMAND``, or not at all in order to work.
There are also certain "special" arguments that can be passed.
1. ``\LOCATION``: pass the x, y, and z coordinates of the work tile of the building which completed the job as separate arguments.
2. ``\WORKER_ID``: pass the unit id of the unit that finished the job as an argument.
3. ``\REACTION_INDEX``: pass the id of the completed reaction as an argument.
A note on spaces: when a plugin command executes in dfhack, it always has a list of arguments. Arguments are strings which tell the plugin what the user wants it to do. When the user types in a command, arguments will be separated by whitespace. However, if autoSyndrome is given a tag like ``[SYN_CLASS:123 abcde]`` after a ``[SYN_CLASS:\COMMAND]`` tag, this will still be treated as ONE argument. This may or may not cause problems, depending on the command in question. To be safe, never include spaces in as an argument to a command.
For example, suppose a reaction creates a rock which has a syndrome with the ``SYN_CLASS`` tags ``\AUTO_SYNDROME``, ``\COMMAND``, ``printArgs``, ``id_comes_next``, ``\WORKER_ID``, ``location_comes_next``, ``\LOCATION`` in that order. Suppose the reaction is done at ``(35,96,112)`` by unit number 15. This would be equivalent to typing ``printArgs id_comes_next 15 location_comes_next 35 96 112`` into the DFHack console and pressing enter.
Other syndrome classes that occur before ``\COMMAND`` (or in absence of any ``\COMMAND`` synclass) are ignored.
It is not currently possible to execute more than one command per syndrome. Instead, use multiple syndromes to achieve the same effect. Note that it is possible to have multiple syndromes on the same stone.
Again, note that plugins AND scripts can be executed this way, and arguments will be passed according to the same rules.
outsideOnly
-----------
This plugin makes it so that buildings whose names begin with ``OUTSIDE_ONLY`` cannot be built inside. If the player attempts to do so, the building will automatically be deconstructed.
syndromeTrigger
---------------
This plugin allows DFHack commands to be executed whenever a unit becomes afflicted with a syndrome. This can happen due to a boiling rock, an interaction, autoSyndrome, etc. Regardless of the cause, if the appropriate ``SYN_CLASS`` tags are present, the command will execute.
The syntax is very similar to autoSyndrome. If the syndrome has the ``\COMMAND`` tag, every subsequent ``SYN_CLASS`` tag will be used to create a console command. The following tags are "special":
1. ``\LOCATION``: this will be replaced by three arguments, one for each coordinate of the location of the unit.
2. ``\UNIT_ID``: this will be replaced by the identifier of the unit afllicted with the syndrome.
3. ``\SYNDROME_ID``: this will be replaced by the identifier of the syndrome in question.
If there is a ``[SYN_CLASS:\AUTO_SYNDROME]`` tag, then the command, if any, will NOT be executed by syndromeTrigger, because it should already have been executed by autoSyndrome.
True Transformation
...................
The syndromeTrigger plugin also allows true, permanent transformations. In vanilla DF, if syndrome A transforms dwarves into goblins permanently, and syndrome B transforms goblins into dragons permanently, then syndrome B would NOT properly transform goblins that had been transformed from dwarves. True transformations can be achieved with this plugin.
True transformations work differently. First, the unit transforms into a temporary, distinct, intermediate form. While transformed, this plugin overwrites their "original" unit type with the desired type. When the transformation wears off, they will turn "back" into the new unit type. Once truly transformed, units will function as if they had always been the new unit type. Equipment may be dropped on transformation, but relationships and experience should be maintained.
Suppose you want to transform dwarves into goblins. First, make a syndrome that turns dwarves into ducks for 1 tick (start:0:end:1). It should work with ``END:1``, but if it doesn't, try ``END:5``. You MUST use ``START:0``. Setting the end time very high will make the intermediate form take longer, and should have no other influence on the behavior of this plugin. The intermediate form must NOT be the same as the original form, and it must NOT be the same as the final form, or the game will crash. Add the following tags:
``[SYN_CLASS:\PERMANENT]``
``[SYN_CLASS:GOBLIN]``
``[SYN_CLASS:MALE]``
Note that you must use the "official" (usually allcaps) name of the target creature/caste, not necessarily the name used in game. For example, you would use ``BIRD_DUCK``, ``MALE``, instead of ``drake``.
It is perfectly fine to use syndromeTrigger along with autoSyndrome. This means that you can, for example, trigger a true transformation using a reaction. It is also possible to trigger a true transformation using an interaction, or another plugin that adds syndromes, so long as that other plugin does not interfere with the tags required for this one to work properly.
Designations
============
@ -910,6 +1090,23 @@ Options:
:down: down stairs
:clear: clear designation
digFlood
--------
Automatically digs out specified veins as they are discovered. It runs once every time a dwarf finishes a dig job. It will only dig out appropriate tiles that are adjacent to the finished dig job. To add a vein type, use `digFlood 1 [type]`. This will also enable the plugin. To remove a vein type, use `digFlood 0 [type] 1` to disable, then remove, then re-enable.
`digFlood 0` disable
`digFlood 1` enable
`digFlood 0 MICROCLINE COAL_BITUMINOUS 1` disable plugin, remove microcline and bituminous coal from monitoring, then re-enable the plugin
`digFlood CLEAR` remove all inorganics from monitoring
`digFlood digAll1` ignore the monitor list and dig any vein
`digFlood digAll0` disable digAll mode
See `help digFlood` for details.
filltraffic
-----------
@ -1146,6 +1343,9 @@ Subcommands that persist until disabled or DF quit:
(i.e. the more units you have, the slower it becomes), and making
the units spar more.
:hive-crash: The hive code crashes if there are ungathered products in a hive without bees (bug 6368).
This tweak prevents it by auto-gathering the products if this happens.
fix-armory
----------
@ -1446,7 +1646,6 @@ Maintain 10-100 locally-made crafts of exceptional quality.
workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
Fortress activity management
============================
@ -1974,6 +2173,28 @@ To skip a row in your design, use a single ``;``.
The script takes the plan filename, starting from the root df folder.
invasion-now
============
Triggers an invasion, or several in the near future.
`invasion-now civName` trigger an invasion from the civilization with the id civName, starting in about ten ticks
`invasion-now civName start` trigger an invasion from civName in a number of ticks between 10*start and 11*start-1 (inclusive)
`invasion-now civName start end` trigger an invasion from civName in about 10*start ticks, and continue triggering invasions every ten ticks afterward until about 10*end ticks have passed
Probably fails if the start time of a triggered invasion is later than the start of the next year.
digmat
======
Designates a tile for digging. Monitors the tile, and when it is dug out, add
surrounding discovered tiles of the same material for digging. Similar to 'digv',
but less cheaty. Works for stone layers, soil layers, veins, etc.
If the tile you call the script on already has a digging designation, reuse the
same designation for future digging (eg dig up/downstairs). When digging stairs,
also designate tiles on z-1 and z+1 when they are discovered.
superdwarf
==========
Similar to fastdwarf, per-creature.

@ -147,8 +147,27 @@ tweak military-color-assigned
# remove inverse dependency of squad training speed on unit list size and use more sparring
tweak military-training
# enable autoSyndrome
autoSyndrome enable
# prevent crash if bees die in a hive with ungathered products by insta-gathering them
tweak hive-crash
###########################
# Globally acting plugins #
###########################
# Dwarf Manipulator (simple in-game Dwarf Therapist replacement)
enable manipulator
# Search tool in various screens (by falconne)
enable search
# Improved build material selection interface (by falconne)
enable automaterial
# Other interface improvement tools
#enable dwarfmonitor mousequery autotrade buildingplan resume zone
# Auto Syndrome
#autoSyndrome enable
###########
# Scripts #

@ -120,11 +120,13 @@ include/modules/Maps.h
include/modules/MapCache.h
include/modules/Materials.h
include/modules/Notes.h
include/modules/Random.h
include/modules/Screen.h
include/modules/Translation.h
include/modules/Vermin.h
include/modules/World.h
include/modules/Graphic.h
include/modules/Once.h
)
SET( MODULE_SOURCES
@ -138,15 +140,18 @@ modules/Gui.cpp
modules/Items.cpp
modules/Job.cpp
modules/kitchen.cpp
modules/MapCache.cpp
modules/Maps.cpp
modules/Materials.cpp
modules/Notes.cpp
modules/Random.cpp
modules/Screen.cpp
modules/Translation.cpp
modules/Vermin.cpp
modules/World.cpp
modules/Graphic.cpp
modules/Windows.cpp
modules/Once.cpp
)
IF(WIN32)
@ -360,7 +365,7 @@ if(BUILD_DEVEL)
# without the '/', the directory itself is installed
install(DIRECTORY include/
DESTINATION ${DFHACK_INCLUDES_DESTINATION}
FILES_MATCHING PATTERN "*.h" ) #linux: include
FILES_MATCHING PATTERN "*.h" PATTERN "*.inc" ) #linux: include
# Building the docs
IF(BUILD_DOXYGEN)
add_subdirectory (doc)

@ -259,7 +259,7 @@ static void listScripts(PluginManager *plug_mgr, std::map<string,string> &pset,
pset[prefix + files[i].substr(0, files[i].size()-4)] = help;
}
else if (plug_mgr->eval_ruby && hasEnding(files[i], ".rb"))
else if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() && hasEnding(files[i], ".rb"))
{
std::string help = getScriptHelp(path + files[i], "# ");
@ -312,6 +312,9 @@ static command_result runLuaScript(color_ostream &out, std::string name, vector<
static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, std::string name, vector<string> &args)
{
if (!plug_mgr->ruby || !plug_mgr->ruby->is_enabled())
return CR_FAILURE;
std::string rbcmd = "$script_args = [";
for (size_t i = 0; i < args.size(); i++)
rbcmd += "'" + args[i] + "', ";
@ -319,7 +322,7 @@ static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr,
rbcmd += "catch(:script_finished) { load './hack/scripts/" + name + ".rb' }";
return plug_mgr->eval_ruby(out, rbcmd.c_str());
return plug_mgr->ruby->eval_ruby(out, rbcmd.c_str());
}
command_result Core::runCommand(color_ostream &out, const std::string &command)
@ -450,7 +453,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
con.print("%s: %s\n", parts[0].c_str(), help.c_str());
return CR_OK;
}
if (plug_mgr->eval_ruby && fileExists(filename + ".rb"))
if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() && fileExists(filename + ".rb"))
{
string help = getScriptHelp(filename + ".rb", "# ");
con.print("%s: %s\n", parts[0].c_str(), help.c_str());
@ -544,6 +547,56 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
}
}
}
else if( first == "enable" || first == "disable" )
{
CoreSuspender suspend;
bool enable = (first == "enable");
if(parts.size())
{
command_result res = CR_OK;
for (size_t i = 0; i < parts.size(); i++)
{
Plugin * plug = plug_mgr->getPluginByName(parts[i]);
if(!plug)
{
res = CR_NOT_FOUND;
con.printerr("No such plugin: %s\n", parts[i].c_str());
}
else if (!plug->can_set_enabled())
{
res = CR_NOT_IMPLEMENTED;
con.printerr("Cannot %s plugin: %s\n", first.c_str(), parts[i].c_str());
}
else
{
res = plug->set_enabled(con, enable);
if (res != CR_OK || plug->is_enabled() != enable)
con.printerr("Could not %s plugin: %s\n", first.c_str(), parts[i].c_str());
}
}
return res;
}
else
{
for(size_t i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
if (!plug->can_be_enabled()) continue;
con.print(
"%20s\t%-3s%s\n",
(plug->getName()+":").c_str(),
plug->is_enabled() ? "on" : "off",
plug->can_set_enabled() ? "" : " (controlled elsewhere)"
);
}
}
}
else if(first == "ls" || first == "dir")
{
bool all = false;
@ -584,6 +637,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
" load PLUGIN|all - Load a plugin by name or load all possible plugins.\n"
" unload PLUGIN|all - Unload a plugin or all loaded plugins.\n"
" reload PLUGIN|all - Reload a plugin or all loaded plugins.\n"
" enable/disable PLUGIN - Enable or disable a plugin if supported.\n"
"\n"
"plugins:\n"
);
@ -716,7 +770,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
if (fileExists(filename + ".lua"))
res = runLuaScript(con, first, parts);
else if (plug_mgr->eval_ruby && fileExists(filename + ".rb"))
else if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() && fileExists(filename + ".rb"))
res = runRubyScript(con, plug_mgr, first, parts);
else if (try_autocomplete(con, first, completed))
return CR_NOT_IMPLEMENTED;// runCommand(con, completed, parts);
@ -1160,7 +1214,8 @@ void Core::doUpdate(color_ostream &out, bool first_update)
{
df::world_data *wdata = df::global::world->world_data;
// when the game is unloaded, world_data isn't deleted, but its contents are
if (wdata && !wdata->sites.empty())
// regions work to detect arena too
if (wdata && !wdata->regions.empty())
new_wdata = wdata;
new_mapdata = df::global::world->map.block_index;
}
@ -1298,6 +1353,26 @@ void Core::onUpdate(color_ostream &out)
Lua::Core::onUpdate(out);
}
static void handleLoadAndUnloadScripts(Core* core, color_ostream& out, state_change_event event) {
//TODO: use different separators for windows
#ifdef _WIN32
static const std::string separator = "\\";
#else
static const std::string separator = "/";
#endif
std::string rawFolder = "data" + separator + "save" + separator + (df::global::world->cur_savegame.save_dir) + separator + "raw" + separator;
switch(event) {
case SC_WORLD_LOADED:
core->loadScriptFile(out, rawFolder + "onLoad.init", true);
break;
case SC_WORLD_UNLOADED:
core->loadScriptFile(out, rawFolder + "onUnload.init", true);
break;
default:
break;
}
}
void Core::onStateChange(color_ostream &out, state_change_event event)
{
EventManager::onStateChange(out, event);
@ -1307,6 +1382,8 @@ void Core::onStateChange(color_ostream &out, state_change_event event)
plug_mgr->OnStateChange(out, event);
Lua::Core::onStateChange(out, event);
handleLoadAndUnloadScripts(this, out, event);
}
// FIXME: needs to terminate the IO threads and properly dismantle all the machinery involved.

@ -393,6 +393,8 @@ SDL_GetKeyState
Uint8 * SDLCALL SDL_GetKeyState(int *numkeys);
SDL_PollEvent
int SDLCALL SDL_PollEvent(SDL_Event *event);
SDL_PushEvent
int SDLCALL SDL_PushEvent(SDL_Event *event);
*/
static int (*_SDL_EnableKeyRepeat)(int delay, int interval) = 0;
@ -437,6 +439,12 @@ DFhackCExport int SDL_PollEvent(SDL::Event* event)
return orig_return;
}
static int (*_SDL_PushEvent)(SDL::Event* event) = 0;
DFhackCExport int SDL_PushEvent(SDL::Event* event)
{
return _SDL_PushEvent(event);
}
/***** error handling
SDL_GetError
char * SDLCALL SDL_GetError(void);
@ -762,6 +770,7 @@ bool FirstCall()
_SDL_LockSurface = (int (*)(void*))GetProcAddress(realSDLlib,"SDL_LockSurface");
_SDL_MapRGB = (uint32_t (*)(void*, uint8_t, uint8_t, uint8_t))GetProcAddress(realSDLlib,"SDL_MapRGB");
_SDL_PollEvent = (int (*)(SDL::Event*))GetProcAddress(realSDLlib,"SDL_PollEvent");
_SDL_PushEvent = (int (*)(SDL::Event*))GetProcAddress(realSDLlib,"SDL_PushEvent");
_SDL_Quit = (void (*)())GetProcAddress(realSDLlib,"SDL_Quit");
_SDL_RWFromFile = (void*(*)(const char*, const char*))GetProcAddress(realSDLlib,"SDL_RWFromFile");
_SDL_RemoveTimer = (bool (*)(void*))GetProcAddress(realSDLlib,"SDL_RemoveTimer");

@ -51,6 +51,7 @@ distribution.
#include "modules/Burrows.h"
#include "modules/Buildings.h"
#include "modules/Constructions.h"
#include "modules/Random.h"
#include "LuaWrapper.h"
#include "LuaTools.h"
@ -92,6 +93,10 @@ using namespace DFHack;
using namespace DFHack::LuaWrapper;
using Screen::Pen;
using Random::MersenneRNG;
using Random::PerlinNoise1D;
using Random::PerlinNoise2D;
using Random::PerlinNoise3D;
void dfhack_printerr(lua_State *S, const std::string &str);
@ -739,12 +744,10 @@ void Lua::Push(lua_State *L, const Screen::Pen &info)
return;
}
void *pdata = lua_newuserdata(L, sizeof(Pen));
new (L) Pen(info);
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
lua_setmetatable(L, -2);
new (pdata) Pen(info);
}
static Pen *check_pen_native(lua_State *L, int index)
@ -1035,6 +1038,200 @@ static void OpenPen(lua_State *state)
lua_pop(state, 1);
}
/********************
* Random generator *
********************/
static int DFHACK_RANDOM_TOKEN = 0;
static MersenneRNG *check_random_native(lua_State *L, int index)
{
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN);
if (!lua_getmetatable(L, index) || !lua_rawequal(L, -1, -2))
luaL_argerror(L, index, "not a random generator object");
lua_pop(L, 2);
return (MersenneRNG*)lua_touserdata(L, index);
}
static int dfhack_random_init(lua_State *L)
{
lua_settop(L, 3);
MersenneRNG *prng = check_random_native(L, 1);
if (lua_isnil(L, 2))
prng->init();
else
{
std::vector<uint32_t> data;
int tcnt = luaL_optint(L, 3, 1);
if (lua_isnumber(L, 2))
data.push_back(lua_tounsigned(L, 2));
else if (lua_istable(L, 2))
{
int cnt = lua_rawlen(L, 2);
if (cnt <= 0)
luaL_argerror(L, 2, "empty list in dfhack.random.init");
for (int i = 1; i <= cnt; i++)
{
lua_rawgeti(L, 2, i);
if (!lua_isnumber(L, -1))
luaL_argerror(L, 2, "not a number in dfhack.random.init argument");
data.push_back(lua_tounsigned(L, -1));
lua_pop(L, 1);
}
}
else
luaL_argerror(L, 2, "dfhack.random.init argument not number or table");
prng->init(data.data(), data.size(), tcnt);
}
lua_settop(L, 1);
return 1;
}
static int dfhack_random_new(lua_State *L)
{
new (L) MersenneRNG();
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN);
lua_setmetatable(L, -2);
lua_insert(L, 1);
return dfhack_random_init(L);
}
static int dfhack_random_random(lua_State *L)
{
MersenneRNG *prng = check_random_native(L, 1);
lua_settop(L, 2);
if (lua_gettop(L) < 2 || lua_isnil(L, 2))
lua_pushunsigned(L, prng->random());
else
lua_pushunsigned(L, prng->random(luaL_optunsigned(L, 2, 0)));
return 1;
}
static int dfhack_random_drandom(lua_State *L)
{
lua_pushnumber(L, check_random_native(L, 1)->drandom());
return 1;
}
static int dfhack_random_drandom0(lua_State *L)
{
lua_pushnumber(L, check_random_native(L, 1)->drandom0());
return 1;
}
static int dfhack_random_drandom1(lua_State *L)
{
lua_pushnumber(L, check_random_native(L, 1)->drandom1());
return 1;
}
static int dfhack_random_unitrandom(lua_State *L)
{
lua_pushnumber(L, check_random_native(L, 1)->unitrandom());
return 1;
}
static int dfhack_random_unitvector(lua_State *L)
{
MersenneRNG *prng = check_random_native(L, 1);
int size = luaL_optint(L, 2, 3);
if (size <= 0 || size > 32)
luaL_argerror(L, 2, "vector size must be positive");
luaL_checkstack(L, size, "not enough stack in dfhack.random.unitvector");
std::vector<double> buf(size);
prng->unitvector(buf.data(), size);
for (int i = 0; i < size; i++)
lua_pushnumber(L, buf[i]);
return size;
}
static int eval_perlin_1(lua_State *L)
{
auto &gen = *(PerlinNoise1D<float>*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushnumber(L, gen(luaL_checknumber(L, 1)));
return 1;
}
static int eval_perlin_2(lua_State *L)
{
auto &gen = *(PerlinNoise2D<float>*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushnumber(L, gen(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
return 1;
}
static int eval_perlin_3(lua_State *L)
{
auto &gen = *(PerlinNoise3D<float>*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushnumber(L, gen(luaL_checknumber(L, 1), luaL_checknumber(L, 2), luaL_checknumber(L, 3)));
return 1;
}
static int dfhack_random_perlin(lua_State *L)
{
MersenneRNG *prng = check_random_native(L, 1);
int size = luaL_optint(L, 2, 3);
switch (size)
{
case 1: {
auto pdata = new (L) PerlinNoise1D<float>();
pdata->init(*prng);
lua_pushcclosure(L, eval_perlin_1, 1);
break;
}
case 2: {
auto pdata = new (L) PerlinNoise2D<float>();
pdata->init(*prng);
lua_pushcclosure(L, eval_perlin_2, 1);
break;
}
case 3: {
auto pdata = new (L) PerlinNoise3D<float>();
pdata->init(*prng);
lua_pushcclosure(L, eval_perlin_3, 1);
break;
}
default:
luaL_argerror(L, 2, "perlin noise dimension must be 1, 2 or 3");
}
return 1;
}
static const luaL_Reg dfhack_random_funcs[] = {
{ "new", dfhack_random_new },
{ "init", dfhack_random_init },
{ "random", dfhack_random_random },
{ "drandom", dfhack_random_drandom },
{ "drandom0", dfhack_random_drandom0 },
{ "drandom1", dfhack_random_drandom1 },
{ "unitrandom", dfhack_random_unitrandom },
{ "unitvector", dfhack_random_unitvector },
{ "perlin", dfhack_random_perlin },
{ NULL, NULL }
};
static void OpenRandom(lua_State *state)
{
luaL_getsubtable(state, lua_gettop(state), "random");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN);
luaL_setfuncs(state, dfhack_random_funcs, 0);
lua_pop(state, 1);
}
/************************
* Wrappers for C++ API *
************************/
@ -2023,6 +2220,7 @@ void OpenDFHackApi(lua_State *state)
OpenPersistent(state);
OpenMatinfo(state);
OpenPen(state);
OpenRandom(state);
LuaWrapper::SetFunctionWrappers(state, dfhack_module);
OpenModule(state, "gui", dfhack_gui_module);

@ -172,6 +172,8 @@ Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _f
plugin_onupdate = 0;
plugin_onstatechange = 0;
plugin_rpcconnect = 0;
plugin_enable = 0;
plugin_is_enabled = 0;
state = PS_UNLOADED;
access = new RefLock();
}
@ -245,6 +247,8 @@ bool Plugin::load(color_ostream &con)
plugin_shutdown = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_shutdown");
plugin_onstatechange = (command_result (*)(color_ostream &, state_change_event)) LookupPlugin(plug, "plugin_onstatechange");
plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect");
plugin_enable = (command_result (*)(color_ostream &,bool)) LookupPlugin(plug, "plugin_enable");
plugin_is_enabled = (bool*) LookupPlugin(plug, "plugin_is_enabled");
plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby");
index_lua(plug);
this->name = *plug_name;
@ -254,11 +258,15 @@ bool Plugin::load(color_ostream &con)
{
state = PS_LOADED;
parent->registerCommands(this);
if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled)
con.printerr("Plugin %s has no enabled var!\n", name.c_str());
return true;
}
else
{
con.printerr("Plugin %s has failed to initialize properly.\n", filename.c_str());
plugin_is_enabled = 0;
plugin_onupdate = 0;
reset_lua();
ClosePlugin(plugin_lib);
state = PS_BROKEN;
@ -294,6 +302,8 @@ bool Plugin::unload(color_ostream &con)
if(plugin_shutdown)
cr = plugin_shutdown(con);
// cleanup...
plugin_is_enabled = 0;
plugin_onupdate = 0;
reset_lua();
parent->unregisterCommands(this);
commands.clear();
@ -408,6 +418,12 @@ bool Plugin::can_invoke_hotkey(const std::string & command, df::viewscreen *top
command_result Plugin::on_update(color_ostream &out)
{
// Check things that are implicitly protected by the suspend lock
if (!plugin_onupdate)
return CR_NOT_IMPLEMENTED;
if (plugin_is_enabled && !*plugin_is_enabled)
return CR_OK;
// Grab mutex and call the thing
command_result cr = CR_NOT_IMPLEMENTED;
access->lock_add();
if(state == PS_LOADED && plugin_onupdate)
@ -419,6 +435,21 @@ command_result Plugin::on_update(color_ostream &out)
return cr;
}
command_result Plugin::set_enabled(color_ostream &out, bool enable)
{
command_result cr = CR_NOT_IMPLEMENTED;
access->lock_add();
if(state == PS_LOADED && plugin_is_enabled && plugin_enable)
{
cr = plugin_enable(out, enable);
if (cr == CR_OK && enable != is_enabled())
cr = CR_FAILURE;
}
access->lock_sub();
return cr;
}
command_result Plugin::on_state_change(color_ostream &out, state_change_event event)
{
command_result cr = CR_NOT_IMPLEMENTED;
@ -524,6 +555,37 @@ void Plugin::reset_lua()
}
}
int Plugin::lua_is_enabled(lua_State *state)
{
auto obj = (Plugin*)lua_touserdata(state, lua_upvalueindex(1));
RefAutoinc lock(obj->access);
if (obj->state == PS_LOADED && obj->plugin_is_enabled)
lua_pushboolean(state, obj->is_enabled());
else
lua_pushnil(state);
return 1;
}
int Plugin::lua_set_enabled(lua_State *state)
{
lua_settop(state, 1);
bool val = lua_toboolean(state, 1);
auto obj = (Plugin*)lua_touserdata(state, lua_upvalueindex(1));
RefAutoinc lock(obj->access);
color_ostream *out = Lua::GetOutput(state);
if (obj->state == PS_LOADED && obj->plugin_enable)
lua_pushboolean(state, obj->set_enabled(*out, val) == CR_OK);
else
luaL_error(state, "plugin %s unloaded, cannot enable or disable", obj->name.c_str());
return 1;
}
int Plugin::lua_cmd_wrapper(lua_State *state)
{
auto cmd = (LuaCommand*)lua_touserdata(state, lua_upvalueindex(1));
@ -561,6 +623,19 @@ void Plugin::open_lua(lua_State *state, int table)
RefAutolock lock(access);
if (plugin_is_enabled)
{
lua_pushlightuserdata(state, this);
lua_pushcclosure(state, lua_is_enabled, 1);
lua_setfield(state, table, "isEnabled");
}
if (plugin_enable)
{
lua_pushlightuserdata(state, this);
lua_pushcclosure(state, lua_set_enabled, 1);
lua_setfield(state, table, "setEnabled");
}
for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it)
{
lua_pushlightuserdata(state, it->second);
@ -604,7 +679,7 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn)
PluginManager::PluginManager(Core * core)
{
cmdlist_mutex = new mutex();
eval_ruby = NULL;
ruby = NULL;
}
PluginManager::~PluginManager()
@ -699,7 +774,7 @@ void PluginManager::registerCommands( Plugin * p )
belongs[cmds[i].name] = p;
}
if (p->plugin_eval_ruby)
eval_ruby = p->plugin_eval_ruby;
ruby = p;
cmdlist_mutex->unlock();
}
@ -713,6 +788,6 @@ void PluginManager::unregisterCommands( Plugin * p )
belongs.erase(cmds[i].name);
}
if (p->plugin_eval_ruby)
eval_ruby = NULL;
ruby = NULL;
cmdlist_mutex->unlock();
}

@ -26,8 +26,250 @@ distribution.
#include "TileTypes.h"
#include "Export.h"
#include <map>
using namespace DFHack;
const int NUM_TILETYPES = 1+(int)ENUM_LAST_ITEM(tiletype);
const int NUM_MATERIALS = 1+(int)ENUM_LAST_ITEM(tiletype_material);
const int NUM_CVTABLES = 1+(int)tiletype_material::CONSTRUCTION;
typedef std::map<df::tiletype_variant, df::tiletype> T_VariantMap;
typedef std::map<std::string, T_VariantMap> T_DirectionMap;
typedef std::map<df::tiletype_special, T_DirectionMap> T_SpecialMap;
typedef std::map<df::tiletype_shape, T_SpecialMap> T_ShapeMap;
typedef T_ShapeMap T_MaterialMap[NUM_MATERIALS];
static bool tables_ready = false;
static T_MaterialMap tile_table;
static df::tiletype tile_to_mat[NUM_CVTABLES][NUM_TILETYPES];
static df::tiletype find_match(
df::tiletype_material mat, df::tiletype_shape shape, df::tiletype_special special,
std::string dir, df::tiletype_variant variant, bool warn
) {
using namespace df::enums::tiletype_shape;
using namespace df::enums::tiletype_special;
if (mat < 0 || mat >= NUM_MATERIALS)
return tiletype::Void;
auto &sh_map = tile_table[mat];
if (!sh_map.count(shape))
{
if (warn)
{
fprintf(
stderr, "NOTE: No shape %s in %s.\n",
enum_item_key(shape).c_str(), enum_item_key(mat).c_str()
);
}
switch (shape)
{
case BROOK_BED:
if (sh_map.count(FORTIFICATION)) { shape = FORTIFICATION; break; }
case FORTIFICATION:
if (sh_map.count(WALL)) { shape = WALL; break; }
return tiletype::Void;
case BROOK_TOP:
case BOULDER:
case PEBBLES:
if (sh_map.count(FLOOR)) { shape = FLOOR; break; }
return tiletype::Void;
default:
return tiletype::Void;
};
}
auto &sp_map = sh_map[shape];
if (!sp_map.count(special))
{
if (warn)
{
fprintf(
stderr, "NOTE: No special %s in %s:%s.\n",
enum_item_key(special).c_str(), enum_item_key(mat).c_str(),
enum_item_key(shape).c_str()
);
}
switch (special)
{
case TRACK:
if (sp_map.count(SMOOTH)) {
special = SMOOTH; break;
}
case df::enums::tiletype_special::NONE:
case NORMAL:
case SMOOTH:
case WET:
case FURROWED:
case RIVER_SOURCE:
case WATERFALL:
case WORN_1:
case WORN_2:
case WORN_3:
if (sp_map.count(NORMAL)) {
special = NORMAL; break;
}
if (sp_map.count(df::enums::tiletype_special::NONE)) {
special = df::enums::tiletype_special::NONE; break;
}
// For targeting construction
if (sp_map.count(SMOOTH)) {
special = SMOOTH; break;
}
default:
return tiletype::Void;
}
}
auto &dir_map = sp_map[special];
if (!dir_map.count(dir))
{
if (warn)
{
fprintf(
stderr, "NOTE: No direction '%s' in %s:%s:%s.\n",
dir.c_str(), enum_item_key(mat).c_str(),
enum_item_key(shape).c_str(), enum_item_key(special).c_str()
);
}
if (dir_map.count("--------"))
dir = "--------";
else if (dir_map.count("NSEW"))
dir = "NSEW";
else if (dir_map.count("N-S-W-E-"))
dir = "N-S-W-E-";
else
dir = dir_map.begin()->first;
}
auto &var_map = dir_map[dir];
if (!var_map.count(variant))
{
if (warn)
{
fprintf(
stderr, "NOTE: No variant '%s' in %s:%s:%s:%s.\n",
enum_item_key(variant).c_str(), enum_item_key(mat).c_str(),
enum_item_key(shape).c_str(), enum_item_key(special).c_str(), dir.c_str()
);
}
variant = var_map.begin()->first;
}
return var_map[variant];
}
static void init_tables()
{
tables_ready = true;
memset(tile_to_mat, 0, sizeof(tile_to_mat));
// Index tile types
FOR_ENUM_ITEMS(tiletype, tt)
{
auto &attrs = df::enum_traits<df::tiletype>::attrs(tt);
if (attrs.material < 0)
continue;
tile_table[attrs.material][attrs.shape][attrs.special][attrs.direction][attrs.variant] = tt;
if (isCoreMaterial(attrs.material))
{
assert(attrs.material < NUM_CVTABLES);
tile_to_mat[attrs.material][tt] = tt;
}
}
// Build mapping of everything to STONE and back
FOR_ENUM_ITEMS(tiletype, tt)
{
auto &attrs = df::enum_traits<df::tiletype>::attrs(tt);
if (!isCoreMaterial(attrs.material))
continue;
if (attrs.material != tiletype_material::STONE)
{
df::tiletype ttm = find_match(
tiletype_material::STONE,
attrs.shape, attrs.special, attrs.direction, attrs.variant,
isStoneMaterial(attrs.material)
);
tile_to_mat[tiletype_material::STONE][tt] = ttm;
if (ttm == tiletype::Void)
fprintf(stderr, "No match for tile %s in STONE.\n",
enum_item_key(tt).c_str());
}
else
{
FOR_ENUM_ITEMS(tiletype_material, mat)
{
if (!isCoreMaterial(mat) || mat == attrs.material)
continue;
df::tiletype ttm = find_match(
mat,
attrs.shape, attrs.special, attrs.direction, attrs.variant,
isStoneMaterial(mat)
);
tile_to_mat[mat][tt] = ttm;
if (ttm == tiletype::Void)
fprintf(stderr, "No match for tile %s in %s.\n",
enum_item_key(tt).c_str(), enum_item_key(mat).c_str());
}
}
}
// Transitive closure via STONE
FOR_ENUM_ITEMS(tiletype_material, mat)
{
if (!isCoreMaterial(mat) || mat == tiletype_material::STONE)
continue;
FOR_ENUM_ITEMS(tiletype, tt)
{
if (!tt || tile_to_mat[mat][tt])
continue;
auto stone = tile_to_mat[tiletype_material::STONE][tt];
if (stone)
tile_to_mat[mat][tt] = tile_to_mat[mat][stone];
}
}
}
df::tiletype DFHack::matchTileMaterial(df::tiletype source, df::tiletype_material tmat)
{
if (!isCoreMaterial(tmat) || !source || source >= NUM_TILETYPES)
return tiletype::Void;
if (!tables_ready)
init_tables();
return tile_to_mat[tmat][source];
}
namespace DFHack
{
df::tiletype findSimilarTileType (const df::tiletype sourceTileType, const df::tiletype_shape tshape)
{
df::tiletype match = tiletype::Void;

@ -288,9 +288,9 @@ void VMethodInterposeLinkBase::set_chain(void *chain)
addr_to_method_pointer_(chain_mptr, chain);
}
VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority)
VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority, const char *name)
: host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method),
chain_mptr(chain_mptr), priority(priority),
chain_mptr(chain_mptr), priority(priority), name_str(name),
applied(false), saved_chain(NULL), next(NULL), prev(NULL)
{
if (vmethod_idx < 0 || interpose_method == NULL)
@ -303,8 +303,8 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v
* - interpose_method comes from method_pointer_to_addr_
*/
fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n",
vmethod_idx, unsigned(interpose_method));
fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x (%s)\n",
vmethod_idx, unsigned(interpose_method), name_str);
fflush(stderr);
abort();
}

@ -29,15 +29,24 @@ distribution.
#ifndef DFHACK_EXPORT
#define DFHACK_EXPORT __attribute__ ((visibility("default")))
#endif
#ifndef DFHACK_IMPORT
#define DFHACK_IMPORT DFHACK_EXPORT
#endif
#else
#ifdef BUILD_DFHACK_LIB
#ifndef DFHACK_EXPORT
#define DFHACK_EXPORT __declspec(dllexport)
#endif
#ifndef DFHACK_IMPORT
#define DFHACK_IMPORT
#endif
#else
#ifndef DFHACK_EXPORT
#define DFHACK_EXPORT __declspec(dllimport)
#endif
#ifndef DFHACK_IMPORT
#define DFHACK_IMPORT DFHACK_EXPORT
#endif
#endif
#endif

@ -34,6 +34,11 @@ distribution.
#include <lua.h>
#include <lauxlib.h>
/// Allocate a new user data object and push it on the stack
inline void *operator new (std::size_t size, lua_State *L) {
return lua_newuserdata(L, size);
}
namespace DFHack {
class function_identity_base;
struct MaterialInfo;

@ -32,7 +32,9 @@ distribution.
#include <sstream>
#include <cstdio>
using namespace std;
using std::ostream;
using std::stringstream;
using std::endl;
template <typename T>
void print_bits ( T val, ostream& out )

@ -144,6 +144,11 @@ namespace DFHack
bool unload(color_ostream &out);
bool reload(color_ostream &out);
bool can_be_enabled() { return plugin_is_enabled != 0; }
bool is_enabled() { return plugin_is_enabled && *plugin_is_enabled; }
bool can_set_enabled() { return plugin_is_enabled != 0 && plugin_enable; }
command_result set_enabled(color_ostream &out, bool enable);
command_result invoke(color_ostream &out, const std::string & command, std::vector <std::string> & parameters);
bool can_invoke_hotkey(const std::string & command, df::viewscreen *top );
plugin_state getState () const;
@ -165,6 +170,12 @@ namespace DFHack
void open_lua(lua_State *state, int table);
command_result eval_ruby(color_ostream &out, const char* cmd) {
if (!plugin_eval_ruby || !is_enabled())
return CR_FAILURE;
return plugin_eval_ruby(out, cmd);
}
private:
RefLock * access;
std::vector <PluginCommand> commands;
@ -184,17 +195,22 @@ namespace DFHack
static int lua_fun_wrapper(lua_State *state);
void push_function(lua_State *state, LuaFunction *fn);
static int lua_is_enabled(lua_State *state);
static int lua_set_enabled(lua_State *state);
struct LuaEvent;
std::map<std::string, LuaEvent*> lua_events;
void index_lua(DFLibrary *lib);
void reset_lua();
bool *plugin_is_enabled;
command_result (*plugin_init)(color_ostream &, std::vector <PluginCommand> &);
command_result (*plugin_status)(color_ostream &, std::string &);
command_result (*plugin_shutdown)(color_ostream &);
command_result (*plugin_onupdate)(color_ostream &);
command_result (*plugin_onstatechange)(color_ostream &, state_change_event);
command_result (*plugin_enable)(color_ostream &, bool);
RPCService* (*plugin_rpcconnect)(color_ostream &);
command_result (*plugin_eval_ruby)(color_ostream &, const char*);
};
@ -226,7 +242,7 @@ namespace DFHack
{
return all_plugins.size();
}
command_result (*eval_ruby)(color_ostream &, const char*);
Plugin *ruby;
// DATA
private:
tthread::mutex * cmdlist_mutex;
@ -250,6 +266,10 @@ namespace DFHack
DFhackDataExport const char * name = plugin_name;\
DFhackDataExport Plugin *plugin_self = NULL;
#define DFHACK_PLUGIN_IS_ENABLED(varname) \
DFhackDataExport bool plugin_is_enabled = false; \
bool &varname = plugin_is_enabled;
#define DFHACK_PLUGIN_LUA_COMMANDS \
DFhackCExport const DFHack::CommandReg plugin_lua_commands[] =
#define DFHACK_PLUGIN_LUA_FUNCTIONS \

@ -56,6 +56,8 @@ distribution.
#pragma warning( disable: 4018)
// nonstandard extension used: enum 'df::whatever::etc' used in qualified name
#pragma warning( disable: 4482)
// nonstandard extension used: 'extern' before template explicit instantiation
#pragma warning( disable: 4231)
#endif
#endif

@ -119,10 +119,10 @@ namespace DFHack
inline char * getStr() const
{
static char str[16];
//type punning trick
*( (uint64_t *)str ) = *( (uint64_t *)"--------" );
str[8]=0;
#define DIRECTION(x,i,c) \
str[i] = str[i+1] = '-'; \
if(x){ \
str[i]=c; \
if(1==x) ; \
@ -183,6 +183,56 @@ namespace DFHack
return TileDirection(ENUM_ATTR(tiletype, direction, tiletype));
}
// Air
inline bool isAirMaterial(df::tiletype_material mat) { return mat == tiletype_material::AIR; }
inline bool isAirMaterial(df::tiletype tt) { return isAirMaterial(tileMaterial(tt)); }
// Soil
inline bool isSoilMaterial(df::tiletype_material mat) { return mat == tiletype_material::SOIL; }
inline bool isSoilMaterial(df::tiletype tt) { return isSoilMaterial(tileMaterial(tt)); }
// Stone materials - their tiles are completely interchangable
inline bool isStoneMaterial(df::tiletype_material mat)
{
using namespace df::enums::tiletype_material;
switch (mat) {
case STONE: case LAVA_STONE: case MINERAL: case FEATURE:
return true;
default:
return false;
}
}
inline bool isStoneMaterial(df::tiletype tt) { return isStoneMaterial(tileMaterial(tt)); }
// Regular ground materials = stone + soil
inline bool isGroundMaterial(df::tiletype_material mat)
{
using namespace df::enums::tiletype_material;
switch (mat) {
case SOIL:
case STONE: case LAVA_STONE: case MINERAL: case FEATURE:
return true;
default:
return false;
}
}
inline bool isGroundMaterial(df::tiletype tt) { return isGroundMaterial(tileMaterial(tt)); }
// Core materials - their tile sets are sufficiently close to stone
inline bool isCoreMaterial(df::tiletype_material mat)
{
using namespace df::enums::tiletype_material;
switch (mat) {
case SOIL:
case STONE: case LAVA_STONE: case MINERAL: case FEATURE:
case FROZEN_LIQUID: case CONSTRUCTION:
return true;
default:
return false;
}
}
inline bool isCoreMaterial(df::tiletype tt) { return isCoreMaterial(tileMaterial(tt)); }
// tile is missing a floor
inline
bool LowPassable(df::tiletype tiletype)
@ -295,5 +345,11 @@ namespace DFHack
* If there are no variants, returns the same tile
*/
DFHACK_EXPORT df::tiletype findRandomVariant(const df::tiletype tile);
/**
* Map a tile type to a different core material (see above for the list).
* Returns Void (0) in case of failure.
*/
DFHACK_EXPORT df::tiletype matchTileMaterial(df::tiletype source, df::tiletype_material tmat);
}

@ -141,7 +141,7 @@ namespace DFHack
#define IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,priority) \
DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \
class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name, priority);
class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name, priority, #class"::"#name);
#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,0)
@ -161,6 +161,7 @@ namespace DFHack
void *interpose_method; // Pointer to the code of the interposing method
void *chain_mptr; // Pointer to the chain field in the subclass below
int priority; // Higher priority hooks are called earlier
const char *name_str; // Name of the hook
bool applied; // True if this hook is currently applied
void *saved_chain; // Pointer to the code of the original vmethod or next hook
@ -179,12 +180,14 @@ namespace DFHack
VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id);
bool find_child_hosts(virtual_identity *cur, void *vmptr);
public:
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority);
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority, const char *name);
~VMethodInterposeLinkBase();
bool is_applied() { return applied; }
bool apply(bool enable = true);
void remove();
const char *name() { return name_str; }
};
template<class Base, class Ptr>
@ -198,13 +201,13 @@ namespace DFHack
operator Ptr () { return chain; }
template<class Ptr2>
VMethodInterposeLink(Ptr target, Ptr2 src, int priority)
VMethodInterposeLink(Ptr target, Ptr2 src, int priority, const char *name)
: VMethodInterposeLinkBase(
&Base::_identity,
vmethod_pointer_to_idx(target),
method_pointer_to_addr(src),
&chain,
priority
priority, name
)
{ src = target; /* check compatibility */ }
};

@ -1,3 +1,5 @@
static const int SIZE = 16;
inline uint16_t &operator[] (int y)
{
return bits[y];
@ -36,3 +38,18 @@ bool has_assignments()
return true;
return false;
}
df::tile_bitmask &operator |= (const df::tile_bitmask &b) {
for (int i = 0; i < 16; i++)
bits[i] |= b.bits[i];
return *this;
}
df::tile_bitmask &operator &= (const df::tile_bitmask &b) {
for (int i = 0; i < 16; i++)
bits[i] &= b.bits[i];
return *this;
}
df::tile_bitmask &operator -= (const df::tile_bitmask &b) {
for (int i = 0; i < 16; i++)
bits[i] &= ~b.bits[i];
return *this;
}

@ -7,6 +7,10 @@
#include "ColorText.h"
#include "PluginManager.h"
#include "Console.h"
#include "DataDefs.h"
#include <df/coord.h>
#include <df/unit_inventory_item.h>
namespace DFHack {
namespace EventManager {
@ -21,21 +25,23 @@ namespace DFHack {
CONSTRUCTION,
SYNDROME,
INVASION,
INVENTORY_CHANGE,
EVENT_MAX
};
}
struct EventHandler {
void (*eventHandler)(color_ostream&, void*); //called when the event happens
typedef void (*callback_t)(color_ostream&, void*); //called when the event happens
callback_t eventHandler;
int32_t freq;
EventHandler(void (*eventHandlerIn)(color_ostream&, void*), int32_t freqIn): eventHandler(eventHandlerIn), freq(freqIn) {
EventHandler(callback_t eventHandlerIn, int32_t freqIn): eventHandler(eventHandlerIn), freq(freqIn) {
}
bool operator==(EventHandler& handle) const {
bool operator==(const EventHandler& handle) const {
return eventHandler == handle.eventHandler && freq == handle.freq;
}
bool operator!=(EventHandler& handle) const {
bool operator!=(const EventHandler& handle) const {
return !( *this == handle);
}
};
@ -48,8 +54,23 @@ namespace DFHack {
}
};
struct InventoryItem {
//it has to keep the id of an item because the item itself may have been deallocated
int32_t itemId;
df::unit_inventory_item item;
InventoryItem() {}
InventoryItem(int32_t id_in, df::unit_inventory_item item_in): itemId(id_in), item(item_in) {}
};
struct InventoryChangeData {
int32_t unitId;
InventoryItem* item_old;
InventoryItem* item_new;
InventoryChangeData() {}
InventoryChangeData(int32_t id_in, InventoryItem* old_in, InventoryItem* new_in): unitId(id_in), item_old(old_in), item_new(new_in) {}
};
DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin);
DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false);
DFHACK_EXPORT int32_t registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false);
DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin);
DFHACK_EXPORT void unregisterAll(Plugin* plugin);
void manageEvents(color_ostream& out);
@ -57,4 +78,28 @@ namespace DFHack {
}
}
namespace std {
template <>
struct hash<df::coord> {
std::size_t operator()(const df::coord& c) const {
size_t r = 17;
const size_t m = 65537;
r = m*(r+c.x);
r = m*(r+c.y);
r = m*(r+c.z);
return r;
}
};
template <>
struct hash<DFHack::EventManager::EventHandler> {
std::size_t operator()(const DFHack::EventManager::EventHandler& h) const {
size_t r = 17;
const size_t m = 65537;
r = m*(r+(int32_t)h.eventHandler);
r = m*(r+h.freq);
return r;
}
};
}
#endif

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

@ -35,6 +35,7 @@ distribution.
#include "df/block_square_event_mineralst.h"
#include "df/construction.h"
#include "df/item.h"
#include "df/inclusion_type.h"
using namespace DFHack;
@ -49,6 +50,22 @@ class DFHACK_EXPORT MapCache;
class Block;
struct BiomeInfo {
// Determined by the 4-bit index in the designation bitfield
static const unsigned MAX_LAYERS = 16;
df::coord2d pos;
int default_soil, default_stone, lava_stone;
int geo_index;
df::region_map_entry *biome;
df::world_geo_biome *geobiome;
df::world_region_details *details;
int16_t layer_stone[MAX_LAYERS];
};
typedef uint8_t t_veintype[16][16];
typedef df::tiletype t_tilearr[16][16];
class BlockInfo
{
Block *mblock;
@ -56,8 +73,15 @@ class BlockInfo
df::map_block *block;
public:
enum GroundType {
G_UNKNOWN = 0, G_STONE, G_SOIL
};
static GroundType getGroundType(int material);
typedef df::block_square_event_mineralst::T_flags DFVeinFlags;
t_veintype veintype;
t_blockmaterials veinmats;
t_blockmaterials basemats;
t_blockmaterials grass;
std::map<df::coord,df::plant*> plants;
@ -72,7 +96,10 @@ public:
t_matpair getBaseMaterial(df::tiletype tt, df::coord2d pos);
static void SquashVeins(df::map_block *mb, t_blockmaterials & materials);
static df::inclusion_type getVeinType(DFVeinFlags &flags);
static void setVeinType(DFVeinFlags &flags, df::inclusion_type type);
static void SquashVeins(df::map_block *mb, t_blockmaterials & materials, t_veintype &veintype);
static void SquashFrozenLiquids (df::map_block *mb, tiletypes40d & frozen);
static void SquashRocks (df::map_block *mb, t_blockmaterials & materials,
std::vector< std::vector <int16_t> > * layerassign);
@ -95,18 +122,19 @@ public:
* All coordinates are taken mod 16.
*/
//Arbitrary tag field for flood fills etc.
/// Arbitrary tag field for flood fills etc.
int16_t &tag(df::coord2d p) {
if (!tags) init_tags();
return index_tile<int16_t&>(tags, p);
}
// Base layer
/// Base layer tile type (i.e. layer stone, veins, feature stone)
df::tiletype baseTiletypeAt(df::coord2d p)
{
if (!tiles) init_tiles();
return index_tile<df::tiletype>(tiles->base_tiles,p);
}
/// Base layer material (i.e. layer stone, veins, feature stone)
t_matpair baseMaterialAt(df::coord2d p)
{
if (!basemats) init_tiles(true);
@ -115,12 +143,14 @@ public:
index_tile<int16_t>(basemats->mat_index,p)
);
}
/// Check if the base layer tile is a vein
bool isVeinAt(df::coord2d p)
{
using namespace df::enums::tiletype_material;
auto tm = tileMaterial(baseTiletypeAt(p));
return tm == MINERAL;
}
/// Check if the base layer tile is layer stone or soil
bool isLayerAt(df::coord2d p)
{
using namespace df::enums::tiletype_material;
@ -128,17 +158,49 @@ public:
return tm == STONE || tm == SOIL;
}
/// Vein material at pos (even if there is no vein tile), or -1 if none
int16_t veinMaterialAt(df::coord2d p)
{
return isVeinAt(p) ? baseMaterialAt(p).mat_index : -1;
if (!basemats) init_tiles(true);
return index_tile<int16_t>(basemats->veinmat,p);
}
int16_t layerMaterialAt(df::coord2d p)
/// Vein type at pos (even if there is no vein tile)
df::inclusion_type veinTypeAt(df::coord2d p)
{
if (!basemats) init_tiles(true);
return index_tile<int16_t>(basemats->layermat,p);
return (df::inclusion_type)index_tile<uint8_t>(basemats->veintype,p);
}
// Static layer (base + constructions)
/** Sets the vein material at the specified tile position.
* Use -1 to clear the tile from all veins. Does not update tile types.
* Returns false in case of some error, e.g. non-stone mat.
*/
bool setVeinMaterialAt(df::coord2d p, int16_t mat, df::inclusion_type type = df::enums::inclusion_type::CLUSTER);
/// Geological layer soil or stone material at pos
int16_t layerMaterialAt(df::coord2d p) {
return biomeInfoAt(p).layer_stone[layerIndexAt(p)];
}
/// Biome-specific lava stone at pos
int16_t lavaStoneAt(df::coord2d p) { return biomeInfoAt(p).lava_stone; }
/**
* Sets the stone tile and material at specified position, automatically
* choosing between layer, lava or vein stone.
* The force_type flags ensures the correct inclusion type, even forcing
* a vein format if necessary. If kill_veins is true and the chosen mode
* isn't vein, it will clear any old veins from the tile.
*/
bool setStoneAt(df::coord2d p, df::tiletype tile, int16_t mat, df::inclusion_type type = df::enums::inclusion_type::CLUSTER, bool force_type = false, bool kill_veins = false);
/**
* Sets the tile at the position to SOIL material. The actual material
* is completely determined by geological layers and cannot be set.
*/
bool setSoilAt(df::coord2d p, df::tiletype tile, bool kill_veins = false);
/// Static layer tile (i.e. base + constructions)
df::tiletype staticTiletypeAt(df::coord2d p)
{
if (!tiles) init_tiles();
@ -146,6 +208,7 @@ public:
return index_tile<df::tiletype>(tiles->con_info->tiles,p);
return baseTiletypeAt(p);
}
/// Static layer material (i.e. base + constructions)
t_matpair staticMaterialAt(df::coord2d p)
{
if (!basemats) init_tiles(true);
@ -223,6 +286,15 @@ public:
return true;
}
bool getFlagAt(df::coord2d p, df::tile_designation::Mask mask) {
return (index_tile<df::tile_designation&>(designation,p).whole & mask) != 0;
}
bool getFlagAt(df::coord2d p, df::tile_occupancy::Mask mask) {
return (index_tile<df::tile_occupancy&>(occupancy,p).whole & mask) != 0;
}
bool setFlagAt(df::coord2d p, df::tile_designation::Mask mask, bool set);
bool setFlagAt(df::coord2d p, df::tile_occupancy::Mask mask, bool set);
int itemCountAt(df::coord2d p)
{
if (!item_counts) init_item_counts();
@ -235,9 +307,16 @@ public:
}
bool Write();
bool isDirty();
int biomeIndexAt(df::coord2d p);
int layerIndexAt(df::coord2d p) {
return index_tile<df::tile_designation&>(designation,p).bits.geolayer_index;
}
df::coord2d biomeRegionAt(df::coord2d p);
int16_t GeoIndexAt(df::coord2d p);
const BiomeInfo &biomeInfoAt(df::coord2d p);
int16_t GeoIndexAt(df::coord2d p) { return biomeInfoAt(p).geo_index; }
bool GetGlobalFeature(t_feature *out);
bool GetLocalFeature(t_feature *out);
@ -258,11 +337,10 @@ private:
void init();
int biomeIndexAt(df::coord2d p);
bool valid;
bool dirty_designations:1;
bool dirty_tiles:1;
bool dirty_veins:1;
bool dirty_temperatures:1;
bool dirty_occupancies:1;
@ -281,40 +359,53 @@ private:
bool addItemOnGround(df::item *item);
bool removeItemOnGround(df::item *item);
struct IceInfo {
df::tile_bitmask frozen;
df::tile_bitmask dirty;
};
struct ConInfo {
df::tile_bitmask constructed;
df::tiletype tiles[16][16];
df::tile_bitmask dirty;
t_tilearr tiles;
t_blockmaterials mat_type;
t_blockmaterials mat_index;
};
struct TileInfo {
df::tile_bitmask frozen;
df::tile_bitmask dirty_raw;
df::tiletype raw_tiles[16][16];
t_tilearr raw_tiles;
IceInfo *ice_info;
ConInfo *con_info;
df::tile_bitmask dirty_base;
df::tiletype base_tiles[16][16];
t_tilearr base_tiles;
TileInfo();
~TileInfo();
void init_iceinfo();
void init_coninfo();
void set_base_tile(df::coord2d pos, df::tiletype tile);
};
struct BasematInfo {
df::tile_bitmask dirty;
t_blockmaterials mat_type;
t_blockmaterials mat_index;
t_blockmaterials layermat;
df::tile_bitmask vein_dirty;
t_veintype veintype;
t_blockmaterials veinmat;
BasematInfo();
void set_base_mat(TileInfo *tiles, df::coord2d pos, int16_t type, int16_t idx);
};
TileInfo *tiles;
BasematInfo *basemats;
void init_tiles(bool basemat = false);
void ParseTiles(TileInfo *tiles);
void WriteTiles(TileInfo*);
void ParseBasemats(TileInfo *tiles, BasematInfo *bmats);
void WriteVeins(TileInfo *tiles, BasematInfo *bmats);
designations40d designation;
occupancies40d occupancy;
@ -349,6 +440,9 @@ class DFHACK_EXPORT MapCache
return b ? b->Allocate() : false;
}
/// delete the block from memory
void discardBlock(Block *block);
df::tiletype baseTiletypeAt (DFCoord tilecoord)
{
Block *b = BlockAtTile(tilecoord);
@ -507,10 +601,17 @@ class DFHACK_EXPORT MapCache
uint32_t maxTileY() { return y_tmax; }
uint32_t maxZ() { return z_max; }
size_t getBiomeCount() { return biomes.size(); }
const BiomeInfo &getBiomeByIndex(unsigned idx) {
return (idx < biomes.size()) ? biomes[idx] : biome_stub;
}
private:
friend class Block;
friend class BlockInfo;
static const BiomeInfo biome_stub;
bool valid;
bool validgeo;
uint32_t x_bmax;
@ -518,10 +619,7 @@ private:
uint32_t x_tmax;
uint32_t y_tmax;
uint32_t z_max;
std::vector<df::coord2d> geoidx;
std::vector<int> default_soil;
std::vector<int> default_stone;
std::vector< std::vector <int16_t> > layer_mats;
std::vector<BiomeInfo> biomes;
std::map<df::coord2d, df::world_region_details*> region_details;
std::map<DFCoord, Block *> blocks;
};

@ -169,6 +169,9 @@ namespace DFHack
return a.type != b.type || a.index != b.index;
}
DFHACK_EXPORT bool isSoilInorganic(int material);
DFHACK_EXPORT bool isStoneInorganic(int material);
typedef int32_t t_materialIndex;
typedef int16_t t_materialType, t_itemType, t_itemSubtype;

@ -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);
}
}

@ -0,0 +1,131 @@
#pragma once
namespace DFHack {
namespace Random {
/*
* A good explanation:
* http://webstaff.itn.liu.se/~stegu/TNM022-2005/perlinnoiselinks/perlin-noise-math-faq.html
*/
// Interpolation functions
template<class T>
inline T s_curve(T t)
{
// Classical function
//return t * t * (3 - 2*t);
// 2002 version from http://mrl.nyu.edu/~perlin/paper445.pdf
return t * t * t * (t * (t * 6 - 15) + 10);
}
template<class T>
inline T lerp(T s, T a, T b)
{
return a + s * (b-a);
}
// Dot product of VSIZE vectors pointed by pa, pb
template<class T, unsigned i>
struct DotProduct {
static inline T eval(T *pa, T *pb);
};
template<class T>
struct DotProduct<T,0> {
static inline T eval(T *pa, T *pb) { return pa[0]*pb[0]; }
};
template<class T, unsigned i>
inline T DotProduct<T,i>::eval(T *pa, T *pb) {
return DotProduct<T,i-1>::eval(pa, pb) + pa[i]*pb[i];
}
// Templates used to force unrolling and inlining of the loops
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask>
struct PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,-1> {
typedef typename PerlinNoise<T,VSIZE,BITS,IDXT>::Temp Temp;
static inline void setup(PerlinNoise<T,VSIZE,BITS,IDXT> *, const T *, Temp *) {}
static inline T eval(PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq);
};
// Initialization of the temporaries from input coordinates
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask, int i>
inline void PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,i>::setup(
PerlinNoise<T,VSIZE,BITS,IDXT> *self, const T *pv, Temp *pt
) {
Impl<mask,i-1>::setup(self, pv, pt);
int32_t t = int32_t(pv[i]);
t -= (pv[i]<t);
pt[i].s = s_curve(pt[i].r0 = pv[i] - t);
unsigned b = unsigned(int32_t(t));
pt[i].b0 = self->idxmap[i][b & mask];
pt[i].b1 = self->idxmap[i][(b+1) & mask];
}
// Main recursion. Uses tables from self and pt.
// Recursion changes current index idx, and current offset vector pq.
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask>
inline T PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask, -1>::eval(
PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq
) {
return DotProduct<T,VSIZE-1>::eval(pq, self->gradients[idx]);
}
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask, int i>
inline T PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,i>::eval(
PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq
) {
pq[i] = pt[i].r0;
T u = Impl<mask,i-1>::eval(self, pt, idx ^ pt[i].b0, pq);
pq[i] -= 1;
T v = Impl<mask,i-1>::eval(self, pt, idx ^ pt[i].b1, pq);
return lerp(pt[i].s, u, v);
}
// Actual methods of the object
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
void PerlinNoise<T,VSIZE,BITS,IDXT>::init(MersenneRNG &rng)
{
STATIC_ASSERT(VSIZE > 0 && BITS <= 8*sizeof(IDXT));
// Random unit gradient vectors
for (unsigned i = 0; i < TSIZE; i++)
rng.unitvector(gradients[i], VSIZE);
// Random permutation tables
for (unsigned j = 0; j < VSIZE; j++)
{
for (unsigned i = 0; i < TSIZE; i++)
idxmap[j][i] = i;
rng.permute(idxmap[j], TSIZE);
}
}
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
T PerlinNoise<T,VSIZE,BITS,IDXT>::eval(const T coords[VSIZE])
{
// Precomputed properties from the coordinates
Temp tmp[VSIZE];
// Temporary used to build the current offset vector
T q[VSIZE];
Impl<TSIZE-1,VSIZE-1>::setup(this, coords, tmp);
return Impl<TSIZE-1,VSIZE-1>::eval(this, tmp, 0, q);
}
}} // namespace

@ -0,0 +1,179 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#ifndef CL_MOD_RANDOM
#define CL_MOD_RANDOM
/**
* \defgroup grp_random Random: Random number and noise generation
* @ingroup grp_modules
*/
#include "Export.h"
#include "Module.h"
#include "Types.h"
#include "DataDefs.h"
namespace DFHack
{
namespace Random
{
class DFHACK_EXPORT MersenneRNG
{
static const unsigned MT_LEN = 624;
unsigned mt_index;
uint32_t mt_buffer[MT_LEN];
void twist();
void prefill(unsigned step, int twist_cnt);
public:
/* No constructor or destructor - safe to treat as data */
void init(const uint32_t *pseed, unsigned cnt, int twist_cnt = 1);
void init(); // uses time
void init(uint32_t seed, int twist_cnt = 1) { init(&seed, 1, twist_cnt); }
// [0, 2^32)
uint32_t random() {
if (mt_index >= MT_LEN) twist();
return mt_buffer[mt_index++];
}
// [0, limit)
uint32_t random(uint32_t limit) {
return uint32_t(uint64_t(random())*limit >> 32);
}
// (0, 1)
double drandom0() {
return (double(random())+1)/4294967297.0;
}
// [0, 1)
double drandom() {
return double(random())/4294967296.0;
}
// [0, 1]
double drandom1() {
return double(random())/4294967295.0;
}
// [-1, 1]
double unitrandom() {
return drandom1()*2.0 - 1.0;
}
// Two exact replicas of functions in DF code
int32_t df_trandom(uint32_t max=2147483647LU);
int32_t df_loadtrandom(uint32_t max=2147483647LU);
template<class T>
void unitvector(T *p, int size);
template<class T>
void permute(T *p, int size) {
while(size > 1)
{
int j = random(size--);
T c = p[j]; p[j] = p[size]; p[size] = c;
}
}
};
#ifndef DFHACK_RANDOM_CPP
extern template void MersenneRNG::unitvector<float>(float *p, int size);
extern template void MersenneRNG::unitvector<double>(double *p, int size);
#endif
/*
* Classical Perlin noise function in template form.
* http://mrl.nyu.edu/~perlin/doc/oscar.html#noise
*
* Using an improved hash function from:
* http://www.cs.utah.edu/~aek/research/noise.pdf
*/
template<class T, unsigned VSIZE, unsigned BITS = 8, class IDXT = uint8_t>
class PerlinNoise
{
// Size of randomness tables
static const unsigned TSIZE = 1<<BITS;
T gradients[TSIZE][VSIZE];
IDXT idxmap[VSIZE][TSIZE];
// Templates used to unwind and inline recursion and loops
struct Temp {
T r0, s;
unsigned b0, b1;
};
template<unsigned mask, int i>
struct Impl {
static inline void setup(PerlinNoise<T,VSIZE,BITS,IDXT> *self, const T *pv, Temp *pt);
static inline T eval(PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq);
};
public:
/* No constructor or destructor - safe to treat as data */
void init(MersenneRNG &rng);
T eval(const T coords[VSIZE]);
};
#ifndef DFHACK_RANDOM_CPP
extern template class DFHACK_IMPORT PerlinNoise<float, 1>;
extern template class DFHACK_IMPORT PerlinNoise<float, 2>;
extern template class DFHACK_IMPORT PerlinNoise<float, 3>;
#endif
template<class T, unsigned BITS = 8, class IDXT = uint8_t>
class PerlinNoise1D : public PerlinNoise<T, 1, BITS, IDXT>
{
public:
T operator() (T x) { return this->eval(&x); }
};
template<class T, unsigned BITS = 8, class IDXT = uint8_t>
class PerlinNoise2D : public PerlinNoise<T, 2, BITS, IDXT>
{
public:
T operator() (T x, T y) {
T tmp[2] = { x, y };
return this->eval(tmp);
}
};
template<class T, unsigned BITS = 8, class IDXT = uint8_t>
class PerlinNoise3D : public PerlinNoise<T, 3, BITS, IDXT>
{
public:
T operator() (T x, T y, T z) {
T tmp[3] = { x, y, z };
return this->eval(tmp);
}
};
}
}
#endif

@ -220,6 +220,12 @@ function dfhack.matinfo:__tostring()
return "<material "..self.type..":"..self.index.." "..self:getToken()..">"
end
dfhack.random.__index = dfhack.random
function dfhack.random:__tostring()
return "<random generator>"
end
function dfhack.maps.getSize()
local map = df.global.world.map
return map.x_count_block, map.y_count_block, map.z_count_block

@ -153,7 +153,7 @@ function showMaterialPrompt(title, prompt)
require('gui.materials').MaterialDialog{
frame_title = title,
prompt = prompt,
on_select = mkresume(true,
on_select = mkresume(true),
on_cancel = mkresume(false),
on_close = qresume(nil)
}:show()

@ -0,0 +1,302 @@
local _ENV = mkmodule('makeown')
--[[
'tweak makeown' as a lua include
make_own(unit) -- removes foreign flags, sets civ_id to fort civ_id, and sets clothes ownership
make_citizen(unit) -- called by make_own if unit.race == fort race
eventually ought to migrate to hack/lua/plugins/tweak.lua
and local _ENV = mkmodule('plugin.tweak')
in order to link to functions in the compiled plugin (when/if they become available to lua)
--]]
local utils = require 'utils'
local function fix_clothing_ownership(unit)
-- extracted/translated from tweak makeown plugin
-- to be called by tweak-fixmigrant/makeown
-- units forced into the fort by removing the flags do not own their clothes
-- which has the result that they drop all their clothes and become unhappy because they are naked
-- so we need to make them own their clothes and add them to their uniform
local fixcount = 0 --int fixcount = 0;
for j=0,#unit.inventory-1 do --for(size_t j=0; j<unit->inventory.size(); j++)
local inv_item = unit.inventory[j] --unidf::unit_inventory_item* inv_item = unit->inventory[j];
local item = inv_item.item --df::item* item = inv_item->item;
-- unforbid items (for the case of kidnapping caravan escorts who have their stuff forbidden by default)
-- moved forbid false to inside if so that armor/weapons stay equiped
if inv_item.mode == df.unit_inventory_item.T_mode.Worn then --if(inv_item->mode == df::unit_inventory_item::T_mode::Worn)
-- ignore armor?
-- it could be leather boots, for example, in which case it would not be nice to forbid ownership
--if(item->getEffectiveArmorLevel() != 0)
-- continue;
if not dfhack.items.getOwner(item) then --if(!Items::getOwner(item))
if dfhack.items.setOwner(item,unit) then --if(Items::setOwner(item, unit))
item.flags.forbid = false --inv_item->item->flags.bits.forbid = 0;
-- add to uniform, so they know they should wear their clothes
unit.military.uniforms[0]:insert('#',item.id) --insert_into_vector(unit->military.uniforms[0], item->id);
fixcount = fixcount + 1 --fixcount++;
else
----out << "could not change ownership for item!" << endl;
print("Makeown: could not change ownership for an item!")
end
end
end
end
-- clear uniform_drop (without this they would drop their clothes and pick them up some time later)
-- dirty?
unit.military.uniform_drop:resize(0) --unit->military.uniform_drop.clear();
----out << "ownership for " << fixcount << " clothes fixed" << endl;
print("Makeown: claimed ownership for "..tostring(fixcount).." worn items")
--return true --return CR_OK;
end
local function entity_link(hf, eid, do_event, add, replace_idx)
do_event = (do_event == nil) and true or do_event
add = (add == nil) and true or add
replace_idx = replace_idx or -1
local link = add and df.histfig_entity_link_memberst:new() or df.histfig_entity_link_former_memberst:new()
link.entity_id = eid
if replace_idx > -1 then
local e = hf.entity_links[replace_idx]
link.link_strength = (e.link_strength > 3) and (e.link_strength - 2) or e.link_strength
hf.entity_links[replace_idx] = link -- replace member link with former member link
e:delete()
else
link.link_strength = 100
hf.entity_links:insert('#', link)
end
if do_event then
event = add and df.history_event_add_hf_entity_linkst:new() or df.history_event_remove_hf_entity_linkst:new()
event.year = df.global.cur_year
event.seconds = df.global.cur_year_tick
event.civ = eid
event.histfig = hf.id
event.link_type = 0
event.position_id = -1
event.id = df.global.hist_event_next_id
df.global.world.history.events:insert('#',event)
df.global.hist_event_next_id = df.global.hist_event_next_id + 1
end
end
local function change_state(hf, site_id, pos)
hf.info.unk_14.unk_0 = 3 -- state? arrived?
hf.info.unk_14.region:assign(pos)
hf.info.unk_14.site = site_id
event = df.history_event_change_hf_statest:new()
event.year = df.global.cur_year
event.seconds = df.global.cur_year_tick
event.hfid = hf.id
event.state = 3
event.site = site_id
event.region_pos:assign(pos)
event.substate = -1; event.region = -1; event.layer = -1;
event.id = df.global.hist_event_next_id
df.global.world.history.events:insert('#',event)
df.global.hist_event_next_id = df.global.hist_event_next_id + 1
end
function make_citizen(unit)
local dfg = df.global
local civ_id = dfg.ui.civ_id
local group_id = dfg.ui.group_id
local events = dfg.world.history.events
local fortent = dfg.ui.main.fortress_entity
local civent = fortent and df.historical_entity.find(fortent.entity_links[0].target)
-- utils.binsearch(dfg.world.entities.all, fortent.entity_links[0].target, 'id')
local event
local region_pos = df.world_site.find(dfg.ui.site_id).pos -- used with state events and hf state
local hf
-- assume that hf id 1 and hf id 2 are equal. I am unaware of instances of when they are not.
-- occationally a unit does not have both flags set (missing flags1.important_historical_figure)
-- and I don't know what that means yet.
if unit.flags1.important_historical_figure and unit.flags2.important_historical_figure then
-- aready hf, find it (unlikely to happen)
hf = utils.binsearch(dfg.world.history.figures, unit.hist_figure_id, 'id')
--elseif unit.flags1.important_historical_figure or unit.flags2.important_historical_figure then
-- something wrong, try to fix it?
--[[
if unit.hist_figure_id == -1 then
unit.hist_figure_id = unit.hist_figure_id2
end
if unit.hist_figure_id > -1 then
unit.hist_figure_id2 = unit.hist_figure_id
unit.flags1.important_historical_figure = true
unit.flags2.important_historical_figure = true
hf = utils.binsearch(dfg.world.history.figures, unit.hist_figure_id, 'id')
else
unit.flags1.important_historical_figure = false
unit.flags2.important_historical_figure = false
end
--]]
--else
-- make one
end
--local new_hf = false
if not hf then
--new_hf = true
hf = df.historical_figure:new()
hf.profession = unit.profession
hf.race = unit.race
hf.caste = unit.caste
hf.sex = unit.sex
hf.appeared_year = dfg.cur_year
hf.born_year = unit.relations.birth_year
hf.born_seconds = unit.relations.birth_time
hf.curse_year = unit.relations.curse_year
hf.curse_seconds = unit.relations.curse_time
hf.anon_1 = unit.relations.anon_2
hf.anon_2 = unit.relations.anon_3
hf.old_year = unit.relations.old_year
hf.old_seconds = unit.relations.old_time
hf.died_year = -1
hf.died_seconds = -1
hf.name:assign(unit.name)
hf.civ_id = unit.civ_id
hf.population_id = unit.population_id
hf.breed_id = -1
hf.unit_id = unit.id
hf.id = dfg.hist_figure_next_id -- id must be set before adding links (for the events)
--history_event_add_hf_entity_linkst not reported for civ on starting 7
entity_link(hf, civ_id, false) -- so lets skip event here
entity_link(hf, group_id)
hf.info = df.historical_figure_info:new()
hf.info.unk_14 = df.historical_figure_info.T_unk_14:new() -- hf state?
--unk_14.region_id = -1; unk_14.beast_id = -1; unk_14.unk_14 = 0
hf.info.unk_14.unk_18 = -1; hf.info.unk_14.unk_1c = -1
-- set values that seem related to state and do event
change_state(hf, dfg.ui.site_id, region_pos)
--lets skip skills for now
--local skills = df.historical_figure_info.T_skills:new() -- skills snap shot
-- ...
--info.skills = skills
dfg.world.history.figures:insert('#', hf)
dfg.hist_figure_next_id = dfg.hist_figure_next_id + 1
--new_hf_loc = df.global.world.history.figures[#df.global.world.history.figures - 1]
fortent.histfig_ids:insert('#', hf.id)
fortent.hist_figures:insert('#', hf)
civent.histfig_ids:insert('#', hf.id)
civent.hist_figures:insert('#', hf)
unit.flags1.important_historical_figure = true
unit.flags2.important_historical_figure = true
unit.hist_figure_id = hf.id
unit.hist_figure_id2 = hf.id
print("Makeown-citizen: created historical figure")
else
-- only insert into civ/fort if not already there
-- Migrants change previous histfig_entity_link_memberst to histfig_entity_link_former_memberst
-- for group entities, add link_member for new group, and reports events for remove from group,
-- remove from civ, change state, add civ, and add group
hf.civ_id = civ_id -- ensure current civ_id
local found_civlink = false
local found_fortlink = false
local v = hf.entity_links
for k=#v-1,0,-1 do
if df.histfig_entity_link_memberst:is_instance(v[k]) then
entity_link(hf, v[k].entity_id, true, false, k)
end
end
if hf.info and hf.info.unk_14 then
change_state(hf, dfg.ui.site_id, region_pos)
-- leave info nil if not found for now
end
if not found_civlink then entity_link(hf,civ_id) end
if not found_fortlink then entity_link(hf,group_id) end
--change entity_links
local found = false
for _,v in ipairs(civent.histfig_ids) do
if v == hf.id then found = true; break end
end
if not found then
civent.histfig_ids:insert('#', hf.id)
civent.hist_figures:insert('#', hf)
end
found = false
for _,v in ipairs(fortent.histfig_ids) do
if v == hf.id then found = true; break end
end
if not found then
fortent.histfig_ids:insert('#', hf.id)
fortent.hist_figures:insert('#', hf)
end
print("Makeown-citizen: migrated historical figure")
end -- hf
local nemesis = dfhack.units.getNemesis(unit)
if not nemesis then
nemesis = df.nemesis_record:new()
nemesis.figure = hf
nemesis.unit = unit
nemesis.unit_id = unit.id
nemesis.save_file_id = civent.save_file_id
nemesis.unk10, nemesis.unk11, nemesis.unk12 = -1, -1, -1
--group_leader_id = -1
nemesis.id = dfg.nemesis_next_id
nemesis.member_idx = civent.next_member_idx
civent.next_member_idx = civent.next_member_idx + 1
dfg.world.nemesis.all:insert('#', nemesis)
dfg.nemesis_next_id = dfg.nemesis_next_id + 1
nemesis_link = df.general_ref_is_nemesisst:new()
nemesis_link.nemesis_id = nemesis.id
unit.general_refs:insert('#', nemesis_link)
--new_nemesis_loc = df.global.world.nemesis.all[#df.global.world.nemesis.all - 1]
fortent.nemesis_ids:insert('#', nemesis.id)
fortent.nemesis:insert('#', nemesis)
civent.nemesis_ids:insert('#', nemesis.id)
civent.nemesis:insert('#', nemesis)
print("Makeown-citizen: created nemesis entry")
else-- only insert into civ/fort if not already there
local found = false
for _,v in ipairs(civent.nemesis_ids) do
if v == nemesis.id then found = true; break end
end
if not found then
civent.nemesis_ids:insert('#', nemesis.id)
civent.nemesis:insert('#', nemesis)
end
found = false
for _,v in ipairs(fortent.nemesis_ids) do
if v == nemesis.id then found = true; break end
end
if not found then
fortent.nemesis_ids:insert('#', nemesis.id)
fortent.nemesis:insert('#', nemesis)
end
print("Makeown-citizen: migrated nemesis entry")
end -- nemesis
end
function make_own(unit)
--tweak makeown
unit.flags2.resident = false; unit.flags1.merchant = false; unit.flags1.forest = false;
unit.civ_id = df.global.ui.civ_id
if unit.profession == df.profession.MERCHANT then unit.profession = df.profession.TRADER end
if unit.profession2 == df.profession.MERCHANT then unit.profession2 = df.profession.TRADER end
fix_clothing_ownership(unit)
if unit.race == df.global.ui.race_id then
make_citizen(unit)
end
end
return _ENV

@ -3,17 +3,23 @@
#include "modules/Buildings.h"
#include "modules/Constructions.h"
#include "modules/EventManager.h"
#include "modules/Once.h"
#include "modules/Job.h"
#include "modules/World.h"
#include "df/building.h"
#include "df/construction.h"
#include "df/general_ref.h"
#include "df/general_ref_type.h"
#include "df/general_ref_unit_workerst.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/job.h"
#include "df/job_list_link.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/unit_flags1.h"
#include "df/unit_inventory_item.h"
#include "df/unit_syndrome.h"
#include "df/world.h"
@ -31,48 +37,59 @@ using namespace EventManager;
* consider a typedef instead of a struct for EventHandler
**/
//map<uint32_t, vector<DFHack::EventManager::EventHandler> > tickQueue;
multimap<uint32_t, EventHandler> tickQueue;
static multimap<int32_t, EventHandler> tickQueue;
//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever
multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
uint32_t eventLastTick[EventType::EVENT_MAX];
static multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
static int32_t eventLastTick[EventType::EVENT_MAX];
const uint32_t ticksPerYear = 403200;
static const int32_t ticksPerYear = 403200;
void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) {
handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler));
}
void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
if ( !Core::getInstance().isWorldLoaded() ) {
tick = 0;
if ( absolute ) {
Core::getInstance().getConsole().print("Warning: absolute flag will not be honored.\n");
int32_t DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) {
if ( !absolute ) {
df::world* world = df::global::world;
if ( world ) {
when += world->frame_counter;
} else {
if ( Once::doOnce("EventManager registerTick unhonored absolute=false") )
Core::getInstance().getConsole().print("EventManager::registerTick: warning! absolute flag=false not honored.\n");
}
}
if ( absolute ) {
tick = 0;
}
tickQueue.insert(pair<uint32_t, EventHandler>(tick+(uint32_t)when, handler));
handler.freq = when;
tickQueue.insert(pair<int32_t, EventHandler>(handler.freq, handler));
handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,handler));
return;
return when;
}
static void removeFromTickQueue(EventHandler getRidOf) {
for ( auto j = tickQueue.find(getRidOf.freq); j != tickQueue.end(); ) {
if ( (*j).first > getRidOf.freq )
break;
if ( (*j).second != getRidOf ) {
j++;
continue;
}
j = tickQueue.erase(j);
}
}
void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) {
for ( multimap<Plugin*, EventHandler>::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) {
for ( auto i = handlers[e].find(plugin); i != handlers[e].end(); ) {
if ( (*i).first != plugin )
break;
EventHandler handle = (*i).second;
if ( handle == handler ) {
handlers[e].erase(i);
break;
if ( handle != handler ) {
i++;
continue;
}
i = handlers[e].erase(i);
if ( e == EventType::TICK )
removeFromTickQueue(handler);
}
return;
}
void DFHack::EventManager::unregisterAll(Plugin* plugin) {
@ -80,20 +97,7 @@ void DFHack::EventManager::unregisterAll(Plugin* plugin) {
if ( (*i).first != plugin )
break;
//shenanigans to avoid concurrent modification
EventHandler getRidOf = (*i).second;
bool didSomething;
do {
didSomething = false;
for ( auto j = tickQueue.begin(); j != tickQueue.end(); j++ ) {
EventHandler candidate = (*j).second;
if ( getRidOf != candidate )
continue;
tickQueue.erase(j);
didSomething = true;
break;
}
} while(didSomething);
removeFromTickQueue((*i).second);
}
for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) {
handlers[a].erase(plugin);
@ -110,9 +114,22 @@ static void manageBuildingEvent(color_ostream& out);
static void manageConstructionEvent(color_ostream& out);
static void manageSyndromeEvent(color_ostream& out);
static void manageInvasionEvent(color_ostream& out);
//tick event
static uint32_t lastTick = 0;
static void manageEquipmentEvent(color_ostream& out);
typedef void (*eventManager_t)(color_ostream&);
static const eventManager_t eventManager[] = {
manageTickEvent,
manageJobInitiatedEvent,
manageJobCompletedEvent,
manageUnitDeathEvent,
manageItemCreationEvent,
manageBuildingEvent,
manageConstructionEvent,
manageSyndromeEvent,
manageInvasionEvent,
manageEquipmentEvent,
};
//job initiated
static int32_t lastJobId = -1;
@ -131,14 +148,23 @@ static int32_t nextBuilding;
static unordered_set<int32_t> buildings;
//construction
static unordered_set<df::construction*> constructions;
static unordered_map<df::coord, df::construction> constructions;
static bool gameLoaded;
//syndrome
static int32_t lastSyndromeTime;
//invasion
static int32_t nextInvasion;
//equipment change
//static unordered_map<int32_t, vector<df::unit_inventory_item> > equipmentLog;
static unordered_map<int32_t, vector<InventoryItem> > equipmentLog;
void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) {
static bool doOnce = false;
// const string eventNames[] = {"world loaded", "world unloaded", "map loaded", "map unloaded", "viewscreen changed", "core initialized", "begin unload", "paused", "unpaused"};
// out.print("%s,%d: onStateChange %d: \"%s\"\n", __FILE__, __LINE__, (int32_t)event, eventNames[event].c_str());
if ( !doOnce ) {
//TODO: put this somewhere else
doOnce = true;
@ -146,38 +172,72 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event
DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL);
//out.print("Registered listeners.\n %d", __LINE__);
}
if ( event == DFHack::SC_WORLD_UNLOADED ) {
lastTick = 0;
if ( event == DFHack::SC_MAP_UNLOADED ) {
lastJobId = -1;
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
Job::deleteJobStruct((*i).second, true);
}
prevJobs.clear();
tickQueue.clear();
livingUnits.clear();
nextItem = -1;
nextBuilding = -1;
buildings.clear();
constructions.clear();
equipmentLog.clear();
Buildings::clearBuildings(out);
gameLoaded = false;
nextInvasion = -1;
} else if ( event == DFHack::SC_WORLD_LOADED ) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
multimap<uint32_t,EventHandler> newTickQueue;
for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ ) {
newTickQueue.insert(pair<uint32_t,EventHandler>(tick + (*i).first, (*i).second));
}
} else if ( event == DFHack::SC_MAP_LOADED ) {
/*
int32_t tick = df::global::world->frame_counter;
multimap<int32_t,EventHandler> newTickQueue;
for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ )
newTickQueue.insert(pair<int32_t,EventHandler>(tick+(*i).first, (*i).second));
tickQueue.clear();
tickQueue.insert(newTickQueue.begin(), newTickQueue.end());
nextItem = 0;
nextBuilding = 0;
lastTick = 0;
//out.print("%s,%d: on load, frame_counter = %d\n", __FILE__, __LINE__, tick);
*/
//tickQueue.clear();
nextItem = *df::global::item_next_id;
nextBuilding = *df::global::building_next_id;
nextInvasion = df::global::ui->invasions.next_id;
lastJobId = -1 + *df::global::job_next_id;
constructions.clear();
for ( auto i = df::global::world->constructions.begin(); i != df::global::world->constructions.end(); i++ ) {
df::construction* constr = *i;
if ( !constr ) {
if ( Once::doOnce("EventManager.onLoad null constr") ) {
out.print("EventManager.onLoad: null construction.\n");
}
continue;
}
if ( constr->pos == df::coord() ) {
if ( Once::doOnce("EventManager.onLoad null position of construction.\n") )
out.print("EventManager.onLoad null position of construction.\n");
continue;
}
constructions[constr->pos] = *constr;
}
for ( size_t a = 0; a < df::global::world->buildings.all.size(); a++ ) {
df::building* b = df::global::world->buildings.all[a];
Buildings::updateBuildings(out, (void*)b);
buildings.insert(b->id);
}
lastSyndromeTime = -1;
for ( size_t a = 0; a < df::global::world->units.all.size(); a++ ) {
df::unit* unit = df::global::world->units.all[a];
for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) {
df::unit_syndrome* syndrome = unit->syndromes.active[b];
int32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time;
if ( startTime > lastSyndromeTime )
lastSyndromeTime = startTime;
}
}
for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) {
eventLastTick[a] = -1;//-1000000;
}
gameLoaded = true;
}
}
@ -186,83 +246,62 @@ void DFHack::EventManager::manageEvents(color_ostream& out) {
if ( !gameLoaded ) {
return;
}
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
CoreSuspender suspender;
if ( tick <= lastTick )
return;
lastTick = tick;
int32_t tick = df::global::world->frame_counter;
int32_t eventFrequency[EventType::EVENT_MAX];
for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) {
int32_t min = 1000000000;
for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) {
EventHandler bob = (*b).second;
if ( bob.freq < min )
min = bob.freq;
}
eventFrequency[a] = min;
}
manageTickEvent(out);
if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) {
manageJobInitiatedEvent(out);
eventLastTick[EventType::JOB_INITIATED] = tick;
}
if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) {
manageJobCompletedEvent(out);
eventLastTick[EventType::JOB_COMPLETED] = tick;
}
if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) {
manageUnitDeathEvent(out);
eventLastTick[EventType::UNIT_DEATH] = tick;
}
if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) {
manageItemCreationEvent(out);
eventLastTick[EventType::ITEM_CREATED] = tick;
}
if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) {
manageBuildingEvent(out);
eventLastTick[EventType::BUILDING] = tick;
}
if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) {
manageConstructionEvent(out);
eventLastTick[EventType::CONSTRUCTION] = tick;
}
if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) {
manageSyndromeEvent(out);
eventLastTick[EventType::SYNDROME] = tick;
}
if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) {
manageInvasionEvent(out);
eventLastTick[EventType::INVASION] = tick;
if ( handlers[a].empty() )
continue;
int32_t eventFrequency = -100;
if ( a != EventType::TICK )
for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) {
EventHandler bob = (*b).second;
if ( bob.freq < eventFrequency || eventFrequency == -100 )
eventFrequency = bob.freq;
}
else eventFrequency = 1;
if ( tick - eventLastTick[a] < eventFrequency )
continue;
eventManager[a](out);
eventLastTick[a] = tick;
}
return;
}
static void manageTickEvent(color_ostream& out) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
unordered_set<EventHandler> toRemove;
int32_t tick = df::global::world->frame_counter;
while ( !tickQueue.empty() ) {
if ( tick < (*tickQueue.begin()).first )
break;
EventHandler handle = (*tickQueue.begin()).second;
tickQueue.erase(tickQueue.begin());
handle.eventHandler(out, (void*)tick);
toRemove.insert(handle);
}
if ( toRemove.empty() )
return;
for ( auto a = handlers[EventType::TICK].begin(); a != handlers[EventType::TICK].end(); ) {
EventHandler handle = (*a).second;
if ( toRemove.find(handle) == toRemove.end() ) {
a++;
continue;
}
a = handlers[EventType::TICK].erase(a);
toRemove.erase(handle);
if ( toRemove.empty() )
break;
}
}
static void manageJobInitiatedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_INITIATED].empty() )
return;
if ( lastJobId == -1 ) {
lastJobId = *df::global::job_next_id - 1;
return;
}
if ( lastJobId+1 == *df::global::job_next_id ) {
return; //no new jobs
}
@ -277,14 +316,26 @@ static void manageJobInitiatedEvent(color_ostream& out) {
(*i).second.eventHandler(out, (void*)link->item);
}
}
lastJobId = *df::global::job_next_id - 1;
}
static void manageJobCompletedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_COMPLETED].empty() ) {
return;
//helper function for manageJobCompletedEvent
static int32_t getWorkerID(df::job* job) {
for ( size_t a = 0; a < job->general_refs.size(); a++ ) {
if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER )
continue;
return ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id;
}
return -1;
}
/*
TODO: consider checking item creation / experience gain just in case
*/
static void manageJobCompletedEvent(color_ostream& out) {
int32_t tick0 = eventLastTick[EventType::JOB_COMPLETED];
int32_t tick1 = df::global::world->frame_counter;
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
map<int32_t, df::job*> nowJobs;
@ -293,20 +344,105 @@ static void manageJobCompletedEvent(color_ostream& out) {
continue;
nowJobs[link->item->id] = link->item;
}
#if 0
//testing info on job initiation/completion
//newly allocated jobs
for ( auto j = nowJobs.begin(); j != nowJobs.end(); j++ ) {
if ( prevJobs.find((*j).first) != prevJobs.end() )
continue;
df::job& job1 = *(*j).second;
out.print("new job\n"
" location : 0x%X\n"
" id : %d\n"
" type : %d %s\n"
" working : %d\n"
" completion_timer : %d\n"
" workerID : %d\n"
" time : %d -> %d\n"
"\n", job1.list_link->item, job1.id, job1.job_type, ENUM_ATTR(job_type, caption, job1.job_type), job1.flags.bits.working, job1.completion_timer, getWorkerID(&job1), tick0, tick1);
}
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
if ( nowJobs.find((*i).first) != nowJobs.end() )
df::job& job0 = *(*i).second;
auto j = nowJobs.find((*i).first);
if ( j == nowJobs.end() ) {
out.print("job deallocated\n"
" location : 0x%X\n"
" id : %d\n"
" type : %d %s\n"
" working : %d\n"
" completion_timer : %d\n"
" workerID : %d\n"
" time : %d -> %d\n"
,job0.list_link == NULL ? 0 : job0.list_link->item, job0.id, job0.job_type, ENUM_ATTR(job_type, caption, job0.job_type), job0.flags.bits.working, job0.completion_timer, getWorkerID(&job0), tick0, tick1);
continue;
//recently finished or cancelled job!
}
df::job& job1 = *(*j).second;
if ( job0.flags.bits.working == job1.flags.bits.working &&
(job0.completion_timer == job1.completion_timer || (job1.completion_timer > 0 && job0.completion_timer-1 == job1.completion_timer)) &&
getWorkerID(&job0) == getWorkerID(&job1) )
continue;
out.print("job change\n"
" location : 0x%X -> 0x%X\n"
" id : %d -> %d\n"
" type : %d -> %d\n"
" type : %s -> %s\n"
" working : %d -> %d\n"
" completion timer : %d -> %d\n"
" workerID : %d -> %d\n"
" time : %d -> %d\n"
"\n",
job0.list_link->item, job1.list_link->item,
job0.id, job1.id,
job0.job_type, job1.job_type,
ENUM_ATTR(job_type, caption, job0.job_type), ENUM_ATTR(job_type, caption, job1.job_type),
job0.flags.bits.working, job1.flags.bits.working,
job0.completion_timer, job1.completion_timer,
getWorkerID(&job0), getWorkerID(&job1),
tick0, tick1
);
}
#endif
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
//if it happened within a tick, must have been cancelled by the user or a plugin: not completed
if ( tick1 <= tick0 )
continue;
if ( nowJobs.find((*i).first) != nowJobs.end() ) {
//could have just finished if it's a repeat job
df::job& job0 = *(*i).second;
if ( !job0.flags.bits.repeat )
continue;
df::job& job1 = *nowJobs[(*i).first];
if ( job0.completion_timer != 0 )
continue;
if ( job1.completion_timer != -1 )
continue;
//still false positive if cancelled at EXACTLY the right time, but experiments show this doesn't happen
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
(*j).second.eventHandler(out, (void*)&job0);
}
continue;
}
//recently finished or cancelled job
df::job& job0 = *(*i).second;
if ( job0.flags.bits.repeat || job0.completion_timer != 0 )
continue;
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
(*j).second.eventHandler(out, (void*)(*i).second);
(*j).second.eventHandler(out, (void*)&job0);
}
}
//erase old jobs, copy over possibly altered jobs
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
Job::deleteJobStruct((*i).second, true);
}
prevJobs.clear();
@ -320,29 +456,21 @@ static void manageJobCompletedEvent(color_ostream& out) {
df::job* newJob = Job::cloneJobStruct((*j).second, true);
prevJobs[newJob->id] = newJob;
}
/*//get rid of old pointers to deallocated jobs
for ( size_t a = 0; a < toDelete.size(); a++ ) {
prevJobs.erase(a);
}*/
}
static void manageUnitDeathEvent(color_ostream& out) {
if ( handlers[EventType::UNIT_DEATH].empty() ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[a];
if ( unit->counters.death_id == -1 ) {
for ( size_t a = 0; a < df::global::world->units.all.size(); a++ ) {
df::unit* unit = df::global::world->units.all[a];
//if ( unit->counters.death_id == -1 ) {
if ( ! unit->flags1.bits.dead ) {
livingUnits.insert(unit->id);
continue;
}
//dead: if dead since last check, trigger events
if ( livingUnits.find(unit->id) == livingUnits.end() )
continue;
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
(*i).second.eventHandler(out, (void*)unit->id);
}
@ -351,18 +479,18 @@ static void manageUnitDeathEvent(color_ostream& out) {
}
static void manageItemCreationEvent(color_ostream& out) {
if ( handlers[EventType::ITEM_CREATED].empty() ) {
return;
}
if ( nextItem >= *df::global::item_next_id ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end());
size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false);
if ( index != 0 ) index--;
for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) {
df::item* item = df::global::world->items.all[a];
//already processed
if ( item->id < nextItem )
continue;
//invaders
if ( item->flags.bits.foreign )
continue;
@ -387,9 +515,6 @@ static void manageBuildingEvent(color_ostream& out) {
* TODO: could be faster
* consider looking at jobs: building creation / destruction
**/
if ( handlers[EventType::BUILDING].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) {
@ -408,74 +533,74 @@ static void manageBuildingEvent(color_ostream& out) {
nextBuilding = *df::global::building_next_id;
//now alert people about destroyed buildings
unordered_set<int32_t> toDelete;
for ( auto a = buildings.begin(); a != buildings.end(); a++ ) {
for ( auto a = buildings.begin(); a != buildings.end(); ) {
int32_t id = *a;
int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id);
if ( index != -1 )
if ( index != -1 ) {
a++;
continue;
toDelete.insert(id);
}
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler bob = (*b).second;
bob.eventHandler(out, (void*)id);
}
a = buildings.erase(a);
}
for ( auto a = toDelete.begin(); a != toDelete.end(); a++ ) {
int32_t id = *a;
buildings.erase(id);
}
//out.print("Sent building event.\n %d", __LINE__);
}
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());
for ( auto a = constructions.begin(); a != constructions.end(); a++ ) {
df::construction* construction = *a;
if ( constructionsNow.find(construction) != constructionsNow.end() )
for ( auto a = constructions.begin(); a != constructions.end(); ) {
df::construction& construction = (*a).second;
if ( df::construction::find(construction.pos) != NULL ) {
a++;
continue;
}
//construction removed
//out.print("Removed construction (%d,%d,%d)\n", construction.pos.x,construction.pos.y,construction.pos.z);
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler handle = (*b).second;
handle.eventHandler(out, (void*)construction);
handle.eventHandler(out, (void*)&construction);
}
a = constructions.erase(a);
}
for ( auto a = constructionsNow.begin(); a != constructionsNow.end(); a++ ) {
//for ( auto a = constructionsNow.begin(); a != constructionsNow.end(); a++ ) {
for ( auto a = df::global::world->constructions.begin(); a != df::global::world->constructions.end(); a++ ) {
df::construction* construction = *a;
if ( constructions.find(construction) != constructions.end() )
bool b = constructions.find(construction->pos) != constructions.end();
constructions[construction->pos] = *construction;
if ( b )
continue;
//construction created
//out.print("Created construction (%d,%d,%d)\n", construction->pos.x,construction->pos.y,construction->pos.z);
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler handle = (*b).second;
handle.eventHandler(out, (void*)construction);
}
}
constructions.clear();
constructions.insert(constructionsNow.begin(), constructionsNow.end());
}
static void manageSyndromeEvent(color_ostream& out) {
if ( handlers[EventType::SYNDROME].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) {
int32_t highestTime = -1;
for ( auto a = df::global::world->units.all.begin(); a != df::global::world->units.all.end(); a++ ) {
df::unit* unit = *a;
/*
if ( unit->flags1.bits.dead )
continue;
*/
for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) {
df::unit_syndrome* syndrome = unit->syndromes.active[b];
uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time;
if ( startTime <= eventLastTick[EventType::SYNDROME] )
int32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time;
if ( startTime > highestTime )
highestTime = startTime;
if ( startTime <= lastSyndromeTime )
continue;
SyndromeData data(unit->id, b);
for ( auto c = copy.begin(); c != copy.end(); c++ ) {
EventHandler handle = (*c).second;
@ -483,12 +608,10 @@ static void manageSyndromeEvent(color_ostream& out) {
}
}
}
lastSyndromeTime = highestTime;
}
static void manageInvasionEvent(color_ostream& out) {
if ( handlers[EventType::INVASION].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end());
if ( df::global::ui->invasions.next_id <= nextInvasion )
@ -501,3 +624,76 @@ static void manageInvasionEvent(color_ostream& out) {
}
}
static void manageEquipmentEvent(color_ostream& out) {
multimap<Plugin*,EventHandler> copy(handlers[EventType::INVENTORY_CHANGE].begin(), handlers[EventType::INVENTORY_CHANGE].end());
unordered_map<int32_t, InventoryItem> itemIdToInventoryItem;
unordered_set<int32_t> currentlyEquipped;
for ( auto a = df::global::world->units.all.begin(); a != df::global::world->units.all.end(); a++ ) {
itemIdToInventoryItem.clear();
currentlyEquipped.clear();
df::unit* unit = *a;
/*if ( unit->flags1.bits.dead )
continue;
*/
auto oldEquipment = equipmentLog.find(unit->id);
if ( oldEquipment != equipmentLog.end() ) {
vector<InventoryItem>& v = (*oldEquipment).second;
for ( auto b = v.begin(); b != v.end(); b++ ) {
InventoryItem& i = *b;
itemIdToInventoryItem[i.itemId] = i;
}
for ( size_t b = 0; b < unit->inventory.size(); b++ ) {
df::unit_inventory_item* dfitem_new = unit->inventory[b];
currentlyEquipped.insert(dfitem_new->item->id);
InventoryItem item_new(dfitem_new->item->id, *dfitem_new);
auto c = itemIdToInventoryItem.find(dfitem_new->item->id);
if ( c == itemIdToInventoryItem.end() ) {
//new item equipped (probably just picked up)
InventoryChangeData data(unit->id, NULL, &item_new);
for ( auto h = copy.begin(); h != copy.end(); h++ ) {
EventHandler handle = (*h).second;
handle.eventHandler(out, (void*)&data);
}
continue;
}
InventoryItem item_old = (*c).second;
df::unit_inventory_item& item0 = item_old.item;
df::unit_inventory_item& item1 = item_new.item;
if ( item0.mode == item1.mode && item0.body_part_id == item1.body_part_id && item0.wound_id == item1.wound_id )
continue;
//some sort of change in how it's equipped
InventoryChangeData data(unit->id, &item_old, &item_new);
for ( auto h = copy.begin(); h != copy.end(); h++ ) {
EventHandler handle = (*h).second;
handle.eventHandler(out, (void*)&data);
}
}
//check for dropped items
for ( auto b = v.begin(); b != v.end(); b++ ) {
InventoryItem i = *b;
if ( currentlyEquipped.find(i.itemId) != currentlyEquipped.end() )
continue;
//TODO: delete ptr if invalid
InventoryChangeData data(unit->id, &i, NULL);
for ( auto h = copy.begin(); h != copy.end(); h++ ) {
EventHandler handle = (*h).second;
handle.eventHandler(out, (void*)&data);
}
}
}
//update equipment
vector<InventoryItem>& equipment = equipmentLog[unit->id];
equipment.clear();
for ( size_t b = 0; b < unit->inventory.size(); b++ ) {
df::unit_inventory_item* dfitem = unit->inventory[b];
InventoryItem item(dfitem->item->id, *dfitem);
equipment.push_back(item);
}
}
}

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

File diff suppressed because it is too large Load Diff

@ -61,8 +61,8 @@ using namespace std;
#include "df/z_level_flags.h"
#include "df/region_map_entry.h"
#include "df/flow_info.h"
#include "df/plant.h"
#include "df/building_type.h"
#include "df/plant.h"
using namespace DFHack;
using namespace df::enums;
@ -188,9 +188,13 @@ df::map_block *Maps::ensureTileBlock (int32_t x, int32_t y, int32_t z)
dsgn.bits.outside = true;
for (int tx = 0; tx < 16; tx++)
for (int ty = 0; ty < 16; ty++)
for (int ty = 0; ty < 16; ty++) {
slot->designation[tx][ty] = dsgn;
slot->temperature_1[tx][ty] = column[z2]->temperature_1[tx][ty];
slot->temperature_2[tx][ty] = column[z2]->temperature_2[tx][ty];
}
df::global::world->map.map_blocks.push_back(slot);
return slot;
}
@ -322,7 +326,7 @@ df::feature_init *Maps::getLocalInitFeature(df::coord2d rgn_pos, int32_t index)
return vector_get(features, index);
}
static bool GetLocalFeature(t_feature &feature, df::coord2d rgn_pos, int32_t index)
bool GetLocalFeature(t_feature &feature, df::coord2d rgn_pos, int32_t index)
{
feature.type = (df::feature_type)-1;
@ -564,6 +568,10 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2)
if ( !index_tile<uint16_t>(block1->walkable,pos1) || !index_tile<uint16_t>(block2->walkable,pos2) ) {
return false;
}
if ( block1->designation[pos1.x&0xF][pos1.y&0xF].bits.flow_size >= 4 ||
block2->designation[pos2.x&0xF][pos2.y&0xF].bits.flow_size >= 4 )
return false;
if ( dz == 0 )
return true;
@ -661,702 +669,3 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2)
return false;
}
#define COPY(a,b) memcpy(&a,&b,sizeof(a))
MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
{
dirty_designations = false;
dirty_tiles = false;
dirty_temperatures = false;
dirty_occupancies = false;
valid = false;
bcoord = _bcoord;
block = Maps::getBlock(bcoord);
tags = NULL;
init();
}
void MapExtras::Block::init()
{
item_counts = NULL;
tiles = NULL;
basemats = NULL;
if(block)
{
COPY(designation, block->designation);
COPY(occupancy, block->occupancy);
COPY(temp1, block->temperature_1);
COPY(temp2, block->temperature_2);
valid = true;
}
else
{
memset(designation,0,sizeof(designation));
memset(occupancy,0,sizeof(occupancy));
memset(temp1,0,sizeof(temp1));
memset(temp2,0,sizeof(temp2));
}
}
bool MapExtras::Block::Allocate()
{
if (block)
return true;
block = Maps::ensureTileBlock(bcoord.x*16, bcoord.y*16, bcoord.z);
if (!block)
return false;
delete[] item_counts;
delete tiles;
delete basemats;
init();
return true;
}
MapExtras::Block::~Block()
{
delete[] item_counts;
delete[] tags;
delete tiles;
delete basemats;
}
void MapExtras::Block::init_tags()
{
if (!tags)
tags = new T_tags[16];
memset(tags,0,sizeof(T_tags)*16);
}
void MapExtras::Block::init_tiles(bool basemat)
{
if (!tiles)
{
tiles = new TileInfo();
if (block)
ParseTiles(tiles);
}
if (basemat && !basemats)
{
basemats = new BasematInfo();
if (block)
ParseBasemats(tiles, basemats);
}
}
MapExtras::Block::TileInfo::TileInfo()
{
frozen.clear();
dirty_raw.clear();
memset(raw_tiles,0,sizeof(raw_tiles));
con_info = NULL;
dirty_base.clear();
memset(base_tiles,0,sizeof(base_tiles));
}
MapExtras::Block::TileInfo::~TileInfo()
{
delete con_info;
}
void MapExtras::Block::TileInfo::init_coninfo()
{
if (con_info)
return;
con_info = new ConInfo();
con_info->constructed.clear();
COPY(con_info->tiles, base_tiles);
memset(con_info->mat_type, -1, sizeof(con_info->mat_type));
memset(con_info->mat_index, -1, sizeof(con_info->mat_index));
}
MapExtras::Block::BasematInfo::BasematInfo()
{
dirty.clear();
memset(mat_type,0,sizeof(mat_type));
memset(mat_index,-1,sizeof(mat_index));
memset(layermat,-1,sizeof(layermat));
}
bool MapExtras::Block::setTiletypeAt(df::coord2d pos, df::tiletype tt, bool force)
{
if (!block)
return false;
if (!basemats)
init_tiles(true);
pos = pos & 15;
dirty_tiles = true;
tiles->raw_tiles[pos.x][pos.y] = tt;
tiles->dirty_raw.setassignment(pos, true);
return true;
}
void MapExtras::Block::ParseTiles(TileInfo *tiles)
{
tiletypes40d icetiles;
BlockInfo::SquashFrozenLiquids(block, icetiles);
COPY(tiles->raw_tiles, block->tiletype);
for (int x = 0; x < 16; x++)
{
for (int y = 0; y < 16; y++)
{
using namespace df::enums::tiletype_material;
df::tiletype tt = tiles->raw_tiles[x][y];
df::coord coord = block->map_pos + df::coord(x,y,0);
// Frozen liquid comes topmost
if (tileMaterial(tt) == FROZEN_LIQUID)
{
tiles->frozen.setassignment(x,y,true);
if (icetiles[x][y] != tiletype::Void)
{
tt = icetiles[x][y];
}
}
// The next layer may be construction
bool is_con = false;
if (tileMaterial(tt) == CONSTRUCTION)
{
df::construction *con = df::construction::find(coord);
if (con)
{
if (!tiles->con_info)
tiles->init_coninfo();
is_con = true;
tiles->con_info->constructed.setassignment(x,y,true);
tiles->con_info->tiles[x][y] = tt;
tiles->con_info->mat_type[x][y] = con->mat_type;
tiles->con_info->mat_index[x][y] = con->mat_index;
tt = con->original_tile;
}
}
// Finally, base material
tiles->base_tiles[x][y] = tt;
// Copy base info back to construction layer
if (tiles->con_info && !is_con)
tiles->con_info->tiles[x][y] = tt;
}
}
}
void MapExtras::Block::ParseBasemats(TileInfo *tiles, BasematInfo *bmats)
{
BlockInfo info;
info.prepare(this);
COPY(bmats->layermat, info.basemats);
for (int x = 0; x < 16; x++)
{
for (int y = 0; y < 16; y++)
{
using namespace df::enums::tiletype_material;
auto tt = tiles->base_tiles[x][y];
auto mat = info.getBaseMaterial(tt, df::coord2d(x,y));
bmats->mat_type[x][y] = mat.mat_type;
bmats->mat_index[x][y] = mat.mat_index;
// Copy base info back to construction layer
if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y))
{
tiles->con_info->mat_type[x][y] = mat.mat_type;
tiles->con_info->mat_index[x][y] = mat.mat_index;
}
}
}
}
bool MapExtras::Block::Write ()
{
if(!valid) return false;
if(dirty_designations)
{
COPY(block->designation, designation);
block->flags.bits.designated = true;
dirty_designations = false;
}
if(dirty_tiles && tiles)
{
dirty_tiles = false;
for (int x = 0; x < 16; x++)
{
for (int y = 0; y < 16; y++)
{
if (tiles->dirty_raw.getassignment(x,y))
block->tiletype[x][y] = tiles->raw_tiles[x][y];
}
}
delete tiles; tiles = NULL;
delete basemats; basemats = NULL;
}
if(dirty_temperatures)
{
COPY(block->temperature_1, temp1);
COPY(block->temperature_2, temp2);
dirty_temperatures = false;
}
if(dirty_occupancies)
{
COPY(block->occupancy, occupancy);
dirty_occupancies = false;
}
return true;
}
void MapExtras::BlockInfo::prepare(Block *mblock)
{
this->mblock = mblock;
block = mblock->getRaw();
parent = mblock->getParent();
SquashVeins(block,veinmats);
SquashGrass(block, grass);
if (parent->validgeo)
SquashRocks(block,basemats,&parent->layer_mats);
else
memset(basemats,-1,sizeof(basemats));
for (size_t i = 0; i < block->plants.size(); i++)
{
auto pp = block->plants[i];
plants[pp->pos] = pp;
}
global_feature = Maps::getGlobalInitFeature(block->global_feature);
local_feature = Maps::getLocalInitFeature(block->region_pos, block->local_feature);
}
t_matpair MapExtras::BlockInfo::getBaseMaterial(df::tiletype tt, df::coord2d pos)
{
using namespace df::enums::tiletype_material;
t_matpair rv(0,-1);
int x = pos.x, y = pos.y;
switch (tileMaterial(tt)) {
case NONE:
case AIR:
rv.mat_type = -1;
break;
case DRIFTWOOD:
case SOIL:
{
rv.mat_index = basemats[x][y];
if (auto raw = df::inorganic_raw::find(rv.mat_index))
{
if (raw->flags.is_set(inorganic_flags::SOIL_ANY))
break;
int biome = mblock->biomeIndexAt(pos);
int idx = vector_get(parent->default_soil, biome, -1);
if (idx >= 0)
rv.mat_index = idx;
}
break;
}
case STONE:
{
rv.mat_index = basemats[x][y];
if (auto raw = df::inorganic_raw::find(rv.mat_index))
{
if (!raw->flags.is_set(inorganic_flags::SOIL_ANY))
break;
int biome = mblock->biomeIndexAt(pos);
int idx = vector_get(parent->default_stone, biome, -1);
if (idx >= 0)
rv.mat_index = idx;
}
break;
}
case MINERAL:
rv.mat_index = veinmats[x][y];
break;
case LAVA_STONE:
if (auto details = parent->region_details[mblock->biomeRegionAt(pos)])
rv.mat_index = details->lava_stone;
break;
case PLANT:
rv.mat_type = MaterialInfo::PLANT_BASE;
if (auto plant = plants[block->map_pos + df::coord(x,y,0)])
{
if (auto raw = df::plant_raw::find(plant->material))
{
rv.mat_type = raw->material_defs.type_basic_mat;
rv.mat_index = raw->material_defs.idx_basic_mat;
}
}
break;
case GRASS_LIGHT:
case GRASS_DARK:
case GRASS_DRY:
case GRASS_DEAD:
rv.mat_type = MaterialInfo::PLANT_BASE;
if (auto raw = df::plant_raw::find(grass[x][y]))
{
rv.mat_type = raw->material_defs.type_basic_mat;
rv.mat_index = raw->material_defs.idx_basic_mat;
}
break;
case FEATURE:
{
auto dsgn = block->designation[x][y];
if (dsgn.bits.feature_local && local_feature)
local_feature->getMaterial(&rv.mat_type, &rv.mat_index);
else if (dsgn.bits.feature_global && global_feature)
global_feature->getMaterial(&rv.mat_type, &rv.mat_index);
break;
}
case CONSTRUCTION: // just a fallback
case MAGMA:
case HFS:
// use generic 'rock'
break;
case FROZEN_LIQUID:
rv.mat_type = builtin_mats::WATER;
break;
case POOL:
case BROOK:
case RIVER:
rv.mat_index = basemats[x][y];
break;
case ASHES:
case FIRE:
case CAMPFIRE:
rv.mat_type = builtin_mats::ASH;
break;
}
return rv;
}
void MapExtras::BlockInfo::SquashVeins(df::map_block *mb, t_blockmaterials & materials)
{
std::vector <df::block_square_event_mineralst *> veins;
Maps::SortBlockEvents(mb,&veins);
memset(materials,-1,sizeof(materials));
for (uint32_t x = 0;x<16;x++) for (uint32_t y = 0; y< 16;y++)
{
for (size_t i = 0; i < veins.size(); i++)
{
if (veins[i]->getassignment(x,y))
materials[x][y] = veins[i]->inorganic_mat;
}
}
}
void MapExtras::BlockInfo::SquashFrozenLiquids(df::map_block *mb, tiletypes40d & frozen)
{
std::vector <df::block_square_event_frozen_liquidst *> ices;
Maps::SortBlockEvents(mb,NULL,&ices);
memset(frozen,0,sizeof(frozen));
for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++)
{
for (size_t i = 0; i < ices.size(); i++)
{
df::tiletype tt2 = ices[i]->tiles[x][y];
if (tt2 != tiletype::Void)
{
frozen[x][y] = tt2;
break;
}
}
}
}
void MapExtras::BlockInfo::SquashRocks (df::map_block *mb, t_blockmaterials & materials,
std::vector< std::vector <int16_t> > * layerassign)
{
// get the layer materials
for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++)
{
materials[x][y] = -1;
uint8_t test = mb->designation[x][y].bits.biome;
if (test >= 9)
continue;
uint8_t idx = mb->region_offset[test];
if (idx < layerassign->size())
materials[x][y] = layerassign->at(idx)[mb->designation[x][y].bits.geolayer_index];
}
}
void MapExtras::BlockInfo::SquashGrass(df::map_block *mb, t_blockmaterials &materials)
{
std::vector<df::block_square_event_grassst*> grasses;
Maps::SortBlockEvents(mb, NULL, NULL, NULL, &grasses);
memset(materials,-1,sizeof(materials));
for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++)
{
int amount = 0;
for (size_t i = 0; i < grasses.size(); i++)
{
if (grasses[i]->amount[x][y] >= amount)
{
amount = grasses[i]->amount[x][y];
materials[x][y] = grasses[i]->plant_index;
}
}
}
}
int MapExtras::Block::biomeIndexAt(df::coord2d p)
{
if (!block)
return -1;
auto des = index_tile<df::tile_designation>(designation,p);
uint8_t idx = des.bits.biome;
if (idx >= 9)
return -1;
idx = block->region_offset[idx];
if (idx >= parent->geoidx.size())
return -1;
return idx;
}
df::coord2d MapExtras::Block::biomeRegionAt(df::coord2d p)
{
if (!block)
return df::coord2d(-30000,-30000);
int idx = biomeIndexAt(p);
if (idx < 0)
return block->region_pos;
return parent->geoidx[idx];
}
int16_t MapExtras::Block::GeoIndexAt(df::coord2d p)
{
df::coord2d biome = biomeRegionAt(p);
if (!biome.isValid())
return -1;
auto pinfo = Maps::getRegionBiome(biome);
if (!pinfo)
return -1;
return pinfo->geo_index;
}
bool MapExtras::Block::GetGlobalFeature(t_feature *out)
{
out->type = (df::feature_type)-1;
if (!valid || block->global_feature < 0)
return false;
return Maps::GetGlobalFeature(*out, block->global_feature);
}
bool MapExtras::Block::GetLocalFeature(t_feature *out)
{
out->type = (df::feature_type)-1;
if (!valid || block->local_feature < 0)
return false;
return ::GetLocalFeature(*out, block->region_pos, block->local_feature);
}
void MapExtras::Block::init_item_counts()
{
if (item_counts) return;
item_counts = new T_item_counts[16];
memset(item_counts, 0, sizeof(T_item_counts)*16);
if (!block) return;
for (size_t i = 0; i < block->items.size(); i++)
{
auto it = df::item::find(block->items[i]);
if (!it || !it->flags.bits.on_ground)
continue;
df::coord tidx = it->pos - block->map_pos;
if (!is_valid_tile_coord(tidx) || tidx.z != 0)
continue;
item_counts[tidx.x][tidx.y]++;
}
}
bool MapExtras::Block::addItemOnGround(df::item *item)
{
if (!block)
return false;
init_item_counts();
bool inserted;
insert_into_vector(block->items, item->id, &inserted);
if (inserted)
{
int &count = index_tile<int&>(item_counts,item->pos);
if (count++ == 0)
{
index_tile<df::tile_occupancy&>(occupancy,item->pos).bits.item = true;
index_tile<df::tile_occupancy&>(block->occupancy,item->pos).bits.item = true;
}
}
return inserted;
}
bool MapExtras::Block::removeItemOnGround(df::item *item)
{
if (!block)
return false;
init_item_counts();
int idx = binsearch_index(block->items, item->id);
if (idx < 0)
return false;
vector_erase_at(block->items, idx);
int &count = index_tile<int&>(item_counts,item->pos);
if (--count == 0)
{
index_tile<df::tile_occupancy&>(occupancy,item->pos).bits.item = false;
auto &occ = index_tile<df::tile_occupancy&>(block->occupancy,item->pos);
occ.bits.item = false;
// Clear the 'site blocked' flag in the building, if any.
// Otherwise the job would be re-suspended without actually checking items.
if (occ.bits.building == tile_building_occ::Planned)
{
if (auto bld = Buildings::findAtTile(item->pos))
{
// TODO: maybe recheck other tiles like the game does.
bld->flags.bits.site_blocked = false;
}
}
}
return true;
}
MapExtras::MapCache::MapCache()
{
valid = 0;
Maps::getSize(x_bmax, y_bmax, z_max);
x_tmax = x_bmax*16; y_tmax = y_bmax*16;
validgeo = Maps::ReadGeology(&layer_mats, &geoidx);
valid = true;
if (auto data = df::global::world->world_data)
{
for (size_t i = 0; i < data->region_details.size(); i++)
{
auto info = data->region_details[i];
region_details[info->pos] = info;
}
}
default_soil.resize(layer_mats.size());
default_stone.resize(layer_mats.size());
for (size_t i = 0; i < layer_mats.size(); i++)
{
default_soil[i] = -1;
default_stone[i] = -1;
for (size_t j = 0; j < layer_mats[i].size(); j++)
{
auto raw = df::inorganic_raw::find(layer_mats[i][j]);
if (!raw)
continue;
bool is_soil = raw->flags.is_set(inorganic_flags::SOIL_ANY);
if (is_soil)
default_soil[i] = layer_mats[i][j];
else if (default_stone[i] == -1)
default_stone[i] = layer_mats[i][j];
}
}
}
MapExtras::Block *MapExtras::MapCache::BlockAt(DFCoord blockcoord)
{
if(!valid)
return 0;
std::map <DFCoord, Block*>::iterator iter = blocks.find(blockcoord);
if(iter != blocks.end())
{
return (*iter).second;
}
else
{
if(unsigned(blockcoord.x) < x_bmax &&
unsigned(blockcoord.y) < y_bmax &&
unsigned(blockcoord.z) < z_max)
{
Block * nblo = new Block(this, blockcoord);
blocks[blockcoord] = nblo;
return nblo;
}
return 0;
}
}
void MapExtras::MapCache::resetTags()
{
for (auto it = blocks.begin(); it != blocks.end(); ++it)
{
delete[] it->second->tags;
it->second->tags = NULL;
}
}

@ -561,6 +561,25 @@ bool DFHack::parseJobMaterialCategory(df::dfhack_material_category *cat, const s
return true;
}
bool DFHack::isSoilInorganic(int material)
{
auto raw = df::inorganic_raw::find(material);
return raw && raw->flags.is_set(inorganic_flags::SOIL_ANY);
}
bool DFHack::isStoneInorganic(int material)
{
auto raw = df::inorganic_raw::find(material);
if (!raw ||
raw->flags.is_set(inorganic_flags::SOIL_ANY) ||
raw->material.flags.is_set(material_flags::IS_METAL))
return false;
return true;
}
Module* DFHack::createMaterials()
{
return new Materials();

@ -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;
}

@ -0,0 +1,156 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#include "Internal.h"
#include <string>
#include <vector>
#include <map>
using namespace std;
#define DFHACK_RANDOM_CPP
#include "modules/Random.h"
#include "VersionInfo.h"
#include "MemAccess.h"
#include "Types.h"
#include "ModuleFactory.h"
#include "Core.h"
#include "Error.h"
#include "VTableInterpose.h"
#include <cmath>
using namespace DFHack;
using namespace df::enums;
using namespace DFHack::Random;
//public domain RNG stuff by Michael Brundage
//modified to be compatible with the version in DF
#define MT_IA 397
#define MT_IB (MT_LEN - MT_IA)
#define UPPER_MASK 0x80000000
#define LOWER_MASK 0x7FFFFFFF
#define MATRIX_A 0x9908B0DF
#define TWIST(b,i,j) ((b)[i] & UPPER_MASK) | ((b)[j] & LOWER_MASK)
#define MAGIC(s) (((s)&1)*MATRIX_A)
void MersenneRNG::twist()
{
uint32_t *b = mt_buffer;
uint32_t s;
unsigned i;
i = 0;
for (; i < MT_IB; i++) {
s = TWIST(b, i, i+1);
b[i] = b[i + MT_IA] ^ (s >> 1) ^ MAGIC(s);
}
for (; i < MT_LEN-1; i++) {
s = TWIST(b, i, i+1);
b[i] = b[i - MT_IB] ^ (s >> 1) ^ MAGIC(s);
}
s = TWIST(b, MT_LEN-1, 0);
b[MT_LEN-1] = b[MT_IA-1] ^ (s >> 1) ^ MAGIC(s);
mt_index = 0;
}
void MersenneRNG::prefill(unsigned step, int twist_cnt)
{
for(unsigned i=step;i<MT_LEN;i++)
{
//2010: better init line from wikipedia, ultimate source unknown
mt_buffer[i]=1812433253UL * (mt_buffer[i-step] ^ (mt_buffer[i-step]>>30)) + i;
}
mt_index = 0;
for(int j=0;j<twist_cnt;j++)
twist();
}
void MersenneRNG::init()
{
init(Core::getInstance().p->getTickCount(), 20);
}
void MersenneRNG::init(const uint32_t *pseed, unsigned cnt, int twist_cnt)
{
memcpy(mt_buffer, pseed, cnt*sizeof(uint32_t));
prefill(cnt, twist_cnt);
}
int32_t MersenneRNG::df_trandom(uint32_t max)
{
if(max<=1)return 0;
uint32_t seed=random();
seed=seed%2147483647LU;
seed=seed/((2147483647LU/max)+1);
return((int32_t)seed);
}
int32_t MersenneRNG::df_loadtrandom(uint32_t max)
{
uint32_t seed=random();
seed=seed%max;
return((int32_t)seed);
}
template<class T>
void MersenneRNG::unitvector(T *p, int size)
{
for (;;)
{
T rsqr = 0;
for (int i = 0; i < size; i++)
{
p[i] = (T)unitrandom();
rsqr += p[i]*p[i];
}
if (rsqr > 0 && rsqr <= 1)
{
rsqr = std::sqrt(rsqr);
for (int i = 0; i < size; i++)
p[i] /= rsqr;
break;
}
}
}
template void MersenneRNG::unitvector<float>(float *p, int size);
template void MersenneRNG::unitvector<double>(double *p, int size);
#include "modules/PerlinNoise.inc"
template class DFHACK_EXPORT PerlinNoise<float, 1>;
template class DFHACK_EXPORT PerlinNoise<float, 2>;
template class DFHACK_EXPORT PerlinNoise<float, 3>;

@ -66,6 +66,7 @@ using namespace std;
#include "df/game_mode.h"
#include "df/unit_misc_trait.h"
#include "df/unit_skill.h"
#include "df/curse_attr_change.h"
using namespace DFHack;
using namespace df::enums;

File diff suppressed because it is too large Load Diff

@ -13,7 +13,6 @@ endif()
OPTION(BUILD_ISOWORLD "Build isoworld (needs a checkout first)." OFF)
if(BUILD_ISOWORLD)
DFHACK_PLUGIN(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote)
add_subdirectory (isoworld)
IF(UNIX)
if (APPLE)
@ -80,6 +79,8 @@ add_custom_target(generate_proto DEPENDS ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_H
SET_SOURCE_FILES_PROPERTIES( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE )
add_subdirectory(diggingInvaders)
# Plugins
OPTION(BUILD_SUPPORTED "Build the supported plugins (reveal, probe, etc.)." ON)
if (BUILD_SUPPORTED)
@ -146,25 +147,25 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(workNow workNow.cpp)
#DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread)
DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp)
DFHACK_PLUGIN(trueTransformation trueTransformation.cpp)
DFHACK_PLUGIN(syndromeTrigger syndromeTrigger.cpp)
DFHACK_PLUGIN(infiniteSky infiniteSky.cpp)
DFHACK_PLUGIN(digFlood digFlood.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)
DFHACK_PLUGIN(cleanconst cleanconst.cpp)
DFHACK_PLUGIN(3dveins 3dveins.cpp)
endif()
# this is the skeleton plugin. If you want to make your own, make a copy and then change it
OPTION(BUILD_SKELETON "Build the skeleton plugin." OFF)
if(BUILD_SKELETON)
add_subdirectory(skeleton)
endif()
OPTION(BUILD_RENDERMAX "Build the rendermax alt-renderers plugin." OFF)
if(BUILD_RENDERMAX)
add_subdirectory(rendermax)
endif()

@ -48,6 +48,7 @@ using df::global::ui;
typedef df::reaction_product_item_improvementst improvement_product;
DFHACK_PLUGIN("add-spatter");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
struct ReagentSource {
int idx;
@ -378,7 +379,7 @@ static bool find_reactions(color_ostream &out)
parse_product(out, out_prod.back(), it->second.react, itprod);
}
for (size_t i = 0; i < prod.size(); i++)
for (size_t i = 0; i < out_prod.size(); i++)
{
if (out_prod[i].isValid())
products[out_prod[i].product] = &out_prod[i];
@ -390,6 +391,7 @@ static bool find_reactions(color_ostream &out)
static void enable_hooks(bool enable)
{
is_enabled = enable;
INTERPOSE_HOOK(item_hook, isImprovable).apply(enable);
INTERPOSE_HOOK(product_hook, produce).apply(enable);
}

@ -99,7 +99,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap);
static bool in_transient_swap = false;
DFHACK_PLUGIN_IS_ENABLED(in_transient_swap);
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{

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

@ -125,7 +125,7 @@ static command_result autodump_main(color_ostream &out, vector <string> & parame
{
if (!Gui::getCursorCoords(cx,cy,cz))
{
out.printerr("Cursor position not found. Please enabled the cursor.\n");
out.printerr("Cursor position not found. Please enable the cursor.\n");
return CR_FAILURE;
}
pos_cursor = DFCoord(cx,cy,cz);

@ -44,6 +44,7 @@
using std::string;
using std::endl;
using std::vector;
using namespace DFHack;
using namespace df::enums;
using df::global::ui;
@ -76,7 +77,7 @@ using df::global::world;
* (mining, hunting, and woodcutting) need to be handled carefully to minimize churn.
*/
static int enable_autolabor = 0;
DFHACK_PLUGIN_IS_ENABLED(enable_autolabor);
static bool print_debug = 0;
@ -535,6 +536,7 @@ static void setOptionEnabled(ConfigFlags flag, bool on)
static void cleanup_state()
{
enable_autolabor = false;
labor_infos.clear();
}
@ -1009,6 +1011,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
int noble_penalty = 0;
df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id);
if(hf!=NULL) //can be NULL. E.g. script created citizens
for (int i = 0; i < hf->entity_links.size(); i++)
{
df::histfig_entity_link* hfelink = hf->entity_links.at(i);
@ -1297,6 +1300,27 @@ void print_labor (df::unit_labor labor, color_ostream &out)
}
}
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable )
{
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("World is not loaded: please load a game first.\n");
return CR_FAILURE;
}
if (enable && !enable_autolabor)
{
enable_plugin(out);
}
else if(!enable && enable_autolabor)
{
enable_autolabor = false;
setOptionEnabled(CF_ENABLED, false);
out << "Autolabor is disabled." << endl;
}
return CR_OK;
}
command_result autolabor (color_ostream &out, std::vector <std::string> & parameters)
{
@ -1312,19 +1336,8 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
parameters[0] == "1" || parameters[0] == "disable"))
{
bool enable = (parameters[0] == "1" || parameters[0] == "enable");
if (enable && !enable_autolabor)
{
enable_plugin(out);
}
else if(!enable && enable_autolabor)
{
enable_autolabor = false;
setOptionEnabled(CF_ENABLED, false);
out << "The plugin is disabled." << endl;
}
return CR_OK;
return plugin_enable(out, enable);
}
else if (parameters.size() == 2 && parameters[0] == "haulpct")
{

@ -36,6 +36,7 @@
#include "TileTypes.h"
#include "df/job_item.h"
using namespace std;
using std::map;
using std::string;
using std::vector;
@ -1179,11 +1180,27 @@ color_ostream_proxy console_out(Core::getInstance().getConsole());
IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, render);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{
if (!gps || !INTERPOSE_HOOK(jobutils_hook, feed).apply() || !INTERPOSE_HOOK(jobutils_hook, render).apply())
out.printerr("Could not insert jobutils hooks!\n");
if (!gps)
return CR_FAILURE;
if (enable != is_enabled)
{
if (!INTERPOSE_HOOK(jobutils_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(jobutils_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
hotkeys[construction_type::Wall] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_WALL;
hotkeys[construction_type::Floor] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FLOOR;
hotkeys[construction_type::Ramp] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_RAMP;

@ -583,7 +583,6 @@ static command_result autotrade_cmd(color_ostream &out, vector <string> & parame
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event)
@ -600,11 +599,30 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (!gps || !INTERPOSE_HOOK(trade_hook, feed).apply() || !INTERPOSE_HOOK(trade_hook, render).apply())
out.printerr("Could not insert autotrade hooks!\n");
if (!gps)
return CR_FAILURE;
if (enable != is_enabled)
{
depot_info.reset();
monitor.reset();
if (!INTERPOSE_HOOK(trade_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(trade_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(
PluginCommand(
"autotrade", "Automatically send items in marked stockpiles to trade depot, when trading is possible.",

@ -92,6 +92,8 @@ static void debug(const string &msg)
* Material Choice Screen
*/
static string material_to_string_fn(MaterialInfo m) { return m.toString(); }
struct ItemFilter
{
df::dfhack_material_category mat_mask;
@ -114,8 +116,10 @@ struct ItemFilter
bool matches(MaterialInfo &material) const
{
return any_of(materials.begin(), materials.end(),
[&] (const MaterialInfo &m) { return material.matches(m); });
for (auto it = materials.begin(); it != materials.end(); ++it)
if (material.matches(*it))
return true;
return false;
}
bool matches(df::item *item)
@ -137,8 +141,7 @@ struct ItemFilter
{
vector<string> descriptions;
transform_(materials, descriptions,
[] (MaterialInfo m) { return m.toString(); });
transform_(materials, descriptions, material_to_string_fn);
if (descriptions.size() == 0)
bitfield_to_string(&descriptions, mat_mask);
@ -157,8 +160,8 @@ struct ItemFilter
str.append("/");
if (materials.size() > 0)
{
for_each_(materials,
[&] (MaterialInfo &m) { str.append(m.getToken() + ","); });
for (size_t i = 0; i < materials.size(); i++)
str.append(materials[i].getToken() + ",");
if (str[str.size()-1] == ',')
str.resize(str.size () - 1);
@ -217,6 +220,7 @@ private:
bool valid;
};
static MaterialInfo &material_info_identity_fn(MaterialInfo &m) { return m; }
class ViewscreenChooseMaterial : public dfhack_viewscreen
{
@ -284,13 +288,12 @@ public:
// Category masks
auto masks = masks_column.getSelectedElems();
for_each_(masks,
[&] (df::dfhack_material_category &m) { filter->mat_mask.whole |= m.whole; });
for (auto it = masks.begin(); it != masks.end(); ++it)
filter->mat_mask.whole |= it->whole;
// Specific materials
auto materials = materials_column.getSelectedElems();
transform_(materials, filter->materials,
[] (MaterialInfo &m) { return m; });
transform_(materials, filter->materials, material_info_identity_fn);
Screen::dismiss(this);
}
@ -465,6 +468,7 @@ private:
}
};
static void delete_item_fn(df::job_item *x) { delete x; }
// START Planning
class PlannedBuilding
@ -555,7 +559,7 @@ public:
auto job = building->jobs[0];
for_each_(job->job_items, [] (df::job_item *x) { delete x; });
for_each_(job->job_items, delete_item_fn);
job->job_items.clear();
job->flags.bits.suspend = false;
@ -613,9 +617,10 @@ private:
ItemFilter filter;
};
static map<df::building_type, bool> planmode_enabled, saved_planmodes;
static void enable_quickfort_fn(pair<const df::building_type, bool>& pair) { pair.second = true; }
class Planner
{
public:
@ -804,8 +809,7 @@ public:
void enableQuickfortMode()
{
saved_planmodes = planmode_enabled;
for_each_(planmode_enabled,
[] (pair<const df::building_type, bool>& pair) { pair.second = true; } );
for_each_(planmode_enabled, enable_quickfort_fn);
quickfort_mode = true;
}
@ -1093,8 +1097,8 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
OutputHotkeyString(x, y, "Material Filter:", "m", true, left_margin);
auto filter_descriptions = filter->getMaterialFilterAsVector();
for_each_(filter_descriptions,
[&](string d) { OutputString(COLOR_BROWN, x, y, " *" + d, true, left_margin); });
for (auto it = filter_descriptions.begin(); it != filter_descriptions.end(); ++it)
OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin);
}
else
{
@ -1121,8 +1125,8 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin);
auto filters = filter->getMaterialFilterAsVector();
for_each_(filters,
[&](string d) { OutputString(COLOR_BLUE, x, y, "*" + d, true, left_margin); });
for (auto it = filters.begin(); it != filters.end(); ++it)
OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin);
}
else
{
@ -1153,12 +1157,29 @@ static command_result buildingplan_cmd(color_ostream &out, vector <string> & par
return CR_OK;
}
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (!gps || !INTERPOSE_HOOK(buildingplan_hook, feed).apply() || !INTERPOSE_HOOK(buildingplan_hook, render).apply())
out.printerr("Could not insert buildingplan hooks!\n");
if (!gps)
return CR_FAILURE;
if (enable != is_enabled)
{
planner.reset(out);
if (!INTERPOSE_HOOK(buildingplan_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(buildingplan_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(
PluginCommand(
"buildingplan", "Place furniture before it's built",

@ -221,7 +221,8 @@ static void detect_digging(color_ostream &out)
}
}
static bool active = false;
DFHACK_PLUGIN_IS_ENABLED(active);
static bool auto_grow = false;
static std::vector<int> grow_burrows;

@ -120,14 +120,14 @@ command_result catsplosion (color_ostream &out, std::vector <std::string> & para
female->relations.pregnancy_timer = rand() % 100 + 1;
totalchanged++;
}
else if(!female->relations.pregnancy_ptr)
else if(!female->relations.pregnancy_genes)
{
df::unit_genes *preg = new df::unit_genes;
preg->appearance = female->appearance.genes.appearance;
preg->colors = female->appearance.genes.colors;
female->relations.pregnancy_ptr = preg;
female->relations.pregnancy_genes = preg;
female->relations.pregnancy_timer = rand() % 100 + 1;
female->relations.pregnancy_mystery = 1; // WTF is this?
female->relations.pregnancy_caste = 1;
totalcreated ++;
}
}

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

@ -67,6 +67,7 @@
#include <df/training_assignment.h>
#include <df/general_ref_contains_itemst.h>
using namespace std;
using std::string;
using std::endl;
using namespace DFHack;
@ -76,7 +77,7 @@ using df::global::world;
#define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0]))
static int enable_autolabor = 0;
DFHACK_PLUGIN_IS_ENABLED(enable_autolabor);
static bool print_debug = 0;
@ -1375,6 +1376,7 @@ static void setOptionEnabled(ConfigFlags flag, bool on)
static void cleanup_state()
{
enable_autolabor = false;
labor_infos.clear();
}
@ -2384,6 +2386,27 @@ df::unit_labor lookup_labor_by_name (std::string& name)
return labor;
}
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable )
{
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("World is not loaded: please load a game first.\n");
return CR_FAILURE;
}
if (enable && !enable_autolabor)
{
enable_plugin(out);
}
else if(!enable && enable_autolabor)
{
enable_autolabor = false;
setOptionEnabled(CF_ENABLED, false);
out << "Autolabor is disabled." << endl;
}
return CR_OK;
}
command_result autolabor (color_ostream &out, std::vector <std::string> & parameters)
{
@ -2398,19 +2421,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
(parameters[0] == "enable" || parameters[0] == "disable"))
{
bool enable = (parameters[0] == "enable");
if (enable && !enable_autolabor)
{
enable_plugin(out);
}
else if(!enable && enable_autolabor)
{
enable_autolabor = false;
setOptionEnabled(CF_ENABLED, false);
out << "The plugin is disabled." << endl;
}
return CR_OK;
return plugin_enable(out, enable);
}
else if (parameters.size() == 3 &&
(parameters[0] == "max" || parameters[0] == "priority"))

@ -6,7 +6,10 @@
#include "modules/EventManager.h"
#include "DataDefs.h"
#include "df/construction.h"
#include "df/coord.h"
#include "df/item.h"
#include "df/job.h"
#include "df/world.h"
#include <vector>
@ -34,36 +37,54 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginC
}
command_result eventExample(color_ostream& out, vector<string>& parameters) {
EventManager::EventHandler initiateHandler(jobInitiated, 10);
EventManager::EventHandler completeHandler(jobCompleted, 5);
EventManager::EventHandler initiateHandler(jobInitiated, 1);
EventManager::EventHandler completeHandler(jobCompleted, 0);
EventManager::EventHandler timeHandler(timePassed, 1);
EventManager::EventHandler deathHandler(unitDeath, 500);
EventManager::EventHandler itemHandler(itemCreate, 1000);
EventManager::EventHandler itemHandler(itemCreate, 1);
EventManager::EventHandler buildingHandler(building, 500);
EventManager::EventHandler constructionHandler(construction, 100);
EventManager::EventHandler syndromeHandler(syndrome, 1);
EventManager::EventHandler invasionHandler(invasion, 1000);
Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample");
EventManager::unregisterAll(me);
EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me);
EventManager::registerTick(timeHandler, 1, me);
EventManager::registerTick(timeHandler, 2, me);
EventManager::registerTick(timeHandler, 4, me);
EventManager::registerTick(timeHandler, 8, me);
EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, me);
EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, me);
EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, me);
EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, me);
EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, me);
EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, me);
EventManager::unregisterAll(plugin_self);
EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, plugin_self);
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, plugin_self);
EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, plugin_self);
EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, plugin_self);
EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, plugin_self);
EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, plugin_self);
EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, plugin_self);
EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, plugin_self);
EventManager::registerTick(timeHandler, 1, plugin_self);
EventManager::registerTick(timeHandler, 2, plugin_self);
EventManager::registerTick(timeHandler, 4, plugin_self);
EventManager::registerTick(timeHandler, 8, plugin_self);
int32_t t = EventManager::registerTick(timeHandler, 16, plugin_self);
timeHandler.freq = t;
EventManager::unregister(EventManager::EventType::TICK, timeHandler, plugin_self);
t = EventManager::registerTick(timeHandler, 32, plugin_self);
t = EventManager::registerTick(timeHandler, 32, plugin_self);
t = EventManager::registerTick(timeHandler, 32, plugin_self);
timeHandler.freq = t;
EventManager::unregister(EventManager::EventType::TICK, timeHandler, plugin_self);
EventManager::unregister(EventManager::EventType::TICK, timeHandler, plugin_self);
out.print("Events registered.\n");
return CR_OK;
}
void jobInitiated(color_ostream& out, void* job) {
out.print("Job initiated! 0x%X\n", job);
//static int timerCount=0;
//static int timerDenom=0;
void jobInitiated(color_ostream& out, void* job_) {
out.print("Job initiated! 0x%X\n", job_);
/*
df::job* job = (df::job*)job_;
out.print(" completion_timer = %d\n", job->completion_timer);
if ( job->completion_timer != -1 ) timerCount++;
timerDenom++;
out.print(" frac = %d / %d\n", timerCount, timerDenom);
*/
}
void jobCompleted(color_ostream& out, void* job) {
@ -95,6 +116,14 @@ void building(color_ostream& out, void* ptr) {
void construction(color_ostream& out, void* ptr) {
out.print("Construction created/destroyed: 0x%X\n", ptr);
df::construction* constr = (df::construction*)ptr;
df::coord pos = constr->pos;
out.print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z);
if ( df::construction::find(pos) == NULL )
out.print(" construction destroyed\n");
else
out.print(" construction created\n");
}
void syndrome(color_ostream& out, void* ptr) {

@ -16,6 +16,8 @@ using std::vector;
using std::string;
using namespace DFHack;
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
//FIXME: possible race conditions with calling kittens from the IO thread and shutdown from Core.
bool shutdown_flag = false;
bool final_flag = true;
@ -141,6 +143,7 @@ command_result trackmenu (color_ostream &out, vector <string> & parameters)
if(df::global::ui)
{
trackmenu_flg = true;
is_enabled = true;
last_menu = df::global::ui->main.mode;
out.print("Menu: %d\n",last_menu);
return CR_OK;
@ -155,6 +158,7 @@ command_result trackmenu (color_ostream &out, vector <string> & parameters)
command_result trackpos (color_ostream &out, vector <string> & parameters)
{
trackpos_flg = !trackpos_flg;
is_enabled = true;
return CR_OK;
}
@ -214,6 +218,7 @@ command_result ktimer (color_ostream &out, vector <string> & parameters)
// harmless potential data race here...
timeLast = timeend;
timering = true;
is_enabled = true;
return CR_OK;
}

@ -15,6 +15,8 @@ using namespace DFHack;
uint64_t timeLast=0;
static tthread::mutex* mymutex=0;
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
struct memory_data
{
void * addr;
@ -96,6 +98,7 @@ void Deinit()
{
if(memdata.state==STATE_ON)
{
is_enabled = false;
memdata.state=STATE_OFF;
delete [] memdata.buf;
delete [] memdata.lbuf;
@ -140,6 +143,7 @@ command_result memview (color_ostream &out, vector <string> & parameters)
{
Deinit();
memdata.state=STATE_OFF;
is_enabled = false;
mymutex->unlock();
return CR_OK;
}
@ -156,6 +160,7 @@ command_result memview (color_ostream &out, vector <string> & parameters)
mymutex->unlock();
return CR_OK;
}
is_enabled = true;
memdata.state=STATE_ON;
}
if(parameters.size()>1)

@ -31,7 +31,7 @@ static command_result nestboxes(color_ostream &out, vector <string> & parameters
DFHACK_PLUGIN("nestboxes");
static bool enabled = false;
DFHACK_PLUGIN_IS_ENABLED(enabled);
static void eggscan(color_ostream &out)
{
@ -97,6 +97,12 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
enabled = enable;
return CR_OK;
}
static command_result nestboxes(color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;

@ -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;
}

@ -23,6 +23,8 @@ using df::global::gps;
DFHACK_PLUGIN("vshook");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
struct title_hook : df::viewscreen_titlest {
typedef df::viewscreen_titlest interpose_base;
@ -37,17 +39,30 @@ struct title_hook : df::viewscreen_titlest {
IMPLEMENT_VMETHOD_INTERPOSE(title_hook, render);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{
if (gps)
if (!gps)
return CR_FAILURE;
if (enable != is_enabled)
{
if (!INTERPOSE_HOOK(title_hook, render).apply())
out.printerr("Could not interpose viewscreen_titlest::render\n");
if (!INTERPOSE_HOOK(title_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
// DON'T DO THIS IN NON-EXAMPLE PLUGINS
plugin_enable(out, true);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
INTERPOSE_HOOK(title_hook, render).remove();

@ -0,0 +1,211 @@
#include "Core.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/EventManager.h"
#include "modules/MapCache.h"
#include "modules/Maps.h"
#include "df/coord.h"
#include "df/global_objects.h"
#include "df/job.h"
#include "df/map_block.h"
#include "df/tile_dig_designation.h"
#include "df/world.h"
#include <set>
#include <string>
#include <vector>
using namespace DFHack;
using namespace std;
command_result digFlood (color_ostream &out, std::vector <std::string> & parameters);
DFHACK_PLUGIN("digFlood");
void onDig(color_ostream& out, void* ptr);
void maybeExplore(color_ostream& out, MapExtras::MapCache& cache, df::coord pt, set<df::coord>& jobLocations);
EventManager::EventHandler digHandler(onDig, 0);
//bool enabled = false;
DFHACK_PLUGIN_IS_ENABLED(enabled);
bool digAll = false;
set<string> autodigMaterials;
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) {
if (enabled == enable)
return CR_OK;
enabled = enable;
if ( enabled ) {
EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, digHandler, plugin_self);
} else {
EventManager::unregisterAll(plugin_self);
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"digFlood", "Automatically dig out veins as you discover them.",
digFlood, false,
"Example:\n"
" digFlood 0\n"
" disable plugin\n"
" digFlood 1\n"
" enable plugin\n"
" digFlood 0 MICROCLINE COAL_BITUMINOUS 1\n"
" disable plugin and remove microcline and bituminous coal from being monitored, then re-enable plugin"
" digFlood 1 MICROCLINE 0 COAL_BITUMINOUS 1\n"
" do monitor microcline, don't monitor COAL_BITUMINOUS, then enable plugin\n"
" digFlood CLEAR\n"
" remove all inorganics from monitoring\n"
" digFlood digAll1\n"
" enable digAll mode: dig any vein, regardless of the monitor list\n"
" digFlood digAll0\n"
" disable digAll mode\n"
"\n"
"Note that while order matters, multiple commands can be sequenced in one line. It is recommended to alter your dfhack.init file so that you won't have to type in every mineral type you want to dig every time you start the game. Material names are case sensitive.\n"
));
return CR_OK;
}
void onDig(color_ostream& out, void* ptr) {
CoreSuspender bob;
df::job* job = (df::job*)ptr;
if ( job->completion_timer > 0 )
return;
if ( job->job_type != df::enums::job_type::Dig &&
job->job_type != df::enums::job_type::CarveUpwardStaircase &&
job->job_type != df::enums::job_type::CarveDownwardStaircase &&
job->job_type != df::enums::job_type::CarveUpDownStaircase &&
job->job_type != df::enums::job_type::CarveRamp &&
job->job_type != df::enums::job_type::DigChannel )
return;
set<df::coord> jobLocations;
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
if ( link->item->job_type != df::enums::job_type::Dig &&
link->item->job_type != df::enums::job_type::CarveUpwardStaircase &&
link->item->job_type != df::enums::job_type::CarveDownwardStaircase &&
link->item->job_type != df::enums::job_type::CarveUpDownStaircase &&
link->item->job_type != df::enums::job_type::CarveRamp &&
link->item->job_type != df::enums::job_type::DigChannel )
continue;
jobLocations.insert(link->item->pos);
}
MapExtras::MapCache cache;
df::coord pos = job->pos;
for ( int16_t a = -1; a <= 1; a++ ) {
for ( int16_t b = -1; b <= 1; b++ ) {
maybeExplore(out, cache, df::coord(pos.x+a,pos.y+b,pos.z), jobLocations);
}
}
cache.trash();
}
void maybeExplore(color_ostream& out, MapExtras::MapCache& cache, df::coord pt, set<df::coord>& jobLocations) {
if ( !Maps::isValidTilePos(pt) ) {
return;
}
df::map_block* block = Maps::getTileBlock(pt);
if (!block)
return;
if ( block->designation[pt.x&0xF][pt.y&0xF].bits.hidden )
return;
df::tiletype type = block->tiletype[pt.x&0xF][pt.y&0xF];
if ( ENUM_ATTR(tiletype, material, type) != df::enums::tiletype_material::MINERAL )
return;
if ( ENUM_ATTR(tiletype, shape, type) != df::enums::tiletype_shape::WALL )
return;
if ( block->designation[pt.x&0xF][pt.y&0xF].bits.dig != df::enums::tile_dig_designation::No )
return;
uint32_t xMax,yMax,zMax;
Maps::getSize(xMax,yMax,zMax);
if ( pt.x == 0 || pt.y == 0 || pt.x+1 == xMax*16 || pt.y+1 == yMax*16 )
return;
if ( jobLocations.find(pt) != jobLocations.end() ) {
return;
}
int16_t mat = cache.veinMaterialAt(pt);
if ( mat == -1 )
return;
if ( !digAll ) {
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[mat];
if ( autodigMaterials.find(inorganic->id) == autodigMaterials.end() ) {
return;
}
}
block->designation[pt.x&0xF][pt.y&0xF].bits.dig = df::enums::tile_dig_designation::Default;
block->flags.bits.designated = true;
// *df::global::process_dig = true;
// *df::global::process_jobs = true;
}
command_result digFlood (color_ostream &out, std::vector <std::string> & parameters)
{
bool adding = true;
set<string> toAdd, toRemove;
for ( size_t a = 0; a < parameters.size(); a++ ) {
int32_t i = (int32_t)strtol(parameters[a].c_str(), NULL, 0);
if ( i == 0 && parameters[a] == "0" ) {
plugin_enable(out, false);
adding = false;
continue;
} else if ( i == 1 ) {
plugin_enable(out, true);
adding = true;
continue;
}
if ( parameters[a] == "CLEAR" )
autodigMaterials.clear();
if ( parameters[a] == "digAll0" ) {
digAll = false;
continue;
}
if ( parameters[a] == "digAll1" ) {
digAll = true;
continue;
}
for ( size_t b = 0; b < df::global::world->raws.inorganics.size(); b++ ) {
df::inorganic_raw* inorganic = df::global::world->raws.inorganics[b];
if ( parameters[a] == inorganic->id ) {
if ( adding )
toAdd.insert(parameters[a]);
else
toRemove.insert(parameters[a]);
goto loop;
}
}
out.print("Could not find material \"%s\".\n", parameters[a].c_str());
return CR_WRONG_USAGE;
loop: continue;
}
autodigMaterials.insert(toAdd.begin(), toAdd.end());
for ( auto a = toRemove.begin(); a != toRemove.end(); a++ )
autodigMaterials.erase(*a);
return CR_OK;
}

@ -0,0 +1,35 @@
PROJECT (diggingInvaders)
# A list of source files
SET(PROJECT_SRCS
diggingInvaders.cpp
edgeCost.cpp
assignJob.cpp
)
# A list of headers
SET(PROJECT_HDRS
edgeCost.h
assignJob.h
)
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)
# mash them together (headers are marked as headers and nothing will try to compile them)
LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS})
#linux
IF(UNIX)
add_definitions(-DLINUX_BUILD)
SET(PROJECT_LIBS
# add any extra linux libs here
${PROJECT_LIBS}
)
# windows
ELSE(UNIX)
SET(PROJECT_LIBS
# add any extra linux libs here
${PROJECT_LIBS}
$(NOINHERIT)
)
ENDIF(UNIX)
# this makes sure all the stuff is put in proper places and linked to dfhack
DFHACK_PLUGIN(diggingInvaders ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS})

@ -0,0 +1,300 @@
#include "assignJob.h"
#include "modules/Buildings.h"
#include "modules/Items.h"
#include "modules/Job.h"
#include "modules/Materials.h"
#include "df/building.h"
#include "df/construction.h"
#include "df/coord.h"
#include "df/general_ref.h"
#include "df/general_ref_building_holderst.h"
#include "df/general_ref_unit.h"
//#include "df/general_ref_unit_holderst.h"
#include "df/general_ref_unit_workerst.h"
#include "df/historical_entity.h"
#include "df/item.h"
#include "df/itemdef_weaponst.h"
#include "df/item_quality.h"
#include "df/item_type.h"
#include "df/item_weaponst.h"
#include "df/job.h"
#include "df/job_skill.h"
#include "df/job_type.h"
#include "df/reaction_product_itemst.h"
#include "df/reaction_reagent.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/unit_inventory_item.h"
#include "df/world_site.h"
void getRidOfOldJob(df::unit* unit) {
if ( unit->job.current_job == NULL ) {
return;
}
df::job* job = unit->job.current_job;
unit->job.current_job = NULL;
if ( job->list_link->prev != NULL ) {
job->list_link->prev->next = job->list_link->next;
}
if ( job->list_link->next != NULL ) {
job->list_link->next->prev = job->list_link->prev;
}
//TODO: consider building pointers?
//for now, just let the memory leak TODO: fix
//delete job->list_link;
//delete job;
}
int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map<df::coord,df::coord,PointHash> parentMap, unordered_map<df::coord,cost_t,PointHash>& costMap, vector<int32_t>& invaders, unordered_set<df::coord,PointHash>& requiresZNeg, unordered_set<df::coord,PointHash>& requiresZPos, MapExtras::MapCache& cache, DigAbilities& abilities ) {
df::unit* firstInvader = df::unit::find(invaders[0]);
if ( !firstInvader ) {
return -1;
}
//do whatever you need to do at the first important edge
df::coord pt1 = firstImportantEdge.p1;
df::coord pt2 = firstImportantEdge.p2;
if ( costMap[pt1] > costMap[pt2] ) {
df::coord temp = pt1;
pt1 = pt2;
pt2 = temp;
}
//out.print("first important edge: (%d,%d,%d) -> (%d,%d,%d)\n", pt1.x,pt1.y,pt1.z, pt2.x,pt2.y,pt2.z);
int32_t jobId = -1;
df::map_block* block1 = Maps::getTileBlock(pt1);
df::map_block* block2 = Maps::getTileBlock(pt2);
bool passable1 = block1->walkable[pt1.x&0xF][pt1.y&0xF];
bool passable2 = block2->walkable[pt2.x&0xF][pt2.y&0xF];
df::coord location;
df::building* building = Buildings::findAtTile(pt2);
df::coord buildingPos = pt2;
if ( pt1.z > pt2.z ) {
building = Buildings::findAtTile(df::coord(pt2.x,pt2.y,pt2.z+1));
buildingPos = df::coord(pt2.x,pt2.y,pt2.z+1);
}
if ( building != NULL ) {
df::coord destroyFrom = parentMap[buildingPos];
if ( destroyFrom.z != buildingPos.z ) {
//TODO: deal with this
}
//out.print("%s, line %d: Destroying building %d at (%d,%d,%d) from (%d,%d,%d).\n", __FILE__, __LINE__, building->id, buildingPos.x,buildingPos.y,buildingPos.z, destroyFrom.x,destroyFrom.y,destroyFrom.z);
df::job* job = new df::job;
job->job_type = df::enums::job_type::DestroyBuilding;
//job->flags.bits.special = 1;
df::general_ref_building_holderst* buildingRef = new df::general_ref_building_holderst;
buildingRef->building_id = building->id;
job->general_refs.push_back(buildingRef);
df::general_ref_unit_workerst* workerRef = new df::general_ref_unit_workerst;
workerRef->unit_id = firstInvader->id;
job->general_refs.push_back(workerRef);
getRidOfOldJob(firstInvader);
firstInvader->job.current_job = job;
firstInvader->path.path.x.clear();
firstInvader->path.path.y.clear();
firstInvader->path.path.z.clear();
firstInvader->path.dest = destroyFrom;
location = destroyFrom;
firstInvader->job.hunt_target = NULL;
firstInvader->job.destroy_target = NULL;
building->jobs.clear();
building->jobs.push_back(job);
Job::linkIntoWorld(job);
jobId = job->id;
job->completion_timer = abilities.jobDelay[CostDimension::DestroyBuilding];
} else {
df::tiletype* type1 = Maps::getTileType(pt1);
df::tiletype* type2 = Maps::getTileType(pt2);
df::tiletype_shape shape1 = ENUM_ATTR(tiletype, shape, *type1);
df::tiletype_shape shape2 = ENUM_ATTR(tiletype, shape, *type2);
bool construction2 = ENUM_ATTR(tiletype, material, *type2) == df::enums::tiletype_material::CONSTRUCTION;
if ( construction2 ) {
df::job* job = new df::job;
job->job_type = df::enums::job_type::RemoveConstruction;
df::general_ref_unit_workerst* workerRef = new df::general_ref_unit_workerst;
workerRef->unit_id = firstInvader->id;
job->general_refs.push_back(workerRef);
job->pos = pt2;
getRidOfOldJob(firstInvader);
firstInvader->job.current_job = job;
firstInvader->path.path.x.clear();
firstInvader->path.path.y.clear();
firstInvader->path.path.z.clear();
firstInvader->path.dest = pt1;
location = pt1;
firstInvader->job.hunt_target = NULL;
firstInvader->job.destroy_target = NULL;
Job::linkIntoWorld(job);
jobId = job->id;
df::construction* constr = df::construction::find(pt2);
bool smooth = constr != NULL && constr->item_type != df::enums::item_type::BOULDER;
if ( smooth )
job->completion_timer = abilities.jobDelay[CostDimension::DestroySmoothConstruction];
else
job->completion_timer = abilities.jobDelay[CostDimension::DestroyRoughConstruction];
} else {
bool walkable_low1 = shape1 == df::tiletype_shape::STAIR_DOWN || shape1 == df::tiletype_shape::STAIR_UPDOWN;
bool walkable_low2 = shape2 == df::tiletype_shape::STAIR_DOWN || shape2 == df::tiletype_shape::STAIR_UPDOWN;
bool walkable_high1 = shape1 == df::tiletype_shape::STAIR_UP || shape1 == df::tiletype_shape::STAIR_UPDOWN;
bool walkable_high2 = shape2 == df::tiletype_shape::STAIR_UP || shape2 == df::tiletype_shape::STAIR_UPDOWN;
//must be a dig job
bool up1 = !walkable_high1 && requiresZPos.find(pt1) != requiresZPos.end();
bool up2 = !walkable_high2 && requiresZPos.find(pt2) != requiresZPos.end();
bool down1 = !walkable_low1 && requiresZNeg.find(pt1) != requiresZNeg.end();
bool down2 = !walkable_low2 && requiresZNeg.find(pt2) != requiresZNeg.end();
bool up;
bool down;
df::coord goHere;
df::coord workHere;
if ( pt1.z == pt2.z ) {
up = up2;
down = down2;
goHere = pt1;
workHere = pt2;
} else {
if ( up1 || down1 ) {
up = up1;
down = down1;
goHere = pt1;
workHere = pt1;
} else {
up = up2;
down = down2;
goHere = pt1;
workHere = pt2;
}
}
df::job* job = new df::job;
if ( up && down ) {
job->job_type = df::enums::job_type::CarveUpDownStaircase;
//out.print("%s, line %d: type = up/down\n", __FILE__, __LINE__);
} else if ( up && !down ) {
job->job_type = df::enums::job_type::CarveUpwardStaircase;
//out.print("%s, line %d: type = up\n", __FILE__, __LINE__);
} else if ( !up && down ) {
job->job_type = df::enums::job_type::CarveDownwardStaircase;
//out.print("%s, line %d: type = down\n", __FILE__, __LINE__);
} else {
job->job_type = df::enums::job_type::Dig;
//out.print("%s, line %d: type = dig\n", __FILE__, __LINE__);
}
//out.print("%s, line %d: up=%d,up1=%d,up2=%d, down=%d,down1=%d,down2=%d\n", __FILE__, __LINE__, up,up1,up2, down,down1,down2);
job->pos = workHere;
firstInvader->path.dest = goHere;
location = goHere;
df::general_ref_unit_workerst* ref = new df::general_ref_unit_workerst;
ref->unit_id = firstInvader->id;
job->general_refs.push_back(ref);
firstInvader->job.hunt_target = NULL;
firstInvader->job.destroy_target = NULL;
getRidOfOldJob(firstInvader);
firstInvader->job.current_job = job;
firstInvader->path.path.x.clear();
firstInvader->path.path.y.clear();
firstInvader->path.path.z.clear();
Job::linkIntoWorld(job);
jobId = job->id;
job->completion_timer = abilities.jobDelay[CostDimension::Dig];
//TODO: test if he already has a pick
bool hasPick = false;
for ( size_t a = 0; a < firstInvader->inventory.size(); a++ ) {
df::unit_inventory_item* inv_item = firstInvader->inventory[a];
if ( inv_item->mode != df::unit_inventory_item::Weapon || inv_item->body_part_id != firstInvader->body.weapon_bp )
continue;
df::item* oldItem = inv_item->item;
if ( oldItem->getType() != df::enums::item_type::WEAPON )
continue;
df::item_weaponst* oldWeapon = (df::item_weaponst*)oldItem;
df::itemdef_weaponst* oldType = oldWeapon->subtype;
if ( oldType->skill_melee != df::enums::job_skill::MINING )
continue;
hasPick = true;
break;
}
if ( hasPick )
return firstInvader->id;
//create and give a pick
//based on createitem.cpp
df::reaction_product_itemst *prod = NULL;
//TODO: consider filtering based on entity/civ stuff
for ( size_t a = 0; a < df::global::world->raws.itemdefs.weapons.size(); a++ ) {
df::itemdef_weaponst* oldType = df::global::world->raws.itemdefs.weapons[a];
if ( oldType->skill_melee != df::enums::job_skill::MINING )
continue;
prod = df::allocate<df::reaction_product_itemst>();
prod->item_type = df::item_type::WEAPON;
prod->item_subtype = a;
break;
}
if ( prod == NULL ) {
out.print("%s, %d: no valid item.\n", __FILE__, __LINE__);
return -1;
}
DFHack::MaterialInfo material;
if ( !material.find("OBSIDIAN") ) {
out.print("%s, %d: no water.\n", __FILE__, __LINE__);
return -1;
}
prod->mat_type = material.type;
prod->mat_index = material.index;
prod->probability = 100;
prod->count = 1;
prod->product_dimension = 1;
vector<df::item*> out_items;
vector<df::reaction_reagent*> in_reag;
vector<df::item*> in_items;
prod->produce(firstInvader, &out_items, &in_reag, &in_items, 1, df::job_skill::NONE,
df::historical_entity::find(firstInvader->civ_id),
df::world_site::find(df::global::ui->site_id));
if ( out_items.size() != 1 ) {
out.print("%s, %d: wrong size: %d.\n", __FILE__, __LINE__, out_items.size());
return -1;
}
out_items[0]->moveToGround(firstInvader->pos.x, firstInvader->pos.y, firstInvader->pos.z);
#if 0
//check for existing item there
for ( size_t a = 0; a < firstInvader->inventory.size(); a++ ) {
df::unit_inventory_item* inv_item = firstInvader->inventory[a];
if ( false || inv_item->body_part_id == part ) {
//throw it on the ground
Items::moveToGround(cache, inv_item->item, firstInvader->pos);
}
}
#endif
Items::moveToInventory(cache, out_items[0], firstInvader, df::unit_inventory_item::T_mode::Weapon, firstInvader->body.weapon_bp);
delete prod;
}
}
#if 0
//tell EVERYONE to move there
for ( size_t a = 0; a < invaders.size(); a++ ) {
df::unit* invader = invaders[a];
invader->path.path.x.clear();
invader->path.path.y.clear();
invader->path.path.z.clear();
invader->path.dest = location;
//invader->flags1.bits.invades = true;
//invader->flags1.bits.marauder = true;
//invader->flags2.bits.visitor_uninvited = true;
invader->relations.group_leader_id = invader->id;
}
#endif
return firstInvader->id;
}

@ -0,0 +1,13 @@
#pragma once
#include "edgeCost.h"
#include "modules/MapCache.h"
#include <unordered_map>
#include <unordered_set>
using namespace std;
int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map<df::coord,df::coord,PointHash> parentMap, unordered_map<df::coord,cost_t,PointHash>& costMap, vector<int32_t>& invaders, unordered_set<df::coord,PointHash>& requiresZNeg, unordered_set<df::coord,PointHash>& requiresZPos, MapExtras::MapCache& cache, DigAbilities& abilities);

@ -0,0 +1,631 @@
#include "assignJob.h"
#include "edgeCost.h"
#include "Core.h"
#include "Console.h"
#include "DataDefs.h"
#include "Export.h"
#include "PluginManager.h"
#include "Types.h"
#include "modules/Buildings.h"
#include "modules/EventManager.h"
#include "modules/Gui.h"
#include "modules/Job.h"
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "modules/Units.h"
#include "modules/World.h"
#include "df/body_part_raw_flags.h"
#include "df/building.h"
#include "df/building_type.h"
#include "df/caste_body_info.h"
#include "df/coord.h"
#include "df/creature_raw.h"
#include "df/general_ref.h"
#include "df/general_ref_building_holderst.h"
#include "df/general_ref_unit.h"
#include "df/general_ref_unit_holderst.h"
#include "df/general_ref_unit_workerst.h"
#include "df/global_objects.h"
#include "df/invasion_info.h"
#include "df/item.h"
#include "df/itemdef_weaponst.h"
#include "df/item_quality.h"
#include "df/item_weaponst.h"
#include "df/inorganic_raw.h"
#include "df/job.h"
#include "df/job_list_link.h"
#include "df/job_skill.h"
#include "df/job_type.h"
#include "df/map_block.h"
#include "df/strain_type.h"
#include "df/tile_building_occ.h"
#include "df/tile_occupancy.h"
#include "df/tiletype.h"
#include "df/tiletype_material.h"
#include "df/tiletype_shape.h"
#include "df/tiletype_shape_basic.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/unit_inventory_item.h"
#include "df/world.h"
#include <algorithm>
#include <ctime>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <unordered_map>
#include <unordered_set>
using namespace std;
using namespace DFHack;
using namespace df::enums;
command_result diggingInvadersCommand(color_ostream &out, std::vector <std::string> & parameters);
void watchForJobComplete(color_ostream& out, void* ptr);
void newInvasionHandler(color_ostream& out, void* ptr);
void clearDijkstra();
void findAndAssignInvasionJob(color_ostream& out, void*);
//int32_t manageInvasion(color_ostream& out);
DFHACK_PLUGIN_IS_ENABLED(enabled);
DFHACK_PLUGIN("diggingInvaders");
//TODO: when world unloads
static int32_t lastInvasionJob=-1;
static int32_t lastInvasionDigger = -1;
static int32_t edgesPerTick = 100;
//static EventManager::EventHandler jobCompleteHandler(watchForJobComplete, 5);
static bool activeDigging=false;
static unordered_set<string> diggingRaces;
static unordered_set<int32_t> invaderJobs;
static df::coord lastDebugEdgeCostPoint;
unordered_map<string, DigAbilities> digAbilities;
static cost_t costWeightDefault[] = {
//Distance
1,
//Destroy Building
2,
//Dig
10000,
//DestroyRoughConstruction
1000,
//DestroySmoothConstruction
100,
};
static int32_t jobDelayDefault[] = {
//Distance
-1,
//Destroy Building
1000,
//Dig
1000,
//DestroyRoughConstruction
1000,
//DestroySmoothConstruction
100,
};
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"diggingInvaders", "Makes invaders dig to your dwarves.",
diggingInvadersCommand, false, /* true means that the command can't be used from non-interactive user interface */
" diggingInvaders 0\n disables the plugin\n"
" diggingInvaders 1\n enables the plugin\n"
" diggingInvaders enable\n enables the plugin\n"
" diggingInvaders disable\n disables the plugin\n"
" diggingInvaders add GOBLIN\n registers the race GOBLIN as a digging invader. Case-sensitive.\n"
" diggingInvaders remove GOBLIN\n unregisters the race GOBLIN as a digging invader. Case-sensitive.\n"
" diggingInvaders setCost GOBLIN walk n\n sets the walk cost in the path algorithm for the race GOBLIN\n"
" diggingInvaders setCost GOBLIN destroyBuilding n\n"
" diggingInvaders setCost GOBLIN dig n\n"
" diggingInvaders setCost GOBLIN destroyRoughConstruction n\n rough constructions are made from boulders\n"
" diggingInvaders setCost GOBLIN destroySmoothConstruction n\n smooth constructions are made from blocks or bars instead of boulders\n"
" diggingInvaders setDelay GOBLIN destroyBuilding n\n adds to the job_completion_timer of destroy building jobs that are assigned to invaders\n"
" diggingInvaders setDelay GOBLIN dig n\n"
" diggingInvaders setDelay GOBLIN destroyRoughConstruction n\n"
" diggingInvaders setDelay GOBLIN destroySmoothConstruction n\n"
" diggingInvaders now\n makes invaders try to dig now, if plugin is enabled\n"
" diggingInvaders clear\n clears all digging invader races\n"
" diggingInvaders edgesPerTick n\n makes the pathfinding algorithm work on at most n edges per tick. Set to 0 or lower to make it unlimited."
// " diggingInvaders\n Makes invaders try to dig now.\n"
));
//*df::global::debug_showambush = true;
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) {
if ( enabled == enable )
return CR_OK;
enabled = enable;
EventManager::unregisterAll(plugin_self);
clearDijkstra();
lastInvasionJob = lastInvasionDigger = -1;
activeDigging = false;
invaderJobs.clear();
if ( enabled ) {
EventManager::EventHandler handler(newInvasionHandler, 1000);
EventManager::registerListener(EventManager::EventType::INVASION, handler, plugin_self);
findAndAssignInvasionJob(out, (void*)0);
}
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case DFHack::SC_WORLD_LOADED:
//TODO: check game mode
//in case there are invaders when the game is loaded, we check if there's work to be done
activeDigging = enabled;
clearDijkstra();
findAndAssignInvasionJob(out, (void*)0);
break;
case DFHack::SC_WORLD_UNLOADED:
// cleanup
plugin_enable(out, false);
break;
default:
break;
}
return CR_OK;
}
df::coord getRoot(df::coord point, unordered_map<df::coord, df::coord>& rootMap);
class PointComp {
public:
unordered_map<df::coord, cost_t, PointHash> *pointCost;
PointComp(unordered_map<df::coord, cost_t, PointHash> *p): pointCost(p) {
}
int32_t operator()(df::coord p1, df::coord p2) {
if ( p1 == p2 ) return 0;
auto i1 = pointCost->find(p1);
auto i2 = pointCost->find(p2);
if ( i1 == pointCost->end() && i2 == pointCost->end() )
return p1 < p2;
if ( i1 == pointCost->end() )
return true;
if ( i2 == pointCost->end() )
return false;
cost_t c1 = (*i1).second;
cost_t c2 = (*i2).second;
if ( c1 != c2 )
return c1 < c2;
return p1 < p2;
}
};
//bool important(df::coord pos, map<df::coord, set<Edge> >& edges, df::coord prev, set<df::coord>& importantPoints, set<Edge>& importantEdges);
void newInvasionHandler(color_ostream& out, void* ptr) {
if ( activeDigging )
return;
activeDigging = true;
findAndAssignInvasionJob(out, (void*)0);
}
command_result diggingInvadersCommand(color_ostream& out, std::vector<std::string>& parameters) {
for ( size_t a = 0; a < parameters.size(); a++ ) {
if ( parameters[a] == "1" || parameters[a] == "enable" ) {
plugin_enable(out,true);
} else if ( parameters[a] == "0" || parameters[a] == "disable" ) {
plugin_enable(out,false);
} else if ( parameters[a] == "add" || parameters[a] == "remove" ) {
if ( a+1 >= parameters.size() )
return CR_WRONG_USAGE;
string race = parameters[a+1];
if ( parameters[a] == "add" ) {
diggingRaces.insert(race);
DigAbilities& abilities = digAbilities[race];
memcpy(abilities.costWeight, costWeightDefault, costDim*sizeof(cost_t));
memcpy(abilities.jobDelay, jobDelayDefault, costDim*sizeof(int32_t));
} else {
diggingRaces.erase(race);
digAbilities.erase(race);
}
a++;
} else if ( parameters[a] == "setCost" || parameters[a] == "setDelay" ) {
if ( a+3 >= parameters.size() )
return CR_WRONG_USAGE;
string raceString = parameters[a+1];
if ( digAbilities.find(raceString) == digAbilities.end() ) {
DigAbilities bob;
memset(&bob, 0xFF, sizeof(bob));
digAbilities[raceString] = bob;
}
DigAbilities& abilities = digAbilities[raceString];
string costStr = parameters[a+2];
int32_t costDim = -1;
if ( costStr == "walk" ) {
costDim = CostDimension::Walk;
if ( parameters[a] == "setDelay" )
return CR_WRONG_USAGE;
} else if ( costStr == "destroyBuilding" ) {
costDim = CostDimension::DestroyBuilding;
} else if ( costStr == "dig" ) {
costDim = CostDimension::Dig;
} else if ( costStr == "destroyRoughConstruction" ) {
costDim = CostDimension::DestroyRoughConstruction;
} else if ( costStr == "destroySmoothConstruction" ) {
costDim = CostDimension::DestroySmoothConstruction;
} else {
return CR_WRONG_USAGE;
}
cost_t value;
stringstream asdf(parameters[a+3]);
asdf >> value;
//if ( parameters[a] == "setCost" && value <= 0 )
// return CR_WRONG_USAGE;
if ( parameters[a] == "setCost" ) {
abilities.costWeight[costDim] = value;
} else {
abilities.jobDelay[costDim] = value;
}
a += 3;
} else if ( parameters[a] == "edgeCost" ) {
if ( a+1 >= parameters.size() )
return CR_WRONG_USAGE;
string raceString = parameters[a+1];
if ( digAbilities.find(raceString) == digAbilities.end() ) {
out.print("Race %s does not have dig abilities assigned.\n", raceString.c_str());
return CR_WRONG_USAGE;
}
DigAbilities& abilities = digAbilities[raceString];
df::coord bob = Gui::getCursorPos();
out.print("(%d,%d,%d), (%d,%d,%d): cost = %lld\n", lastDebugEdgeCostPoint.x, lastDebugEdgeCostPoint.y, lastDebugEdgeCostPoint.z, bob.x, bob.y, bob.z, getEdgeCost(out, lastDebugEdgeCostPoint, bob, abilities));
lastDebugEdgeCostPoint = bob;
a++;
} else if ( parameters[a] == "now" ) {
activeDigging = true;
findAndAssignInvasionJob(out, (void*)0);
} else if ( parameters[a] == "clear" ) {
diggingRaces.clear();
digAbilities.clear();
} else if ( parameters[a] == "edgesPerTick" ) {
if ( a+1 >= parameters.size() )
return CR_WRONG_USAGE;
stringstream asdf(parameters[a+1]);
int32_t edgeCount = 100;
asdf >> edgeCount;
edgesPerTick = edgeCount;
a++;
}
else {
return CR_WRONG_USAGE;
}
}
activeDigging = enabled;
out.print("diggingInvaders: enabled = %d, activeDigging = %d, edgesPerTick = %d\n", enabled, activeDigging, edgesPerTick);
return CR_OK;
}
/////////////////////////////////////////////////////////////////////////////////////////
//dijkstra globals
vector<int32_t> invaders;
unordered_set<df::coord, PointHash> invaderPts;
unordered_set<df::coord, PointHash> localPts;
unordered_map<df::coord,df::coord,PointHash> parentMap;
unordered_map<df::coord,cost_t,PointHash> costMap;
PointComp comp(&costMap);
set<df::coord, PointComp> fringe(comp);
EventManager::EventHandler findJobTickHandler(findAndAssignInvasionJob, 1);
int32_t localPtsFound = 0;
unordered_set<df::coord,PointHash> closedSet;
unordered_map<df::coord,int32_t,PointHash> workNeeded; //non-walking work needed to get there
bool foundTarget = false;
int32_t edgeCount = 0;
void clearDijkstra() {
invaders.clear();
invaderPts.clear();
localPts.clear();
parentMap.clear();
costMap.clear();
comp = PointComp(&costMap);
fringe = set<df::coord,PointComp>(comp);
localPtsFound = edgeCount = 0;
foundTarget = false;
closedSet.clear();
workNeeded.clear();
}
/////////////////////////////////////////////////////////////////////////////////////////
void findAndAssignInvasionJob(color_ostream& out, void* tickTime) {
CoreSuspender suspend;
//returns the worker id of the job created //used to
//out.print("%s, %d: %d\n", __FILE__, __LINE__, (int32_t)tickTime);
if ( !enabled || !activeDigging ) {
clearDijkstra();
return;
}
EventManager::unregister(EventManager::EventType::TICK, findJobTickHandler, plugin_self);
EventManager::registerTick(findJobTickHandler, 1, plugin_self);
if ( fringe.empty() ) {
df::unit* lastDigger = df::unit::find(lastInvasionDigger);
if ( lastDigger && lastDigger->job.current_job && lastDigger->job.current_job->id == lastInvasionJob ) {
return;
}
//out.print("%s,%d: lastDigger = %d, last job = %d, last digger's job = %d\n", __FILE__, __LINE__, lastInvasionDigger, lastInvasionJob, !lastDigger ? -1 : (!lastDigger->job.current_job ? -1 : lastDigger->job.current_job->id));
lastInvasionDigger = lastInvasionJob = -1;
clearDijkstra();
unordered_set<uint16_t> invaderConnectivity;
unordered_set<uint16_t> localConnectivity;
//find all locals and invaders
for ( size_t a = 0; a < df::global::world->units.all.size(); a++ ) {
df::unit* unit = df::global::world->units.all[a];
if ( unit->flags1.bits.dead )
continue;
if ( Units::isCitizen(unit) ) {
if ( localPts.find(unit->pos) != localPts.end() )
continue;
localPts.insert(unit->pos);
df::map_block* block = Maps::getTileBlock(unit->pos);
localConnectivity.insert(block->walkable[unit->pos.x&0xF][unit->pos.y&0xF]);
} else if ( unit->flags1.bits.active_invader ) {
df::creature_raw* raw = df::creature_raw::find(unit->race);
if ( raw == NULL ) {
out.print("%s,%d: WTF? Couldn't find creature raw.\n", __FILE__, __LINE__);
continue;
}
/*
if ( diggingRaces.find(raw->creature_id) == diggingRaces.end() )
continue;
*/
if ( digAbilities.find(raw->creature_id) == digAbilities.end() )
continue;
if ( invaderPts.find(unit->pos) != invaderPts.end() )
continue;
//must be able to wield a pick: this is overly pessimistic
if ( unit->status2.limbs_grasp_max <= 0 || unit->status2.limbs_grasp_count < unit->status2.limbs_grasp_max )
continue;
df::map_block* block = Maps::getTileBlock(unit->pos);
invaderConnectivity.insert(block->walkable[unit->pos.x&0xF][unit->pos.y&0xF]);
if ( invaderPts.size() > 0 )
continue;
invaderPts.insert(unit->pos);
costMap[unit->pos] = 0;
fringe.insert(unit->pos);
invaders.push_back(unit->id);
} else {
continue;
}
}
if ( invaders.empty() || localPts.empty() ) {
activeDigging = false;
return;
}
//if local connectivity is not disjoint from invader connectivity, no digging required
bool overlap = false;
for ( auto a = localConnectivity.begin(); a != localConnectivity.end(); a++ ) {
uint16_t conn = *a;
if ( invaderConnectivity.find(conn) == invaderConnectivity.end() )
continue;
overlap = true;
break;
}
if ( overlap ) {
//still keep checking next frame: might kill a few outsiders then dig down
return;
}
}
df::unit* firstInvader = df::unit::find(invaders[0]);
if ( firstInvader == NULL ) {
fringe.clear();
return;
}
df::creature_raw* creature_raw = df::creature_raw::find(firstInvader->race);
if ( creature_raw == NULL || digAbilities.find(creature_raw->creature_id) == digAbilities.end() ) {
//inappropriate digger: no dig abilities
fringe.clear();
return;
}
DigAbilities& abilities = digAbilities[creature_raw->creature_id];
//TODO: check that firstInvader is an appropriate digger
//out << firstInvader->id << endl;
//out << firstInvader->pos.x << ", " << firstInvader->pos.y << ", " << firstInvader->pos.z << endl;
//out << __LINE__ << endl;
uint32_t xMax, yMax, zMax;
Maps::getSize(xMax,yMax,zMax);
xMax *= 16;
yMax *= 16;
MapExtras::MapCache cache;
clock_t t0 = clock();
clock_t totalEdgeTime = 0;
int32_t edgesExpanded = 0;
while(!fringe.empty()) {
if ( edgesPerTick > 0 && edgesExpanded++ >= edgesPerTick ) {
return;
}
df::coord pt = *(fringe.begin());
fringe.erase(fringe.begin());
//out.print("line %d: fringe size = %d, localPtsFound = %d / %d, closedSetSize = %d, pt = %d,%d,%d\n", __LINE__, fringe.size(), localPtsFound, localPts.size(), closedSet.size(), pt.x,pt.y,pt.z);
if ( closedSet.find(pt) != closedSet.end() ) {
out.print("%s, line %d: Double closure! Bad!\n", __FILE__, __LINE__);
break;
}
closedSet.insert(pt);
if ( localPts.find(pt) != localPts.end() ) {
localPtsFound++;
if ( true || localPtsFound >= localPts.size() ) {
foundTarget = true;
break;
}
if ( workNeeded.find(pt) == workNeeded.end() || workNeeded[pt] == 0 ) {
//there are still dwarves to kill that don't require digging to get to
return;
}
}
cost_t myCost = costMap[pt];
clock_t edgeTime = clock();
vector<Edge>* myEdges = getEdgeSet(out, pt, cache, xMax, yMax, zMax, abilities);
totalEdgeTime += (clock() - edgeTime);
for ( auto a = myEdges->begin(); a != myEdges->end(); a++ ) {
Edge &e = *a;
if ( e.p1 == df::coord() )
break;
edgeCount++;
df::coord& other = e.p1;
if ( other == pt )
other = e.p2;
//if ( closedSet.find(other) != closedSet.end() )
// continue;
auto i = costMap.find(other);
if ( i != costMap.end() ) {
cost_t cost = (*i).second;
if ( cost <= myCost + e.cost ) {
continue;
}
fringe.erase((*i).first);
}
costMap[other] = myCost + e.cost;
fringe.insert(other);
parentMap[other] = pt;
workNeeded[other] = (e.cost > 1 ? 1 : 0) + workNeeded[pt];
}
delete myEdges;
}
clock_t time = clock() - t0;
//out.print("tickTime = %d, time = %d, totalEdgeTime = %d, total points = %d, total edges = %d, time per point = %.3f, time per edge = %.3f, clocks/sec = %d\n", (int32_t)tickTime, time, totalEdgeTime, closedSet.size(), edgeCount, (float)time / closedSet.size(), (float)time / edgeCount, CLOCKS_PER_SEC);
fringe.clear();
if ( !foundTarget )
return;
unordered_set<df::coord, PointHash> requiresZNeg;
unordered_set<df::coord, PointHash> requiresZPos;
//find important edges
Edge firstImportantEdge(df::coord(), df::coord(), -1);
//df::coord closest;
//cost_t closestCostEstimate=0;
//cost_t closestCostActual=0;
for ( auto i = localPts.begin(); i != localPts.end(); i++ ) {
df::coord pt = *i;
if ( costMap.find(pt) == costMap.end() )
continue;
if ( parentMap.find(pt) == parentMap.end() )
continue;
//closest = pt;
//closestCostEstimate = costMap[closest];
//if ( workNeeded[pt] == 0 )
// continue;
while ( parentMap.find(pt) != parentMap.end() ) {
//out.print("(%d,%d,%d)\n", pt.x, pt.y, pt.z);
df::coord parent = parentMap[pt];
cost_t cost = getEdgeCost(out, parent, pt, abilities);
if ( cost < 0 ) {
//path invalidated
return;
}
//closestCostActual += cost;
if ( Maps::canStepBetween(parent, pt) ) {
} else {
if ( pt.x == parent.x && pt.y == parent.y ) {
if ( pt.z < parent.z ) {
requiresZNeg.insert(parent);
requiresZPos.insert(pt);
} else if ( pt.z > parent.z ) {
requiresZNeg.insert(pt);
requiresZPos.insert(parent);
}
}
//if ( workNeeded[pt] > workNeeded[parent] ) {
//importantEdges.push_front(Edge(pt,parent,0));
//}
firstImportantEdge = Edge(pt,parent,0);
//out.print("(%d,%d,%d) -> (%d,%d,%d)\n", parent.x,parent.y,parent.z, pt.x,pt.y,pt.z);
}
pt = parent;
}
break;
}
if ( firstImportantEdge.p1 == df::coord() )
return;
/*
if ( closestCostActual != closestCostEstimate ) {
out.print("%s,%d: closest = (%d,%d,%d), estimate = %lld != actual = %lld\n", __FILE__, __LINE__, closest.x,closest.y,closest.z, closestCostEstimate, closestCostActual);
return;
}
*/
assignJob(out, firstImportantEdge, parentMap, costMap, invaders, requiresZNeg, requiresZPos, cache, abilities);
lastInvasionDigger = firstInvader->id;
lastInvasionJob = firstInvader->job.current_job ? firstInvader->job.current_job->id : -1;
invaderJobs.erase(lastInvasionJob);
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
df::job* job = link->item;
if ( invaderJobs.find(job->id) == invaderJobs.end() ) {
continue;
}
//cancel it
job->flags.bits.item_lost = 1;
out.print("%s,%d: cancelling job %d.\n", __FILE__,__LINE__, job->id);
//invaderJobs.remove(job->id);
}
invaderJobs.erase(lastInvasionJob);
return;
}
df::coord getRoot(df::coord point, map<df::coord, df::coord>& rootMap) {
map<df::coord, df::coord>::iterator i = rootMap.find(point);
if ( i == rootMap.end() ) {
rootMap[point] = point;
return point;
}
df::coord parent = (*i).second;
if ( parent == point )
return parent;
df::coord root = getRoot(parent, rootMap);
rootMap[point] = root;
return root;
}

@ -0,0 +1,481 @@
#include "edgeCost.h"
#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "df/building.h"
#include "df/building_bridgest.h"
#include "df/building_hatchst.h"
#include "df/building_type.h"
#include "df/construction.h"
#include "df/coord.h"
#include "df/item_type.h"
#include "df/map_block.h"
#include "df/tile_building_occ.h"
#include "df/tiletype.h"
#include "df/tiletype_material.h"
#include "df/tiletype_shape.h"
#include <iostream>
/*
cost_t costWeight[] = {
//Distance
1,
//Destroy Building
2,
//Dig
10000,
//DestroyConstruction
100,
};
int32_t jobDelay[] = {
//Distance
-1,
//Destroy Building
1000,
//Dig
1000,
//DestroyConstruction
1000
};
*/
using namespace std;
/*
limitations
ramps
cave-ins
*/
cost_t getEdgeCost(color_ostream& out, df::coord pt1, df::coord pt2, DigAbilities& abilities) {
int32_t dx = pt2.x - pt1.x;
int32_t dy = pt2.y - pt1.y;
int32_t dz = pt2.z - pt1.z;
cost_t cost = abilities.costWeight[CostDimension::Walk];
if ( cost < 0 )
return -1;
if ( Maps::getTileBlock(pt1) == NULL || Maps::getTileBlock(pt2) == NULL )
return -1;
df::tiletype* type2 = Maps::getTileType(pt2);
df::tiletype_shape shape2 = ENUM_ATTR(tiletype, shape, *type2);
if ( Maps::getTileBlock(pt1)->designation[pt1.x&0xF][pt1.y&0xF].bits.flow_size >= 4 )
return -1;
if ( Maps::getTileBlock(pt2)->designation[pt2.x&0xF][pt2.y&0xF].bits.flow_size >= 4 )
return -1;
if ( shape2 == df::enums::tiletype_shape::EMPTY ) {
return -1;
}
if ( shape2 == df::enums::tiletype_shape::TREE )
return -1;
/*
if () {
df::map_block* temp = Maps::getTileBlock(df::coord(pt1.x,pt1.y,pt1.z-1));
if ( temp && temp->designation[pt1.x&0xF][pt1.y&0xF]
}
*/
if ( Maps::canStepBetween(pt1, pt2) ) {
return cost;
}
df::building* building2 = Buildings::findAtTile(pt2);
if ( building2 ) {
if ( abilities.costWeight[CostDimension::DestroyBuilding] < 0 )
return -1;
cost += abilities.costWeight[CostDimension::DestroyBuilding];
if ( dx*dx + dy*dy > 1 )
return -1;
}
bool construction2 = ENUM_ATTR(tiletype, material, *type2) == df::enums::tiletype_material::CONSTRUCTION;
if ( construction2 ) {
//smooth or not?
df::construction* constr = df::construction::find(pt2);
bool smooth = constr != NULL && constr->item_type != df::enums::item_type::BOULDER;
if ( smooth ) {
if ( abilities.costWeight[CostDimension::DestroySmoothConstruction] < 0 )
return -1;
cost += abilities.costWeight[CostDimension::DestroySmoothConstruction];
} else {
if ( abilities.costWeight[CostDimension::DestroyRoughConstruction] < 0 )
return -1;
cost += abilities.costWeight[CostDimension::DestroyRoughConstruction];
}
}
if ( dz == 0 ) {
if ( !building2 && !construction2 ) {
//it has to be a wall
if ( shape2 == df::enums::tiletype_shape::RAMP_TOP ) {
return -1;
} else if ( shape2 != df::enums::tiletype_shape::WALL ) {
//out << "shape = " << (int32_t)shape2 << endl;
//out << __FILE__ << ", line " << __LINE__ << ": WTF?" << endl;
return cost;
}
if ( abilities.costWeight[CostDimension::Dig] < 0 ) {
return -1;
}
cost += abilities.costWeight[CostDimension::Dig];
}
} else {
if ( dx == 0 && dy == 0 ) {
df::tiletype* type1 = Maps::getTileType(pt1);
df::tiletype_shape shape1 = ENUM_ATTR(tiletype, shape, *type1);
if ( dz > 0 ) {
bool walkable_low2 = shape2 == df::tiletype_shape::STAIR_DOWN || shape2 == df::tiletype_shape::STAIR_UPDOWN;
if ( !walkable_low2 ) {
if ( building2 || construction2 )
return -1;
if ( abilities.costWeight[CostDimension::Dig] < 0 ) {
return -1;
}
cost += abilities.costWeight[CostDimension::Dig];
}
bool walkable_high1 = shape1 == df::tiletype_shape::STAIR_UP || shape1 == df::tiletype_shape::STAIR_UPDOWN;
if ( !walkable_high1 ) {
if ( shape1 != df::enums::tiletype_shape::WALL ) {
return -1;
}
if ( abilities.costWeight[CostDimension::Dig] < 0 ) {
return -1;
}
cost += abilities.costWeight[CostDimension::Dig];
}
if ( building2 ) {
//moving up through an open bridge or a usable hatch is fine. other buildings are not
bool unforbiddenHatch = false;
if ( building2->getType() == df::building_type::Hatch ) {
df::building_hatchst* hatch = (df::building_hatchst*)building2;
if ( !hatch->door_flags.bits.forbidden && !(hatch->door_flags.bits.operated_by_mechanisms&&hatch->door_flags.bits.closed) )
unforbiddenHatch = true;
}
bool inactiveBridge = false;
if ( building2->getType() == df::building_type::Bridge ) {
df::building_bridgest* bridge = (df::building_bridgest*)building2;
bool xMin = pt2.x == bridge->x1;
bool xMax = pt2.x == bridge->x2;
bool yMin = pt2.y == bridge->y1;
bool yMax = pt2.y == bridge->y2;
if ( !bridge->gate_flags.bits.closed ) {
//if it's open, we could still be in the busy part of it
if ( bridge->direction == df::building_bridgest::T_direction::Left && !xMin ) {
inactiveBridge = true;
} else if ( bridge->direction == df::building_bridgest::T_direction::Right && !xMax ) {
inactiveBridge = true;
} else if ( bridge->direction == df::building_bridgest::T_direction::Up && !yMax ) {
inactiveBridge = true;
} else if ( bridge->direction == df::building_bridgest::T_direction::Down && !yMin ) {
inactiveBridge = true;
} else if ( bridge->direction == df::building_bridgest::T_direction::Retracting ) {
inactiveBridge = true;
}
}
}
if ( !unforbiddenHatch && !inactiveBridge )
return -1;
}
/*bool forbidden = false;
if ( building2 && building2->getType() == df::building_type::Hatch ) {
df::building_hatchst* hatch = (df::building_hatchst*)building2;
if ( hatch->door_flags.bits.forbidden )
forbidden = true;
}
if ( forbidden )
return -1;*/
} else {
bool walkable_high2 = shape2 == df::tiletype_shape::STAIR_UP || shape2 == df::tiletype_shape::STAIR_UPDOWN;
if ( !walkable_high2 ) {
if ( building2 || construction2 )
return -1;
if ( shape2 != df::enums::tiletype_shape::WALL )
return -1;
if ( abilities.costWeight[CostDimension::Dig] < 0 ) {
return -1;
}
cost += abilities.costWeight[CostDimension::Dig];
}
bool walkable_low1 = shape1 == df::tiletype_shape::STAIR_DOWN || shape1 == df::tiletype_shape::STAIR_UPDOWN;
if ( !walkable_low1 ) {
//if ( building1 || construction1 )
//return -1;
//TODO: consider ramps
if ( shape1 == df::tiletype_shape::RAMP )
return -1;
if ( abilities.costWeight[CostDimension::Dig] < 0 ) {
return -1;
}
cost += abilities.costWeight[CostDimension::Dig];
}
df::building* building1 = Buildings::findAtTile(pt1);
//if you're moving down, and you're on a bridge, and that bridge is lowered, then you can't do it
if ( building1 && building1->getType() == df::building_type::Bridge ) {
df::building_bridgest* bridge = (df::building_bridgest*)building2;
if ( bridge->gate_flags.bits.closed ) {
return -1;
}
//open bridges moving down, standing on bad spot
if ( bridge->direction == df::building_bridgest::T_direction::Left && pt1.x == bridge->x1 )
return -1;
if ( bridge->direction == df::building_bridgest::T_direction::Right && pt1.x == bridge->x2 )
return -1;
if ( bridge->direction == df::building_bridgest::T_direction::Up && pt1.y == bridge->y1 )
return -1;
if ( bridge->direction == df::building_bridgest::T_direction::Down && pt1.y == bridge->y2 )
return -1;
}
bool forbidden = false;
if ( building1 && building1->getType() == df::building_type::Hatch ) {
df::building_hatchst* hatch = (df::building_hatchst*)building1;
if ( hatch->door_flags.bits.forbidden || hatch->door_flags.bits.closed && hatch->door_flags.bits.operated_by_mechanisms )
forbidden = true;
}
//you can deconstruct a hatch from the side
if ( building1 && forbidden /*&& building1->getType() == df::building_type::Hatch*/ ) {
/*
df::coord support[] = {df::coord(pt1.x-1, pt1.y, pt1.z), df::coord(pt1.x+1,pt1.y,pt1.z), df::coord(pt1.x,pt1.y-1,pt1.z), df::coord(pt1.x,pt1.y+1,pt1.z)};
if ( abilities.costWeight[CostDimension::DestroyBuilding] < 0 ) {
return -1;
}
cost_t minCost = -1;
for ( size_t a = 0; a < 4; a++ ) {
df::tiletype* supportType = Maps::getTileType(support[a]);
df::tiletype_shape shape = ENUM_ATTR(tiletype, shape, *supportType);
df::tiletype_shape_basic basic = ENUM_ATTR(tiletype_shape, basic_shape, shape);
cost_t cost2 = 2*abilities.costWeight[CostDimension::Walk] + abilities.costWeight[CostDimension::DestroyBuilding];
if ( !Maps::canStepBetween(pt1, support[a]) ) {
switch(basic) {
case tiletype_shape_basic::Open:
//TODO: check for a hatch or a bridge: that makes it ok
continue;
case tiletype_shape_basic::Wall:
if ( ENUM_ATTR(tiletype, material, *supportType) == df::enums::tiletype_material::CONSTRUCTION ) {
if ( abilities.costWeight[CostDimension::DestroyConstruction] < 0 ) {
continue;
}
cost2 += abilities.costWeight[CostDimension::DestroyConstruction];
} else {
if ( abilities.costWeight[CostDimension::Dig] < 0 ) {
continue;
}
cost2 += abilities.costWeight[CostDimension::Dig];
}
case tiletype_shape_basic::Ramp:
//TODO: check for a hatch or a bridge: that makes it ok
if ( shape == df::enums::tiletype_shape::RAMP_TOP ) {
continue;
}
case tiletype_shape_basic::Stair:
case tiletype_shape_basic::Floor:
break;
}
if ( Buildings::findAtTile(support[a]) ) {
if ( abilities.costWeight[CostDimension::DestroyBuilding] < 0 ) {
continue;
}
cost2 += abilities.costWeight[CostDimension::DestroyBuilding];
}
}
if ( minCost == -1 || cost2 < minCost )
minCost = cost2;
}
if ( minCost == -1 )
return -1;
cost += minCost;
*/
//note: assignJob is not ready for this level of sophistication, so don't allow it
return -1;
}
}
} else {
//nonvertical
//out.print("%s, line %d: (%d,%d,%d)->(%d,%d,%d)\n", __FILE__, __LINE__, pt1.x,pt1.y,pt1.z, pt2.x,pt2.y,pt2.z);
return -1;
}
}
return cost;
}
/*
cost_t getEdgeCostOld(color_ostream& out, df::coord pt1, df::coord pt2) {
//first, list all the facts
int32_t dx = pt2.x - pt1.x;
int32_t dy = pt2.y - pt1.y;
int32_t dz = pt2.z - pt1.z;
cost_t cost = costWeight[CostDimension::Walk];
if ( false ) {
if ( Maps::canStepBetween(pt1,pt2) )
return cost;
return 100 + cost;
}
Maps::ensureTileBlock(pt1);
Maps::ensureTileBlock(pt2);
df::tiletype* type1 = Maps::getTileType(pt1);
df::tiletype* type2 = Maps::getTileType(pt2);
df::map_block* block1 = Maps::getTileBlock(pt1);
df::map_block* block2 = Maps::getTileBlock(pt2);
df::tiletype_shape shape1 = ENUM_ATTR(tiletype, shape, *type1);
df::tiletype_shape shape2 = ENUM_ATTR(tiletype, shape, *type2);
bool construction1 = ENUM_ATTR(tiletype, material, *type1) == df::enums::tiletype_material::CONSTRUCTION;
bool construction2 = ENUM_ATTR(tiletype, material, *type2) == df::enums::tiletype_material::CONSTRUCTION;
bool passable1 = block1->walkable[pt1.x&0xF][pt1.y&0xF] != 0;
bool passable2 = block2->walkable[pt2.x&0xF][pt2.y&0xF] != 0;
bool passable_high1 = ENUM_ATTR(tiletype_shape, passable_high, shape1);
bool passable_high2 = ENUM_ATTR(tiletype_shape, passable_high, shape2);
bool passable_low1 = ENUM_ATTR(tiletype_shape, passable_low, shape1);
bool passable_low2 = ENUM_ATTR(tiletype_shape, passable_low, shape2);
bool building1, building2;
bool sameBuilding = false;
{
df::enums::tile_building_occ::tile_building_occ awk = block1->occupancy[pt1.x&0x0F][pt1.y&0x0F].bits.building;
building1 = awk == df::enums::tile_building_occ::Obstacle || awk == df::enums::tile_building_occ::Impassable;
awk = block2->occupancy[pt2.x&0x0F][pt2.y&0x0F].bits.building;
building2 = awk == df::enums::tile_building_occ::Obstacle || awk == df::enums::tile_building_occ::Impassable;
if ( building1 && building2 ) {
df::building* b1 = Buildings::findAtTile(pt1);
df::building* b2 = Buildings::findAtTile(pt2);
sameBuilding = b1 == b2;
}
}
if ( Maps::canStepBetween(pt1, pt2) ) {
if ( building2 && !sameBuilding ) {
cost += costWeight[CostDimension::DestroyBuilding];
}
return cost;
}
if ( shape2 == df::enums::tiletype_shape::EMPTY ) {
return -1;
}
//cannot step between: find out why
if ( dz == 0 ) {
if ( passable2 && !passable1 ) {
return cost;
}
if ( passable1 && passable2 ) {
out << __FILE__ << ", line " << __LINE__ << ": WTF?" << endl;
return cost;
}
//pt2 is not passable. it must be a construction, a building, or a wall.
if ( building2 ) {
if ( sameBuilding ) {
//don't charge twice for destroying the same building
return cost;
}
cost += costWeight[CostDimension::DestroyBuilding];
return cost;
}
if ( construction2 ) {
//impassible constructions must be deconstructed
cost += costWeight[CostDimension::DestroyConstruction];
return cost;
}
if ( shape2 == df::enums::tiletype_shape::TREE ) {
return -1;
}
if ( shape2 == df::enums::tiletype_shape::RAMP_TOP ) {
return -1;
}
//it has to be a wall
if ( shape2 != df::enums::tiletype_shape::WALL ) {
out << "shape = " << (int32_t)shape2 << endl;
out << __FILE__ << ", line " << __LINE__ << ": WTF?" << endl;
return cost;
}
cost += costWeight[CostDimension::Dig];
return cost;
}
//dz != 0
if ( dx == 0 && dy == 0 ) {
if ( dz > 0 ) {
if ( passable_low2 )
return cost;
if ( building2 || construction2 ) {
return -1;
}
cost += costWeight[CostDimension::Dig];
return cost;
}
//descending
if ( passable_high2 )
return cost;
if ( building2 || construction2 ) {
return -1;
}
//must be a wall?
if ( shape2 != df::enums::tiletype_shape::WALL ) {
out.print("%s, line %n: WTF?\n", __FILE__, __LINE__);
return cost;
}
cost += costWeight[CostDimension::Dig];
return cost;
}
//moving diagonally
return -1;
}
*/
vector<Edge>* getEdgeSet(color_ostream &out, df::coord point, MapExtras::MapCache& cache, int32_t xMax, int32_t yMax, int32_t zMax, DigAbilities& abilities) {
//vector<Edge>* result = new vector<Edge>(26);
vector<Edge>* result = new vector<Edge>();
result->reserve(26);
//size_t count = 0;
for ( int32_t dx = -1; dx <= 1; dx++ ) {
for ( int32_t dy = -1; dy <= 1; dy++ ) {
for ( int32_t dz = -1; dz <= 1; dz++ ) {
df::coord neighbor(point.x+dx, point.y+dy, point.z+dz);
if ( !Maps::isValidTilePos(neighbor) )
continue;
if ( dz != 0 && /*(point.x == 0 || point.y == 0 || point.z == 0 || point.x == xMax-1 || point.y == yMax-1 || point.z == zMax-1) ||*/ (neighbor.x == 0 || neighbor.y == 0 || neighbor.z == 0 || neighbor.x == xMax-1 || neighbor.y == yMax-1 || neighbor.z == zMax-1) )
continue;
if ( dx == 0 && dy == 0 && dz == 0 )
continue;
cost_t cost = getEdgeCost(out, point, neighbor, abilities);
if ( cost == -1 )
continue;
Edge edge(point, neighbor, cost);
//(*result)[count] = edge;
result->push_back(edge);
//count++;
}
}
}
return result;
}

@ -0,0 +1,98 @@
#pragma once
#include "Core.h"
#include "Console.h"
#include "DataDefs.h"
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "df/coord.h"
#include <unordered_map>
#include <vector>
//cost is [path cost, building destruction cost, dig cost, construct cost]. Minimize constructions, then minimize dig cost, then minimize path cost.
enum CostDimension {
Walk,
DestroyBuilding,
Dig,
DestroyRoughConstruction,
DestroySmoothConstruction,
//Construct,
costDim
};
typedef int64_t cost_t;
struct DigAbilities {
cost_t costWeight[costDim];
int32_t jobDelay[costDim];
};
//extern cost_t costWeight[costDim];
//extern int32_t jobDelay[costDim];
extern std::unordered_map<std::string, DigAbilities> digAbilities;
/*
const cost_t costWeight[] = {
//Distance
1,
//Destroy Building
2,
//Dig
10000,
//DestroyConstruction
100,
};
*/
class Edge {
public:
//static map<df::coord, int32_t> pointCost;
df::coord p1;
df::coord p2;
cost_t cost;
Edge() {
cost = -1;
}
Edge(const Edge& e): p1(e.p1), p2(e.p2), cost(e.cost) {
}
Edge(df::coord p1In, df::coord p2In, cost_t costIn): cost(costIn) {
if ( p2In < p1In ) {
p1 = p2In;
p2 = p1In;
} else {
p1 = p1In;
p2 = p2In;
}
}
bool operator==(const Edge& e) const {
return (cost == e.cost && p1 == e.p1 && p2 == e.p2);
}
bool operator<(const Edge& e) const {
if ( cost != e.cost )
return cost < e.cost;
if ( p1.z != e.p1.z )
return p1.z < e.p1.z;
if ( p1 != e.p1 )
return p1 < e.p1;
if ( p2.z != e.p2.z )
return p2.z < e.p2.z;
if ( p2 != e.p2 )
return p2 < e.p2;
return false;
}
};
struct PointHash {
size_t operator()(const df::coord c) const {
return c.x * 65537 + c.y * 17 + c.z;
}
};
cost_t getEdgeCost(color_ostream& out, df::coord pt1, df::coord pt2, DigAbilities& abilities);
std::vector<Edge>* getEdgeSet(color_ostream &out, df::coord point, MapExtras::MapCache& cache, int32_t xMax, int32_t yMax, int32_t zMax, DigAbilities& abilities);

@ -179,9 +179,9 @@ public:
addDwarfActivity(unit, *entry);
}
for_each_(dwarf_activity_values[unit],
[&] (const pair<activity_type, size_t> &x)
{ dwarf_activity_values[unit][x.first] = getPercentage(x.second, dwarf_total); } );
auto &values = dwarf_activity_values[unit];
for (auto it = values.begin(); it != values.end(); ++it)
it->second = getPercentage(it->second, dwarf_total);
dwarves_column.add(getUnitName(unit), unit);
}
@ -212,9 +212,8 @@ public:
vector<pair<activity_type, size_t>> rev_vec(dwarf_activities->begin(), dwarf_activities->end());
sort(rev_vec.begin(), rev_vec.end(), less_second<activity_type, size_t>());
for_each_(rev_vec,
[&] (pair<activity_type, size_t> x)
{ dwarf_activity_column.add(getActivityItem(x.first, x.second), x.first); });
for (auto it = rev_vec.begin(); it != rev_vec.end(); ++it)
dwarf_activity_column.add(getActivityItem(it->first, it->second), it->first);
}
dwarf_activity_column.fixWidth();
@ -754,9 +753,8 @@ public:
vector<pair<df::unit *, size_t>> rev_vec(dwarf_activities->begin(), dwarf_activities->end());
sort(rev_vec.begin(), rev_vec.end(), less_second<df::unit *, size_t>());
for_each_(rev_vec,
[&] (pair<df::unit *, size_t> x)
{ dwarf_activity_column.add(getDwarfAverage(x.first, x.second), x.first); });
for (auto it = rev_vec.begin(); it != rev_vec.end(); ++it)
dwarf_activity_column.add(getDwarfAverage(it->first, it->second), it->first);
}
}
@ -778,9 +776,8 @@ public:
vector<pair<activity_type, size_t>> rev_vec(category_activities->begin(), category_activities->end());
sort(rev_vec.begin(), rev_vec.end(), less_second<activity_type, size_t>());
for_each_(rev_vec,
[&] (pair<activity_type, size_t> x)
{ category_breakdown_column.add(getBreakdownAverage(x.first, x.second), x.first); });
for (auto it = rev_vec.begin(); it != rev_vec.end(); ++it)
category_breakdown_column.add(getBreakdownAverage(it->first, it->second), it->first);
}
category_breakdown_column.fixWidth();
@ -1179,11 +1176,15 @@ IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, render);
DFHACK_PLUGIN("dwarfmonitor");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
static bool set_monitoring_mode(const string &mode, const bool &state)
{
bool mode_recognized = false;
if (!is_enabled)
return false;
if (mode == "work" || mode == "all")
{
mode_recognized = true;
@ -1201,6 +1202,24 @@ static bool set_monitoring_mode(const string &mode, const bool &state)
return mode_recognized;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (!gps)
return CR_FAILURE;
if (is_enabled != enable)
{
if (!INTERPOSE_HOOK(dwarf_monitor_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable))
return CR_FAILURE;
reset();
is_enabled = enable;
}
return CR_OK;
}
static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & parameters)
{
bool show_help = false;
@ -1222,6 +1241,9 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & par
}
else if ((cmd == 'e' || cmd == 'E') && !mode.empty())
{
if (!is_enabled)
plugin_enable(out, true);
if (set_monitoring_mode(mode, true))
{
out << "Monitoring enabled: " << mode << endl;
@ -1257,9 +1279,6 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & par
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps || !INTERPOSE_HOOK(dwarf_monitor_hook, feed).apply() || !INTERPOSE_HOOK(dwarf_monitor_hook, render).apply())
out.printerr("Could not insert dwarfmonitor hooks!\n");
activity_labels[JOB_IDLE] = "Idle";
activity_labels[JOB_MILITARY] = "Military Duty";
activity_labels[JOB_LEISURE] = "Leisure";

@ -9,6 +9,7 @@
#include "df/building_workshopst.h"
#include "df/unit.h"
#include "df/unit_inventory_item.h"
#include "df/item.h"
#include "df/item_actual.h"
#include "df/unit_wound.h"
@ -23,6 +24,12 @@
#include "MiscUtils.h"
#include "LuaTools.h"
#include "modules/EventManager.h"
#include "df/job.h"
#include "df/building.h"
#include "df/construction.h"
using std::vector;
using std::string;
using std::stack;
@ -108,7 +115,22 @@ DEFINE_LUA_EVENT_2(onProjItemCheckImpact, handle_projitem_ci, df::proj_itemst*,b
DEFINE_LUA_EVENT_1(onProjItemCheckMovement, handle_projitem_cm, df::proj_itemst*);
DEFINE_LUA_EVENT_2(onProjUnitCheckImpact, handle_projunit_ci, df::proj_unitst*,bool );
DEFINE_LUA_EVENT_1(onProjUnitCheckMovement, handle_projunit_cm, df::proj_unitst* );
//event manager
static void handle_int32t(color_ostream &out,int32_t){}; //we don't use this so why not use it everywhere
static void handle_job_init(color_ostream &out,df::job*){};
static void handle_job_complete(color_ostream &out,df::job*){};
static void handle_constructions(color_ostream &out,df::construction*){};
static void handle_syndrome(color_ostream &out,int32_t,int32_t){};
static void handle_inventory_change(color_ostream& out,int32_t,int32_t,df::unit_inventory_item*,df::unit_inventory_item*){};
DEFINE_LUA_EVENT_1(onBuildingCreatedDestroyed, handle_int32t, int32_t);
DEFINE_LUA_EVENT_1(onJobInitiated,handle_job_init,df::job*);
DEFINE_LUA_EVENT_1(onJobCompleted,handle_job_complete,df::job*);
DEFINE_LUA_EVENT_1(onUnitDeath,handle_int32t,int32_t);
DEFINE_LUA_EVENT_1(onItemCreated,handle_int32t,int32_t);
DEFINE_LUA_EVENT_1(onConstructionCreatedDestroyed, handle_constructions, df::construction*);
DEFINE_LUA_EVENT_2(onSyndrome, handle_syndrome, int32_t,int32_t);
DEFINE_LUA_EVENT_1(onInvasion,handle_int32t,int32_t);
DEFINE_LUA_EVENT_4(onInventoryChange,handle_inventory_change,int32_t,int32_t,df::unit_inventory_item*,df::unit_inventory_item*);
DFHACK_PLUGIN_LUA_EVENTS {
DFHACK_LUA_EVENT(onWorkshopFillSidebarMenu),
DFHACK_LUA_EVENT(postWorkshopFillSidebarMenu),
@ -118,6 +140,110 @@ DFHACK_PLUGIN_LUA_EVENTS {
DFHACK_LUA_EVENT(onProjItemCheckMovement),
DFHACK_LUA_EVENT(onProjUnitCheckImpact),
DFHACK_LUA_EVENT(onProjUnitCheckMovement),
/* event manager events */
DFHACK_LUA_EVENT(onBuildingCreatedDestroyed),
DFHACK_LUA_EVENT(onConstructionCreatedDestroyed),
DFHACK_LUA_EVENT(onJobInitiated),
DFHACK_LUA_EVENT(onJobCompleted),
DFHACK_LUA_EVENT(onUnitDeath),
DFHACK_LUA_EVENT(onItemCreated),
DFHACK_LUA_EVENT(onSyndrome),
DFHACK_LUA_EVENT(onInvasion),
DFHACK_LUA_EVENT(onInventoryChange),
DFHACK_LUA_END
};
static void ev_mng_jobInitiated(color_ostream& out, void* job)
{
df::job* ptr=reinterpret_cast<df::job*>(job);
onJobInitiated(out,ptr);
}
void ev_mng_jobCompleted(color_ostream& out, void* job)
{
df::job* ptr=reinterpret_cast<df::job*>(job);
onJobCompleted(out,ptr);
}
void ev_mng_unitDeath(color_ostream& out, void* ptr)
{
int32_t myId=int32_t(ptr);
onUnitDeath(out,myId);
}
void ev_mng_itemCreate(color_ostream& out, void* ptr)
{
int32_t myId=int32_t(ptr);
onItemCreated(out,myId);
}
void ev_mng_construction(color_ostream& out, void* ptr)
{
df::construction* cons=reinterpret_cast<df::construction*>(ptr);
onConstructionCreatedDestroyed(out,cons);
}
void ev_mng_syndrome(color_ostream& out, void* ptr)
{
EventManager::SyndromeData* data=reinterpret_cast<EventManager::SyndromeData*>(ptr);
onSyndrome(out,data->unitId,data->syndromeIndex);
}
void ev_mng_invasion(color_ostream& out, void* ptr)
{
int32_t myId=int32_t(ptr);
onInvasion(out,myId);
}
static void ev_mng_building(color_ostream& out, void* ptr)
{
int32_t myId=int32_t(ptr);
onBuildingCreatedDestroyed(out,myId);
}
static void ev_mng_inventory(color_ostream& out, void* ptr)
{
EventManager::InventoryChangeData* data = reinterpret_cast<EventManager::InventoryChangeData*>(ptr);
int32_t unitId = data->unitId;
int32_t itemId = -1;
df::unit_inventory_item* item_old = NULL;
df::unit_inventory_item* item_new = NULL;
if ( data->item_old ) {
itemId = data->item_old->itemId;
item_old = &data->item_old->item;
}
if ( data->item_new ) {
itemId = data->item_new->itemId;
item_new = &data->item_new->item;
}
onInventoryChange(out,unitId,itemId,item_old,item_new);
}
std::vector<int> enabledEventManagerEvents(EventManager::EventType::EVENT_MAX,-1);
typedef void (*handler_t) (color_ostream&,void*);
static const handler_t eventHandlers[] = {
NULL,
ev_mng_jobInitiated,
ev_mng_jobCompleted,
ev_mng_unitDeath,
ev_mng_itemCreate,
ev_mng_building,
ev_mng_construction,
ev_mng_syndrome,
ev_mng_invasion,
ev_mng_inventory,
};
static void enableEvent(int evType,int freq)
{
if (freq < 0)
return;
if (evType < 0 || evType >= EventManager::EventType::EVENT_MAX || evType == EventManager::EventType::TICK)
throw std::runtime_error("invalid event type to enable");
EventManager::EventHandler::callback_t fun_ptr = eventHandlers[evType];
EventManager::EventType::EventType typeToEnable=static_cast<EventManager::EventType::EventType>(evType);
int oldFreq = enabledEventManagerEvents[typeToEnable];
if (oldFreq != -1) {
if (freq >= oldFreq)
return;
EventManager::unregister(typeToEnable,EventManager::EventHandler(fun_ptr,oldFreq),plugin_self);
}
EventManager::registerListener(typeToEnable,EventManager::EventHandler(fun_ptr,freq),plugin_self);
enabledEventManagerEvents[typeToEnable] = freq;
}
DFHACK_PLUGIN_LUA_FUNCTIONS{
DFHACK_LUA_FUNCTION(enableEvent),
DFHACK_LUA_END
};
struct workshop_hook : df::building_workshopst{

@ -19,6 +19,8 @@ using df::global::world;
// dfhack interface
DFHACK_PLUGIN("fastdwarf");
DFHACK_PLUGIN_IS_ENABLED(active);
static bool enable_fastdwarf = false;
static bool enable_teledwarf = false;
@ -49,7 +51,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (!Units::isCitizen(unit))
continue;
if (enable_fastdwarf)
if (enable_fastdwarf && !enable_teledwarf )
{
if (unit->counters.job_counter > 0)
unit->counters.job_counter = 0;
@ -59,8 +61,11 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (enable_teledwarf) do
{
// don't do anything if the dwarf isn't going anywhere
if (!unit->path.dest.isValid())
if (!unit->pos.isValid() || !unit->path.dest.isValid() || unit->pos == unit->path.dest) {
if ( enable_fastdwarf && unit->counters.job_counter > 0 )
unit->counters.job_counter = 0;
break;
}
// skip dwarves that are dragging creatures or being dragged
if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1))
@ -152,6 +157,8 @@ static command_result fastdwarf (color_ostream &out, vector <string> & parameter
return CR_WRONG_USAGE;
}
active = enable_fastdwarf || enable_teledwarf;
out.print("Current state: fast = %d, teleport = %d.\n",
(df::global::debug_turbospeed && *df::global::debug_turbospeed) ? 2 : (enable_fastdwarf ? 1 : 0),
enable_teledwarf ? 1 : 0);
@ -159,6 +166,17 @@ static command_result fastdwarf (color_ostream &out, vector <string> & parameter
return CR_OK;
}
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable )
{
if (active != enable)
{
active = enable_fastdwarf = enable;
enable_teledwarf = false;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand("fastdwarf",

@ -698,7 +698,7 @@ static void try_store_ammo(df::squad *squad)
}
}
static bool is_enabled = false;
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event)
{
@ -810,6 +810,21 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("World is not loaded: please load a game first.\n");
return CR_FAILURE;
}
if (enable)
enable_plugin(out);
else
disable_plugin(out);
return CR_OK;
}
static command_result fix_armory(color_ostream &out, vector <string> &parameters)
{
CoreSuspender suspend;
@ -820,13 +835,9 @@ static command_result fix_armory(color_ostream &out, vector <string> &parameters
string cmd = parameters[0];
if (cmd == "enable")
{
enable_plugin(out);
}
return plugin_enable(out, true);
else if (cmd == "disable")
{
disable_plugin(out);
}
return plugin_enable(out, false);
else
return CR_WRONG_USAGE;

@ -24,6 +24,7 @@ int32_t prevX, prevY, prevZ;
uint8_t prevMenuWidth;
DFHACK_PLUGIN("follow");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
@ -53,6 +54,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
followedUnit = 0;
prevX=prevY=prevZ = -1;
prevMenuWidth = 0;
is_enabled = false;
break;
default:
break;
@ -97,6 +99,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
}
else if((prevX != x || prevY != y || prevZ != z) && prevMenuWidth == menu_width) //User has manually moved the window, stop following the unit
{
is_enabled = false;
followedUnit = 0;
prevX=prevY=prevZ = -1;
prevMenuWidth = 0;
@ -146,6 +149,7 @@ command_result follow (color_ostream &out, std::vector <std::string> & parameter
followedUnit = Gui::getSelectedUnit(out);
if (followedUnit)
{
is_enabled = true;
std::ostringstream ss;
ss << "Unpause to begin following " << df::global::world->raws.creatures.all[followedUnit->race]->name[0];
if (followedUnit->name.has_name) ss << " " << followedUnit->name.first_name;
@ -153,5 +157,6 @@ command_result follow (color_ostream &out, std::vector <std::string> & parameter
out.print(ss.str().c_str());
}
else followedUnit = 0;
is_enabled = (followedUnit != NULL);
return CR_OK;
}

@ -71,7 +71,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
*/
static size_t constructionSize = 0;
static bool enabled = false;
DFHACK_PLUGIN_IS_ENABLED(enabled);
void doInfiniteSky(color_ostream& out, int32_t howMany);
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
@ -155,6 +155,12 @@ void doInfiniteSky(color_ostream& out, int32_t howMany) {
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
enabled = enable;
return CR_OK;
}
command_result infiniteSky (color_ostream &out, std::vector <std::string> & parameters)
{
if ( parameters.size() > 1 )

@ -159,22 +159,22 @@ function empregnate(unit)
unit.curse.add_tags2.STERILE=false
end
local genes = unit.appearance.genes
if unit.relations.pregnancy_ptr == nil then
if unit.relations.pregnancy_genes == nil then
print("creating preg ptr.")
if false then
print(string.format("%x %x",df.sizeof(unit.relations:_field("pregnancy_ptr"))))
print(string.format("%x %x",df.sizeof(unit.relations:_field("pregnancy_genes"))))
return
end
unit.relations.pregnancy_ptr = { new = true, assign = genes }
unit.relations.pregnancy_genes = { new = true, assign = genes }
end
local ngenes = unit.relations.pregnancy_ptr
local ngenes = unit.relations.pregnancy_genes
if #ngenes.appearance ~= #genes.appearance or #ngenes.colors ~= #genes.colors then
print("Array sizes incorrect, fixing.")
ngenes:assign(genes);
end
print("Setting preg timer.")
unit.relations.pregnancy_timer=10
unit.relations.pregnancy_mystery=1
unit.relations.pregnancy_caste=1
end
menu:add("Empregnate",empregnate)
function healunit(unit)

@ -108,5 +108,13 @@ function addReactionToShop(reaction_name,shop_name)
postWorkshopFillSidebarMenu._library=onPostSidebar
dfhack.onStateChange.eventful=unregall
end
return _ENV
local function invertTable(tbl)
local ret={}
for k,v in pairs(tbl) do
ret[v]=k
end
return ret
end
eventType=invertTable{[0]="TICK","JOB_INITIATED","JOB_COMPLETED","UNIT_DEATH","ITEM_CREATED",
"BUILDING","CONSTRUCTION","SYNDROME","INVASION","INVENTORY_CHANGE","EVENT_MAX"}
return _ENV

@ -1209,10 +1209,27 @@ IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, render);
DFHACK_PLUGIN("manipulator");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (!gps)
return CR_FAILURE;
if (enable != is_enabled)
{
if (!INTERPOSE_HOOK(unitlist_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(unitlist_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{
if (!gps || !INTERPOSE_HOOK(unitlist_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_hook, render).apply())
out.printerr("Could not insert Dwarf Manipulator hooks!\n");
return CR_OK;
}

@ -15,6 +15,8 @@ using namespace std;
using namespace DFHack;
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
static int factor = 1;
static map<int, int> processedThoughtCountTable;
@ -38,6 +40,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out) {
if ( wasLoaded ) {
//we just unloaded the game: clear all data
factor = 1;
is_enabled = false;
processedThoughtCountTable.clear();
fakeThoughts.clear();
wasLoaded = false;
@ -138,6 +141,17 @@ DFhackCExport command_result plugin_init(color_ostream& out, vector<PluginComman
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (enable != is_enabled)
{
is_enabled = enable;
factor = enable ? 2 : 1;
}
return CR_OK;
}
command_result misery(color_ostream &out, vector<string>& parameters) {
if ( !df::global::world || !df::global::world->map.block_index ) {
out.printerr("misery can only be enabled in fortress mode with a fully-loaded game.\n");
@ -153,15 +167,18 @@ command_result misery(color_ostream &out, vector<string>& parameters) {
return CR_WRONG_USAGE;
}
factor = 1;
is_enabled = false;
return CR_OK;
} else if ( parameters[0] == "enable" ) {
is_enabled = true;
factor = 2;
if ( parameters.size() == 2 ) {
factor = atoi(parameters[1].c_str());
if ( factor < 1 ) {
int a = atoi(parameters[1].c_str());
if ( a <= 1 ) {
out.printerr("Second argument must be a positive integer.\n");
return CR_WRONG_USAGE;
}
factor = a;
}
} else if ( parameters[0] == "clear" ) {
for ( size_t a = 0; a < fakeThoughts.size(); a++ ) {
@ -176,6 +193,7 @@ command_result misery(color_ostream &out, vector<string>& parameters) {
return CR_WRONG_USAGE;
}
factor = a;
is_enabled = factor > 1;
}
return CR_OK;

@ -33,12 +33,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
// add tracking here
return CR_OK;
}
void printCurrentModes(t_gamemodes gm, Console & con)
{
con << "Current game type:\t" << gm.g_type << " (";

@ -239,12 +239,28 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
IMPLEMENT_VMETHOD_INTERPOSE(mousequery_hook, feed);
DFHACK_PLUGIN("mousequery");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{
if (!gps || !INTERPOSE_HOOK(mousequery_hook, feed).apply())
out.printerr("Could not insert mousequery hooks!\n");
if (!gps)
return CR_FAILURE;
if (is_enabled != enable)
{
last_x = last_y = last_z = -1;
if (!INTERPOSE_HOOK(mousequery_hook, feed).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
last_x = last_y = last_z = -1;
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);
}

@ -145,7 +145,7 @@ struct trap_hook : df::building_trapst {
INTERPOSE_NEXT(updateAction)();
}
DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, void *unk))
DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, int16_t unk))
{
INTERPOSE_NEXT(drawBuilding)(db, unk);
@ -160,7 +160,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, getName);
IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, updateAction);
IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, drawBuilding);
static bool enabled = false;
DFHACK_PLUGIN_IS_ENABLED(enabled);
static void enable_hooks(bool enable)
{

@ -263,6 +263,9 @@ command_result df_probe (color_ostream &out, vector <string> & parameters)
out << " / " << inorganic[vein_rock].id
<< " / "
<< inorganic[vein_rock].name
<< " ("
<< ENUM_KEY_STR(inclusion_type,b->veinTypeAt(cursor))
<< ")"
<< endl;
else
out << endl;

@ -234,11 +234,18 @@ static coord2d biome_delta[] = {
struct EmbarkTileLayout {
coord2d biome_off, biome_pos;
df::region_map_entry *biome;
df::world_geo_biome *geo_biome;
int elevation, max_soil_depth;
int min_z, base_z;
std::map<int, float> penalty;
};
static df::world_region_details *get_details(df::world_data *data, df::coord2d pos)
{
int d_idx = linear_index(data->region_details, &df::world_region_details::pos, pos);
return vector_get(data->region_details, d_idx);
}
bool estimate_underground(color_ostream &out, EmbarkTileLayout &tile, df::world_region_details *details, int x, int y)
{
// Find actual biome
@ -251,15 +258,37 @@ bool estimate_underground(color_ostream &out, EmbarkTileLayout &tile, df::world_
tile.biome_pos = coord2d(bx, by);
tile.biome = &data->region_map[bx][by];
tile.geo_biome = df::world_geo_biome::find(tile.biome->geo_index);
// Compute surface elevation
tile.elevation = (
details->elevation[x][y] + details->elevation[x][y+1] +
details->elevation[x+1][y] + details->elevation[x+1][y+1]
) / 4;
tile.max_soil_depth = std::max((154-tile.biome->elevation)/5,0);
tile.base_z = tile.elevation;
tile.elevation = details->elevation[x][y];
tile.max_soil_depth = std::max((154-tile.elevation)/5,1);
tile.penalty.clear();
// Special biome adjustments
if (!tile.biome->flags.is_set(region_map_entry_flags::is_lake))
{
// Mountain biome
if (tile.biome->elevation >= 150)
tile.max_soil_depth = 0;
// Ocean biome
else if (tile.biome->elevation < 100)
{
if (tile.elevation == 99)
tile.elevation = 98;
if (tile.geo_biome && (tile.geo_biome->unk1 == 4 || tile.geo_biome->unk1 == 5))
{
auto b_details = get_details(data, tile.biome_pos);
if (b_details && b_details->unk12e8 < 500)
tile.max_soil_depth = 0;
}
}
}
tile.base_z = tile.elevation-1;
auto &features = details->features[x][y];
// Collect global feature layer depths and apply penalties
@ -301,8 +330,8 @@ bool estimate_underground(color_ostream &out, EmbarkTileLayout &tile, df::world_
if (!sea_found)
{
out.printerr("Could not find magma sea.\n");
return false;
out.printerr("Could not find magma sea; depth may be incorrect.\n");
tile.min_z = tile.base_z;
}
// Scan for big local features and apply their penalties
@ -340,7 +369,7 @@ bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &laye
{
using namespace geo_layer_type;
df::world_geo_biome *geo_biome = df::world_geo_biome::find(tile.biome->geo_index);
df::world_geo_biome *geo_biome = tile.geo_biome;
if (!geo_biome)
{
@ -350,35 +379,58 @@ bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &laye
}
// soil depth increases by 1 every 5 levels below 150
int top_z_level = tile.elevation - tile.max_soil_depth;
unsigned nlayers = std::min<unsigned>(16, geo_biome->layers.size());
int soil_size = 0;
for (unsigned i = 0; i < nlayers; i++)
{
auto layer = geo_biome->layers[i];
if (layer->type == SOIL || layer->type == SOIL_SAND)
soil_size += layer->top_height - layer->bottom_height + 1;
}
for (unsigned i = 0; i < geo_biome->layers.size(); i++)
// Compute shifts for layers in the stack
int soil_erosion = soil_size - std::min(soil_size,tile.max_soil_depth);
int layer_shift[16];
int cur_shift = tile.elevation+soil_erosion-1;
for (unsigned i = 0; i < nlayers; i++)
{
auto layer = geo_biome->layers[i];
switch (layer->type)
layer_shift[i] = cur_shift;
if (layer->type == SOIL || layer->type == SOIL_SAND)
{
case SOIL:
case SOIL_OCEAN:
case SOIL_SAND:
top_z_level += layer->top_height - layer->bottom_height + 1;
break;
default:;
int size = layer->top_height - layer->bottom_height + 1;
// This is to replicate the behavior of a probable bug in the
// map generation code: if a layer is partially eroded, the
// removed levels are in fact transferred to the layer below,
// because unlike the case of removing the whole layer, the code
// does not execute a loop to shift the lower part of the stack up.
if (size > soil_erosion)
cur_shift -= soil_erosion;
soil_erosion -= std::min(soil_erosion, size);
}
}
top_z_level = std::max(top_z_level, tile.elevation)-1;
// Estimate amounts
int last_bottom = tile.elevation;
for (unsigned i = 0; i < geo_biome->layers.size(); i++)
for (unsigned i = 0; i < nlayers; i++)
{
auto layer = geo_biome->layers[i];
int top_z = std::min(layer->top_height + top_z_level, tile.elevation-1);
int bottom_z = std::max(layer->bottom_height + top_z_level, tile.min_z);
if (i+1 == geo_biome->layers.size()) // stretch layer if needed
int top_z = last_bottom-1;
int bottom_z = std::max(layer->bottom_height + layer_shift[i], tile.min_z);
if (i+1 == nlayers) // stretch layer if needed
bottom_z = tile.min_z;
if (top_z < bottom_z)
continue;
last_bottom = bottom_z;
float layer_size = 48*48;
int sums[ENUM_LAST_ITEM(inclusion_type)+1] = { 0 };
@ -438,8 +490,7 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos
df::world_data *data = world->world_data;
coord2d cur_region = screen->region_pos;
int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region);
auto cur_details = vector_get(data->region_details, d_idx);
auto cur_details = get_details(data, cur_region);
if (!cur_details)
{

@ -55,6 +55,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
static command_result rename(color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("rename");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
@ -174,6 +175,9 @@ KNOWN_BUILDINGS
static bool enable_building_rename(char code, bool enable)
{
if (enable)
is_enabled = true;
if (code == 'Z')
INTERPOSE_HOOK(dwarf_render_zone_hook, render).apply(enable);
@ -189,6 +193,7 @@ KNOWN_BUILDINGS
static void disable_building_rename()
{
is_enabled = false;
INTERPOSE_HOOK(dwarf_render_zone_hook, render).remove();
#define BUILDING(code, cname, tag) \

@ -107,7 +107,7 @@ struct SuspendedBuilding
}
};
static bool enabled = false;
DFHACK_PLUGIN_IS_ENABLED(enabled);
static bool buildings_scanned = false;
static vector<SuspendedBuilding> suspended_buildings, resumed_buildings;
@ -125,8 +125,10 @@ void scan_for_suspended_buildings()
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; });
auto it = resumed_buildings.begin();
for (; it != resumed_buildings.end(); ++it)
if (it->bld == bld) break;
sb.was_resumed = it != resumed_buildings.end();
@ -234,6 +236,23 @@ struct resume_hook : public df::viewscreen_dwarfmodest
IMPLEMENT_VMETHOD_INTERPOSE(resume_hook, render);
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{
if (!gps)
return CR_FAILURE;
if (enabled != enable)
{
clear_scanned();
if (!INTERPOSE_HOOK(resume_hook, render).apply(enable))
return CR_FAILURE;
enabled = enable;
}
return CR_OK;
}
static command_result resume_cmd(color_ostream &out, vector <string> & parameters)
{
@ -251,12 +270,12 @@ static command_result resume_cmd(color_ostream &out, vector <string> & parameter
}
else if (cmd == 's')
{
enabled = true;
plugin_enable(out, true);
out << "Overlay enabled" << endl;
}
else if (cmd == 'h')
{
enabled = false;
plugin_enable(out, false);
out << "Overlay disabled" << endl;
}
else if (cmd == 'a')
@ -275,12 +294,8 @@ static command_result resume_cmd(color_ostream &out, vector <string> & parameter
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",

@ -85,6 +85,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCom
return CR_OK;
}
DFHACK_PLUGIN_IS_ENABLED(is_active);
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
t_gamemodes gm;
@ -117,6 +119,7 @@ command_result nopause (color_ostream &out, vector <string> & parameters)
nopause_state = 0;
else
nopause_state = 1;
is_active = nopause_state || (revealed == REVEALED);
out.print("nopause %sactivated.\n", (nopause_state ? "" : "de"));
}
else
@ -237,6 +240,7 @@ command_result reveal(color_ostream &out, vector<string> & params)
else
revealed = DEMON_REVEALED;
}
is_active = nopause_state || (revealed == REVEALED);
con.print("Map revealed.\n");
if(!no_hell)
con.print("Unpausing can unleash the forces of hell, so it has been temporarily disabled.\n");
@ -296,6 +300,7 @@ command_result unreveal(color_ostream &out, vector<string> & params)
// give back memory.
hidesaved.clear();
revealed = NOT_REVEALED;
is_active = nopause_state || (revealed == REVEALED);
con.print("Map hidden!\n");
return CR_OK;
}
@ -490,6 +495,7 @@ command_result revforget(color_ostream &out, vector<string> & params)
// give back memory.
hidesaved.clear();
revealed = NOT_REVEALED;
is_active = nopause_state || (revealed == REVEALED);
con.print("Reveal data forgotten!\n");
return CR_OK;
}

@ -175,7 +175,7 @@ module DFHack
# return the RegionMapEntry (from designation.biome)
def region_map_entry
b = designation.biome
b = mapblock.region_offset[designation.biome]
wd = df.world.world_data
# region coords + [[-1, -1], [0, -1], ..., [1, 1]][b]
@ -231,11 +231,23 @@ module DFHack
mat_index = df.world.world_data.region_details[idx].lava_stone
MaterialInfo.new(0, mat_index)
when :FEATURE
if designation.feature_local
mx = mapblock.region_pos.x
my = mapblock.region_pos.y
df.decode_mat(df.world.world_data.feature_map[mx/16][my/16].features.feature_init[mx%16][my%16][mapblock.local_feature])
elsif designation.feature_global
df.decode_mat(df.world.world_data.underground_regions[mapblock.global_feature].feature_init)
else
MaterialInfo.new(-1, -1)
end
when :FROZEN_LIQUID
MaterialInfo.new('WATER')
# TODO
#when :PLANT
#when :GRASS_DARK, :GRASS_DEAD, :GRASS_DRY, :GRASS_LIGHT
#when :FEATURE
#when :FROZEN_LIQUID
#when :CONSTRUCTION
else # AIR ASHES BROOK CAMPFIRE DRIFTWOOD FIRE HFS MAGMA POOL RIVER
MaterialInfo.new(-1, -1)

@ -475,7 +475,7 @@ module DFHack
class StlVector32 < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
@_tg = tg || Number.new(32, false, 0, nil)
end
def length

@ -46,6 +46,16 @@ static std::vector<std::string> *dfhack_run_queue;
DFHACK_PLUGIN("ruby")
DFhackDataExport bool plugin_is_enabled = true;
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
plugin_is_enabled = enable;
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
onupdate_active = 0;

@ -28,6 +28,7 @@
#include "df/misc_trait_type.h"
#include "df/unit_misc_trait.h"
using namespace std;
using std::set;
using std::vector;
using std::string;
@ -1609,6 +1610,7 @@ IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, burrow_search);
DFHACK_PLUGIN("search");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
#define SEARCH_HOOKS \
HOOK_ACTION(unitlist_search_hook) \
@ -1624,15 +1626,28 @@ DFHACK_PLUGIN("search");
HOOK_ACTION(burrow_search_hook) \
HOOK_ACTION(stockpile_search_hook)
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{
if (!gps || !gview)
return CR_FAILURE;
if (is_enabled != enable)
{
#define HOOK_ACTION(hook) \
!INTERPOSE_HOOK(hook, feed).apply() || \
!INTERPOSE_HOOK(hook, render).apply() ||
!INTERPOSE_HOOK(hook, feed).apply(enable) || \
!INTERPOSE_HOOK(hook, render).apply(enable) ||
if (SEARCH_HOOKS 0)
return CR_FAILURE;
is_enabled = enable;
}
if (!gps || !gview || SEARCH_HOOKS 0)
out.printerr("Could not insert Search hooks!\n");
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{
#undef HOOK_ACTION
const string a[] = {"Meager Quarters", "Modest Quarters", "Quarters", "Decent Quarters", "Fine Quarters", "Great Bedroom", "Grand Bedroom", "Royal Bedroom"};

@ -22,7 +22,7 @@ using namespace df::enums;
using df::global::world;
const int buffer = 20; // seed number buffer - 20 is reasonable
bool running = false; // whether seedwatch is counting the seeds or not
DFHACK_PLUGIN_IS_ENABLED(running); // whether seedwatch is counting the seeds or not
// abbreviations for the standard plants
map<string, string> abbreviations;
@ -96,6 +96,12 @@ string searchAbbreviations(string in)
}
};
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
running = enable;
return CR_OK;
}
command_result df_seedwatch(color_ostream &out, vector<string>& parameters)
{
CoreSuspender suspend;

@ -1766,7 +1766,7 @@ DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_END
};
static bool is_enabled = false;
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
static void enable_hooks(bool enable)
{
@ -1809,6 +1809,25 @@ static void clear_caches(color_ostream &out)
}
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (gamemode && *gamemode != game_mode::DWARF)
return CR_FAILURE;
if (enable != is_enabled)
{
if (enable)
enable_plugin();
else
{
World::DeletePersistentData(World::GetPersistentData("siege-engine/enabled"));
enable_hooks(false);
}
}
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {

@ -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;
}

@ -113,6 +113,7 @@
using std::vector;
using std::string;
using std::stack;
using std::set;
using namespace DFHack;
using namespace df::enums;
@ -721,7 +722,7 @@ struct workshop_hook : df::building_workshopst {
INTERPOSE_NEXT(updateAction)();
}
DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, void *unk))
DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, int16_t unk))
{
INTERPOSE_NEXT(drawBuilding)(db, unk);
@ -950,8 +951,12 @@ static bool find_engines(color_ostream &out)
return !engines.empty();
}
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
static void enable_hooks(bool enable)
{
is_enabled = enable;
INTERPOSE_HOOK(liquid_hook, getItemDescription).apply(enable);
INTERPOSE_HOOK(liquid_hook, adjustTemperature).apply(enable);
INTERPOSE_HOOK(liquid_hook, checkTemperatureDamage).apply(enable);

@ -1 +1 @@
Subproject commit 25eae7ef6f19a24b7fc7e0d72ed45cbef7ebe092
Subproject commit 580ee33fd545fa7198690daecfdd92938c21d65f

@ -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!
}

@ -97,6 +97,8 @@ void help( color_ostream & out, std::vector<std::string> &commands, int start, i
<< " Subterranean / st: set subterranean flag" << std::endl
<< " Skyview / sv: set skyview flag" << std::endl
<< " Aquifer / aqua: set aquifer flag" << std::endl
<< " Stone: paint specific stone material" << std::endl
<< " Veintype: use specific vein type for stone" << std::endl
<< "See help [option] for more information" << std::endl;
}
else if (option == "shape" || option == "s" ||option == "sh")
@ -165,6 +167,22 @@ void help( color_ostream & out, std::vector<std::string> &commands, int start, i
out << "Available aquifer flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
}
else if (option == "stone")
{
out << "The stone option allows painting any specific stone material." << std::endl
<< "The normal 'material' option is forced to STONE, and cannot" << std::endl
<< "be changed without cancelling the specific stone selection." << std::endl
<< "Note: this feature paints under ice and constructions," << std::endl
<< "instead of replacing them with brute force." << std::endl;
}
else if (option == "veintype")
{
out << "Specifies which vein type to use when painting specific stone." << std::endl
<< "The vein type determines stone drop rate. Available types:" << std::endl;
FOR_ENUM_ITEMS(inclusion_type,i)
out << " " << ENUM_KEY_STR(inclusion_type,i) << std::endl;
out << "Vein type other than CLUSTER forces creation of a vein." << std::endl;
}
}
struct TileType
@ -179,6 +197,8 @@ struct TileType
int subterranean;
int skyview;
int aquifer;
int stone_material;
df::inclusion_type vein_type;
TileType()
{
@ -197,21 +217,27 @@ struct TileType
subterranean = -1;
skyview = -1;
aquifer = -1;
stone_material = -1;
vein_type = inclusion_type::CLUSTER;
}
bool empty()
{
return shape == -1 && material == -1 && special == -1 && variant == -1
&& dig == -1 && hidden == -1 && light == -1 && subterranean == -1
&& skyview == -1 && aquifer == -1;
&& skyview == -1 && aquifer == -1 && stone_material == -1;
}
inline bool matches(const df::tiletype source,
const df::tile_designation des)
const df::tile_designation des,
const t_matpair mat)
{
bool rv = true;
rv &= (shape == -1 || shape == tileShape(source));
rv &= (material == -1 || material == tileMaterial(source));
if (stone_material >= 0)
rv &= isStoneMaterial(source) && mat.mat_type == 0 && mat.mat_index == stone_material;
else
rv &= (material == -1 || material == tileMaterial(source));
rv &= (special == -1 || special == tileSpecial(source));
rv &= (variant == -1 || variant == tileVariant(source));
rv &= (dig == -1 || (dig != 0) == (des.bits.dig != tile_dig_designation::No));
@ -353,6 +379,20 @@ std::ostream &operator<<(std::ostream &stream, const TileType &paint)
needSpace = true;
}
if (paint.stone_material >= 0)
{
if (needSpace)
{
stream << " ";
needSpace = false;
}
stream << MaterialInfo(0,paint.stone_material).getToken()
<< " " << ENUM_KEY_STR(inclusion_type, paint.vein_type);
used = true;
needSpace = true;
}
if (!used)
{
stream << "any";
@ -497,6 +537,9 @@ bool processTileType(color_ostream & out, TileType &paint, std::vector<std::stri
}
else if (option == "material" || option == "mat" || option == "m")
{
// Setting the material conflicts with stone_material
paint.stone_material = -1;
if (is_valid_enum_item((df::tiletype_material)valInt))
{
paint.material = (df::tiletype_material)valInt;
@ -630,6 +673,25 @@ bool processTileType(color_ostream & out, TileType &paint, std::vector<std::stri
found = true;
}
else if (option == "stone")
{
MaterialInfo mat;
if (!mat.findInorganic(value))
out << "Unknown inorganic material: " << value << std::endl;
else if (!isStoneInorganic(mat.index))
out << "Not a stone material: " << value << std::endl;
else
{
paint.material = tiletype_material::STONE;
paint.stone_material = mat.index;
}
}
else if (option == "veintype")
{
if (!find_enum_item(&paint.vein_type, value))
out << "Unknown vein type: " << value << std::endl;
}
else
{
out << "Unknown option: '" << option << "'" << std::endl;
@ -672,12 +734,24 @@ command_result executePaintJob(color_ostream &out)
// Force the game to recompute its walkability cache
df::global::world->reindex_pathfinding = true;
int failures = 0;
for (coord_vec::iterator iter = all_tiles.begin(); iter != all_tiles.end(); ++iter)
{
const df::tiletype source = map.tiletypeAt(*iter);
MapExtras::Block *blk = map.BlockAtTile(*iter);
if (!blk)
continue;
df::tiletype source = map.tiletypeAt(*iter);
df::tile_designation des = map.designationAt(*iter);
if (!filter.matches(source, des))
// Stone painting operates on the base layer
if (paint.stone_material >= 0)
source = blk->baseTiletypeAt(*iter);
t_matpair basemat = blk->baseMaterialAt(*iter);
if (!filter.matches(source, des, basemat))
{
continue;
}
@ -725,7 +799,7 @@ command_result executePaintJob(color_ostream &out)
*/
// Remove direction from directionless tiles
DFHack::TileDirection direction = tileDirection(source);
if (!(material == tiletype_material::RIVER || shape == tiletype_shape::BROOK_BED || shape == tiletype_shape::WALL && (material == tiletype_material::CONSTRUCTION || special == tiletype_special::SMOOTH)))
if (!(material == tiletype_material::RIVER || shape == tiletype_shape::BROOK_BED || special == tiletype_special::TRACK || shape == tiletype_shape::WALL && (material == tiletype_material::CONSTRUCTION || special == tiletype_special::SMOOTH)))
{
direction.whole = 0;
}
@ -738,7 +812,15 @@ command_result executePaintJob(color_ostream &out)
}
// make sure it's not invalid
if(type != tiletype::Void)
map.setTiletypeAt(*iter, type);
{
if (paint.stone_material >= 0)
{
if (!blk->setStoneAt(*iter, type, paint.stone_material, paint.vein_type, true, true))
failures++;
}
else
map.setTiletypeAt(*iter, type);
}
if (paint.hidden > -1)
{
@ -780,6 +862,11 @@ command_result executePaintJob(color_ostream &out)
map.setDesignationAt(*iter, des);
}
if (failures > 0)
out.printerr("Could not update %d tiles of %d.\n", failures, all_tiles.size());
else
out.print("Processed %d tiles.\n", all_tiles.size());
if (map.WriteAll())
{
out.print("OK\n");

@ -0,0 +1,156 @@
#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_IS_ENABLED(enabled);
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_enable(color_ostream& out, bool enable) {
enabled = enable;
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);
if ( !enabled )
return;
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 ) {
plugin_enable(out, false);
out.print("treefarm disabled\n");
return CR_OK;
}
plugin_enable(out, true);
frequency = i;
}
if ( enabled ) {
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!
}

Some files were not shown because too many files have changed in this diff Show More