Merge branch 'master' of https://github.com/angavrilov/dfhack into experimental-dontmerge

develop
Warmist 2012-09-05 21:55:17 +03:00
commit 3dcaee7cb9
39 changed files with 1791 additions and 193 deletions

@ -550,6 +550,20 @@ Exception handling
The default value of the ``verbose`` argument of ``err:tostring()``.
Miscellaneous
-------------
* ``dfhack.VERSION``
DFHack version string constant.
* ``dfhack.curry(func,args...)``, or ``curry(func,args...)``
Returns a closure that invokes the function with args combined
both from the curry call and the closure call itself. I.e.
``curry(func,a,b)(c,d)`` equals ``func(a,b,c,d)``.
Locking and finalization
------------------------
@ -709,6 +723,10 @@ can be omitted.
Returns the dfhack directory path, i.e. ``".../df/hack/"``.
* ``dfhack.getTickCount()``
Returns the tick count in ms, exactly as DF ui uses.
* ``dfhack.isWorldLoaded()``
Checks if the world is loaded.
@ -764,10 +782,20 @@ Gui module
Adds a regular announcement with given text, color, and brightness.
The is_bright boolean actually seems to invert the brightness.
* ``dfhack.gui.showZoomAnnouncement(type,pos,text,color[,is_bright])``
Like above, but also specifies a position you can zoom to from the announcement menu.
* ``dfhack.gui.showPopupAnnouncement(text,color[,is_bright])``
Pops up a titan-style modal announcement window.
* ``dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])``
Uses the type to look up options from announcements.txt, and calls the
above operations accordingly. If enabled, pauses and zooms to position.
Job module
----------
@ -959,6 +987,10 @@ Maps module
Returns a map block object for given x,y,z in local block coordinates.
* ``dfhack.maps.isValidTilePos(coords)``, or isValidTilePos(x,y,z)``
Checks if the given df::coord or x,y,z in local tile coordinates are valid.
* ``dfhack.maps.getTileBlock(coords)``, or ``getTileBlock(x,y,z)``
Returns a map block object for given df::coord or x,y,z in local tile coordinates.
@ -971,6 +1003,11 @@ Maps module
Enables updates for liquid flow or temperature, unless already active.
* ``dfhack.maps.spawnFlow(pos,type,mat_type,mat_index,dimension)``
Spawns a new flow (i.e. steam/mist/dust/etc) at the given pos, and with
the given parameters. Returns it, or *nil* if unsuccessful.
* ``dfhack.maps.getGlobalInitFeature(index)``
Returns the global feature object with the given index.

@ -337,42 +337,43 @@ ul.auto-toc {
<li><a class="reference internal" href="#native-utilities" id="id11">Native utilities</a><ul>
<li><a class="reference internal" href="#input-output" id="id12">Input &amp; Output</a></li>
<li><a class="reference internal" href="#exception-handling" id="id13">Exception handling</a></li>
<li><a class="reference internal" href="#locking-and-finalization" id="id14">Locking and finalization</a></li>
<li><a class="reference internal" href="#persistent-configuration-storage" id="id15">Persistent configuration storage</a></li>
<li><a class="reference internal" href="#material-info-lookup" id="id16">Material info lookup</a></li>
<li><a class="reference internal" href="#miscellaneous" id="id14">Miscellaneous</a></li>
<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>
</ul>
</li>
<li><a class="reference internal" href="#c-function-wrappers" id="id17">C++ function wrappers</a><ul>
<li><a class="reference internal" href="#gui-module" id="id18">Gui module</a></li>
<li><a class="reference internal" href="#job-module" id="id19">Job module</a></li>
<li><a class="reference internal" href="#units-module" id="id20">Units module</a></li>
<li><a class="reference internal" href="#items-module" id="id21">Items module</a></li>
<li><a class="reference internal" href="#maps-module" id="id22">Maps module</a></li>
<li><a class="reference internal" href="#burrows-module" id="id23">Burrows module</a></li>
<li><a class="reference internal" href="#buildings-module" id="id24">Buildings module</a></li>
<li><a class="reference internal" href="#constructions-module" id="id25">Constructions module</a></li>
<li><a class="reference internal" href="#screen-api" id="id26">Screen API</a></li>
<li><a class="reference internal" href="#internal-api" id="id27">Internal API</a></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>
</ul>
</li>
<li><a class="reference internal" href="#core-interpreter-context" id="id28">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id29">Event type</a></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>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#lua-modules" id="id30">Lua Modules</a><ul>
<li><a class="reference internal" href="#global-environment" id="id31">Global environment</a></li>
<li><a class="reference internal" href="#utils" id="id32">utils</a></li>
<li><a class="reference internal" href="#dumper" id="id33">dumper</a></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>
</ul>
</li>
<li><a class="reference internal" href="#plugins" id="id34">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id35">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id36">sort</a></li>
<li><a class="reference internal" href="#plugins" id="id35">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id36">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id37">sort</a></li>
</ul>
</li>
<li><a class="reference internal" href="#scripts" id="id37">Scripts</a></li>
<li><a class="reference internal" href="#scripts" id="id38">Scripts</a></li>
</ul>
</div>
<p>The current version of DFHack has extensive support for
@ -842,8 +843,21 @@ following properties:</p>
</li>
</ul>
</div>
<div class="section" id="miscellaneous">
<h3><a class="toc-backref" href="#id14">Miscellaneous</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.VERSION</tt></p>
<p>DFHack version string constant.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.curry(func,args...)</span></tt>, or <tt class="docutils literal"><span class="pre">curry(func,args...)</span></tt></p>
<p>Returns a closure that invokes the function with args combined
both from the curry call and the closure call itself. I.e.
<tt class="docutils literal"><span class="pre">curry(func,a,b)(c,d)</span></tt> equals <tt class="docutils literal">func(a,b,c,d)</tt>.</p>
</li>
</ul>
</div>
<div class="section" id="locking-and-finalization">
<h3><a class="toc-backref" href="#id14">Locking and finalization</a></h3>
<h3><a class="toc-backref" href="#id15">Locking and finalization</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.with_suspend(f[,args...])</span></tt></p>
<p>Calls <tt class="docutils literal">f</tt> with arguments after grabbing the DF core suspend lock.
@ -876,7 +890,7 @@ Implemented using <tt class="docutils literal"><span class="pre">call_with_final
</ul>
</div>
<div class="section" id="persistent-configuration-storage">
<h3><a class="toc-backref" href="#id15">Persistent configuration storage</a></h3>
<h3><a class="toc-backref" href="#id16">Persistent configuration storage</a></h3>
<p>This api is intended for storing configuration options in the world itself.
It probably should be restricted to data that is world-dependent.</p>
<p>Entries are identified by a string <tt class="docutils literal">key</tt>, but it is also possible to manage
@ -911,7 +925,7 @@ functions can just copy values in memory without doing any actual I/O.
However, currently every entry has a 180+-byte dead-weight overhead.</p>
</div>
<div class="section" id="material-info-lookup">
<h3><a class="toc-backref" href="#id16">Material info lookup</a></h3>
<h3><a class="toc-backref" href="#id17">Material info lookup</a></h3>
<p>A material info record has fields:</p>
<ul>
<li><p class="first"><tt class="docutils literal">type</tt>, <tt class="docutils literal">index</tt>, <tt class="docutils literal">material</tt></p>
@ -956,7 +970,7 @@ Accept dfhack_material_category auto-assign table.</p>
</div>
</div>
<div class="section" id="c-function-wrappers">
<h2><a class="toc-backref" href="#id17">C++ function wrappers</a></h2>
<h2><a class="toc-backref" href="#id18">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
@ -974,6 +988,9 @@ can be omitted.</p>
<li><p class="first"><tt class="docutils literal">dfhack.getHackPath()</tt></p>
<p>Returns the dfhack directory path, i.e. <tt class="docutils literal"><span class="pre">&quot;.../df/hack/&quot;</span></tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.getTickCount()</tt></p>
<p>Returns the tick count in ms, exactly as DF ui uses.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.isWorldLoaded()</tt></p>
<p>Checks if the world is loaded.</p>
</li>
@ -985,7 +1002,7 @@ can be omitted.</p>
</li>
</ul>
<div class="section" id="gui-module">
<h3><a class="toc-backref" href="#id18">Gui module</a></h3>
<h3><a class="toc-backref" href="#id19">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>,
@ -1019,13 +1036,20 @@ the container itself.</p>
<p>Adds a regular announcement with given text, color, and brightness.
The is_bright boolean actually seems to invert the brightness.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showZoomAnnouncement(type,pos,text,color[,is_bright])</span></tt></p>
<p>Like above, but also specifies a position you can zoom to from the announcement menu.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showPopupAnnouncement(text,color[,is_bright])</span></tt></p>
<p>Pops up a titan-style modal announcement window.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])</span></tt></p>
<p>Uses the type to look up options from announcements.txt, and calls the
above operations accordingly. If enabled, pauses and zooms to position.</p>
</li>
</ul>
</div>
<div class="section" id="job-module">
<h3><a class="toc-backref" href="#id19">Job module</a></h3>
<h3><a class="toc-backref" href="#id20">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>
@ -1062,7 +1086,7 @@ a lua list containing them.</p>
</ul>
</div>
<div class="section" id="units-module">
<h3><a class="toc-backref" href="#id20">Units module</a></h3>
<h3><a class="toc-backref" href="#id21">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>
@ -1123,7 +1147,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="#id21">Items module</a></h3>
<h3><a class="toc-backref" href="#id22">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>
@ -1166,7 +1190,7 @@ Returns <em>false</em> in case of error.</p>
</ul>
</div>
<div class="section" id="maps-module">
<h3><a class="toc-backref" href="#id22">Maps module</a></h3>
<h3><a class="toc-backref" href="#id23">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>
@ -1177,6 +1201,9 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getBlock(x,y,z)</tt></p>
<p>Returns a map block object for given x,y,z in local block coordinates.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.isValidTilePos(coords)</tt>, or isValidTilePos(x,y,z)``</p>
<p>Checks if the given df::coord or x,y,z in local tile coordinates are valid.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBlock(coords)</tt>, or <tt class="docutils literal">getTileBlock(x,y,z)</tt></p>
<p>Returns a map block object for given df::coord or x,y,z in local tile coordinates.</p>
</li>
@ -1186,6 +1213,10 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.maps.enableBlockUpdates(block[,flow,temperature])</span></tt></p>
<p>Enables updates for liquid flow or temperature, unless already active.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.spawnFlow(pos,type,mat_type,mat_index,dimension)</tt></p>
<p>Spawns a new flow (i.e. steam/mist/dust/etc) at the given pos, and with
the given parameters. Returns it, or <em>nil</em> if unsuccessful.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getGlobalInitFeature(index)</tt></p>
<p>Returns the global feature object with the given index.</p>
</li>
@ -1207,7 +1238,7 @@ burrows, or the presence of invaders.</p>
</ul>
</div>
<div class="section" id="burrows-module">
<h3><a class="toc-backref" href="#id23">Burrows module</a></h3>
<h3><a class="toc-backref" href="#id24">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>
@ -1242,7 +1273,7 @@ burrows, or the presence of invaders.</p>
</ul>
</div>
<div class="section" id="buildings-module">
<h3><a class="toc-backref" href="#id24">Buildings module</a></h3>
<h3><a class="toc-backref" href="#id25">Buildings module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.setOwner(item,unit)</tt></p>
<p>Replaces the owner of the building. If unit is <em>nil</em>, removes ownership.
@ -1386,7 +1417,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="#id25">Constructions module</a></h3>
<h3><a class="toc-backref" href="#id26">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
@ -1402,7 +1433,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="#id26">Screen API</a></h3>
<h3><a class="toc-backref" href="#id27">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>
@ -1541,7 +1572,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="#id27">Internal API</a></h3>
<h3><a class="toc-backref" href="#id28">Internal API</a></h3>
<p>These functions are intended for the use by dfhack developers,
and are only documented here for completeness:</p>
<ul>
@ -1589,7 +1620,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="#id28">Core interpreter context</a></h2>
<h2><a class="toc-backref" href="#id29">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>
@ -1620,7 +1651,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="#id29">Event type</a></h3>
<h3><a class="toc-backref" href="#id30">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.
@ -1652,7 +1683,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="#id30">Lua Modules</a></h1>
<h1><a class="toc-backref" href="#id31">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
@ -1681,7 +1712,7 @@ in this document.</p>
</li>
</ul>
<div class="section" id="global-environment">
<h2><a class="toc-backref" href="#id31">Global environment</a></h2>
<h2><a class="toc-backref" href="#id32">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>
@ -1722,7 +1753,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="#id32">utils</a></h2>
<h2><a class="toc-backref" href="#id33">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>
@ -1835,7 +1866,7 @@ throws an error.</p>
</ul>
</div>
<div class="section" id="dumper">
<h2><a class="toc-backref" href="#id33">dumper</a></h2>
<h2><a class="toc-backref" href="#id34">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>
@ -1849,14 +1880,14 @@ the other arguments see the original documentation link above.</p>
</div>
</div>
<div class="section" id="plugins">
<h1><a class="toc-backref" href="#id34">Plugins</a></h1>
<h1><a class="toc-backref" href="#id35">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="#id35">burrows</a></h2>
<h2><a class="toc-backref" href="#id36">burrows</a></h2>
<p>Implements extended burrow manipulations.</p>
<p>Events:</p>
<ul>
@ -1894,13 +1925,13 @@ 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="#id36">sort</a></h2>
<h2><a class="toc-backref" href="#id37">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>
<div class="section" id="scripts">
<h1><a class="toc-backref" href="#id37">Scripts</a></h1>
<h1><a class="toc-backref" href="#id38">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

@ -16,6 +16,10 @@ keybinding add Ctrl-K autodump-destroy-item
# quicksave, only in main dwarfmode screen and menu page
keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave
# gui/rename script
keybinding add Ctrl-Shift-N gui/rename
keybinding add Ctrl-Shift-P "gui/rename unit-profession"
##############################
# Generic adv mode bindings #
##############################
@ -64,3 +68,9 @@ tweak stable-cursor
# stop military from considering training as 'patrol duty'
tweak patrol-duty
# display creature weight in build plate menu as ??K, instead of (???df: Max
tweak readable-build-plate
# improve FPS by squashing endless item temperature update loops
tweak stable-temp

@ -374,7 +374,7 @@ void DFHack::bitfieldToString(std::vector<std::string> *pvec, const void *p,
unsigned size, const bitfield_item_info *items)
{
for (unsigned i = 0; i < size; i++) {
int value = getBitfieldField(p, i, std::min(1,items[i].size));
int value = getBitfieldField(p, i, std::max(1,items[i].size));
if (value) {
std::string name = format_key(items[i].name, i);

@ -78,6 +78,7 @@ distribution.
#include "df/burrow.h"
#include "df/building_civzonest.h"
#include "df/region_map_entry.h"
#include "df/flow_info.h"
#include <lua.h>
#include <lauxlib.h>
@ -727,6 +728,7 @@ static std::string getOSType()
}
static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); }
static uint32_t getTickCount() { return Core::getInstance().p->getTickCount(); }
static std::string getDFPath() { return Core::getInstance().p->getPath(); }
static std::string getHackPath() { return Core::getInstance().getHackPath(); }
@ -738,6 +740,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(getOSType),
WRAP(getDFVersion),
WRAP(getDFPath),
WRAP(getTickCount),
WRAP(getHackPath),
WRAP(isWorldLoaded),
WRAP(isMapLoaded),
@ -756,7 +759,9 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getSelectedUnit),
WRAPM(Gui, getSelectedItem),
WRAPM(Gui, showAnnouncement),
WRAPM(Gui, showZoomAnnouncement),
WRAPM(Gui, showPopupAnnouncement),
WRAPM(Gui, showAutoAnnouncement),
{ NULL, NULL }
};
@ -912,9 +917,17 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPM(Maps, getGlobalInitFeature),
WRAPM(Maps, getLocalInitFeature),
WRAPM(Maps, canWalkBetween),
WRAPM(Maps, spawnFlow),
{ NULL, NULL }
};
static int maps_isValidTilePos(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
lua_pushboolean(L, Maps::isValidTilePos(pos));
return 1;
}
static int maps_getTileBlock(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
@ -936,6 +949,7 @@ static int maps_getTileBiomeRgn(lua_State *L)
}
static const luaL_Reg dfhack_maps_funcs[] = {
{ "isValidTilePos", maps_isValidTilePos },
{ "getTileBlock", maps_getTileBlock },
{ "getRegionBiome", maps_getRegionBiome },
{ "getTileBiomeRgn", maps_getTileBiomeRgn },

@ -1211,6 +1211,39 @@ static int dfhack_open_plugin(lua_State *L)
return 0;
}
static int dfhack_curry_wrap(lua_State *L)
{
int nargs = lua_gettop(L);
int ncurry = lua_tointeger(L, lua_upvalueindex(1));
int scount = nargs + ncurry;
luaL_checkstack(L, ncurry, "stack overflow in curry");
// Insert values in O(N+M) by first shifting the existing data
lua_settop(L, scount);
for (int i = 0; i < nargs; i++)
lua_copy(L, nargs-i, scount-i);
for (int i = 1; i <= ncurry; i++)
lua_copy(L, lua_upvalueindex(i+1), i);
lua_callk(L, scount-1, LUA_MULTRET, 0, lua_gettop);
return lua_gettop(L);
}
static int dfhack_curry(lua_State *L)
{
luaL_checkany(L, 1);
if (lua_isnil(L, 1))
luaL_argerror(L, 1, "nil function in curry");
if (lua_gettop(L) == 1)
return 1;
lua_pushinteger(L, lua_gettop(L));
lua_insert(L, 1);
lua_pushcclosure(L, dfhack_curry_wrap, lua_gettop(L));
return 1;
}
bool Lua::IsCoreContext(lua_State *state)
{
// This uses a private field of the lua state to
@ -1234,6 +1267,7 @@ static const luaL_Reg dfhack_funcs[] = {
{ "call_with_finalizer", dfhack_call_with_finalizer },
{ "with_suspend", lua_dfhack_with_suspend },
{ "open_plugin", dfhack_open_plugin },
{ "curry", dfhack_curry },
{ NULL, NULL }
};
@ -1546,6 +1580,9 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN);
lua_setfield(state, -2, "BASE_G");
lua_pushstring(state, DFHACK_VERSION);
lua_setfield(state, -2, "VERSION");
lua_pushboolean(state, IsCoreContext(state));
lua_setfield(state, -2, "is_core_context");

@ -894,7 +894,7 @@ static int meta_bitfield_len(lua_State *state)
static void read_bitfield(lua_State *state, uint8_t *ptr, bitfield_identity *id, int idx)
{
int size = id->getBits()[idx].size;
int size = std::max(1, id->getBits()[idx].size);
int value = getBitfieldField(ptr, idx, size);
if (size <= 1)
@ -951,7 +951,7 @@ static int meta_bitfield_newindex(lua_State *state)
}
int idx = check_container_index(state, id->getNumBits(), 2, iidx, "write");
int size = id->getBits()[idx].size;
int size = std::max(1, id->getBits()[idx].size);
if (lua_isboolean(state, 3) || lua_isnil(state, 3))
setBitfieldField(ptr, idx, size, lua_toboolean(state, 3));

@ -27,6 +27,7 @@ distribution.
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <mach-o/dyld.h>
@ -262,6 +263,13 @@ bool Process::getThreadIDs(vector<uint32_t> & threads )
return true;
}
uint32_t Process::getTickCount()
{
struct timeval tp;
gettimeofday(&tp, NULL);
return (tp.tv_sec * 1000) + (tp.tv_usec / 1000);
}
string Process::getPath()
{
char path[1024];

@ -27,6 +27,7 @@ distribution.
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <string>
#include <vector>
@ -192,6 +193,13 @@ bool Process::getThreadIDs(vector<uint32_t> & threads )
return true;
}
uint32_t Process::getTickCount()
{
struct timeval tp;
gettimeofday(&tp, NULL);
return (tp.tv_sec * 1000) + (tp.tv_usec / 1000);
}
string Process::getPath()
{
const char * cwd_name = "/proc/self/cwd";

@ -410,6 +410,11 @@ string Process::doReadClassName (void * vptr)
return raw;
}
uint32_t Process::getTickCount()
{
return GetTickCount();
}
string Process::getPath()
{
HMODULE hmod;

@ -335,8 +335,14 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
}
}
bool VMethodInterposeLinkBase::apply()
bool VMethodInterposeLinkBase::apply(bool enable)
{
if (!enable)
{
remove();
return true;
}
if (is_applied())
return true;
if (!host->vtable_ptr)

@ -64,7 +64,7 @@ namespace DFHack
if (newsize == size)
return;
uint8_t* mem = (uint8_t *) realloc(bits, newsize);
if(!mem)
if(!mem && newsize != 0)
throw std::bad_alloc();
bits = mem;
if (newsize > size)
@ -207,7 +207,7 @@ namespace DFHack
else
{
T* mem = (T*) realloc(m_data, sizeof(T)*new_size);
if(!mem)
if(!mem && new_size != 0)
throw std::bad_alloc();
m_data = mem;
}

@ -390,7 +390,7 @@ namespace df
}
virtual bool resize(void *ptr, int size) {
((container*)ptr)->resize(size);
((container*)ptr)->resize(size*8);
return true;
}

@ -281,6 +281,9 @@ namespace DFHack
/// get the DF Process FilePath
std::string getPath();
/// millisecond tick count, exactly as DF uses
uint32_t getTickCount();
/// modify permisions of memory range
bool setPermisions(const t_memrange & range,const t_memrange &trgrange);

@ -159,7 +159,7 @@ namespace DFHack
~VMethodInterposeLinkBase();
bool is_applied() { return applied; }
bool apply();
bool apply(bool enable = true);
void remove();
};

@ -32,6 +32,7 @@ distribution.
#include "DataDefs.h"
#include "df/init.h"
#include "df/ui.h"
#include "df/announcement_type.h"
namespace df {
struct viewscreen;
@ -92,14 +93,32 @@ namespace DFHack
// Show a plain announcement, or a titan-style popup message
DFHACK_EXPORT void showAnnouncement(std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT void showZoomAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT void showPopupAnnouncement(std::string message, int color = 7, bool bright = true);
// Show an announcement with effects determined by announcements.txt
DFHACK_EXPORT void showAutoAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true);
/*
* Cursor and window coords
*/
DFHACK_EXPORT df::coord getViewportPos();
DFHACK_EXPORT df::coord getCursorPos();
static const int AREA_MAP_WIDTH = 23;
static const int MENU_WIDTH = 30;
struct DwarfmodeDims {
int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2;
int y1, y2;
bool menu_on, area_on, menu_forced;
};
DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims();
DFHACK_EXPORT void resetDwarfmodeView(bool pause = false);
DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false);
DFHACK_EXPORT bool getViewCoords (int32_t &x, int32_t &y, int32_t &z);
DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z);

@ -50,6 +50,7 @@ distribution.
#include "df/tile_dig_designation.h"
#include "df/tile_traffic.h"
#include "df/feature_init.h"
#include "df/flow_type.h"
/**
* \defgroup grp_maps Maps module and its types
@ -232,6 +233,9 @@ extern DFHACK_EXPORT void getSize(uint32_t& x, uint32_t& y, uint32_t& z);
/// get the position of the map on world map
extern DFHACK_EXPORT void getPosition(int32_t& x, int32_t& y, int32_t& z);
extern DFHACK_EXPORT bool isValidTilePos(int32_t x, int32_t y, int32_t z);
inline bool isValidTilePos(df::coord pos) { return isValidTilePos(pos.x, pos.y, pos.z); }
/**
* Get the map block or NULL if block is not valid
*/
@ -272,6 +276,8 @@ inline df::coord2d getTileBiomeRgn(df::coord pos) {
// Enables per-frame updates for liquid flow and/or temperature.
DFHACK_EXPORT void enableBlockUpdates(df::map_block *blk, bool flow = false, bool temperature = false);
DFHACK_EXPORT df::flow_info *spawnFlow(df::coord pos, df::flow_type type, int mat_type = 0, int mat_index = -1, int density = 100);
/// sorts the block event vector into multiple vectors by type
/// mineral veins, what's under ice, blood smears and mud
extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block,

@ -46,6 +46,7 @@ end
-- Error handling
safecall = dfhack.safecall
curry = dfhack.curry
function dfhack.pcall(f, ...)
return xpcall(f, dfhack.onerror, ...)
@ -118,7 +119,12 @@ function defclass(class,parent)
if parent then
setmetatable(class, parent)
else
rawset_default(class, { init_fields = rawset_default })
rawset_default(class, {
init_fields = rawset_default,
callback = function(self, name, ...)
return dfhack.curry(self[name], self, ...)
end
})
end
return class
end

@ -94,6 +94,9 @@ function Painter:isValidPos()
end
function Painter:viewport(x,y,w,h)
if type(x) == 'table' then
x,y,w,h = x.x1, x.y1, x.width, x.height
end
local x1,y1 = self.x1+x, self.y1+y
local x2,y2 = x1+w-1, y1+h-1
local vp = {
@ -353,11 +356,16 @@ local function hint_coord(gap,hint)
end
end
function FramedScreen:getWantedFrameSize()
return self.frame_width, self.frame_height
end
function FramedScreen:updateFrameSize()
local sw, sh = dscreen.getWindowSize()
local iw, ih = sw-2, sh-2
local width = math.min(self.frame_width or iw, iw)
local height = math.min(self.frame_height or ih, ih)
local fw, fh = self:getWantedFrameSize()
local width = math.min(fw or iw, iw)
local height = math.min(fh or ih, ih)
local gw, gh = iw-width, ih-height
local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint)
self.frame_rect = mkdims_wh(x1+1,y1+1,width,height)

@ -0,0 +1,178 @@
-- Some simple dialog screens
local _ENV = mkmodule('gui.dialogs')
local gui = require('gui')
local utils = require('utils')
local dscreen = dfhack.screen
MessageBox = defclass(MessageBox, gui.FramedScreen)
MessageBox.focus_path = 'MessageBox'
MessageBox.frame_style = gui.GREY_LINE_FRAME
function MessageBox:init(info)
info = info or {}
self:init_fields{
text = info.text or {},
frame_title = info.title,
frame_width = info.frame_width,
on_accept = info.on_accept,
on_cancel = info.on_cancel,
on_close = info.on_close,
text_pen = info.text_pen
}
if type(self.text) == 'string' then
self.text = utils.split_string(self.text, "\n")
end
gui.FramedScreen.init(self, info)
return self
end
function MessageBox:getWantedFrameSize()
local text = self.text
local w = #(self.frame_title or '') + 4
w = math.max(w, 20)
w = math.max(self.frame_width or w, w)
for _, l in ipairs(text) do
w = math.max(w, #l)
end
local h = #text+1
if h > 1 then
h = h+1
end
return w+2, #text+2
end
function MessageBox:onRenderBody(dc)
if #self.text > 0 then
dc:newline(1):pen(self.text_pen or COLOR_GREY)
for _, l in ipairs(self.text or {}) do
dc:string(l):newline(1)
end
end
if self.on_accept then
local x,y = self.frame_rect.x1+1, self.frame_rect.y2+1
dscreen.paintString({fg=COLOR_LIGHTGREEN},x,y,'ESC')
dscreen.paintString({fg=COLOR_GREY},x+3,y,'/')
dscreen.paintString({fg=COLOR_LIGHTGREEN},x+4,y,'y')
end
end
function MessageBox:onDestroy()
if self.on_close then
self.on_close()
end
end
function MessageBox:onInput(keys)
if keys.MENU_CONFIRM then
self:dismiss()
if self.on_accept then
self.on_accept()
end
elseif keys.LEAVESCREEN or (keys.SELECT and not self.on_accept) then
self:dismiss()
if self.on_cancel then
self.on_cancel()
end
end
end
function showMessage(title, text, tcolor, on_close)
mkinstance(MessageBox):init{
text = text,
title = title,
text = text,
text_pen = tcolor,
on_close = on_close
}:show()
end
function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel)
mkinstance(MessageBox):init{
title = title,
text = text,
text_pen = tcolor,
on_accept = on_accept,
on_cancel = on_cancel,
}:show()
end
InputBox = defclass(InputBox, MessageBox)
InputBox.focus_path = 'InputBox'
function InputBox:init(info)
info = info or {}
self:init_fields{
input = info.input or '',
input_pen = info.input_pen,
on_input = info.on_input,
}
MessageBox.init(self, info)
self.on_accept = nil
return self
end
function InputBox:getWantedFrameSize()
local mw, mh = MessageBox.getWantedFrameSize(self)
return mw, mh+2
end
function InputBox:onRenderBody(dc)
MessageBox.onRenderBody(self, dc)
dc:newline(1)
dc:pen(self.input_pen or COLOR_LIGHTCYAN)
dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y)
local cursor = '_'
if math.floor(dfhack.getTickCount()/300) % 2 == 0 then
cursor = ' '
end
local txt = self.input .. cursor
if #txt > dc.width-2 then
txt = string.sub(txt, #txt-dc.width+3)
-- Add prefix arrow
dc:advance(-1):char(27)
end
dc:string(txt)
end
function InputBox:onInput(keys)
if keys.SELECT then
self:dismiss()
if self.on_input then
self.on_input(self.input)
end
elseif keys.LEAVESCREEN then
self:dismiss()
if self.on_cancel then
self.on_cancel()
end
elseif keys._STRING then
if keys._STRING == 0 then
self.input = string.sub(self.input, 1, #self.input-1)
else
self.input = self.input .. string.char(keys._STRING)
end
end
end
function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width)
mkinstance(InputBox):init{
title = title,
text = text,
text_pen = tcolor,
input = input,
on_input = on_input,
on_cancel = on_cancel,
frame_width = min_width,
}:show()
end
return _ENV

@ -381,6 +381,19 @@ function getBuildingCenter(building)
return xyz2pos(building.centerx, building.centery, building.z)
end
function split_string(self, delimiter)
local result = { }
local from = 1
local delim_from, delim_to = string.find( self, delimiter, from )
while delim_from do
table.insert( result, string.sub( self, from , delim_from-1 ) )
from = delim_to + 1
delim_from, delim_to = string.find( self, delimiter, from )
end
table.insert( result, string.sub( self, from ) )
return result
end
-- Ask a yes-no question
function prompt_yes_no(msg,default)
local prompt = msg

@ -43,6 +43,7 @@ using namespace DFHack;
#include "modules/Job.h"
#include "modules/Screen.h"
#include "modules/Maps.h"
#include "DataDefs.h"
#include "df/world.h"
@ -81,6 +82,8 @@ using namespace DFHack;
#include "df/graphic.h"
#include "df/layer_object_listst.h"
#include "df/assign_trade_status.h"
#include "df/announcement_flags.h"
#include "df/announcements.h"
using namespace df::enums;
using df::global::gview;
@ -88,6 +91,9 @@ using df::global::init;
using df::global::gps;
using df::global::ui;
using df::global::world;
using df::global::selection_rect;
using df::global::ui_menu_width;
using df::global::ui_area_map_width;
static df::layer_object_listst *getLayerList(df::viewscreen_layerst *layer, int idx)
{
@ -921,8 +927,9 @@ df::item *Gui::getSelectedItem(color_ostream &out, bool quiet)
//
void Gui::showAnnouncement(std::string message, int color, bool bright)
{
static void doShowAnnouncement(
df::announcement_type type, df::coord pos, std::string message, int color, bool bright
) {
using df::global::world;
using df::global::cur_year;
using df::global::cur_year_tick;
@ -948,6 +955,9 @@ void Gui::showAnnouncement(std::string message, int color, bool bright)
{
df::report *new_rep = new df::report();
new_rep->type = type;
new_rep->pos = pos;
new_rep->color = color;
new_rep->bright = bright;
new_rep->year = year;
@ -969,7 +979,17 @@ void Gui::showAnnouncement(std::string message, int color, bool bright)
world->status.announcements.push_back(new_rep);
world->status.display_timer = 2000;
}
}
void Gui::showAnnouncement(std::string message, int color, bool bright)
{
doShowAnnouncement(df::announcement_type(0), df::coord(), message, color, bright);
}
void Gui::showZoomAnnouncement(
df::announcement_type type, df::coord pos, std::string message, int color, bool bright
) {
doShowAnnouncement(type, pos, message, color, bright);
}
void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
@ -983,6 +1003,29 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
world->status.popups.push_back(popup);
}
void Gui::showAutoAnnouncement(
df::announcement_type type, df::coord pos, std::string message, int color, bool bright
) {
using df::global::announcements;
df::announcement_flags flags;
if (is_valid_enum_item(type) && announcements)
flags = announcements->flags[type];
doShowAnnouncement(type, pos, message, color, bright);
if (flags.bits.DO_MEGA || flags.bits.PAUSE || flags.bits.RECENTER)
{
resetDwarfmodeView(flags.bits.DO_MEGA || flags.bits.PAUSE);
if (flags.bits.RECENTER && pos.isValid())
revealInDwarfmodeMap(pos, true);
}
if (flags.bits.DO_MEGA)
showPopupAnnouncement(message, color, bright);
}
df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed)
{
df::viewscreen * ws = &gview->view;
@ -1015,6 +1058,110 @@ df::coord Gui::getCursorPos()
return df::coord(cursor->x, cursor->y, cursor->z);
}
Gui::DwarfmodeDims Gui::getDwarfmodeViewDims()
{
DwarfmodeDims dims;
auto ws = Screen::getWindowSize();
dims.y1 = 1;
dims.y2 = ws.y-2;
dims.map_x1 = 1;
dims.map_x2 = ws.x-2;
dims.area_x1 = dims.area_x2 = dims.menu_x1 = dims.menu_x2 = -1;
dims.menu_forced = false;
int menu_pos = (ui_menu_width ? *ui_menu_width : 2);
int area_pos = (ui_area_map_width ? *ui_area_map_width : 3);
if (ui && ui->main.mode && menu_pos >= area_pos)
{
dims.menu_forced = true;
menu_pos = area_pos-1;
}
dims.area_on = (area_pos < 3);
dims.menu_on = (menu_pos < area_pos);
if (dims.menu_on)
{
dims.menu_x2 = ws.x - 2;
dims.menu_x1 = dims.menu_x2 - Gui::MENU_WIDTH + 1;
if (menu_pos == 1)
dims.menu_x1 -= Gui::AREA_MAP_WIDTH + 1;
dims.map_x2 = dims.menu_x1 - 2;
}
if (dims.area_on)
{
dims.area_x2 = ws.x-2;
dims.area_x1 = dims.area_x2 - Gui::AREA_MAP_WIDTH + 1;
if (dims.menu_on)
dims.menu_x2 = dims.area_x1 - 2;
else
dims.map_x2 = dims.area_x1 - 2;
}
return dims;
}
void Gui::resetDwarfmodeView(bool pause)
{
using df::global::cursor;
if (ui)
{
ui->follow_unit = -1;
ui->follow_item = -1;
ui->main.mode = ui_sidebar_mode::Default;
}
if (selection_rect)
{
selection_rect->start_x = -30000;
selection_rect->end_x = -30000;
}
if (cursor)
cursor->x = cursor->y = cursor->z = -30000;
if (pause && df::global::pause_state)
*df::global::pause_state = true;
}
bool Gui::revealInDwarfmodeMap(df::coord pos, bool center)
{
using df::global::window_x;
using df::global::window_y;
using df::global::window_z;
if (!window_x || !window_y || !window_z || !world)
return false;
if (!Maps::isValidTilePos(pos))
return false;
auto dims = getDwarfmodeViewDims();
int w = dims.map_x2 - dims.map_x1 + 1;
int h = dims.y2 - dims.y1 + 1;
*window_z = pos.z;
if (center)
{
*window_x = pos.x - w/2;
*window_y = pos.y - h/2;
}
else
{
while (*window_x + w < pos.x+5) *window_x += 10;
while (*window_y + h < pos.y+5) *window_y += 10;
while (*window_x + 5 > pos.x) *window_x -= 10;
while (*window_y + 5 > pos.y) *window_y -= 10;
}
*window_x = std::max(0, std::min(*window_x, world->map.x_count-w));
*window_y = std::max(0, std::min(*window_y, world->map.y_count-h));
return true;
}
bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z)
{
x = *df::global::window_x;

@ -58,6 +58,7 @@ using namespace std;
#include "df/block_square_event_grassst.h"
#include "df/z_level_flags.h"
#include "df/region_map_entry.h"
#include "df/flow_info.h"
using namespace DFHack;
using namespace df::enums;
@ -138,13 +139,20 @@ df::map_block *Maps::getBlock (int32_t blockx, int32_t blocky, int32_t blockz)
return world->map.block_index[blockx][blocky][blockz];
}
df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z)
bool Maps::isValidTilePos(int32_t x, int32_t y, int32_t z)
{
if (!IsValid())
return NULL;
return false;
if ((x < 0) || (y < 0) || (z < 0))
return NULL;
return false;
if ((x >= world->map.x_count) || (y >= world->map.y_count) || (z >= world->map.z_count))
return false;
return true;
}
df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z)
{
if (!isValidTilePos(x,y,z))
return NULL;
return world->map.block_index[x >> 4][y >> 4][z];
}
@ -204,6 +212,26 @@ void Maps::enableBlockUpdates(df::map_block *blk, bool flow, bool temperature)
}
}
df::flow_info *Maps::spawnFlow(df::coord pos, df::flow_type type, int mat_type, int mat_index, int density)
{
using df::global::flows;
auto block = getTileBlock(pos);
if (!flows || !block)
return NULL;
auto flow = new df::flow_info();
flow->type = type;
flow->mat_type = mat_type;
flow->mat_index = mat_index;
flow->density = std::min(100, density);
flow->pos = pos;
block->flows.push_back(flow);
flows->push_back(flow);
return flow;
}
df::feature_init *Maps::getGlobalInitFeature(int32_t index)
{
auto data = world->world_data;

@ -285,13 +285,13 @@ PersistentDataItem World::GetPersistentData(int entry_id)
PersistentDataItem World::GetPersistentData(const std::string &key, bool *added)
{
*added = false;
if (added) *added = false;
PersistentDataItem rv = GetPersistentData(key);
if (!rv.isValid())
{
*added = true;
if (added) *added = true;
rv = AddPersistentData(key);
}

@ -1 +1 @@
Subproject commit 9b3ded15848e830784ef2dc4dea6093175669bc9
Subproject commit df8178a989373ec7868d9195d82ae5f85145ef81

@ -92,7 +92,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(seedwatch seedwatch.cpp)
DFHACK_PLUGIN(initflags initflags.cpp)
DFHACK_PLUGIN(stockpiles stockpiles.cpp)
DFHACK_PLUGIN(rename rename.cpp PROTOBUFS rename)
DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename)
DFHACK_PLUGIN(jobutils jobutils.cpp)
DFHACK_PLUGIN(workflow workflow.cpp)
DFHACK_PLUGIN(showmood showmood.cpp)

@ -1,4 +1,4 @@
building_zsteam_engine
building_steam_engine
[OBJECT:BUILDING]
@ -15,33 +15,36 @@ building_zsteam_engine
[TILE:0:1:240:' ':254]
[TILE:0:2:' ':' ':128]
[TILE:0:3:246:' ':' ']
[COLOR:0:1:MAT:0:0:0:7:0:0]
[COLOR:0:1:6:0:0:0:0:0:7:0:0]
[COLOR:0:2:0:0:0:0:0:0:7:0:0]
[COLOR:0:3:6:0:0:0:0:0:0:0:0]
[COLOR:0:3:MAT:0:0:0:0:0:0]
[TILE:1:1:246:128:' ']
[TILE:1:2:' ':' ':254]
[TILE:1:3:254:240:240]
[COLOR:1:1:6:0:0:7:0:0:0:0:0]
[TILE:1:3:254:'/':240]
[COLOR:1:1:MAT:7:0:0:0:0:0]
[COLOR:1:2:0:0:0:0:0:0:7:0:0]
[COLOR:1:3:7:0:0:MAT:MAT]
[COLOR:1:3:7:0:0:6:0:0:6:0:0]
[TILE:2:1:21:' ':128]
[TILE:2:2:128:' ':246]
[TILE:2:3:177:19:177]
[COLOR:2:1:6:0:0:0:0:0:7:0:0]
[COLOR:2:2:7:0:0:0:0:0:6:0:0]
[COLOR:2:3:7:0:0:MAT:7:0:0]
[COLOR:2:2:7:0:0:0:0:0:MAT]
[COLOR:2:3:7:0:0:6:0:0:7:0:0]
Tile 15 marks places where machines can connect.
Tile 19 marks the hearth (color changed to reflect power).
[TILE:3:1:15:246:15]
[TILE:3:2:'\':19:'/']
[TILE:3:3:7:' ':7]
[COLOR:3:1:6:0:0:6:0:0:6:0:0]
[COLOR:3:2:6:7:0:0:0:1:6:7:0]
Color 1:?:1 water indicator, 4:?:1 magma indicator:
[COLOR:3:1:7:0:0:MAT:7:0:0]
[COLOR:3:2:6:0:0:0:0:1:6:0:0]
[COLOR:3:3:1:7:1:0:0:0:4:7:1]
[BUILD_ITEM:1:BARREL:NONE:INORGANIC:NONE][EMPTY][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:PIPE_SECTION:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:WEAPON:WEAPON_MACE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:CHAIN:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPPARTS:NONE:NONE:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:2:BLOCKS:NONE:NONE:NONE][BUILDMAT][FIRE_BUILD_SAFE]
[BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][FIRE_BUILD_SAFE]
[BUILDING_WORKSHOP:MAGMA_STEAM_ENGINE]
[NAME:Magma Steam Engine]
@ -57,30 +60,33 @@ building_zsteam_engine
[TILE:0:1:240:' ':254]
[TILE:0:2:' ':' ':128]
[TILE:0:3:246:' ':' ']
[COLOR:0:1:MAT:0:0:0:7:0:0]
[COLOR:0:1:6:0:0:0:0:0:7:0:0]
[COLOR:0:2:0:0:0:0:0:0:7:0:0]
[COLOR:0:3:6:0:0:0:0:0:0:0:0]
[COLOR:0:3:MAT:0:0:0:0:0:0]
[TILE:1:1:246:128:' ']
[TILE:1:2:' ':' ':254]
[TILE:1:3:254:240:240]
[COLOR:1:1:6:0:0:7:0:0:0:0:0]
[TILE:1:3:254:'/':240]
[COLOR:1:1:MAT:7:0:0:0:0:0]
[COLOR:1:2:0:0:0:0:0:0:7:0:0]
[COLOR:1:3:7:0:0:MAT:MAT]
[COLOR:1:3:7:0:0:6:0:0:6:0:0]
[TILE:2:1:21:' ':128]
[TILE:2:2:128:' ':246]
[TILE:2:3:177:19:177]
[COLOR:2:1:6:0:0:0:0:0:7:0:0]
[COLOR:2:2:7:0:0:0:0:0:6:0:0]
[COLOR:2:3:7:0:0:MAT:7:0:0]
[COLOR:2:2:7:0:0:0:0:0:MAT]
[COLOR:2:3:7:0:0:6:0:0:7:0:0]
Tile 15 marks places where machines can connect.
Tile 19 marks the hearth (color changed to reflect power).
[TILE:3:1:15:246:15]
[TILE:3:2:'\':19:'/']
[TILE:3:3:7:' ':7]
[COLOR:3:1:6:0:0:6:0:0:6:0:0]
[COLOR:3:2:6:7:0:0:0:1:6:7:0]
Color 1:?:1 water indicator, 4:?:1 magma indicator:
[COLOR:3:1:7:0:0:MAT:7:0:0]
[COLOR:3:2:6:0:0:0:0:1:6:0:0]
[COLOR:3:3:1:7:1:0:0:0:4:7:1]
[BUILD_ITEM:1:BARREL:NONE:INORGANIC:NONE][EMPTY][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:PIPE_SECTION:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:WEAPON:WEAPON_MACE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:CHAIN:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPPARTS:NONE:NONE:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:2:BLOCKS:NONE:NONE:NONE][BUILDMAT][MAGMA_BUILD_SAFE]
[BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][MAGMA_BUILD_SAFE]

@ -0,0 +1,12 @@
item_trapcomp_steam_engine
[OBJECT:ITEM]
[ITEM_TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON]
[NAME:piston:pistons]
[ADJECTIVE:heavy]
[SIZE:1800]
[HITS:1]
[MATERIAL_SIZE:6]
[METAL]
[ATTACK:BLUNT:40:200:bash:bashes:NO_SUB:2000]

@ -0,0 +1,14 @@
reaction_steam_engine
[OBJECT:REACTION]
[REACTION:STOKE_BOILER]
[NAME:stoke the boiler]
[BUILDING:STEAM_ENGINE:CUSTOM_S]
[BUILDING:MAGMA_STEAM_ENGINE:CUSTOM_S]
[FUEL]
[SKILL:SMELT]
Dimension is the number of days it can produce 100 power * 100.
I.e. with 2000 it means energy of 1 job = 1 water wheel for 20 days.
[PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:2000]

@ -1,12 +0,0 @@
reaction_other
[OBJECT:REACTION]
[REACTION:STOKE_BOILER]
[NAME:stoke the boiler]
[BUILDING:STEAM_ENGINE:CUSTOM_S]
[BUILDING:MAGMA_STEAM_ENGINE:CUSTOM_S]
[FUEL]
[PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:333]
[SKILL:SMELT]

@ -4,6 +4,8 @@
#include <PluginManager.h>
#include <modules/Gui.h>
#include <modules/Screen.h>
#include <modules/Maps.h>
#include <TileTypes.h>
#include <vector>
#include <cstdio>
#include <stack>
@ -22,9 +24,92 @@
#include "df/world.h"
#include "df/buildings_other_id.h"
#include "df/machine.h"
#include "df/job.h"
#include "df/building_drawbuffer.h"
#include "df/ui.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/ui_build_selector.h"
#include "df/flow_info.h"
#include "df/report.h"
#include "MiscUtils.h"
/*
* This plugin implements a steam engine workshop. It activates
* if there are any workshops in the raws with STEAM_ENGINE in
* their token, and provides the necessary behavior.
*
* Construction:
*
* The workshop needs water as its input, which it takes via a
* passable floor tile below it, like usual magma workshops do.
* The magma version also needs magma.
*
* ISSUE: Since this building is a machine, and machine collapse
* code cannot be modified, it would collapse over true open space.
* As a loophole, down stair provides support to machines, while
* being passable, so use them.
*
* After constructing the building itself, machines can be connected
* to the edge tiles that look like gear boxes. Their exact position
* is extracted from the workshop raws.
*
* ISSUE: Like with collapse above, part of the code involved in
* machine connection cannot be modified. As a result, the workshop
* can only immediately connect to machine components built AFTER it.
* This also means that engines cannot be chained without intermediate
* short axles that can be built later.
*
* Operation:
*
* In order to operate the engine, queue the Stoke Boiler job.
* A furnace operator will come, possibly bringing a bar of fuel,
* and perform it. As a result, a "boiling water" item will appear
* in the 't' view of the workshop.
*
* Note: The completion of the job will actually consume one unit
* of appropriate liquids from below the workshop.
*
* Every such item gives 100 power, up to a limit of 300 for coal,
* and 500 for a magma engine. The building can host twice that
* amount of items to provide longer autonomous running. When the
* boiler gets filled to capacity, all queued jobs are suspended;
* once it drops back to 3+1 or 5+1 items, they are re-enabled.
*
* While the engine is providing power, steam is being consumed.
* The consumption speed includes a fixed 10% waste rate, and
* the remaining 90% are applied proportionally to the actual
* load in the machine. With the engine at nominal 300 power with
* 150 load in the system, it will consume steam for actual
* 300*(10% + 90%*150/300) = 165 power.
*
* Masterpiece mechanism and chain will decrease the mechanical
* power drawn by the engine itself from 10 to 5. Masterpiece
* barrel decreases waste rate by 4%. Masterpiece piston and pipe
* decrease it by further 4%, and also decrease the whole steam
* use rate by 10%.
*
* Explosions:
*
* The engine must be constructed using barrel, pipe and piston
* from fire-safe, or in the magma version magma-safe metals.
*
* During operation weak parts get gradually worn out, and
* eventually the engine explodes. It should also explode if
* toppled during operation by a building destroyer, or a
* tantruming dwarf.
*
* Save files:
*
* It should be safe to load and view fortresses using engines
* from a DF version without DFHack installed, except that in such
* case the engines won't work. However actually making modifications
* to them, or machines they connect to (including by pulling levers),
* can easily result in inconsistent state once this plugin is
* available again. The effects may be as weird as negative power
* being generated.
*/
using std::vector;
using std::string;
using std::stack;
@ -33,13 +118,23 @@ using namespace df::enums;
using df::global::gps;
using df::global::world;
using df::global::ui;
using df::global::ui_build_selector;
DFHACK_PLUGIN("steam-engine");
/*
* List of known steam engine workshop raws.
*/
struct steam_engine_workshop {
int id;
df::building_def_workshopst *def;
// Cached properties
bool is_magma;
int max_power, max_capacity;
int wear_temp;
// Special tiles (relative position)
std::vector<df::coord2d> gear_tiles;
df::coord2d hearth_tile;
df::coord2d water_tile;
@ -48,42 +143,268 @@ struct steam_engine_workshop {
std::vector<steam_engine_workshop> engines;
steam_engine_workshop *find_steam_engine(int id)
{
for (size_t i = 0; i < engines.size(); i++)
if (engines[i].id == id)
return &engines[i];
return NULL;
}
/*
* Misc utilities.
*/
static const int hearth_colors[6][2] = {
{ COLOR_BLACK, 1 },
{ COLOR_BROWN, 0 },
{ COLOR_RED, 0 },
{ COLOR_RED, 1 },
{ COLOR_BROWN, 1 },
{ COLOR_GREY, 1 }
};
void enable_updates_at(df::coord pos, bool flow, bool temp)
{
static const int delta[4][2] = { { -1, -1 }, { 1, -1 }, { -1, 1 }, { 1, 1 } };
for (int i = 0; i < 4; i++)
{
auto blk = Maps::getTileBlock(pos.x+delta[i][0], pos.y+delta[i][1], pos.z);
Maps::enableBlockUpdates(blk, flow, temp);
}
}
void decrement_flow(df::coord pos, int amount)
{
auto pldes = Maps::getTileDesignation(pos);
if (!pldes) return;
int nsize = std::max(0, int(pldes->bits.flow_size - amount));
pldes->bits.flow_size = nsize;
pldes->bits.flow_forbid = (nsize > 3 || pldes->bits.liquid_type == tile_liquid::Magma);
enable_updates_at(pos, true, false);
}
void make_explosion(df::coord center, int power)
{
static const int bias[9] = {
60, 30, 60,
30, 0, 30,
60, 30, 60
};
int mat_type = builtin_mats::WATER, mat_index = -1;
int i = 0;
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
int size = power - bias[i++];
auto pos = center + df::coord(dx,dy,0);
if (size > 0)
Maps::spawnFlow(pos, flow_type::MaterialDust, mat_type, mat_index, size);
}
}
Gui::showAutoAnnouncement(
announcement_type::CAVE_COLLAPSE, center,
"A boiler has exploded!", COLOR_RED, true
);
}
static const int WEAR_TICKS = 806400;
bool add_wear_nodestroy(df::item_actual *item, int rate)
{
if (item->incWearTimer(rate))
{
while (item->wear_timer >= WEAR_TICKS)
{
item->wear_timer -= WEAR_TICKS;
item->wear++;
}
}
return item->wear > 3;
}
/*
* Hook for the liquid item. Implements a special 'boiling'
* matter state with a modified description and temperature
* locked at boiling-1.
*/
struct liquid_hook : df::item_liquid_miscst {
typedef df::item_liquid_miscst interpose_base;
static const uint32_t BOILING_FLAG = 0x80000000U;
DEFINE_VMETHOD_INTERPOSE(void, getItemDescription, (std::string *buf, int8_t mode))
{
if (mat_state.whole & BOILING_FLAG)
buf->append("boiling ");
INTERPOSE_NEXT(getItemDescription)(buf, mode);
}
DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t unk))
{
if (mat_state.whole & BOILING_FLAG)
temp = std::max(int(temp), getBoilingPoint()-1);
return INTERPOSE_NEXT(adjustTemperature)(temp, unk);
}
DEFINE_VMETHOD_INTERPOSE(bool, checkTemperatureDamage, ())
{
if (mat_state.whole & BOILING_FLAG)
temperature = std::max(int(temperature), getBoilingPoint()-1);
return INTERPOSE_NEXT(checkTemperatureDamage)();
}
};
IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, getItemDescription);
IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, adjustTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, checkTemperatureDamage);
/*
* Hook for the workshop itself. Implements core logic.
*/
struct workshop_hook : df::building_workshopst {
typedef df::building_workshopst interpose_base;
// Engine detection
steam_engine_workshop *get_steam_engine()
{
if (type == workshop_type::Custom)
for (size_t i = 0; i < engines.size(); i++)
if (engines[i].id == custom_type)
return &engines[i];
return find_steam_engine(custom_type);
return NULL;
}
inline bool is_fully_built()
{
return getBuildStage() >= getMaxBuildStage();
}
// Use high bits of flags to store current steam amount.
// This is necessary for consistency if items disappear unexpectedly.
int get_steam_amount()
{
int cnt = 0;
return (flags.whole >> 28) & 15;
}
for (size_t i = 0; i < contained_items.size(); i++)
void set_steam_amount(int count)
{
flags.whole = (flags.whole & 0x0FFFFFFFU) | uint32_t((count & 15) << 28);
}
// Find liquids to consume below the engine.
bool find_liquids(df::coord *pwater, df::coord *pmagma, bool is_magma, int min_level)
{
if (!is_magma)
pmagma = NULL;
for (int x = x1; x <= x2; x++)
{
if (contained_items[i]->use_mode == 0 &&
contained_items[i]->item->flags.bits.in_building)
cnt++;
for (int y = y1; y <= y2; y++)
{
auto ptile = Maps::getTileType(x,y,z);
if (!ptile || !LowPassable(*ptile))
continue;
auto pltile = Maps::getTileType(x,y,z-1);
if (!pltile || !FlowPassable(*pltile))
continue;
auto pldes = Maps::getTileDesignation(x,y,z-1);
if (!pldes || pldes->bits.flow_size < min_level)
continue;
if (pldes->bits.liquid_type == tile_liquid::Magma)
{
if (pmagma)
*pmagma = df::coord(x,y,z-1);
if (pwater->isValid())
return true;
}
else
{
*pwater = df::coord(x,y,z-1);
if (!pmagma || pmagma->isValid())
return true;
}
}
}
return cnt;
return false;
}
int get_power_output(steam_engine_workshop *engine)
// Absorbs a water item produced by stoke reaction into the engine.
bool absorb_unit(steam_engine_workshop *engine, df::item_liquid_miscst *liquid)
{
int maxv = engine->def->needs_magma ? 5 : 3;
return std::min(get_steam_amount(), maxv)*100;
// Consume liquid inputs
df::coord water, magma;
if (!find_liquids(&water, &magma, engine->is_magma, 1))
{
// Destroy the item with enormous wear amount.
liquid->addWear(WEAR_TICKS*5, true, false);
return false;
}
decrement_flow(water, 1);
if (engine->is_magma)
decrement_flow(magma, 1);
// Update flags
liquid->flags.bits.in_building = true;
liquid->mat_state.whole |= liquid_hook::BOILING_FLAG;
liquid->temperature = liquid->getBoilingPoint()-1;
liquid->temperature_fraction = 0;
// This affects where the steam appears to come from
if (engine->hearth_tile.isValid())
liquid->pos = df::coord(x1+engine->hearth_tile.x, y1+engine->hearth_tile.y, z);
// Enable block temperature updates
enable_updates_at(liquid->pos, false, true);
return true;
}
df::item_liquid_miscst *collect_steam()
bool boil_unit(df::item_liquid_miscst *liquid)
{
liquid->wear = 4;
liquid->flags.bits.in_building = false;
liquid->temperature = liquid->getBoilingPoint() + 10;
return liquid->checkMeltBoil();
}
void suspend_jobs(bool suspend)
{
for (size_t i = 0; i < jobs.size(); i++)
if (jobs[i]->job_type == job_type::CustomReaction)
jobs[i]->flags.bits.suspend = suspend;
}
// Scan contained items for boiled steam to absorb.
df::item_liquid_miscst *collect_steam(steam_engine_workshop *engine, int *count)
{
df::item_liquid_miscst *first = NULL;
*count = 0;
for (int i = contained_items.size()-1; i >= 0; i--)
{
@ -98,19 +419,194 @@ struct workshop_hook : df::building_workshopst {
if (!liquid->flags.bits.in_building)
{
if (liquid->mat_type != builtin_mats::WATER ||
liquid->dimension != 333 ||
liquid->age > 1 ||
liquid->wear != 0)
continue;
liquid->flags.bits.in_building = true;
// This may destroy the item
if (!absorb_unit(engine, liquid))
continue;
}
first = liquid;
if (*count < engine->max_capacity)
{
first = liquid;
++*count;
}
else
{
// Overpressure valve
boil_unit(liquid);
suspend_jobs(true);
}
}
return first;
}
void random_boil()
{
int cnt = 0;
for (int i = contained_items.size()-1; i >= 0; i--)
{
auto item = contained_items[i];
if (item->use_mode != 0 || !item->item->flags.bits.in_building)
continue;
auto liquid = strict_virtual_cast<df::item_liquid_miscst>(item->item);
if (!liquid)
continue;
if (cnt == 0 || rand() < RAND_MAX/2)
{
cnt++;
boil_unit(liquid);
}
}
}
int classify_component(df::building_actual::T_contained_items *item)
{
if (item->use_mode != 2 || item->item->isBuildMat())
return -1;
switch (item->item->getType())
{
case item_type::TRAPPARTS:
case item_type::CHAIN:
return 0;
case item_type::BARREL:
return 2;
default:
return 1;
}
}
bool check_component_wear(steam_engine_workshop *engine, int count, int power)
{
int coeffs[3] = { 0, power, count };
for (int i = contained_items.size()-1; i >= 0; i--)
{
int type = classify_component(contained_items[i]);
if (type < 0)
continue;
df::item *item = contained_items[i]->item;
int melt_temp = item->getMeltingPoint();
if (coeffs[type] == 0 || melt_temp >= engine->wear_temp)
continue;
// let 500 degree delta at 4 pressure work 1 season
float ticks = coeffs[type]*(engine->wear_temp - melt_temp)*3.0f/500.0f/4.0f;
if (item->addWear(int(8*(1 + ticks)), true, true))
return true;
}
return false;
}
float get_component_quality(int use_type)
{
float sum = 0, cnt = 0;
for (size_t i = 0; i < contained_items.size(); i++)
{
int type = classify_component(contained_items[i]);
if (type != use_type)
continue;
sum += contained_items[i]->item->getQuality();
cnt += 1;
}
return (cnt > 0 ? sum/cnt : 0);
}
int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level)
{
// total ticks to wear off completely
float ticks = WEAR_TICKS * 4.0f;
// dimension == days it lasts * 100
ticks /= 1200.0f * dimension / 100.0f;
// true power use
float power_rate = 1.0f;
// check the actual load
if (auto mptr = df::machine::find(machine.machine_id))
{
if (mptr->cur_power >= mptr->min_power)
power_rate = float(mptr->min_power) / mptr->cur_power;
else
power_rate = 0.0f;
}
// waste rate: 1-10% depending on piston assembly quality
float piston_qual = get_component_quality(1);
float waste = 0.1f - 0.016f * 0.5f * (piston_qual + get_component_quality(2));
float efficiency_coeff = 1.0f - 0.02f * piston_qual;
// apply rate and waste factor
ticks *= (waste + 0.9f*power_rate)*power_level*efficiency_coeff;
// end result
return std::max(1, int(ticks));
}
void update_under_construction(steam_engine_workshop *engine)
{
if (machine.machine_id != -1)
return;
int cur_count = 0;
if (auto first = collect_steam(engine, &cur_count))
{
if (add_wear_nodestroy(first, WEAR_TICKS*4/10))
{
boil_unit(first);
cur_count--;
}
}
set_steam_amount(cur_count);
}
void update_working(steam_engine_workshop *engine)
{
int old_count = get_steam_amount();
int old_power = std::min(engine->max_power, old_count);
int cur_count = 0;
if (auto first = collect_steam(engine, &cur_count))
{
int rate = get_steam_use_rate(engine, first->dimension, old_power);
if (add_wear_nodestroy(first, rate))
{
boil_unit(first);
cur_count--;
}
if (check_component_wear(engine, old_count, old_power))
return;
}
if (old_count < engine->max_capacity && cur_count == engine->max_capacity)
suspend_jobs(true);
else if (cur_count <= engine->max_power+1 && old_count > engine->max_power+1)
suspend_jobs(false);
set_steam_amount(cur_count);
int cur_power = std::min(engine->max_power, cur_count);
if (cur_power != old_power)
{
auto mptr = df::machine::find(machine.machine_id);
if (mptr)
mptr->cur_power += (cur_power - old_power)*100;
}
}
// Furnaces need architecture, and this is a workshop
// only because furnaces cannot connect to machines.
DEFINE_VMETHOD_INTERPOSE(bool, needsDesign, ())
{
if (get_steam_engine())
@ -119,12 +615,13 @@ struct workshop_hook : df::building_workshopst {
return INTERPOSE_NEXT(needsDesign)();
}
// Machine interface
DEFINE_VMETHOD_INTERPOSE(void, getPowerInfo, (df::power_info *info))
{
if (auto engine = get_steam_engine())
{
info->produced = get_power_output(engine);
info->consumed = 10;
info->produced = std::min(engine->max_power, get_steam_amount())*100;
info->consumed = 10 - int(get_component_quality(0));
return;
}
@ -178,6 +675,7 @@ struct workshop_hook : df::building_workshopst {
for (size_t i = 0; i < engine->gear_tiles.size(); i++)
{
// the original function connects to the center tile
centerx = x1 + engine->gear_tiles[i].x;
centery = y1 + engine->gear_tiles[i].y;
@ -195,40 +693,106 @@ struct workshop_hook : df::building_workshopst {
return INTERPOSE_NEXT(canConnectToMachine)(info);
}
// Operation logic
DEFINE_VMETHOD_INTERPOSE(bool, isUnpowered, ())
{
if (auto engine = get_steam_engine())
{
df::coord water, magma;
return !find_liquids(&water, &magma, engine->is_magma, 3);
}
return INTERPOSE_NEXT(isUnpowered)();
}
DEFINE_VMETHOD_INTERPOSE(void, updateAction, ())
{
if (auto engine = get_steam_engine())
{
int output = get_power_output(engine);
if (is_fully_built())
update_working(engine);
else
update_under_construction(engine);
if (flags.bits.almost_deleted)
return;
}
INTERPOSE_NEXT(updateAction)();
}
DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, void *unk))
{
INTERPOSE_NEXT(drawBuilding)(db, unk);
if (auto engine = get_steam_engine())
{
if (!is_fully_built())
return;
if (auto first = collect_steam())
// If machine is running, tweak gear assemblies
auto mptr = df::machine::find(machine.machine_id);
if (mptr && (mptr->visual_phase & 1) != 0)
{
if (first->incWearTimer(output))
for (size_t i = 0; i < engine->gear_tiles.size(); i++)
{
while (first->wear_timer >= 806400)
{
first->wear_timer -= 806400;
first->wear++;
}
auto pos = engine->gear_tiles[i];
db->tile[pos.x][pos.y] = 42;
}
}
if (first->wear > 3)
{
first->flags.bits.in_building = 0;
first->temperature = first->getBoilingPoint()+50;
}
// Use the hearth color to display power level
if (engine->hearth_tile.isValid())
{
auto pos = engine->hearth_tile;
int power = std::min(engine->max_power, get_steam_amount());
db->fore[pos.x][pos.y] = hearth_colors[power][0];
db->bright[pos.x][pos.y] = hearth_colors[power][1];
}
// Set liquid indicator state
if (engine->water_tile.isValid() || engine->magma_tile.isValid())
{
df::coord water, magma;
find_liquids(&water, &magma, engine->is_magma, 3);
df::coord dwater, dmagma;
find_liquids(&dwater, &dmagma, engine->is_magma, 5);
if (engine->water_tile.isValid())
{
if (!water.isValid())
db->fore[engine->water_tile.x][engine->water_tile.y] = 0;
else if (!dwater.isValid())
db->bright[engine->water_tile.x][engine->water_tile.y] = 0;
}
if (engine->magma_tile.isValid() && engine->is_magma)
{
if (!magma.isValid())
db->fore[engine->magma_tile.x][engine->magma_tile.y] = 0;
else if (!dmagma.isValid())
db->bright[engine->magma_tile.x][engine->magma_tile.y] = 0;
}
}
}
}
int new_out = get_power_output(engine);
if (new_out != output)
DEFINE_VMETHOD_INTERPOSE(void, deconstructItems, (bool noscatter, bool lost))
{
if (get_steam_engine())
{
// Explode if any steam left
if (int amount = get_steam_amount())
{
auto mptr = df::machine::find(machine.machine_id);
if (mptr)
mptr->cur_power += (new_out - output);
make_explosion(
df::coord((x1+x2)/2, (y1+y2)/2, z),
40 + amount * 20
);
random_boil();
}
}
INTERPOSE_NEXT(updateAction)();
INTERPOSE_NEXT(deconstructItems)(noscatter, lost);
}
};
@ -239,9 +803,95 @@ IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, isPowerSource);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, categorize);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, uncategorize);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, canConnectToMachine);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, isUnpowered);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, updateAction);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, drawBuilding);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, deconstructItems);
/*
* Hook for the dwarfmode screen. Tweaks the build menu
* behavior to suit the steam engine building more.
*/
struct dwarfmode_hook : df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
steam_engine_workshop *get_steam_engine()
{
if (ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector->stage == 1 &&
ui_build_selector->building_type == building_type::Workshop &&
ui_build_selector->building_subtype == workshop_type::Custom)
{
return find_steam_engine(ui_build_selector->custom_type);
}
return NULL;
}
void check_hanging_tiles(steam_engine_workshop *engine)
{
using df::global::cursor;
if (!engine) return;
static void find_engines()
bool error = false;
int x1 = cursor->x - engine->def->workloc_x;
int y1 = cursor->y - engine->def->workloc_y;
for (int x = 0; x < engine->def->dim_x; x++)
{
for (int y = 0; y < engine->def->dim_y; y++)
{
if (ui_build_selector->tiles[x][y] >= 5)
continue;
auto ptile = Maps::getTileType(x1+x,y1+y,cursor->z);
if (ptile && !isOpenTerrain(*ptile))
continue;
ui_build_selector->tiles[x][y] = 6;
error = true;
}
}
if (error)
{
const char *msg = "Hanging - cover channels with down stairs.";
ui_build_selector->errors.push_back(new std::string(msg));
}
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
steam_engine_workshop *engine = get_steam_engine();
// Selector insists that workshops cannot be placed hanging
// unless they require magma, so pretend we always do.
if (engine)
engine->def->needs_magma = true;
INTERPOSE_NEXT(feed)(input);
// Restore the flag
if (engine)
engine->def->needs_magma = engine->is_magma;
// And now, check for open space. Since these workshops
// are machines, they will collapse over true open space.
check_hanging_tiles(get_steam_engine());
}
};
IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_hook, feed);
/*
* Scan raws for matching workshop buildings.
*/
static bool find_engines()
{
engines.clear();
@ -261,70 +911,78 @@ static void find_engines()
{
for (int y = 0; y < ws.def->dim_y; y++)
{
if (ws.def->tile[bs][x][y] == 15)
switch (ws.def->tile[bs][x][y])
{
case 15:
ws.gear_tiles.push_back(df::coord2d(x,y));
break;
case 19:
ws.hearth_tile = df::coord2d(x,y);
break;
}
if (ws.def->tile_color[2][bs][x][y])
{
switch (ws.def->tile_color[0][bs][x][y])
{
case 0:
ws.hearth_tile = df::coord2d(x,y);
break;
case 1:
ws.water_tile = df::coord2d(x,y);
break;
case 4:
ws.magma_tile = df::coord2d(x,y);
break;
default:
break;
}
}
}
}
engines.push_back(ws);
ws.is_magma = ws.def->needs_magma;
ws.max_power = ws.is_magma ? 5 : 3;
ws.max_capacity = ws.is_magma ? 10 : 6;
ws.wear_temp = ws.is_magma ? 12000 : 11000;
if (!ws.gear_tiles.empty())
engines.push_back(ws);
}
}
static void enable_hooks()
{
INTERPOSE_HOOK(workshop_hook, needsDesign).apply();
INTERPOSE_HOOK(workshop_hook, getPowerInfo).apply();
INTERPOSE_HOOK(workshop_hook, getMachineInfo).apply();
INTERPOSE_HOOK(workshop_hook, isPowerSource).apply();
INTERPOSE_HOOK(workshop_hook, categorize).apply();
INTERPOSE_HOOK(workshop_hook, uncategorize).apply();
INTERPOSE_HOOK(workshop_hook, canConnectToMachine).apply();
INTERPOSE_HOOK(workshop_hook, updateAction).apply();
return !engines.empty();
}
static void disable_hooks()
static void enable_hooks(bool enable)
{
INTERPOSE_HOOK(workshop_hook, needsDesign).remove();
INTERPOSE_HOOK(workshop_hook, getPowerInfo).remove();
INTERPOSE_HOOK(workshop_hook, getMachineInfo).remove();
INTERPOSE_HOOK(workshop_hook, isPowerSource).remove();
INTERPOSE_HOOK(workshop_hook, categorize).remove();
INTERPOSE_HOOK(workshop_hook, uncategorize).remove();
INTERPOSE_HOOK(workshop_hook, canConnectToMachine).remove();
INTERPOSE_HOOK(workshop_hook, updateAction).remove();
INTERPOSE_HOOK(liquid_hook, getItemDescription).apply(enable);
INTERPOSE_HOOK(liquid_hook, adjustTemperature).apply(enable);
INTERPOSE_HOOK(liquid_hook, checkTemperatureDamage).apply(enable);
INTERPOSE_HOOK(workshop_hook, needsDesign).apply(enable);
INTERPOSE_HOOK(workshop_hook, getPowerInfo).apply(enable);
INTERPOSE_HOOK(workshop_hook, getMachineInfo).apply(enable);
INTERPOSE_HOOK(workshop_hook, isPowerSource).apply(enable);
INTERPOSE_HOOK(workshop_hook, categorize).apply(enable);
INTERPOSE_HOOK(workshop_hook, uncategorize).apply(enable);
INTERPOSE_HOOK(workshop_hook, canConnectToMachine).apply(enable);
INTERPOSE_HOOK(workshop_hook, isUnpowered).apply(enable);
INTERPOSE_HOOK(workshop_hook, updateAction).apply(enable);
INTERPOSE_HOOK(workshop_hook, drawBuilding).apply(enable);
INTERPOSE_HOOK(workshop_hook, deconstructItems).apply(enable);
INTERPOSE_HOOK(dwarfmode_hook, feed).apply(enable);
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
find_engines();
if (!engines.empty())
if (find_engines())
{
out.print("Detected steam engine workshops - enabling plugin.\n");
enable_hooks();
enable_hooks(true);
}
else
enable_hooks(false);
break;
case SC_MAP_UNLOADED:
disable_hooks();
enable_hooks(false);
engines.clear();
break;
default:
@ -344,6 +1002,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
disable_hooks();
enable_hooks(false);
return CR_OK;
}

@ -0,0 +1,13 @@
local _ENV = mkmodule('plugins.rename')
--[[
Native functions:
* canRenameBuilding(building)
* isRenamingBuilding(building)
* renameBuilding(building, name)
--]]
return _ENV

@ -39,8 +39,6 @@ using df::global::ui;
using df::global::gps;
using df::global::enabler;
DFHACK_PLUGIN("manipulator");
struct SkillLevel
{
const char *name;
@ -668,7 +666,7 @@ void viewscreen_unitlaborsst::render()
int col_offset = col + first_column;
fg = 15;
bg = 0;
char c = 0xFA;
uint8_t c = 0xFA;
if ((col_offset == sel_column) && (row_offset == sel_row))
fg = 9;
if (columns[col_offset].skill != job_skill::NONE)
@ -747,7 +745,7 @@ void viewscreen_unitlaborsst::render()
canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE);
}
int x = 1;
int x = 2;
OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key
OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, ");
@ -760,7 +758,7 @@ void viewscreen_unitlaborsst::render()
OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key
OutputString(15, x, gps->dimy - 3, ": Zoom-Cre");
x = 1;
x = 2;
OutputString(10, x, gps->dimy - 2, "Esc"); // LEAVESCREEN key
OutputString(15, x, gps->dimy - 2, ": Done, ");
@ -803,23 +801,35 @@ struct unitlist_hook : df::viewscreen_unitlistst
}
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (units[page].size())
{
int x = 2;
OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key
OutputString(15, x, gps->dimy - 2, ": Manage labors");
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, render);
DFHACK_PLUGIN("manipulator");
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{
if (gps)
{
if (!INTERPOSE_HOOK(unitlist_hook, feed).apply())
out.printerr("Could not interpose viewscreen_unitlistst::feed\n");
}
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;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
INTERPOSE_HOOK(unitlist_hook, feed).remove();
INTERPOSE_HOOK(unitlist_hook, render).remove();
return CR_OK;
}

@ -17,3 +17,10 @@ message RenameUnitIn {
optional string nickname = 2;
optional string profession = 3;
}
// RPC RenameBuilding : RenameBuildingIn -> EmptyMessage
message RenameBuildingIn {
required int32 building_id = 1;
optional string name = 2;
}

@ -3,11 +3,15 @@
#include "Export.h"
#include "PluginManager.h"
#include <Error.h>
#include <LuaTools.h>
#include "modules/Gui.h"
#include "modules/Translation.h"
#include "modules/Units.h"
#include "modules/World.h"
#include "DataDefs.h"
#include <VTableInterpose.h>
#include "df/ui.h"
#include "df/world.h"
#include "df/squad.h"
@ -18,6 +22,11 @@
#include "df/historical_figure_info.h"
#include "df/assumed_identity.h"
#include "df/language_name.h"
#include "df/building_stockpilest.h"
#include "df/building_workshopst.h"
#include "df/building_furnacest.h"
#include "df/building_trapst.h"
#include "df/building_siegeenginest.h"
#include "RemoteServer.h"
#include "rename.pb.h"
@ -36,6 +45,8 @@ using namespace dfproto;
using df::global::ui;
using df::global::world;
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event);
static command_result rename(color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("rename");
@ -51,8 +62,32 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" rename unit \"nickname\"\n"
" rename unit-profession \"custom profession\"\n"
" (a unit must be highlighted in the ui)\n"
" rename building \"nickname\"\n"
" (a building must be highlighted via 'q')\n"
));
if (Core::getInstance().isMapLoaded())
plugin_onstatechange(out, SC_MAP_LOADED);
}
return CR_OK;
}
static void init_buildings(bool enable);
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
init_buildings(true);
break;
case SC_MAP_UNLOADED:
init_buildings(false);
break;
default:
break;
}
return CR_OK;
}
@ -61,6 +96,133 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
/*
* Building renaming - it needs some per-type hacking.
*/
#define KNOWN_BUILDINGS \
BUILDING('p', building_stockpilest, "Stockpile") \
BUILDING('w', building_workshopst, NULL) \
BUILDING('e', building_furnacest, NULL) \
BUILDING('T', building_trapst, NULL) \
BUILDING('i', building_siegeenginest, NULL)
#define BUILDING(code, cname, tag) \
struct cname##_hook : df::cname { \
typedef df::cname interpose_base; \
DEFINE_VMETHOD_INTERPOSE(void, getName, (std::string *buf)) { \
if (!name.empty()) {\
buf->clear(); \
*buf += name; \
*buf += " ("; \
if (tag) *buf += (const char*)tag; \
else { std::string tmp; INTERPOSE_NEXT(getName)(&tmp); *buf += tmp; } \
*buf += ")"; \
return; \
} \
else \
INTERPOSE_NEXT(getName)(buf); \
} \
}; \
IMPLEMENT_VMETHOD_INTERPOSE(cname##_hook, getName);
KNOWN_BUILDINGS
#undef BUILDING
static char getBuildingCode(df::building *bld)
{
CHECK_NULL_POINTER(bld);
#define BUILDING(code, cname, tag) \
if (strict_virtual_cast<df::cname>(bld)) return code;
KNOWN_BUILDINGS
#undef BUILDING
return 0;
}
static bool enable_building_rename(char code, bool enable)
{
switch (code) {
#define BUILDING(code, cname, tag) \
case code: return INTERPOSE_HOOK(cname##_hook, getName).apply(enable);
KNOWN_BUILDINGS
#undef BUILDING
default:
return false;
}
}
static void disable_building_rename()
{
#define BUILDING(code, cname, tag) \
INTERPOSE_HOOK(cname##_hook, getName).remove();
KNOWN_BUILDINGS
#undef BUILDING
}
static bool is_enabled_building(char code)
{
switch (code) {
#define BUILDING(code, cname, tag) \
case code: return INTERPOSE_HOOK(cname##_hook, getName).is_applied();
KNOWN_BUILDINGS
#undef BUILDING
default:
return false;
}
}
static void init_buildings(bool enable)
{
disable_building_rename();
if (enable)
{
auto pworld = Core::getInstance().getWorld();
auto entry = pworld->GetPersistentData("rename/building_types");
if (entry.isValid())
{
std::string val = entry.val();
for (size_t i = 0; i < val.size(); i++)
enable_building_rename(val[i], true);
}
}
}
static bool canRenameBuilding(df::building *bld)
{
return getBuildingCode(bld) != 0;
}
static bool isRenamingBuilding(df::building *bld)
{
return is_enabled_building(getBuildingCode(bld));
}
static bool renameBuilding(df::building *bld, std::string name)
{
char code = getBuildingCode(bld);
if (code == 0 && !name.empty())
return false;
if (!name.empty() && !is_enabled_building(code))
{
auto pworld = Core::getInstance().getWorld();
auto entry = pworld->GetPersistentData("rename/building_types", NULL);
if (!entry.isValid())
return false;
if (!enable_building_rename(code, true))
return false;
entry.val().push_back(code);
}
bld->name = name;
return true;
}
static df::squad *getSquadByIndex(unsigned idx)
{
auto entity = df::historical_entity::find(ui->group_id);
@ -101,14 +263,37 @@ static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in)
return CR_OK;
}
static command_result RenameBuilding(color_ostream &stream, const RenameBuildingIn *in)
{
auto building = df::building::find(in->building_id());
if (!building)
return CR_NOT_FOUND;
if (in->has_name())
{
if (!renameBuilding(building, in->name()))
return CR_FAILURE;
}
return CR_OK;
}
DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
{
RPCService *svc = new RPCService();
svc->addFunction("RenameSquad", RenameSquad);
svc->addFunction("RenameUnit", RenameUnit);
svc->addFunction("RenameBuilding", RenameBuilding);
return svc;
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(canRenameBuilding),
DFHACK_LUA_FUNCTION(isRenamingBuilding),
DFHACK_LUA_FUNCTION(renameBuilding),
DFHACK_LUA_END
};
static command_result rename(color_ostream &out, vector <string> &parameters)
{
CoreSuspender suspend;
@ -167,6 +352,20 @@ static command_result rename(color_ostream &out, vector <string> &parameters)
unit->custom_profession = parameters[1];
}
else if (cmd == "building")
{
if (parameters.size() != 2)
return CR_WRONG_USAGE;
if (ui->main.mode != ui_sidebar_mode::QueryBuilding)
return CR_WRONG_USAGE;
if (!renameBuilding(world->selected_building, parameters[1]))
{
out.printerr("This type of building is not supported.\n");
return CR_FAILURE;
}
}
else
{
if (!parameters.empty() && cmd != "?")

@ -32,6 +32,8 @@
#include "df/squad_order_trainst.h"
#include "df/ui_build_selector.h"
#include "df/building_trapst.h"
#include "df/item_actual.h"
#include "df/contaminant.h"
#include <stdlib.h>
@ -85,6 +87,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" when soldiers go off-duty (i.e. civilian).\n"
" tweak readable-build-plate [disable]\n"
" Fixes rendering of creature weight limits in pressure plate build menu.\n"
" tweak stable-temp [disable]\n"
" Fixes performance bug 6012 by squashing jitter in temperature updates.\n"
));
return CR_OK;
}
@ -211,9 +215,6 @@ struct patrol_duty_hook : df::squad_order_trainst
IMPLEMENT_VMETHOD_INTERPOSE(patrol_duty_hook, isPatrol);
static const int AREA_MAP_WIDTH = 23;
static const int MENU_WIDTH = 30;
struct readable_build_plate_hook : df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
@ -228,10 +229,8 @@ struct readable_build_plate_hook : df::viewscreen_dwarfmodest
ui_build_selector->building_subtype == trap_type::PressurePlate &&
ui_build_selector->plate_info.flags.bits.units)
{
auto wsize = Screen::getWindowSize();
int x = wsize.x - MENU_WIDTH - 1;
if (*ui_menu_width == 1 || *ui_area_map_width == 2)
x -= AREA_MAP_WIDTH + 1;
auto dims = Gui::getDwarfmodeViewDims();
int x = dims.menu_x1;
Screen::Pen pen(' ',COLOR_WHITE);
@ -248,6 +247,52 @@ struct readable_build_plate_hook : df::viewscreen_dwarfmodest
IMPLEMENT_VMETHOD_INTERPOSE(readable_build_plate_hook, render);
struct stable_temp_hook : df::item_actual {
typedef df::item_actual interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t rate_mult))
{
if (temperature != temp)
{
// Bug 6012 is caused by fixed-point precision mismatch jitter
// when an item is being pushed by two sources at N and N+1.
// This check suppresses it altogether.
if (temp == temperature+1 ||
(temp == temperature-1 && temperature_fraction == 0))
temp = temperature;
// When SPEC_HEAT is NONE, the original function seems to not
// change the temperature, yet return true, which is silly.
else if (getSpecHeat() == 60001)
temp = temperature;
}
return INTERPOSE_NEXT(adjustTemperature)(temp, rate_mult);
}
DEFINE_VMETHOD_INTERPOSE(bool, updateContaminants, ())
{
if (contaminants)
{
// Force 1-degree difference in contaminant temperature to 0
for (size_t i = 0; i < contaminants->size(); i++)
{
auto obj = (*contaminants)[i];
if (abs(obj->temperature - temperature) == 1)
{
obj->temperature = temperature;
obj->temperature_fraction = temperature_fraction;
}
}
}
return INTERPOSE_NEXT(updateContaminants)();
}
};
IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, adjustTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, updateContaminants);
static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector <string> &parameters)
{
if (vector_get(parameters, 1) == "disable")
@ -380,6 +425,11 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
enable_hook(out, INTERPOSE_HOOK(readable_build_plate_hook, render), parameters);
}
else if (cmd == "stable-temp")
{
enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, adjustTemperature), parameters);
enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, updateContaminants), parameters);
}
else
return CR_WRONG_USAGE;

@ -0,0 +1,14 @@
-- Prints memory ranges of the process.
for _,v in ipairs(dfhack.internal.getMemRanges()) do
local access = { '-', '-', '-', 'p' }
if v.read then access[1] = 'r' end
if v.write then access[2] = 'w' end
if v.execute then access[3] = 'x' end
if not v.valid then
access[4] = '?'
elseif v.shared then
access[4] = 's'
end
print(string.format('%08x-%08x %s %s', v.start_addr, v.end_addr, table.concat(access), v.name))
end

@ -122,7 +122,7 @@ function MechanismList:onInput(keys)
end
end
if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some' then
if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then
qerror("This script requires the main dwarfmode view in 'q' mode")
end

@ -0,0 +1,63 @@
-- Rename various objects via gui.
local gui = require 'gui'
local dlg = require 'gui.dialogs'
local plugin = require 'plugins.rename'
local mode = ...
local focus = dfhack.gui.getCurFocus()
local function verify_mode(expected)
if mode ~= nil and mode ~= expected then
qerror('Invalid UI state for mode '..mode)
end
end
if string.match(focus, '^dwarfmode/QueryBuilding/Some') then
verify_mode('building')
local building = df.global.world.selected_building
if plugin.canRenameBuilding(building) then
dlg.showInputPrompt(
'Rename Building',
'Enter a new name for the building:', COLOR_GREEN,
building.name,
curry(plugin.renameBuilding, building)
)
else
dlg.showMessage(
'Rename Building',
'Cannot rename this type of building.', COLOR_LIGHTRED
)
end
elseif dfhack.gui.getSelectedUnit(true) then
local unit = dfhack.gui.getSelectedUnit(true)
if mode == 'unit-profession' then
dlg.showInputPrompt(
'Rename Unit',
'Enter a new profession for the unit:', COLOR_GREEN,
unit.custom_profession,
function(newval)
unit.custom_profession = newval
end
)
else
verify_mode('unit')
local vname = dfhack.units.getVisibleName(unit)
local vnick = ''
if vname and vname.has_name then
vnick = vname.nickname
end
dlg.showInputPrompt(
'Rename Unit',
'Enter a new nickname for the unit:', COLOR_GREEN,
vnick,
curry(dfhack.units.setNickname, unit)
)
end
elseif mode then
verify_mode(nil)
end