Merge remote-tracking branch 'q/master'

develop
Kelly Martin 2012-09-16 16:36:51 -05:00
commit 38f920dd65
66 changed files with 4656 additions and 351 deletions

@ -550,6 +550,20 @@ Exception handling
The default value of the ``verbose`` argument of ``err:tostring()``. 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 Locking and finalization
------------------------ ------------------------
@ -709,6 +723,10 @@ can be omitted.
Returns the dfhack directory path, i.e. ``".../df/hack/"``. Returns the dfhack directory path, i.e. ``".../df/hack/"``.
* ``dfhack.getTickCount()``
Returns the tick count in ms, exactly as DF ui uses.
* ``dfhack.isWorldLoaded()`` * ``dfhack.isWorldLoaded()``
Checks if the world is loaded. Checks if the world is loaded.
@ -850,6 +868,26 @@ Units module
Returns the nemesis record of the unit if it has one, or *nil*. Returns the nemesis record of the unit if it has one, or *nil*.
* ``dfhack.units.isHidingCurse(unit)``
Checks if the unit hides improved attributes from its curse.
* ``dfhack.units.getPhysicalAttrValue(unit, attr_type)``
* ``dfhack.units.getMentalAttrValue(unit, attr_type)``
Computes the effective attribute value, including curse effect.
* ``dfhack.units.isCrazed(unit)``
* ``dfhack.units.isOpposedToLife(unit)``
* ``dfhack.units.hasExtravision(unit)``
* ``dfhack.units.isBloodsucker(unit)``
Simple checks of caste attributes that can be modified by curses.
* ``dfhack.units.getMiscTrait(unit, type[, create])``
Finds (or creates if requested) a misc trait object with the given id.
* ``dfhack.units.isDead(unit)`` * ``dfhack.units.isDead(unit)``
The unit is completely dead and passive, or a ghost. The unit is completely dead and passive, or a ghost.
@ -876,6 +914,14 @@ Units module
Returns the age of the unit in years as a floating-point value. Returns the age of the unit in years as a floating-point value.
If ``true_age`` is true, ignores false identities. If ``true_age`` is true, ignores false identities.
* ``dfhack.units.getEffectiveSkill(unit, skill)``
Computes the effective rating for the given skill, taking into account exhaustion, pain etc.
* ``dfhack.units.computeMovementSpeed(unit)``
Computes number of frames * 100 it takes the unit to move in its current state of mind and body.
* ``dfhack.units.getNoblePositions(unit)`` * ``dfhack.units.getNoblePositions(unit)``
Returns a list of tables describing noble position assignments, or *nil*. Returns a list of tables describing noble position assignments, or *nil*.
@ -953,6 +999,10 @@ Items module
Move the item to the unit inventory. Returns *false* if impossible. Move the item to the unit inventory. Returns *false* if impossible.
* ``dfhack.items.makeProjectile(item)``
Turns the item into a projectile, and returns the new object, or *nil* if impossible.
Maps module Maps module
----------- -----------
@ -977,6 +1027,10 @@ Maps module
Returns a map block object for given df::coord or x,y,z in local tile coordinates. Returns a map block object for given df::coord or x,y,z in local tile coordinates.
* ``dfhack.maps.ensureTileBlock(coords)``, or ``ensureTileBlock(x,y,z)``
Like ``getTileBlock``, but if the block is not allocated, try creating it.
* ``dfhack.maps.getRegionBiome(region_coord2d)``, or ``getRegionBiome(x,y)`` * ``dfhack.maps.getRegionBiome(region_coord2d)``, or ``getRegionBiome(x,y)``
Returns the biome info struct for the given global map region. Returns the biome info struct for the given global map region.
@ -1286,6 +1340,11 @@ Basic painting functions:
Returns *false* if coordinates out of bounds, or other error. Returns *false* if coordinates out of bounds, or other error.
* ``dfhack.screen.readTile(x,y)``
Retrieves the contents of the specified tile from the screen buffers.
Returns a pen, or *nil* if invalid or TrueType.
* ``dfhack.screen.paintString(pen,x,y,text)`` * ``dfhack.screen.paintString(pen,x,y,text)``
Paints the string starting at *x,y*. Uses the string characters Paints the string starting at *x,y*. Uses the string characters

@ -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="#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="#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="#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="#miscellaneous" id="id14">Miscellaneous</a></li>
<li><a class="reference internal" href="#persistent-configuration-storage" id="id15">Persistent configuration storage</a></li> <li><a class="reference internal" href="#locking-and-finalization" id="id15">Locking and finalization</a></li>
<li><a class="reference internal" href="#material-info-lookup" id="id16">Material info lookup</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> </ul>
</li> </li>
<li><a class="reference internal" href="#c-function-wrappers" id="id17">C++ function wrappers</a><ul> <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="id18">Gui module</a></li> <li><a class="reference internal" href="#gui-module" id="id19">Gui module</a></li>
<li><a class="reference internal" href="#job-module" id="id19">Job 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="id20">Units 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="id21">Items 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="id22">Maps 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="id23">Burrows 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="id24">Buildings 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="id25">Constructions 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="id26">Screen API</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="id27">Internal API</a></li> <li><a class="reference internal" href="#internal-api" id="id28">Internal API</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#core-interpreter-context" id="id28">Core interpreter context</a><ul> <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="id29">Event type</a></li> <li><a class="reference internal" href="#event-type" id="id30">Event type</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#lua-modules" id="id30">Lua Modules</a><ul> <li><a class="reference internal" href="#lua-modules" id="id31">Lua Modules</a><ul>
<li><a class="reference internal" href="#global-environment" id="id31">Global environment</a></li> <li><a class="reference internal" href="#global-environment" id="id32">Global environment</a></li>
<li><a class="reference internal" href="#utils" id="id32">utils</a></li> <li><a class="reference internal" href="#utils" id="id33">utils</a></li>
<li><a class="reference internal" href="#dumper" id="id33">dumper</a></li> <li><a class="reference internal" href="#dumper" id="id34">dumper</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#plugins" id="id34">Plugins</a><ul> <li><a class="reference internal" href="#plugins" id="id35">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id35">burrows</a></li> <li><a class="reference internal" href="#burrows" id="id36">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id36">sort</a></li> <li><a class="reference internal" href="#sort" id="id37">sort</a></li>
</ul> </ul>
</li> </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> </ul>
</div> </div>
<p>The current version of DFHack has extensive support for <p>The current version of DFHack has extensive support for
@ -842,8 +843,21 @@ following properties:</p>
</li> </li>
</ul> </ul>
</div> </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"> <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> <ul>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.with_suspend(f[,args...])</span></tt></p> <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. <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> </ul>
</div> </div>
<div class="section" id="persistent-configuration-storage"> <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. <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> 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 <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> However, currently every entry has a 180+-byte dead-weight overhead.</p>
</div> </div>
<div class="section" id="material-info-lookup"> <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> <p>A material info record has fields:</p>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">type</tt>, <tt class="docutils literal">index</tt>, <tt class="docutils literal">material</tt></p> <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> </div>
<div class="section" id="c-function-wrappers"> <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. <p>Thin wrappers around C++ functions, similar to the ones for virtual methods.
One notable difference is that these explicit wrappers allow argument count One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments 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> <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> <p>Returns the dfhack directory path, i.e. <tt class="docutils literal"><span class="pre">&quot;.../df/hack/&quot;</span></tt>.</p>
</li> </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> <li><p class="first"><tt class="docutils literal">dfhack.isWorldLoaded()</tt></p>
<p>Checks if the world is loaded.</p> <p>Checks if the world is loaded.</p>
</li> </li>
@ -985,7 +1002,7 @@ can be omitted.</p>
</li> </li>
</ul> </ul>
<div class="section" id="gui-module"> <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> <ul>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.getCurViewscreen([skip_dismissed])</span></tt></p> <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>, <p>Returns the topmost viewscreen. If <tt class="docutils literal">skip_dismissed</tt> is <em>true</em>,
@ -1032,7 +1049,7 @@ above operations accordingly. If enabled, pauses and zooms to position.</p>
</ul> </ul>
</div> </div>
<div class="section" id="job-module"> <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> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.job.cloneJobStruct(job)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.job.cloneJobStruct(job)</tt></p>
<p>Creates a deep copy of the given job.</p> <p>Creates a deep copy of the given job.</p>
@ -1069,7 +1086,7 @@ a lua list containing them.</p>
</ul> </ul>
</div> </div>
<div class="section" id="units-module"> <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> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.units.getPosition(unit)</tt></p> <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> <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>
@ -1089,6 +1106,26 @@ a lua list containing them.</p>
<li><p class="first"><tt class="docutils literal">dfhack.units.getNemesis(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.getNemesis(unit)</tt></p>
<p>Returns the nemesis record of the unit if it has one, or <em>nil</em>.</p> <p>Returns the nemesis record of the unit if it has one, or <em>nil</em>.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isHidingCurse(unit)</tt></p>
<p>Checks if the unit hides improved attributes from its curse.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getPhysicalAttrValue(unit, attr_type)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getMentalAttrValue(unit, attr_type)</tt></p>
<p>Computes the effective attribute value, including curse effect.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isCrazed(unit)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isOpposedToLife(unit)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.hasExtravision(unit)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isBloodsucker(unit)</tt></p>
<p>Simple checks of caste attributes that can be modified by curses.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getMiscTrait(unit, type[, create])</tt></p>
<p>Finds (or creates if requested) a misc trait object with the given id.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isDead(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.isDead(unit)</tt></p>
<p>The unit is completely dead and passive, or a ghost.</p> <p>The unit is completely dead and passive, or a ghost.</p>
</li> </li>
@ -1109,6 +1146,12 @@ same checks the game uses to decide game-over by extinction.</p>
<p>Returns the age of the unit in years as a floating-point value. <p>Returns the age of the unit in years as a floating-point value.
If <tt class="docutils literal">true_age</tt> is true, ignores false identities.</p> If <tt class="docutils literal">true_age</tt> is true, ignores false identities.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getEffectiveSkill(unit, skill)</tt></p>
<p>Computes the effective rating for the given skill, taking into account exhaustion, pain etc.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.computeMovementSpeed(unit)</tt></p>
<p>Computes number of frames * 100 it takes the unit to move in its current state of mind and body.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getNoblePositions(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.getNoblePositions(unit)</tt></p>
<p>Returns a list of tables describing noble position assignments, or <em>nil</em>. <p>Returns a list of tables describing noble position assignments, or <em>nil</em>.
Every table has fields <tt class="docutils literal">entity</tt>, <tt class="docutils literal">assignment</tt> and <tt class="docutils literal">position</tt>.</p> Every table has fields <tt class="docutils literal">entity</tt>, <tt class="docutils literal">assignment</tt> and <tt class="docutils literal">position</tt>.</p>
@ -1130,7 +1173,7 @@ or raws. The <tt class="docutils literal">ignore_noble</tt> boolean disables the
</ul> </ul>
</div> </div>
<div class="section" id="items-module"> <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> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.items.getPosition(item)</tt></p> <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> <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>
@ -1170,10 +1213,13 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.items.moveToInventory(item,unit,use_mode,body_part)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.items.moveToInventory(item,unit,use_mode,body_part)</tt></p>
<p>Move the item to the unit inventory. Returns <em>false</em> if impossible.</p> <p>Move the item to the unit inventory. Returns <em>false</em> if impossible.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.items.makeProjectile(item)</tt></p>
<p>Turns the item into a projectile, and returns the new object, or <em>nil</em> if impossible.</p>
</li>
</ul> </ul>
</div> </div>
<div class="section" id="maps-module"> <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> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getSize()</tt></p> <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> <p>Returns map size in blocks: <em>x, y, z</em></p>
@ -1190,6 +1236,9 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBlock(coords)</tt>, or <tt class="docutils literal">getTileBlock(x,y,z)</tt></p> <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> <p>Returns a map block object for given df::coord or x,y,z in local tile coordinates.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.ensureTileBlock(coords)</tt>, or <tt class="docutils literal">ensureTileBlock(x,y,z)</tt></p>
<p>Like <tt class="docutils literal">getTileBlock</tt>, but if the block is not allocated, try creating it.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getRegionBiome(region_coord2d)</tt>, or <tt class="docutils literal">getRegionBiome(x,y)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.maps.getRegionBiome(region_coord2d)</tt>, or <tt class="docutils literal">getRegionBiome(x,y)</tt></p>
<p>Returns the biome info struct for the given global map region.</p> <p>Returns the biome info struct for the given global map region.</p>
</li> </li>
@ -1221,7 +1270,7 @@ burrows, or the presence of invaders.</p>
</ul> </ul>
</div> </div>
<div class="section" id="burrows-module"> <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> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.burrows.findByName(name)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.burrows.findByName(name)</tt></p>
<p>Returns the burrow pointer or <em>nil</em>.</p> <p>Returns the burrow pointer or <em>nil</em>.</p>
@ -1256,7 +1305,7 @@ burrows, or the presence of invaders.</p>
</ul> </ul>
</div> </div>
<div class="section" id="buildings-module"> <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> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.setOwner(item,unit)</tt></p> <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. <p>Replaces the owner of the building. If unit is <em>nil</em>, removes ownership.
@ -1400,7 +1449,7 @@ can be determined this way, <tt class="docutils literal">constructBuilding</tt>
</ul> </ul>
</div> </div>
<div class="section" id="constructions-module"> <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> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.constructions.designateNew(pos,type,item_type,mat_index)</tt></p> <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 <p>Designates a new construction at given position. If there already is
@ -1416,7 +1465,7 @@ Returns <em>true, was_only_planned</em> if removed; or <em>false</em> if none fo
</ul> </ul>
</div> </div>
<div class="section" id="screen-api"> <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. <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 Note that drawing only has any effect when done from callbacks, so it can only
be feasibly used in the core context.</p> be feasibly used in the core context.</p>
@ -1460,6 +1509,10 @@ Otherwise should be <em>true/false</em>.</p>
</dl> </dl>
<p>Returns <em>false</em> if coordinates out of bounds, or other error.</p> <p>Returns <em>false</em> if coordinates out of bounds, or other error.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.readTile(x,y)</tt></p>
<p>Retrieves the contents of the specified tile from the screen buffers.
Returns a pen, or <em>nil</em> if invalid or TrueType.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.paintString(pen,x,y,text)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.screen.paintString(pen,x,y,text)</tt></p>
<p>Paints the string starting at <em>x,y</em>. Uses the string characters <p>Paints the string starting at <em>x,y</em>. Uses the string characters
in sequence to override the <tt class="docutils literal">ch</tt> field of pen.</p> in sequence to override the <tt class="docutils literal">ch</tt> field of pen.</p>
@ -1555,7 +1608,7 @@ options; if multiple interpretations exist, the table will contain multiple keys
</ul> </ul>
</div> </div>
<div class="section" id="internal-api"> <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, <p>These functions are intended for the use by dfhack developers,
and are only documented here for completeness:</p> and are only documented here for completeness:</p>
<ul> <ul>
@ -1603,7 +1656,7 @@ Returns: <em>found_index</em>, or <em>nil</em> if end reached.</p>
</div> </div>
</div> </div>
<div class="section" id="core-interpreter-context"> <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, <p>While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.</p> only context that can receive events from DF and plugins.</p>
@ -1634,7 +1687,7 @@ Using <tt class="docutils literal">timeout_active(id,nil)</tt> cancels the timer
</li> </li>
</ul> </ul>
<div class="section" id="event-type"> <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, <p>An event is a native object transparently wrapping a lua table,
and implementing a __call metamethod. When it is invoked, it loops and implementing a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values. through the table with next and calls all contained values.
@ -1666,7 +1719,7 @@ order using <tt class="docutils literal">dfhack.safecall</tt>.</p>
</div> </div>
</div> </div>
<div class="section" id="lua-modules"> <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> <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/. 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 The <tt class="docutils literal">dfhack</tt> namespace reference itself may be obtained via
@ -1695,7 +1748,7 @@ in this document.</p>
</li> </li>
</ul> </ul>
<div class="section" id="global-environment"> <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 <p>A number of variables and functions are provided in the base global
environment by the mandatory init file dfhack.lua:</p> environment by the mandatory init file dfhack.lua:</p>
<ul> <ul>
@ -1736,7 +1789,7 @@ Returns <em>nil</em> if any of obj or indices is <em>nil</em>, or a numeric inde
</ul> </ul>
</div> </div>
<div class="section" id="utils"> <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> <ul>
<li><p class="first"><tt class="docutils literal">utils.compare(a,b)</tt></p> <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> <p>Comparator function; returns <em>-1</em> if a&lt;b, <em>1</em> if a&gt;b, <em>0</em> otherwise.</p>
@ -1849,7 +1902,7 @@ throws an error.</p>
</ul> </ul>
</div> </div>
<div class="section" id="dumper"> <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 <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 <a class="reference external" href="http://lua-users.org/wiki/DataDumper">http://lua-users.org/wiki/DataDumper</a>. Defines one
function:</p> function:</p>
@ -1863,14 +1916,14 @@ the other arguments see the original documentation link above.</p>
</div> </div>
</div> </div>
<div class="section" id="plugins"> <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 <p>DFHack plugins may export native functions and events
to lua contexts. They are automatically imported by 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 <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> module file is still necessary for <tt class="docutils literal">require</tt> to read.</p>
<p>The following plugins have lua support.</p> <p>The following plugins have lua support.</p>
<div class="section" id="burrows"> <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>Implements extended burrow manipulations.</p>
<p>Events:</p> <p>Events:</p>
<ul> <ul>
@ -1908,13 +1961,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> <p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p>
</div> </div>
<div class="section" id="sort"> <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 <p>Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.</p> calls lua code to perform the actual ordering of list items.</p>
</div> </div>
</div> </div>
<div class="section" id="scripts"> <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/* <p>Any files with the .lua extension placed into hack/scripts/*
are automatically used by the DFHack core as commands. The are automatically used by the DFHack core as commands. The
matching command name consists of the name of the file sans matching command name consists of the name of the file sans

@ -7,10 +7,10 @@ IF(CMAKE_COMPILER_IS_GNUCC)
STRING(REGEX MATCHALL "[0-9]+" GCC_VERSION_COMPONENTS ${GCC_VERSION}) STRING(REGEX MATCHALL "[0-9]+" GCC_VERSION_COMPONENTS ${GCC_VERSION})
LIST(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR) LIST(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR)
LIST(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR) LIST(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR)
IF(GCC_MAJOR LESS 4 OR (GCC_MAJOR EQUAL 4 AND GCC_MINOR LESS 2)) #IF(GCC_MAJOR LESS 4 OR (GCC_MAJOR EQUAL 4 AND GCC_MINOR LESS 2))
#GCC is too old #GCC is too old
SET(STL_HASH_OLD_GCC 1) # SET(STL_HASH_OLD_GCC 1)
ENDIF() #ENDIF()
#SET(CMAKE_CXX_FLAGS "-std=c++0x") #SET(CMAKE_CXX_FLAGS "-std=c++0x")
SET(HAVE_HASH_MAP 0) SET(HAVE_HASH_MAP 0)

@ -16,6 +16,10 @@ keybinding add Ctrl-K autodump-destroy-item
# quicksave, only in main dwarfmode screen and menu page # quicksave, only in main dwarfmode screen and menu page
keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave 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 # # Generic adv mode bindings #
############################## ##############################
@ -52,6 +56,9 @@ keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work
# interface for the liquids plugin # interface for the liquids plugin
keybinding add Alt-L@dwarfmode/LookAround gui/liquids keybinding add Alt-L@dwarfmode/LookAround gui/liquids
# machine power sensitive pressure plate construction
keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter
################### ###################
# UI logic tweaks # # UI logic tweaks #
################### ###################

@ -286,6 +286,10 @@ SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "")
TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket)
TARGET_LINK_LIBRARIES(dfhack-run dfhack-client) TARGET_LINK_LIBRARIES(dfhack-run dfhack-client)
if(APPLE)
add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...")
endif()
IF(UNIX) IF(UNIX)
if (APPLE) if (APPLE)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack

@ -79,6 +79,8 @@ distribution.
#include "df/building_civzonest.h" #include "df/building_civzonest.h"
#include "df/region_map_entry.h" #include "df/region_map_entry.h"
#include "df/flow_info.h" #include "df/flow_info.h"
#include "df/unit_misc_trait.h"
#include "df/proj_itemst.h"
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
@ -728,6 +730,7 @@ static std::string getOSType()
} }
static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); } 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 getDFPath() { return Core::getInstance().p->getPath(); }
static std::string getHackPath() { return Core::getInstance().getHackPath(); } static std::string getHackPath() { return Core::getInstance().getHackPath(); }
@ -739,6 +742,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(getOSType), WRAP(getOSType),
WRAP(getDFVersion), WRAP(getDFVersion),
WRAP(getDFPath), WRAP(getDFPath),
WRAP(getTickCount),
WRAP(getHackPath), WRAP(getHackPath),
WRAP(isWorldLoaded), WRAP(isWorldLoaded),
WRAP(isMapLoaded), WRAP(isMapLoaded),
@ -811,12 +815,20 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getVisibleName), WRAPM(Units, getVisibleName),
WRAPM(Units, getIdentity), WRAPM(Units, getIdentity),
WRAPM(Units, getNemesis), WRAPM(Units, getNemesis),
WRAPM(Units, isCrazed),
WRAPM(Units, isOpposedToLife),
WRAPM(Units, hasExtravision),
WRAPM(Units, isBloodsucker),
WRAPM(Units, isMischievous),
WRAPM(Units, getMiscTrait),
WRAPM(Units, isDead), WRAPM(Units, isDead),
WRAPM(Units, isAlive), WRAPM(Units, isAlive),
WRAPM(Units, isSane), WRAPM(Units, isSane),
WRAPM(Units, isDwarf), WRAPM(Units, isDwarf),
WRAPM(Units, isCitizen), WRAPM(Units, isCitizen),
WRAPM(Units, getAge), WRAPM(Units, getAge),
WRAPM(Units, getEffectiveSkill),
WRAPM(Units, computeMovementSpeed),
WRAPM(Units, getProfessionName), WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName), WRAPM(Units, getCasteProfessionName),
WRAPM(Units, getProfessionColor), WRAPM(Units, getProfessionColor),
@ -874,6 +886,12 @@ static bool items_moveToInventory
return Items::moveToInventory(mc, item, unit, mode, body_part); return Items::moveToInventory(mc, item, unit, mode, body_part);
} }
static df::proj_itemst *items_makeProjectile(df::item *item)
{
MapExtras::MapCache mc;
return Items::makeProjectile(mc, item);
}
static const LuaWrapper::FunctionReg dfhack_items_module[] = { static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getGeneralRef), WRAPM(Items, getGeneralRef),
WRAPM(Items, getSpecificRef), WRAPM(Items, getSpecificRef),
@ -885,6 +903,7 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToContainer, items_moveToContainer),
WRAPN(moveToBuilding, items_moveToBuilding), WRAPN(moveToBuilding, items_moveToBuilding),
WRAPN(moveToInventory, items_moveToInventory), WRAPN(moveToInventory, items_moveToInventory),
WRAPN(makeProjectile, items_makeProjectile),
{ NULL, NULL } { NULL, NULL }
}; };
@ -933,6 +952,13 @@ static int maps_getTileBlock(lua_State *L)
return 1; return 1;
} }
static int maps_ensureTileBlock(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Maps::ensureTileBlock(pos));
return 1;
}
static int maps_getRegionBiome(lua_State *L) static int maps_getRegionBiome(lua_State *L)
{ {
auto pos = CheckCoordXY(L, 1, true); auto pos = CheckCoordXY(L, 1, true);
@ -949,6 +975,7 @@ static int maps_getTileBiomeRgn(lua_State *L)
static const luaL_Reg dfhack_maps_funcs[] = { static const luaL_Reg dfhack_maps_funcs[] = {
{ "isValidTilePos", maps_isValidTilePos }, { "isValidTilePos", maps_isValidTilePos },
{ "getTileBlock", maps_getTileBlock }, { "getTileBlock", maps_getTileBlock },
{ "ensureTileBlock", maps_ensureTileBlock },
{ "getRegionBiome", maps_getRegionBiome }, { "getRegionBiome", maps_getRegionBiome },
{ "getTileBiomeRgn", maps_getTileBiomeRgn }, { "getTileBiomeRgn", maps_getTileBiomeRgn },
{ NULL, NULL } { NULL, NULL }
@ -1144,6 +1171,45 @@ static int screen_paintTile(lua_State *L)
return 1; return 1;
} }
static int screen_readTile(lua_State *L)
{
int x = luaL_checkint(L, 1);
int y = luaL_checkint(L, 2);
Pen pen = Screen::readTile(x, y);
if (!pen.valid())
{
lua_pushnil(L);
}
else
{
lua_newtable(L);
lua_pushinteger(L, pen.ch); lua_setfield(L, -2, "ch");
lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg");
lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg");
lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold");
if (pen.tile)
{
lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile");
switch (pen.tile_mode) {
case Pen::CharColor:
lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color");
break;
case Pen::TileColor:
lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg");
lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg");
break;
default:
break;
}
}
}
return 1;
}
static int screen_paintString(lua_State *L) static int screen_paintString(lua_State *L)
{ {
Pen pen; Pen pen;
@ -1248,6 +1314,7 @@ static const luaL_Reg dfhack_screen_funcs[] = {
{ "getMousePos", screen_getMousePos }, { "getMousePos", screen_getMousePos },
{ "getWindowSize", screen_getWindowSize }, { "getWindowSize", screen_getWindowSize },
{ "paintTile", screen_paintTile }, { "paintTile", screen_paintTile },
{ "readTile", screen_readTile },
{ "paintString", screen_paintString }, { "paintString", screen_paintString },
{ "fillRect", screen_fillRect }, { "fillRect", screen_fillRect },
{ "findGraphicsTile", screen_findGraphicsTile }, { "findGraphicsTile", screen_findGraphicsTile },

@ -1211,6 +1211,39 @@ static int dfhack_open_plugin(lua_State *L)
return 0; 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) bool Lua::IsCoreContext(lua_State *state)
{ {
// This uses a private field of the lua state to // 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 }, { "call_with_finalizer", dfhack_call_with_finalizer },
{ "with_suspend", lua_dfhack_with_suspend }, { "with_suspend", lua_dfhack_with_suspend },
{ "open_plugin", dfhack_open_plugin }, { "open_plugin", dfhack_open_plugin },
{ "curry", dfhack_curry },
{ NULL, NULL } { 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_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN);
lua_setfield(state, -2, "BASE_G"); lua_setfield(state, -2, "BASE_G");
lua_pushstring(state, DFHACK_VERSION);
lua_setfield(state, -2, "VERSION");
lua_pushboolean(state, IsCoreContext(state)); lua_pushboolean(state, IsCoreContext(state));
lua_setfield(state, -2, "is_core_context"); lua_setfield(state, -2, "is_core_context");

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

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

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

@ -394,7 +394,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
//out.print("Received %d:%d\n", header.id, header.size); //out.print("Received %d:%d\n", header.id, header.size);
if (header.id == RPC_REPLY_FAIL) if ((DFHack::DFHackReplyCode)header.id == RPC_REPLY_FAIL)
return header.size == CR_OK ? CR_FAILURE : command_result(header.size); return header.size == CR_OK ? CR_FAILURE : command_result(header.size);
if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE)

@ -250,7 +250,7 @@ void ServerConnection::threadFn()
break; break;
} }
if (header.id == RPC_REQUEST_QUIT) if ((DFHack::DFHackReplyCode)header.id == RPC_REQUEST_QUIT)
break; break;
if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE)

@ -287,7 +287,7 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit,
if (mask && mask->profession()) if (mask && mask->profession())
{ {
if (unit->profession >= 0) if (unit->profession >= (df::profession)0)
info->set_profession(unit->profession); info->set_profession(unit->profession);
if (!unit->custom_profession.empty()) if (!unit->custom_profession.empty())
info->set_custom_profession(unit->custom_profession); info->set_custom_profession(unit->custom_profession);

@ -518,7 +518,7 @@ namespace DFHack {
template<class T> template<class T>
inline const char *enum_item_raw_key(T val) { inline const char *enum_item_raw_key(T val) {
typedef df::enum_traits<T> traits; typedef df::enum_traits<T> traits;
return traits::is_valid(val) ? traits::key_table[val - traits::first_item_value] : NULL; return traits::is_valid(val) ? traits::key_table[(short)val - traits::first_item_value] : NULL;
} }
/** /**

@ -287,6 +287,11 @@ namespace DFHack {namespace Lua {
PushDFObject(state, ptr); PushDFObject(state, ptr);
} }
template<class T> inline void SetField(lua_State *L, T val, int idx, const char *name) {
if (idx < 0) idx = lua_absindex(L, idx);
Push(L, val); lua_setfield(L, idx, name);
}
template<class T> template<class T>
void PushVector(lua_State *state, const T &pvec, bool addn = false) void PushVector(lua_State *state, const T &pvec, bool addn = false)
{ {

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

@ -88,7 +88,7 @@ namespace DFHack
{ {
typedef df::enum_traits<T> traits; typedef df::enum_traits<T> traits;
int base = traits::first_item; int base = traits::first_item;
int size = traits::last_item - base + 1; int size = (int)traits::last_item - base + 1;
describeEnum(pf, base, size, traits::key_table); describeEnum(pf, base, size, traits::key_table);
} }

@ -44,6 +44,7 @@ distribution.
namespace df namespace df
{ {
struct itemdef; struct itemdef;
struct proj_itemst;
} }
namespace MapExtras { namespace MapExtras {
@ -155,5 +156,8 @@ DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::
DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode); DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode);
DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit,
df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1); df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1);
/// Detaches the items from its current location and turns it into a projectile
DFHACK_EXPORT df::proj_itemst *makeProjectile(MapExtras::MapCache &mc, df::item *item);
} }
} }

@ -253,6 +253,8 @@ public:
bool is_valid() { return valid; } bool is_valid() { return valid; }
df::map_block *getRaw() { return block; } df::map_block *getRaw() { return block; }
bool Allocate();
MapCache *getParent() { return parent; } MapCache *getParent() { return parent; }
private: private:
@ -262,6 +264,8 @@ private:
MapCache *parent; MapCache *parent;
df::map_block *block; df::map_block *block;
void init();
int biomeIndexAt(df::coord2d p); int biomeIndexAt(df::coord2d p);
bool valid; bool valid;
@ -347,6 +351,12 @@ class DFHACK_EXPORT MapCache
return BlockAt(df::coord(coord.x>>4,coord.y>>4,coord.z)); return BlockAt(df::coord(coord.x>>4,coord.y>>4,coord.z));
} }
bool ensureBlockAt(df::coord coord)
{
Block *b = BlockAtTile(coord);
return b ? b->Allocate() : false;
}
df::tiletype baseTiletypeAt (DFCoord tilecoord) df::tiletype baseTiletypeAt (DFCoord tilecoord)
{ {
Block *b = BlockAtTile(tilecoord); Block *b = BlockAtTile(tilecoord);

@ -241,9 +241,11 @@ inline bool isValidTilePos(df::coord pos) { return isValidTilePos(pos.x, pos.y,
*/ */
extern DFHACK_EXPORT df::map_block * getBlock (int32_t blockx, int32_t blocky, int32_t blockz); extern DFHACK_EXPORT df::map_block * getBlock (int32_t blockx, int32_t blocky, int32_t blockz);
extern DFHACK_EXPORT df::map_block * getTileBlock (int32_t x, int32_t y, int32_t z); extern DFHACK_EXPORT df::map_block * getTileBlock (int32_t x, int32_t y, int32_t z);
extern DFHACK_EXPORT df::map_block * ensureTileBlock (int32_t x, int32_t y, int32_t z);
inline df::map_block * getBlock (df::coord pos) { return getBlock(pos.x, pos.y, pos.z); } inline df::map_block * getBlock (df::coord pos) { return getBlock(pos.x, pos.y, pos.z); }
inline df::map_block * getTileBlock (df::coord pos) { return getTileBlock(pos.x, pos.y, pos.z); } inline df::map_block * getTileBlock (df::coord pos) { return getTileBlock(pos.x, pos.y, pos.z); }
inline df::map_block * ensureTileBlock (df::coord pos) { return ensureTileBlock(pos.x, pos.y, pos.z); }
extern DFHACK_EXPORT df::tiletype *getTileType(int32_t x, int32_t y, int32_t z); extern DFHACK_EXPORT df::tiletype *getTileType(int32_t x, int32_t y, int32_t z);
extern DFHACK_EXPORT df::tile_designation *getTileDesignation(int32_t x, int32_t y, int32_t z); extern DFHACK_EXPORT df::tile_designation *getTileDesignation(int32_t x, int32_t y, int32_t z);

@ -65,6 +65,9 @@ namespace DFHack
} tile_mode; } tile_mode;
int8_t tile_fg, tile_bg; int8_t tile_fg, tile_bg;
bool valid() const { return tile >= 0; }
bool empty() const { return ch == 0 && tile == 0; }
Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false) Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false)
: ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)), : ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)),
tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0) tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0)
@ -92,6 +95,9 @@ namespace DFHack
/// Paint one screen tile with the given pen /// Paint one screen tile with the given pen
DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y); DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y);
/// Retrieves one screen tile from the buffer
DFHACK_EXPORT Pen readTile(int x, int y);
/// Paint a string onto the screen. Ignores ch and tile of pen. /// Paint a string onto the screen. Ignores ch and tile of pen.
DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text); DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text);

@ -32,6 +32,10 @@ distribution.
#include "modules/Items.h" #include "modules/Items.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "df/unit.h" #include "df/unit.h"
#include "df/misc_trait_type.h"
#include "df/physical_attribute_type.h"
#include "df/mental_attribute_type.h"
#include "df/job_skill.h"
namespace df namespace df
{ {
@ -41,6 +45,7 @@ namespace df
struct historical_entity; struct historical_entity;
struct entity_position_assignment; struct entity_position_assignment;
struct entity_position; struct entity_position;
struct unit_misc_trait;
} }
/** /**
@ -208,6 +213,18 @@ DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit);
DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit); DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit);
DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit);
DFHACK_EXPORT bool isHidingCurse(df::unit *unit);
DFHACK_EXPORT int getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr);
DFHACK_EXPORT int getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr);
DFHACK_EXPORT bool isCrazed(df::unit *unit);
DFHACK_EXPORT bool isOpposedToLife(df::unit *unit);
DFHACK_EXPORT bool hasExtravision(df::unit *unit);
DFHACK_EXPORT bool isBloodsucker(df::unit *unit);
DFHACK_EXPORT bool isMischievous(df::unit *unit);
DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false);
DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isDead(df::unit *unit);
DFHACK_EXPORT bool isAlive(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit);
DFHACK_EXPORT bool isSane(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit);
@ -216,6 +233,9 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit);
DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false);
DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id);
DFHACK_EXPORT int computeMovementSpeed(df::unit *unit);
struct NoblePosition { struct NoblePosition {
df::historical_entity *entity; df::historical_entity *entity;
df::entity_position_assignment *assignment; df::entity_position_assignment *assignment;

@ -46,6 +46,7 @@ end
-- Error handling -- Error handling
safecall = dfhack.safecall safecall = dfhack.safecall
curry = dfhack.curry
function dfhack.pcall(f, ...) function dfhack.pcall(f, ...)
return xpcall(f, dfhack.onerror, ...) return xpcall(f, dfhack.onerror, ...)
@ -83,7 +84,7 @@ function mkmodule(module,env)
error("Not a table in package.loaded["..module.."]") error("Not a table in package.loaded["..module.."]")
end end
end end
local plugname = string.match(module,'^plugins%.(%w+)$') local plugname = string.match(module,'^plugins%.([%w%-]+)$')
if plugname then if plugname then
dfhack.open_plugin(pkg,plugname) dfhack.open_plugin(pkg,plugname)
end end
@ -118,7 +119,12 @@ function defclass(class,parent)
if parent then if parent then
setmetatable(class, parent) setmetatable(class, parent)
else 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 end
return class return class
end end
@ -163,6 +169,23 @@ function xyz2pos(x,y,z)
end end
end end
function pos2xy(pos)
if pos then
local x = pos.x
if x and x ~= -30000 then
return x, pos.y
end
end
end
function xy2pos(x,y)
if x then
return {x=x,y=y}
else
return {x=-30000,y=-30000}
end
end
function safe_index(obj,idx,...) function safe_index(obj,idx,...)
if obj == nil or idx == nil then if obj == nil or idx == nil then
return nil return nil

@ -94,6 +94,9 @@ function Painter:isValidPos()
end end
function Painter:viewport(x,y,w,h) 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 x1,y1 = self.x1+x, self.y1+y
local x2,y2 = x1+w-1, y1+h-1 local x2,y2 = x1+w-1, y1+h-1
local vp = { local vp = {
@ -159,10 +162,10 @@ function Painter:fill(x1,y1,x2,y2,pen,bg,bold)
if type(x1) == 'table' then if type(x1) == 'table' then
x1, y1, x2, y2, pen, bg, bold = x1.x1, x1.y1, x1.x2, x1.y2, y1, x2, y2 x1, y1, x2, y2, pen, bg, bold = x1.x1, x1.y1, x1.x2, x1.y2, y1, x2, y2
end end
x1 = math.max(x1,self.clip_x1) x1 = math.max(x1+self.x1,self.clip_x1)
y1 = math.max(y1,self.clip_y1) y1 = math.max(y1+self.y1,self.clip_y1)
x2 = math.min(x2,self.clip_x2) x2 = math.min(x2+self.x1,self.clip_x2)
y2 = math.min(y2,self.clip_y2) y2 = math.min(y2+self.y1,self.clip_y2)
dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2) dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2)
return self return self
end end
@ -353,11 +356,16 @@ local function hint_coord(gap,hint)
end end
end end
function FramedScreen:getWantedFrameSize()
return self.frame_width, self.frame_height
end
function FramedScreen:updateFrameSize() function FramedScreen:updateFrameSize()
local sw, sh = dscreen.getWindowSize() local sw, sh = dscreen.getWindowSize()
local iw, ih = sw-2, sh-2 local iw, ih = sw-2, sh-2
local width = math.min(self.frame_width or iw, iw) local fw, fh = self:getWantedFrameSize()
local height = math.min(self.frame_height or ih, ih) local width = math.min(fw or iw, iw)
local height = math.min(fh or ih, ih)
local gw, gh = iw-width, ih-height local gw, gh = iw-width, ih-height
local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) 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) self.frame_rect = mkdims_wh(x1+1,y1+1,width,height)

@ -0,0 +1,271 @@
-- 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(1,dc:localY(),dc.width-2,dc:localY())
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.char(27)..string.sub(txt, #txt-dc.width+4)
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
ListBox = defclass(ListBox, MessageBox)
ListBox.focus_path = 'ListBox'
function ListBox:init(info)
info = info or {}
self:init_fields{
selection = info.selection or 0,
choices = info.choices or {},
select_pen = info.select_pen,
on_input = info.on_input,
page_top = 0
}
MessageBox.init(self, info)
self.on_accept = nil
return self
end
function ListBox:getWantedFrameSize()
local mw, mh = MessageBox.getWantedFrameSize(self)
return mw, mh+#self.choices
end
function ListBox:onRenderBody(dc)
MessageBox.onRenderBody(self, dc)
dc:newline(1)
if self.selection>dc.height-3 then
self.page_top=self.selection-(dc.height-3)
elseif self.selection<self.page_top and self.selection >0 then
self.page_top=self.selection-1
end
for i,entry in ipairs(self.choices) do
if type(entry)=="table" then
entry=entry[1]
end
if i>self.page_top then
if i == self.selection then
dc:pen(self.select_pen or COLOR_LIGHTCYAN)
else
dc:pen(self.text_pen or COLOR_GREY)
end
dc:string(entry)
dc:newline(1)
end
end
end
function ListBox:moveCursor(delta)
local newsel=self.selection+delta
if #self.choices ~=0 then
if newsel<1 or newsel>#self.choices then
newsel=newsel % #self.choices
end
end
self.selection=newsel
end
function ListBox:onInput(keys)
if keys.SELECT then
self:dismiss()
local choice=self.choices[self.selection]
if self.on_input then
self.on_input(self.selection,choice)
end
if choice and choice[2] then
choice[2](choice,self.selection) -- maybe reverse the arguments?
end
elseif keys.LEAVESCREEN then
self:dismiss()
if self.on_cancel then
self.on_cancel()
end
elseif keys.CURSOR_UP then
self:moveCursor(-1)
elseif keys.CURSOR_DOWN then
self:moveCursor(1)
elseif keys.CURSOR_UP_FAST then
self:moveCursor(-10)
elseif keys.CURSOR_DOWN_FAST then
self:moveCursor(10)
end
end
function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width)
mkinstance(ListBox):init{
title = title,
text = text,
text_pen = tcolor,
choices = choices,
on_input = on_input,
on_cancel = on_cancel,
frame_width = min_width,
}:show()
end
return _ENV

@ -46,7 +46,7 @@ function getPanelLayout()
end end
function getCursorPos() function getCursorPos()
if g_cursor ~= -30000 then if g_cursor.x ~= -30000 then
return copyall(g_cursor) return copyall(g_cursor)
end end
end end
@ -136,6 +136,14 @@ function Viewport:set()
return vp return vp
end end
function Viewport:getPos()
return xyz2pos(self.x1, self.y1, self.z)
end
function Viewport:getSize()
return xy2pos(self.width, self.height)
end
function Viewport:clip(x,y,z) function Viewport:clip(x,y,z)
return self:make( return self:make(
math.max(0, math.min(x or self.x1, world_map.x_count-self.width)), math.max(0, math.min(x or self.x1, world_map.x_count-self.width)),
@ -159,6 +167,18 @@ function Viewport:isVisible(target,gap)
return self:isVisibleXY(target,gap) and target.z == self.z return self:isVisibleXY(target,gap) and target.z == self.z
end end
function Viewport:tileToScreen(coord)
return xyz2pos(coord.x - self.x1, coord.y - self.y1, coord.z - self.z)
end
function Viewport:getCenter()
return xyz2pos(
math.floor((self.x2+self.x1)/2),
math.floor((self.y2+self.y1)/2),
self.z
)
end
function Viewport:centerOn(target) function Viewport:centerOn(target)
return self:clip( return self:clip(
target.x - math.floor(self.width/2), target.x - math.floor(self.width/2),
@ -207,16 +227,24 @@ MOVEMENT_KEYS = {
CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 },
} }
function Viewport:scrollByKey(key) local function get_movement_delta(key, delta, big_step)
local info = MOVEMENT_KEYS[key] local info = MOVEMENT_KEYS[key]
if info then if info then
local delta = 10 if info[4] then
if info[4] then delta = 20 end delta = big_step
end
return delta*info[1], delta*info[2], info[3]
end
end
function Viewport:scrollByKey(key)
local dx, dy, dz = get_movement_delta(key, 10, 20)
if dx then
return self:clip( return self:clip(
self.x1 + delta*info[1], self.x1 + dx,
self.y1 + delta*info[2], self.y1 + dy,
self.z + info[3] self.z + dz
) )
else else
return self return self
@ -237,16 +265,23 @@ function DwarfOverlay:getViewport(old_vp)
end end
end end
function DwarfOverlay:moveCursorTo(cursor,viewport) function DwarfOverlay:moveCursorTo(cursor,viewport,gap)
setCursorPos(cursor) setCursorPos(cursor)
self:getViewport(viewport):reveal(cursor, 5, 0, 10):set() self:zoomViewportTo(cursor,viewport,gap)
end
function DwarfOverlay:zoomViewportTo(target, viewport, gap)
if gap and self:getViewport():isVisible(target, gap) then
return
end
self:getViewport(viewport):reveal(target, 5, 0, 10):set()
end end
function DwarfOverlay:selectBuilding(building,cursor,viewport) function DwarfOverlay:selectBuilding(building,cursor,viewport,gap)
cursor = cursor or utils.getBuildingCenter(building) cursor = cursor or utils.getBuildingCenter(building)
df.global.world.selected_building = building df.global.world.selected_building = building
self:moveCursorTo(cursor, viewport) self:moveCursorTo(cursor, viewport, gap)
end end
function DwarfOverlay:propagateMoveKeys(keys) function DwarfOverlay:propagateMoveKeys(keys)
@ -282,6 +317,31 @@ function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor)
end end
end end
function DwarfOverlay:simulateCursorMovement(keys, anchor)
local layout = self.df_layout
local cursor = getCursorPos()
local cx, cy, cz = pos2xyz(cursor)
if anchor and keys.A_MOVE_SAME_SQUARE then
setCursorPos(anchor)
self:getViewport():centerOn(anchor):set()
return 'A_MOVE_SAME_SQUARE'
end
for code,_ in pairs(MOVEMENT_KEYS) do
if keys[code] then
local dx, dy, dz = get_movement_delta(code, 1, 10)
local ncur = xyz2pos(cx+dx, cy+dy, cz+dz)
if dfhack.maps.isValidTilePos(ncur) then
setCursorPos(ncur)
self:getViewport():reveal(ncur,4,10,6,true):set()
return code
end
end
end
end
function DwarfOverlay:onAboutToShow(below) function DwarfOverlay:onAboutToShow(below)
local screen = dfhack.gui.getCurViewscreen() local screen = dfhack.gui.getCurViewscreen()
if below then screen = below.parent end if below then screen = below.parent end

@ -381,6 +381,19 @@ function getBuildingCenter(building)
return xyz2pos(building.centerx, building.centery, building.z) return xyz2pos(building.centerx, building.centery, building.z)
end 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 -- Ask a yes-no question
function prompt_yes_no(msg,default) function prompt_yes_no(msg,default)
local prompt = msg local prompt = msg

@ -173,11 +173,10 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
else if (id == &df::building_trapst::_identity) else if (id == &df::building_trapst::_identity)
{ {
auto trap = (df::building_trapst*)selected; auto trap = (df::building_trapst*)selected;
if (trap->trap_type == trap_type::Lever) { focus += "/" + enum_item_key(trap->trap_type);
focus += "/Lever"; if (trap->trap_type == trap_type::Lever)
jobs = true; jobs = true;
} }
}
else if (ui_building_in_assign && *ui_building_in_assign && else if (ui_building_in_assign && *ui_building_in_assign &&
ui_building_assign_type && ui_building_assign_units && ui_building_assign_type && ui_building_assign_units &&
ui_building_assign_type->size() == ui_building_assign_units->size()) ui_building_assign_type->size() == ui_building_assign_units->size())
@ -189,6 +188,8 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
focus += unit ? "/Unit" : "/None"; focus += unit ? "/Unit" : "/None";
} }
} }
else
focus += "/" + enum_item_key(selected->getType());
if (jobs) if (jobs)
{ {
@ -211,7 +212,14 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
if (ui_build_selector->building_type < 0) if (ui_build_selector->building_type < 0)
focus += "/Type"; focus += "/Type";
else if (ui_build_selector->stage != 2) else if (ui_build_selector->stage != 2)
{
if (ui_build_selector->stage != 1)
focus += "/NoMaterials";
else
focus += "/Position"; focus += "/Position";
focus += "/" + enum_item_key(ui_build_selector->building_type);
}
else else
{ {
focus += "/Material"; focus += "/Material";

@ -72,8 +72,11 @@ using namespace std;
#include "df/general_ref_contains_itemst.h" #include "df/general_ref_contains_itemst.h"
#include "df/general_ref_contained_in_itemst.h" #include "df/general_ref_contained_in_itemst.h"
#include "df/general_ref_building_holderst.h" #include "df/general_ref_building_holderst.h"
#include "df/general_ref_projectile.h"
#include "df/viewscreen_itemst.h" #include "df/viewscreen_itemst.h"
#include "df/vermin.h" #include "df/vermin.h"
#include "df/proj_itemst.h"
#include "df/proj_list_link.h"
#include "df/unit_inventory_item.h" #include "df/unit_inventory_item.h"
#include "df/body_part_raw.h" #include "df/body_part_raw.h"
@ -88,6 +91,7 @@ using namespace df::enums;
using df::global::world; using df::global::world;
using df::global::ui; using df::global::ui;
using df::global::ui_selected_unit; using df::global::ui_selected_unit;
using df::global::proj_next_id;
#define ITEMDEF_VECTORS \ #define ITEMDEF_VECTORS \
ITEM(WEAPON, weapons, itemdef_weaponst) \ ITEM(WEAPON, weapons, itemdef_weaponst) \
@ -866,3 +870,44 @@ bool DFHack::Items::moveToInventory(
return true; return true;
} }
df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item)
{
CHECK_NULL_POINTER(item);
if (!world || !proj_next_id)
return NULL;
auto pos = getPosition(item);
if (!pos.isValid())
return NULL;
auto ref = df::allocate<df::general_ref_projectile>();
if (!ref)
return NULL;
if (!detachItem(mc, item))
{
delete ref;
return NULL;
}
item->pos = pos;
item->flags.bits.in_job = true;
auto proj = new df::proj_itemst();
proj->link = new df::proj_list_link();
proj->link->item = proj;
proj->id = (*proj_next_id)++;
proj->origin_pos = proj->target_pos = pos;
proj->cur_pos = proj->prev_pos = pos;
proj->item = item;
ref->projectile_id = proj->id;
item->itemrefs.push_back(ref);
linked_list_append(&world->proj_list, proj->link);
return proj;
}

@ -181,7 +181,7 @@ void DFHack::Job::printItemDetails(color_ostream &out, df::job_item *item, int i
out << " reaction class: " << item->reaction_class << endl; out << " reaction class: " << item->reaction_class << endl;
if (!item->has_material_reaction_product.empty()) if (!item->has_material_reaction_product.empty())
out << " reaction product: " << item->has_material_reaction_product << endl; out << " reaction product: " << item->has_material_reaction_product << endl;
if (item->has_tool_use >= 0) if (item->has_tool_use >= (df::tool_uses)0)
out << " tool use: " << ENUM_KEY_STR(tool_uses, item->has_tool_use) << endl; out << " tool use: " << ENUM_KEY_STR(tool_uses, item->has_tool_use) << endl;
} }

@ -157,6 +157,39 @@ df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z)
return world->map.block_index[x >> 4][y >> 4][z]; return world->map.block_index[x >> 4][y >> 4][z];
} }
df::map_block *Maps::ensureTileBlock (int32_t x, int32_t y, int32_t z)
{
if (!isValidTilePos(x,y,z))
return NULL;
auto column = world->map.block_index[x >> 4][y >> 4];
auto &slot = column[z];
if (slot)
return slot;
// Find another block below
int z2 = z;
while (z2 >= 0 && !column[z2]) z2--;
if (z2 < 0)
return NULL;
slot = new df::map_block();
slot->region_pos = column[z2]->region_pos;
slot->map_pos = column[z2]->map_pos;
slot->map_pos.z = z;
// Assume sky
df::tile_designation dsgn(0);
dsgn.bits.light = true;
dsgn.bits.outside = true;
for (int tx = 0; tx < 16; tx++)
for (int ty = 0; ty < 16; ty++)
slot->designation[tx][ty] = dsgn;
return slot;
}
df::tiletype *Maps::getTileType(int32_t x, int32_t y, int32_t z) df::tiletype *Maps::getTileType(int32_t x, int32_t y, int32_t z)
{ {
df::map_block *block = getTileBlock(x,y,z); df::map_block *block = getTileBlock(x,y,z);
@ -274,7 +307,7 @@ df::feature_init *Maps::getLocalInitFeature(df::coord2d rgn_pos, int32_t index)
df::coord2d bigregion = rgn_pos / 16; df::coord2d bigregion = rgn_pos / 16;
// bigregion is 16x16 regions. for each bigregion in X dimension: // bigregion is 16x16 regions. for each bigregion in X dimension:
auto fptr = data->unk_204[bigregion.x][bigregion.y].features; auto fptr = data->feature_map[bigregion.x][bigregion.y].features;
if (!fptr) if (!fptr)
return NULL; return NULL;
@ -513,8 +546,14 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
valid = false; valid = false;
bcoord = _bcoord; bcoord = _bcoord;
block = Maps::getBlock(bcoord); block = Maps::getBlock(bcoord);
item_counts = NULL;
tags = NULL; tags = NULL;
init();
}
void MapExtras::Block::init()
{
item_counts = NULL;
tiles = NULL; tiles = NULL;
basemats = NULL; basemats = NULL;
@ -537,6 +576,23 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
} }
} }
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() MapExtras::Block::~Block()
{ {
delete[] item_counts; delete[] item_counts;

@ -100,7 +100,7 @@ static void doSetTile(const Pen &pen, int index)
bool Screen::paintTile(const Pen &pen, int x, int y) bool Screen::paintTile(const Pen &pen, int x, int y)
{ {
if (!gps) return false; if (!gps || !pen.valid()) return false;
int dimx = gps->dimx, dimy = gps->dimy; int dimx = gps->dimx, dimy = gps->dimy;
if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false; if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false;
@ -109,6 +109,41 @@ bool Screen::paintTile(const Pen &pen, int x, int y)
return true; return true;
} }
Pen Screen::readTile(int x, int y)
{
if (!gps) return Pen(0,0,0,-1);
int dimx = gps->dimx, dimy = gps->dimy;
if (x < 0 || x >= dimx || y < 0 || y >= dimy)
return Pen(0,0,0,-1);
int index = x*dimy + y;
auto screen = gps->screen + index*4;
if (screen[3] & 0x80)
return Pen(0,0,0,-1);
Pen pen(
screen[0], screen[1], screen[2], screen[3]?true:false,
gps->screentexpos[index]
);
if (pen.tile)
{
if (gps->screentexpos_grayscale[index])
{
pen.tile_mode = Screen::Pen::TileColor;
pen.tile_fg = gps->screentexpos_cf[index];
pen.tile_bg = gps->screentexpos_cbr[index];
}
else if (gps->screentexpos_addcolor[index])
{
pen.tile_mode = Screen::Pen::CharColor;
}
}
return pen;
}
bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text)
{ {
if (!gps || y < 0 || y >= gps->dimy) return false; if (!gps || y < 0 || y >= gps->dimy) return false;
@ -132,7 +167,7 @@ bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text)
bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2) bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2)
{ {
if (!gps) return false; if (!gps || !pen.valid()) return false;
if (x1 < 0) x1 = 0; if (x1 < 0) x1 = 0;
if (y1 < 0) y1 = 0; if (y1 < 0) y1 = 0;

@ -63,11 +63,15 @@ using namespace std;
#include "df/burrow.h" #include "df/burrow.h"
#include "df/creature_raw.h" #include "df/creature_raw.h"
#include "df/caste_raw.h" #include "df/caste_raw.h"
#include "df/game_mode.h"
#include "df/unit_misc_trait.h"
#include "df/unit_skill.h"
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
using df::global::world; using df::global::world;
using df::global::ui; using df::global::ui;
using df::global::gamemode;
bool Units::isValid() bool Units::isValid()
{ {
@ -613,6 +617,58 @@ df::nemesis_record *Units::getNemesis(df::unit *unit)
return NULL; return NULL;
} }
bool Units::isHidingCurse(df::unit *unit)
{
if (!unit->job.hunt_target)
{
auto identity = Units::getIdentity(unit);
if (identity && identity->unk_4c == 0)
return true;
}
return false;
}
int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr)
{
auto &aobj = unit->body.physical_attrs[attr];
int value = std::max(0, aobj.value - aobj.soft_demotion);
if (auto mod = unit->curse.attr_change)
{
int mvalue = (value * mod->phys_att_perc[attr] / 100) + mod->phys_att_add[attr];
if (isHidingCurse(unit))
value = std::min(value, mvalue);
else
value = mvalue;
}
return std::max(0, value);
}
int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr)
{
auto soul = unit->status.current_soul;
if (!soul) return 0;
auto &aobj = soul->mental_attrs[attr];
int value = std::max(0, aobj.value - aobj.soft_demotion);
if (auto mod = unit->curse.attr_change)
{
int mvalue = (value * mod->ment_att_perc[attr] / 100) + mod->ment_att_add[attr];
if (isHidingCurse(unit))
value = std::min(value, mvalue);
else
value = mvalue;
}
return std::max(0, value);
}
static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag)
{ {
auto creature = df::creature_raw::find(race); auto creature = df::creature_raw::find(race);
@ -626,8 +682,9 @@ static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag)
return craw->flags.is_set(flag); return craw->flags.is_set(flag);
} }
static bool isCrazed(df::unit *unit) bool Units::isCrazed(df::unit *unit)
{ {
CHECK_NULL_POINTER(unit);
if (unit->flags3.bits.scuttle) if (unit->flags3.bits.scuttle)
return false; return false;
if (unit->curse.rem_tags1.bits.CRAZED) if (unit->curse.rem_tags1.bits.CRAZED)
@ -637,13 +694,64 @@ static bool isCrazed(df::unit *unit)
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED); return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED);
} }
static bool isOpposedToLife(df::unit *unit) bool Units::isOpposedToLife(df::unit *unit)
{ {
CHECK_NULL_POINTER(unit);
if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE) if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE)
return false; return false;
if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE) if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE)
return true; return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CANNOT_UNDEAD); return casteFlagSet(unit->race, unit->caste, caste_raw_flags::OPPOSED_TO_LIFE);
}
bool Units::hasExtravision(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
if (unit->curse.rem_tags1.bits.EXTRAVISION)
return false;
if (unit->curse.add_tags1.bits.EXTRAVISION)
return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION);
}
bool Units::isBloodsucker(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
if (unit->curse.rem_tags1.bits.BLOODSUCKER)
return false;
if (unit->curse.add_tags1.bits.BLOODSUCKER)
return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER);
}
bool Units::isMischievous(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
if (unit->curse.rem_tags1.bits.MISCHIEVOUS)
return false;
if (unit->curse.add_tags1.bits.MISCHIEVOUS)
return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::MISCHIEVOUS);
}
df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create)
{
CHECK_NULL_POINTER(unit);
auto &vec = unit->status.misc_traits;
for (size_t i = 0; i < vec.size(); i++)
if (vec[i]->id == type)
return vec[i];
if (create)
{
auto obj = new df::unit_misc_trait();
obj->id = type;
vec.push_back(obj);
return obj;
}
return NULL;
} }
bool DFHack::Units::isDead(df::unit *unit) bool DFHack::Units::isDead(df::unit *unit)
@ -753,6 +861,371 @@ double DFHack::Units::getAge(df::unit *unit, bool true_age)
return cur_time - birth_time; return cur_time - birth_time;
} }
inline void adjust_skill_rating(int &rating, bool is_adventure, int value, int dwarf3_4, int dwarf1_2, int adv9_10, int adv3_4, int adv1_2)
{
if (is_adventure)
{
if (value >= adv1_2) rating >>= 1;
else if (value >= adv3_4) rating = rating*3/4;
else if (value >= adv9_10) rating = rating*9/10;
}
else
{
if (value >= dwarf1_2) rating >>= 1;
else if (value >= dwarf3_4) rating = rating*3/4;
}
}
int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id)
{
CHECK_NULL_POINTER(unit);
/*
* This is 100% reverse-engineered from DF code.
*/
if (!unit->status.current_soul)
return 0;
// Retrieve skill from unit soul:
df::enum_field<df::job_skill,int16_t> key(skill_id);
auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key);
int rating = 0;
if (skill)
rating = std::max(0, int(skill->rating) - skill->rusty);
// Apply special states
if (unit->counters.soldier_mood == df::unit::T_counters::None)
{
if (unit->counters.nausea > 0) rating >>= 1;
if (unit->counters.winded > 0) rating >>= 1;
if (unit->counters.stunned > 0) rating >>= 1;
if (unit->counters.dizziness > 0) rating >>= 1;
if (unit->counters2.fever > 0) rating >>= 1;
}
if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance)
{
if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle &&
!unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged &&
!hasExtravision(unit))
{
rating >>= 2;
}
if (unit->counters.pain >= 100 && unit->mood == -1)
{
rating >>= 1;
}
if (unit->counters2.exhaustion >= 2000)
{
rating = rating*3/4;
if (unit->counters2.exhaustion >= 4000)
{
rating = rating*3/4;
if (unit->counters2.exhaustion >= 6000)
rating = rating*3/4;
}
}
}
// Hunger etc timers
bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE);
if (!unit->flags3.bits.scuttle && isBloodsucker(unit))
{
using namespace df::enums::misc_trait_type;
if (auto trait = getMiscTrait(unit, TimeSinceSuckedBlood))
{
adjust_skill_rating(
rating, is_adventure, trait->value,
302400, 403200, // dwf 3/4; 1/2
1209600, 1209600, 2419200 // adv 9/10; 3/4; 1/2
);
}
}
adjust_skill_rating(
rating, is_adventure, unit->counters2.thirst_timer,
50000, 50000, 115200, 172800, 345600
);
adjust_skill_rating(
rating, is_adventure, unit->counters2.hunger_timer,
75000, 75000, 172800, 1209600, 2592000
);
if (is_adventure && unit->counters2.sleepiness_timer >= 846000)
rating >>= 2;
else
adjust_skill_rating(
rating, is_adventure, unit->counters2.sleepiness_timer,
150000, 150000, 172800, 259200, 345600
);
return rating;
}
inline void adjust_speed_rating(int &rating, bool is_adventure, int value, int dwarf100, int dwarf200, int adv50, int adv75, int adv100, int adv200)
{
if (is_adventure)
{
if (value >= adv200) rating += 200;
else if (value >= adv100) rating += 100;
else if (value >= adv75) rating += 75;
else if (value >= adv50) rating += 50;
}
else
{
if (value >= dwarf200) rating += 200;
else if (value >= dwarf100) rating += 100;
}
}
static int calcInventoryWeight(df::unit *unit)
{
int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR);
int armor_mul = 15 - std::min(15, armor_skill);
int inv_weight = 0, inv_weight_fraction = 0;
for (size_t i = 0; i < unit->inventory.size(); i++)
{
auto item = unit->inventory[i]->item;
if (!item->flags.bits.weight_computed)
continue;
int wval = item->weight;
int wfval = item->weight_fraction;
auto mode = unit->inventory[i]->mode;
if ((mode == df::unit_inventory_item::Worn ||
mode == df::unit_inventory_item::WrappedAround) &&
item->isArmor() && armor_skill > 1)
{
wval = wval * armor_mul / 16;
wfval = wfval * armor_mul / 16;
}
inv_weight += wval;
inv_weight_fraction += wfval;
}
return inv_weight*100 + inv_weight_fraction/10000;
}
int Units::computeMovementSpeed(df::unit *unit)
{
using namespace df::enums::physical_attribute_type;
/*
* Pure reverse-engineered computation of unit _slowness_,
* i.e. number of ticks to move * 100.
*/
// Base speed
auto creature = df::creature_raw::find(unit->race);
if (!creature)
return 0;
auto craw = vector_get(creature->caste, unit->caste);
if (!craw)
return 0;
int speed = craw->misc.speed;
if (unit->flags3.bits.ghostly)
return speed;
// Curse multiplier
if (unit->curse.speed_mul_percent != 100)
{
speed *= 100;
if (unit->curse.speed_mul_percent != 0)
speed /= unit->curse.speed_mul_percent;
}
speed += unit->curse.speed_add;
// Swimming
auto cur_liquid = unit->status2.liquid_type.bits.liquid_type;
bool in_magma = (cur_liquid == tile_liquid::Magma);
if (unit->flags2.bits.swimming)
{
speed = craw->misc.swim_speed;
if (in_magma)
speed *= 2;
if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED))
{
int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING);
// Originally a switch:
if (skill > 1)
speed = speed * std::max(6, 21-skill) / 20;
}
}
else
{
int delta = 150*unit->status2.liquid_depth;
if (in_magma)
delta *= 2;
speed += delta;
}
// General counters and flags
if (unit->profession == profession::BABY)
speed += 3000;
if (unit->flags3.bits.unk15)
speed /= 20;
if (unit->counters2.exhaustion >= 2000)
{
speed += 200;
if (unit->counters2.exhaustion >= 4000)
{
speed += 200;
if (unit->counters2.exhaustion >= 6000)
speed += 200;
}
}
if (unit->flags2.bits.gutted) speed += 2000;
if (unit->counters.soldier_mood == df::unit::T_counters::None)
{
if (unit->counters.nausea > 0) speed += 1000;
if (unit->counters.winded > 0) speed += 1000;
if (unit->counters.stunned > 0) speed += 1000;
if (unit->counters.dizziness > 0) speed += 1000;
if (unit->counters2.fever > 0) speed += 1000;
}
if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance)
{
if (unit->counters.pain >= 100 && unit->mood == -1)
speed += 1000;
}
// Hunger etc timers
bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE);
if (!unit->flags3.bits.scuttle && Units::isBloodsucker(unit))
{
using namespace df::enums::misc_trait_type;
if (auto trait = Units::getMiscTrait(unit, TimeSinceSuckedBlood))
{
adjust_speed_rating(
speed, is_adventure, trait->value,
302400, 403200, // dwf 100; 200
1209600, 1209600, 1209600, 2419200 // adv 50; 75; 100; 200
);
}
}
adjust_speed_rating(
speed, is_adventure, unit->counters2.thirst_timer,
50000, 0x7fffffff, 172800, 172800, 172800, 345600
);
adjust_speed_rating(
speed, is_adventure, unit->counters2.hunger_timer,
75000, 0x7fffffff, 1209600, 1209600, 1209600, 2592000
);
adjust_speed_rating(
speed, is_adventure, unit->counters2.sleepiness_timer,
57600, 150000, 172800, 259200, 345600, 864000
);
// Activity state
if (unit->relations.draggee_id != -1) speed += 1000;
if (unit->flags1.bits.on_ground)
speed += 2000;
else if (unit->flags3.bits.on_crutch)
{
int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK);
speed += 2000 - 100*std::min(20, skill);
}
if (unit->flags1.bits.hidden_in_ambush && !Units::isMischievous(unit))
{
int skill = Units::getEffectiveSkill(unit, job_skill::SNEAK);
speed += 2000 - 100*std::min(20, skill);
}
if (unsigned(unit->counters2.paralysis-1) <= 98)
speed += unit->counters2.paralysis*10;
if (unsigned(unit->counters.webbed-1) <= 8)
speed += unit->counters.webbed*100;
// Muscle weight vs vascular tissue (?)
auto &attr_tissue = unit->body.physical_attr_tissues;
int muscle = attr_tissue[STRENGTH];
int blood = attr_tissue[AGILITY];
speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*muscle/blood)));
// Attributes
int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH);
int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY);
int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr));
speed = ((total_attr-200)*(speed/2) + (3800-total_attr)*(speed*3/2))/3600;
// Stance
if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2)
{
// WTF
int as = unit->status2.able_stand;
int x = (as-1) - (as>>1);
int y = as - unit->status2.able_stand_impair;
if (unit->flags3.bits.on_crutch) y--;
y = y * 500 / x;
if (y > 0) speed += y;
}
// Mood
if (unit->mood == mood_type::Melancholy) speed += 8000;
// Inventory encumberance
int total_weight = calcInventoryWeight(unit);
int free_weight = std::max(1, muscle/10 + strength_attr*3);
if (free_weight < total_weight)
{
int delta = (total_weight - free_weight)/10 + 1;
if (!is_adventure)
delta = std::min(5000, delta);
speed += delta;
}
// skipped: unknown loop on inventory items that amounts to 0 change
if (is_adventure)
{
auto player = vector_get(world->units.active, 0);
if (player && player->id == unit->relations.group_leader_id)
speed = std::min(speed, computeMovementSpeed(player));
}
return std::min(10000, std::max(0, speed));
}
static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b) static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b)
{ {
if (a.position->precedence < b.position->precedence) if (a.position->precedence < b.position->precedence)
@ -838,7 +1311,7 @@ std::string DFHack::Units::getCasteProfessionName(int race, int casteid, df::pro
{ {
std::string prof, race_prefix; std::string prof, race_prefix;
if (pid < 0 || !is_valid_enum_item(pid)) if (pid < (df::profession)0 || !is_valid_enum_item(pid))
return ""; return "";
bool use_race_prefix = (race >= 0 && race != df::global::ui->race_id); bool use_race_prefix = (race >= 0 && race != df::global::ui->race_id);

@ -285,13 +285,13 @@ PersistentDataItem World::GetPersistentData(int entry_id)
PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) PersistentDataItem World::GetPersistentData(const std::string &key, bool *added)
{ {
*added = false; if (added) *added = false;
PersistentDataItem rv = GetPersistentData(key); PersistentDataItem rv = GetPersistentData(key);
if (!rv.isValid()) if (!rv.isValid())
{ {
*added = true; if (added) *added = true;
rv = AddPersistentData(key); rv = AddPersistentData(key);
} }
@ -300,6 +300,8 @@ PersistentDataItem World::GetPersistentData(const std::string &key, bool *added)
void World::GetPersistentData(std::vector<PersistentDataItem> *vec, const std::string &key, bool prefix) void World::GetPersistentData(std::vector<PersistentDataItem> *vec, const std::string &key, bool prefix)
{ {
vec->clear();
if (!BuildPersistentCache()) if (!BuildPersistentCache())
return; return;
@ -343,8 +345,10 @@ bool World::DeletePersistentData(const PersistentDataItem &item)
auto eqrange = d->persistent_index.equal_range(item.key_value); auto eqrange = d->persistent_index.equal_range(item.key_value);
for (auto it = eqrange.first; it != eqrange.second; ++it) for (auto it2 = eqrange.first; it2 != eqrange.second; )
{ {
auto it = it2; ++it2;
if (it->second != -item.id) if (it->second != -item.id)
continue; continue;

@ -114,7 +114,7 @@ void Kitchen::fillWatchMap(std::map<t_materialIndex, unsigned int>& watchMap)
watchMap.clear(); watchMap.clear();
for(std::size_t i = 0; i < size(); ++i) for(std::size_t i = 0; i < size(); ++i)
{ {
if(ui->kitchen.item_subtypes[i] == limitType && ui->kitchen.item_subtypes[i] == limitSubtype && ui->kitchen.exc_types[i] == limitExclusion) if(ui->kitchen.item_subtypes[i] == (short)limitType && ui->kitchen.item_subtypes[i] == (short)limitSubtype && ui->kitchen.exc_types[i] == limitExclusion)
{ {
watchMap[ui->kitchen.mat_indices[i]] = (unsigned int) ui->kitchen.mat_types[i]; watchMap[ui->kitchen.mat_indices[i]] = (unsigned int) ui->kitchen.mat_types[i];
} }

@ -1 +1 @@
Subproject commit df8178a989373ec7868d9195d82ae5f85145ef81 Subproject commit 2bc8fbdf71143398817d31e06e169a01cce37c50

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

@ -0,0 +1,21 @@
#!/bin/bash
BUILD_DIR=`pwd`
echo "Fixing library dependencies in $BUILD_DIR/library"
install_name_tool -change $BUILD_DIR/library/libdfhack.1.0.0.dylib @executable_path/hack/libdfhack.1.0.0.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change $BUILD_DIR/library/libdfhack-client.dylib @executable_path/hack/libdfhack-client.dylib library/libdfhack-client.dylib
install_name_tool -change $BUILD_DIR/library/libdfhack-client.dylib @executable_path/hack/libdfhack-client.dylib library/dfhack-run
install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/libdfhack-client.dylib
install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/dfhack-run
install_name_tool -change $BUILD_DIR/depends/lua/liblua.dylib @executable_path/hack/liblua.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change @executable_path/../Frameworks/SDL.framework/Versions/A/SDL @executable_path/libs/SDL.framework/Versions/A/SDL library/libdfhack.1.0.0.dylib
install_name_tool -change /usr/local/lib/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack-client.dylib
install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/dfhack-run
install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/libdfhack-client.dylib
install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/dfhack-run

@ -44,6 +44,9 @@ endif()
install(DIRECTORY lua/ install(DIRECTORY lua/
DESTINATION ${DFHACK_LUA_DESTINATION}/plugins DESTINATION ${DFHACK_LUA_DESTINATION}/plugins
FILES_MATCHING PATTERN "*.lua") FILES_MATCHING PATTERN "*.lua")
install(DIRECTORY raw/
DESTINATION ${DFHACK_DATA_DESTINATION}/raw
FILES_MATCHING PATTERN "*.txt")
# Protobuf # Protobuf
FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto)
@ -92,7 +95,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(seedwatch seedwatch.cpp) DFHACK_PLUGIN(seedwatch seedwatch.cpp)
DFHACK_PLUGIN(initflags initflags.cpp) DFHACK_PLUGIN(initflags initflags.cpp)
DFHACK_PLUGIN(stockpiles stockpiles.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(jobutils jobutils.cpp)
DFHACK_PLUGIN(workflow workflow.cpp) DFHACK_PLUGIN(workflow workflow.cpp)
DFHACK_PLUGIN(showmood showmood.cpp) DFHACK_PLUGIN(showmood showmood.cpp)
@ -114,6 +117,8 @@ if (BUILD_SUPPORTED)
# this one exports functions to lua # this one exports functions to lua
DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(steam-engine steam-engine.cpp)
DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua)
# not yet. busy with other crud again... # not yet. busy with other crud again...
#DFHACK_PLUGIN(versionosd versionosd.cpp) #DFHACK_PLUGIN(versionosd versionosd.cpp)
endif() endif()

@ -9,6 +9,7 @@
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include "modules/Units.h"
#include "modules/World.h" #include "modules/World.h"
// DF data structure definition headers // DF data structure definition headers
@ -537,7 +538,7 @@ static void cleanup_state()
labor_infos.clear(); labor_infos.clear();
} }
static void reset_labor(df::enums::unit_labor::unit_labor labor) static void reset_labor(df::unit_labor labor)
{ {
labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs); labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs);
labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs);
@ -576,7 +577,7 @@ static void init_state()
for (auto p = items.begin(); p != items.end(); p++) for (auto p = items.begin(); p != items.end(); p++)
{ {
string key = p->key(); string key = p->key();
df::enums::unit_labor::unit_labor labor = (df::enums::unit_labor::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str());
if (labor >= 0 && labor <= labor_infos.size()) if (labor >= 0 && labor <= labor_infos.size())
{ {
labor_infos[labor].config = *p; labor_infos[labor].config = *p;
@ -597,7 +598,7 @@ static void init_state()
labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive; labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive;
labor_infos[i].active_dwarfs = 0; labor_infos[i].active_dwarfs = 0;
reset_labor((df::enums::unit_labor::unit_labor) i); reset_labor((df::unit_labor) i);
} }
generate_labor_to_skill_map(); generate_labor_to_skill_map();
@ -611,12 +612,12 @@ static void generate_labor_to_skill_map()
// Generate labor -> skill mapping // Generate labor -> skill mapping
for (int i = 0; i <= ENUM_LAST_ITEM(unit_labor); i++) for (int i = 0; i <= ENUM_LAST_ITEM(unit_labor); i++)
labor_to_skill[i] = df::enums::job_skill::NONE; labor_to_skill[i] = job_skill::NONE;
FOR_ENUM_ITEMS(job_skill, skill) FOR_ENUM_ITEMS(job_skill, skill)
{ {
int labor = ENUM_ATTR(job_skill, labor, skill); int labor = ENUM_ATTR(job_skill, labor, skill);
if (labor != df::enums::unit_labor::NONE) if (labor != unit_labor::NONE)
{ {
/* /*
assert(labor >= 0); assert(labor >= 0);
@ -779,7 +780,7 @@ static void assign_labor(unit_labor::unit_labor labor,
int value = dwarf_info[dwarf].mastery_penalty; int value = dwarf_info[dwarf].mastery_penalty;
if (skill != df::enums::job_skill::NONE) if (skill != job_skill::NONE)
{ {
int skill_level = 0; int skill_level = 0;
int skill_experience = 0; int skill_experience = 0;
@ -843,9 +844,9 @@ static void assign_labor(unit_labor::unit_labor labor,
int max_dwarfs = labor_infos[labor].maximum_dwarfs(); int max_dwarfs = labor_infos[labor].maximum_dwarfs();
// Special - don't assign hunt without a butchers, or fish without a fishery // Special - don't assign hunt without a butchers, or fish without a fishery
if (df::enums::unit_labor::HUNT == labor && !has_butchers) if (unit_labor::HUNT == labor && !has_butchers)
min_dwarfs = max_dwarfs = 0; min_dwarfs = max_dwarfs = 0;
if (df::enums::unit_labor::FISH == labor && !has_fishery) if (unit_labor::FISH == labor && !has_fishery)
min_dwarfs = max_dwarfs = 0; min_dwarfs = max_dwarfs = 0;
bool want_idle_dwarf = true; bool want_idle_dwarf = true;
@ -956,15 +957,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
df::building *build = world->buildings.all[i]; df::building *build = world->buildings.all[i];
auto type = build->getType(); auto type = build->getType();
if (df::enums::building_type::Workshop == type) if (building_type::Workshop == type)
{ {
auto subType = build->getSubtype(); df::workshop_type subType = (df::workshop_type)build->getSubtype();
if (df::enums::workshop_type::Butchers == subType) if (workshop_type::Butchers == subType)
has_butchers = true; has_butchers = true;
if (df::enums::workshop_type::Fishery == subType) if (workshop_type::Fishery == subType)
has_fishery = true; has_fishery = true;
} }
else if (df::enums::building_type::TradeDepot == type) else if (building_type::TradeDepot == type)
{ {
df::building_tradedepotst* depot = (df::building_tradedepotst*) build; df::building_tradedepotst* depot = (df::building_tradedepotst*) build;
trader_requested = depot->trade_flags.bits.trader_requested; trader_requested = depot->trade_flags.bits.trader_requested;
@ -978,11 +979,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
} }
} }
for (int i = 0; i < world->units.all.size(); ++i) for (int i = 0; i < world->units.active.size(); ++i)
{ {
df::unit* cre = world->units.all[i]; df::unit* cre = world->units.active[i];
if (cre->race == race && cre->civ_id == civ && !cre->flags1.bits.marauder && !cre->flags1.bits.diplomat && !cre->flags1.bits.merchant && if (Units::isCitizen(cre))
!cre->flags1.bits.dead && !cre->flags1.bits.forest)
{ {
if (cre->burrows.size() > 0) if (cre->burrows.size() > 0)
continue; // dwarfs assigned to burrows are skipped entirely continue; // dwarfs assigned to burrows are skipped entirely
@ -1003,9 +1003,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
dwarf_info[dwarf].single_labor = -1; dwarf_info[dwarf].single_labor = -1;
// assert(dwarfs[dwarf]->status.souls.size() > 0);
// assert fails can cause DF to crash, so don't do that
if (dwarfs[dwarf]->status.souls.size() <= 0) if (dwarfs[dwarf]->status.souls.size() <= 0)
continue; continue;
@ -1076,7 +1073,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.) // Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.)
if (skill_class != df::enums::job_skill_class::Normal && skill_class != df::enums::job_skill_class::Medical) if (skill_class != job_skill_class::Normal && skill_class != job_skill_class::Medical)
continue; continue;
if (dwarf_info[dwarf].highest_skill < skill_level) if (dwarf_info[dwarf].highest_skill < skill_level)
@ -1093,16 +1090,11 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill; dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill;
dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty; dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty;
for (int labor = ENUM_FIRST_ITEM(unit_labor); labor <= ENUM_LAST_ITEM(unit_labor); labor++) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
if (labor_infos[labor].is_exclusive && dwarfs[dwarf]->status.labors[labor]) if (labor_infos[labor].is_exclusive && dwarfs[dwarf]->status.labors[labor])
dwarf_info[dwarf].mastery_penalty -= 100; dwarf_info[dwarf].mastery_penalty -= 100;
} }
@ -1120,15 +1112,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++)
{ {
// 7 / 0x7 = Newly arrived migrant, will not work yet if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak)
// 17 / 0x11 = On break
if ((*p)->id == 0x07 || (*p)->id == 0x11)
is_on_break = true; is_on_break = true;
} }
if (dwarfs[dwarf]->profession == df::enums::profession::BABY || if (dwarfs[dwarf]->profession == profession::BABY ||
dwarfs[dwarf]->profession == df::enums::profession::CHILD || dwarfs[dwarf]->profession == profession::CHILD ||
dwarfs[dwarf]->profession == df::enums::profession::DRUNK) dwarfs[dwarf]->profession == profession::DRUNK)
{ {
dwarf_info[dwarf].state = CHILD; dwarf_info[dwarf].state = CHILD;
} }
@ -1146,11 +1136,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
else else
{ {
int job = dwarfs[dwarf]->job.current_job->job_type; int job = dwarfs[dwarf]->job.current_job->job_type;
/*
assert(job >= 0);
assert(job < ARRAY_COUNT(dwarf_states));
*/
if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) if (job >= 0 && job < ARRAY_COUNT(dwarf_states))
dwarf_info[dwarf].state = dwarf_states[job]; dwarf_info[dwarf].state = dwarf_states[job];
else else
@ -1170,14 +1155,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
labor_infos[labor].active_dwarfs = 0; labor_infos[labor].active_dwarfs = 0;
labors.push_back(labor); labors.push_back(labor);
@ -1217,11 +1197,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
auto labor = *lp; auto labor = *lp;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out); assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out);
} }
@ -1241,7 +1216,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
if (labor_infos[labor].mode() != HAULERS) if (labor_infos[labor].mode() != HAULERS)
continue; continue;
@ -1264,14 +1239,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
if (labor_infos[labor].mode() != HAULERS) if (labor_infos[labor].mode() != HAULERS)
continue; continue;
@ -1311,7 +1281,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
return CR_OK; return CR_OK;
} }
void print_labor (df::enums::unit_labor::unit_labor labor, color_ostream &out) void print_labor (df::unit_labor labor, color_ostream &out)
{ {
string labor_name = ENUM_KEY_STR(unit_labor, labor); string labor_name = ENUM_KEY_STR(unit_labor, labor);
out << labor_name << ": "; out << labor_name << ": ";
@ -1358,7 +1328,6 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 2 && parameters[0] == "haulpct") else if (parameters.size() == 2 && parameters[0] == "haulpct")
{ {
if (!enable_autolabor) if (!enable_autolabor)
@ -1371,15 +1340,15 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
hauler_pct = pct; hauler_pct = pct;
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 2 || parameters.size() == 3) { else if (parameters.size() == 2 || parameters.size() == 3)
{
if (!enable_autolabor) if (!enable_autolabor)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << endl;
return CR_FAILURE; return CR_FAILURE;
} }
df::enums::unit_labor::unit_labor labor = df::enums::unit_labor::NONE; df::unit_labor labor = unit_labor::NONE;
FOR_ENUM_ITEMS(unit_labor, test_labor) FOR_ENUM_ITEMS(unit_labor, test_labor)
{ {
@ -1387,7 +1356,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
labor = test_labor; labor = test_labor;
} }
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
{ {
out.printerr("Could not find labor %s.\n", parameters[0].c_str()); out.printerr("Could not find labor %s.\n", parameters[0].c_str());
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
@ -1430,7 +1399,8 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 1 && parameters[0] == "reset-all") { else if (parameters.size() == 1 && parameters[0] == "reset-all")
{
if (!enable_autolabor) if (!enable_autolabor)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << endl;
@ -1439,12 +1409,13 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
for (int i = 0; i < labor_infos.size(); i++) for (int i = 0; i < labor_infos.size(); i++)
{ {
reset_labor((df::enums::unit_labor::unit_labor) i); reset_labor((df::unit_labor) i);
} }
out << "All labors reset." << endl; out << "All labors reset." << endl;
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") { else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status")
{
if (!enable_autolabor) if (!enable_autolabor)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << endl;
@ -1467,7 +1438,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
{ {
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
print_labor(labor, out); print_labor(labor, out);
@ -1571,7 +1542,7 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
{ {
df::building *build = world->buildings.all[i]; df::building *build = world->buildings.all[i];
auto type = build->getType(); auto type = build->getType();
if (df::enums::building_type::Stockpile == type) if (building_type::Stockpile == type)
{ {
df::building_stockpilest *sp = virtual_cast<df::building_stockpilest>(build); df::building_stockpilest *sp = virtual_cast<df::building_stockpilest>(build);
StockpileInfo *spi = new StockpileInfo(sp); StockpileInfo *spi = new StockpileInfo(sp);
@ -1580,7 +1551,7 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
} }
std::vector<df::item*> &items = world->items.other[df::enums::items_other_id::ANY_FREE]; std::vector<df::item*> &items = world->items.other[items_other_id::ANY_FREE];
// Precompute a bitmask with the bad flags // Precompute a bitmask with the bad flags
df::item_flags bad_flags; df::item_flags bad_flags;
@ -1602,13 +1573,13 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
// we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG
df::item_type typ = item->getType(); df::item_type typ = item->getType();
if (typ != df::enums::item_type::MEAT && if (typ != item_type::MEAT &&
typ != df::enums::item_type::FISH && typ != item_type::FISH &&
typ != df::enums::item_type::FISH_RAW && typ != item_type::FISH_RAW &&
typ != df::enums::item_type::PLANT && typ != item_type::PLANT &&
typ != df::enums::item_type::CHEESE && typ != item_type::CHEESE &&
typ != df::enums::item_type::FOOD && typ != item_type::FOOD &&
typ != df::enums::item_type::EGG) typ != item_type::EGG)
continue; continue;
df::item *container = 0; df::item *container = 0;
@ -1673,11 +1644,11 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
if (building) { if (building) {
df::building_type btype = building->getType(); df::building_type btype = building->getType();
if (btype == df::enums::building_type::TradeDepot || if (btype == building_type::TradeDepot ||
btype == df::enums::building_type::Wagon) btype == building_type::Wagon)
continue; // items in trade depot or the embark wagon do not rot continue; // items in trade depot or the embark wagon do not rot
if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox) if (typ == item_type::EGG && btype ==building_type::NestBox)
continue; // eggs in nest box do not rot continue; // eggs in nest box do not rot
} }

@ -50,12 +50,12 @@ command_result cleanmap (color_ostream &out, bool snow, bool mud)
// filter snow // filter snow
if(!snow if(!snow
&& spatter->mat_type == builtin_mats::WATER && spatter->mat_type == builtin_mats::WATER
&& spatter->mat_state == matter_state::Powder) && spatter->mat_state == (short)matter_state::Powder)
continue; continue;
// filter mud // filter mud
if(!mud if(!mud
&& spatter->mat_type == builtin_mats::MUD && spatter->mat_type == builtin_mats::MUD
&& spatter->mat_state == matter_state::Solid) && spatter->mat_state == (short)matter_state::Solid)
continue; continue;
delete evt; delete evt;

@ -116,7 +116,7 @@ command_result df_cleanowned (color_ostream &out, vector <string> & parameters)
} }
else if (item->flags.bits.on_ground) else if (item->flags.bits.on_ground)
{ {
int32_t type = item->getType(); df::item_type type = item->getType();
if(type == item_type::MEAT || if(type == item_type::MEAT ||
type == item_type::FISH || type == item_type::FISH ||
type == item_type::VERMIN || type == item_type::VERMIN ||

@ -18,7 +18,7 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp)
DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp)
DFHACK_PLUGIN(vshook vshook.cpp) DFHACK_PLUGIN(vshook vshook.cpp)
DFHACK_PLUGIN(steam-engine steam-engine.cpp) DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
IF(UNIX) IF(UNIX)
DFHACK_PLUGIN(ref-index ref-index.cpp) DFHACK_PLUGIN(ref-index ref-index.cpp)
ENDIF() ENDIF()

File diff suppressed because it is too large Load Diff

@ -372,7 +372,7 @@ static command_result job_cmd(color_ostream &out, vector <string> & parameters)
out << "Job item updated." << endl; out << "Job item updated." << endl;
if (item->item_type < 0 && minfo.isValid()) if (item->item_type < (df::item_type)0 && minfo.isValid())
out.printerr("WARNING: Due to a probable bug, creature & plant material subtype\n" out.printerr("WARNING: Due to a probable bug, creature & plant material subtype\n"
" is ignored unless the item type is also specified.\n"); " is ignored unless the item type is also specified.\n");

@ -0,0 +1,11 @@
local _ENV = mkmodule('plugins.power-meter')
--[[
Native functions:
* makePowerMeter(plate_info,min_power,max_power,invert)
--]]
return _ENV

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

@ -0,0 +1,45 @@
local _ENV = mkmodule('plugins.siege-engine')
--[[
Native functions:
* getTargetArea(building) -> point1, point2
* clearTargetArea(building)
* setTargetArea(building, point1, point2) -> true/false
--]]
Z_STEP_COUNT = 15
Z_STEP = 1/31
function findShotHeight(engine, target)
local path = { target = target, delta = 0.0 }
if projPathMetrics(engine, path).goal_step then
return path
end
for i = 1,Z_STEP_COUNT do
path.delta = i*Z_STEP
if projPathMetrics(engine, path).goal_step then
return path
end
path.delta = -i*Z_STEP
if projPathMetrics(engine, path).goal_step then
return path
end
end
end
function doAimProjectile(engine, item, target_min, target_max, skill)
print(item, df.skill_rating[skill])
local targets = proposeUnitHits(engine)
if #targets > 0 then
local rnd = math.random(#targets)
return findShotHeight(engine, targets[rnd].pos)
end
end
return _ENV

@ -252,6 +252,7 @@ struct UnitInfo
enum altsort_mode { enum altsort_mode {
ALTSORT_NAME, ALTSORT_NAME,
ALTSORT_PROFESSION, ALTSORT_PROFESSION,
ALTSORT_HAPPINESS,
ALTSORT_MAX ALTSORT_MAX
}; };
@ -275,6 +276,14 @@ bool sortByProfession (const UnitInfo *d1, const UnitInfo *d2)
return (d1->profession < d2->profession); return (d1->profession < d2->profession);
} }
bool sortByHappiness (const UnitInfo *d1, const UnitInfo *d2)
{
if (descending)
return (d1->unit->status.happiness > d2->unit->status.happiness);
else
return (d1->unit->status.happiness < d2->unit->status.happiness);
}
bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2)
{ {
if (sort_skill != job_skill::NONE) if (sort_skill != job_skill::NONE)
@ -310,6 +319,14 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2)
return sortByName(d1, d2); return sortByName(d1, d2);
} }
enum display_columns {
DISP_COLUMN_HAPPINESS,
DISP_COLUMN_NAME,
DISP_COLUMN_PROFESSION,
DISP_COLUMN_LABORS,
DISP_COLUMN_MAX,
};
class viewscreen_unitlaborsst : public dfhack_viewscreen { class viewscreen_unitlaborsst : public dfhack_viewscreen {
public: public:
void feed(set<df::interface_key> *events); void feed(set<df::interface_key> *events);
@ -328,10 +345,11 @@ protected:
vector<UnitInfo *> units; vector<UnitInfo *> units;
altsort_mode altsort; altsort_mode altsort;
int first_row, sel_row; int first_row, sel_row, num_rows;
int first_column, sel_column; int first_column, sel_column;
int height, name_width, prof_width, labors_width; int col_widths[DISP_COLUMN_MAX];
int col_offsets[DISP_COLUMN_MAX];
void calcSize (); void calcSize ();
}; };
@ -374,34 +392,52 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector<df::unit*> &src)
void viewscreen_unitlaborsst::calcSize() void viewscreen_unitlaborsst::calcSize()
{ {
height = gps->dimy - 10; num_rows = gps->dimy - 10;
if (height > units.size()) if (num_rows > units.size())
height = units.size(); num_rows = units.size();
name_width = prof_width = labors_width = 0; int num_columns = gps->dimx - DISP_COLUMN_MAX - 1;
for (int i = 4; i < gps->dimx; i++) for (int i = 0; i < DISP_COLUMN_MAX; i++)
col_widths[i] = 0;
while (num_columns > 0)
{ {
// 20% for Name, 20% for Profession, 60% for Labors num_columns--;
switch ((i - 4) % 5) // need at least 4 digits for happiness
if (col_widths[DISP_COLUMN_HAPPINESS] < 4)
{
col_widths[DISP_COLUMN_HAPPINESS]++;
continue;
}
// of remaining, 20% for Name, 20% for Profession, 60% for Labors
switch (num_columns % 5)
{ {
case 0: case 2: case 4: case 0: case 2: case 4:
labors_width++; col_widths[DISP_COLUMN_LABORS]++;
break; break;
case 1: case 1:
name_width++; col_widths[DISP_COLUMN_NAME]++;
break; break;
case 3: case 3:
prof_width++; col_widths[DISP_COLUMN_PROFESSION]++;
break; break;
} }
} }
while (labors_width > NUM_COLUMNS)
while (col_widths[DISP_COLUMN_LABORS] > NUM_COLUMNS)
{ {
if (labors_width & 1) col_widths[DISP_COLUMN_LABORS]--;
name_width++; if (col_widths[DISP_COLUMN_LABORS] & 1)
col_widths[DISP_COLUMN_NAME]++;
else else
prof_width++; col_widths[DISP_COLUMN_PROFESSION]++;
labors_width--; }
for (int i = 0; i < DISP_COLUMN_MAX; i++)
{
if (i == 0)
col_offsets[i] = 1;
else
col_offsets[i] = col_offsets[i - 1] + col_widths[i - 1] + 1;
} }
// don't adjust scroll position immediately after the window opened // don't adjust scroll position immediately after the window opened
@ -409,20 +445,20 @@ void viewscreen_unitlaborsst::calcSize()
return; return;
// if the window grows vertically, scroll upward to eliminate blank rows from the bottom // if the window grows vertically, scroll upward to eliminate blank rows from the bottom
if (first_row > units.size() - height) if (first_row > units.size() - num_rows)
first_row = units.size() - height; first_row = units.size() - num_rows;
// if it shrinks vertically, scroll downward to keep the cursor visible // if it shrinks vertically, scroll downward to keep the cursor visible
if (first_row < sel_row - height + 1) if (first_row < sel_row - num_rows + 1)
first_row = sel_row - height + 1; first_row = sel_row - num_rows + 1;
// if the window grows horizontally, scroll to the left to eliminate blank columns from the right // if the window grows horizontally, scroll to the left to eliminate blank columns from the right
if (first_column > NUM_COLUMNS - labors_width) if (first_column > NUM_COLUMNS - col_widths[DISP_COLUMN_LABORS])
first_column = NUM_COLUMNS - labors_width; first_column = NUM_COLUMNS - col_widths[DISP_COLUMN_LABORS];
// if it shrinks horizontally, scroll to the right to keep the cursor visible // if it shrinks horizontally, scroll to the right to keep the cursor visible
if (first_column < sel_column - labors_width + 1) if (first_column < sel_column - col_widths[DISP_COLUMN_LABORS] + 1)
first_column = sel_column - labors_width + 1; first_column = sel_column - col_widths[DISP_COLUMN_LABORS] + 1;
} }
void viewscreen_unitlaborsst::feed(set<df::interface_key> *events) void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
@ -453,8 +489,8 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
if (sel_row < first_row) if (sel_row < first_row)
first_row = sel_row; first_row = sel_row;
if (first_row < sel_row - height + 1) if (first_row < sel_row - num_rows + 1)
first_row = sel_row - height + 1; first_row = sel_row - num_rows + 1;
if (events->count(interface_key::CURSOR_LEFT) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_DOWNLEFT)) if (events->count(interface_key::CURSOR_LEFT) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_DOWNLEFT))
sel_column--; sel_column--;
@ -489,8 +525,8 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
if (sel_column < first_column) if (sel_column < first_column)
first_column = sel_column; first_column = sel_column;
if (first_column < sel_column - labors_width + 1) if (first_column < sel_column - col_widths[DISP_COLUMN_LABORS] + 1)
first_column = sel_column - labors_width + 1; first_column = sel_column - col_widths[DISP_COLUMN_LABORS] + 1;
UnitInfo *cur = units[sel_row]; UnitInfo *cur = units[sel_row];
if (events->count(interface_key::SELECT) && (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE)) if (events->count(interface_key::SELECT) && (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE))
@ -556,6 +592,9 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
case ALTSORT_PROFESSION: case ALTSORT_PROFESSION:
std::sort(units.begin(), units.end(), sortByProfession); std::sort(units.begin(), units.end(), sortByProfession);
break; break;
case ALTSORT_HAPPINESS:
std::sort(units.begin(), units.end(), sortByHappiness);
break;
} }
} }
if (events->count(interface_key::CHANGETAB)) if (events->count(interface_key::CHANGETAB))
@ -566,6 +605,9 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
altsort = ALTSORT_PROFESSION; altsort = ALTSORT_PROFESSION;
break; break;
case ALTSORT_PROFESSION: case ALTSORT_PROFESSION:
altsort = ALTSORT_HAPPINESS;
break;
case ALTSORT_HAPPINESS:
altsort = ALTSORT_NAME; altsort = ALTSORT_NAME;
break; break;
} }
@ -603,9 +645,9 @@ void viewscreen_unitlaborsst::render()
dfhack_viewscreen::render(); dfhack_viewscreen::render();
Screen::clear(); Screen::clear();
Screen::drawBorder(" Manage Labors "); Screen::drawBorder(" Dwarf Manipulator - Manage Labors ");
for (int col = 0; col < labors_width; col++) for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++)
{ {
int col_offset = col + first_column; int col_offset = col + first_column;
if (col_offset >= NUM_COLUMNS) if (col_offset >= NUM_COLUMNS)
@ -620,21 +662,21 @@ void viewscreen_unitlaborsst::render()
bg = 7; bg = 7;
} }
Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 1); Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 1);
Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 2); Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 2);
df::profession profession = columns[col_offset].profession; df::profession profession = columns[col_offset].profession;
if (profession != profession::NONE) if ((profession != profession::NONE) && (ui->race_id != -1))
{ {
auto graphics = world->raws.creatures.all[ui->race_id]->graphics; auto graphics = world->raws.creatures.all[ui->race_id]->graphics;
Screen::paintTile( Screen::paintTile(
Screen::Pen(' ', fg, 0, Screen::Pen(' ', fg, 0,
graphics.profession_add_color[creature_graphics_role::DEFAULT][profession], graphics.profession_add_color[creature_graphics_role::DEFAULT][profession],
graphics.profession_texpos[creature_graphics_role::DEFAULT][profession]), graphics.profession_texpos[creature_graphics_role::DEFAULT][profession]),
1 + name_width + 1 + prof_width + 1 + col, 3); col_offsets[DISP_COLUMN_LABORS] + col, 3);
} }
} }
for (int row = 0; row < height; row++) for (int row = 0; row < num_rows; row++)
{ {
int row_offset = row + first_row; int row_offset = row + first_row;
if (row_offset >= units.size()) if (row_offset >= units.size())
@ -643,6 +685,26 @@ void viewscreen_unitlaborsst::render()
UnitInfo *cur = units[row_offset]; UnitInfo *cur = units[row_offset];
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
int8_t fg = 15, bg = 0; int8_t fg = 15, bg = 0;
int happy = cur->unit->status.happiness;
string happiness = stl_sprintf("%4i", happy);
if (happy == 0) // miserable
fg = 13; // 5:1
else if (happy <= 25) // very unhappy
fg = 12; // 4:1
else if (happy <= 50) // unhappy
fg = 4; // 4:0
else if (happy < 75) // fine
fg = 14; // 6:1
else if (happy < 125) // quite content
fg = 6; // 6:0
else if (happy < 150) // happy
fg = 2; // 2:0
else // ecstatic
fg = 10; // 2:1
Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_HAPPINESS], 4 + row, happiness);
fg = 15;
if (row_offset == sel_row) if (row_offset == sel_row)
{ {
fg = 0; fg = 0;
@ -650,18 +712,18 @@ void viewscreen_unitlaborsst::render()
} }
string name = cur->name; string name = cur->name;
name.resize(name_width); name.resize(col_widths[DISP_COLUMN_NAME]);
Screen::paintString(Screen::Pen(' ', fg, bg), 1, 4 + row, name); Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_NAME], 4 + row, name);
string profession = cur->profession; string profession = cur->profession;
profession.resize(prof_width); profession.resize(col_widths[DISP_COLUMN_PROFESSION]);
fg = cur->color; fg = cur->color;
bg = 0; bg = 0;
Screen::paintString(Screen::Pen(' ', fg, bg), 1 + name_width + 1, 4 + row, profession); Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_PROFESSION], 4 + row, profession);
// Print unit's skills and labor assignments // Print unit's skills and labor assignments
for (int col = 0; col < labors_width; col++) for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++)
{ {
int col_offset = col + first_column; int col_offset = col + first_column;
fg = 15; fg = 15;
@ -693,7 +755,7 @@ void viewscreen_unitlaborsst::render()
} }
else else
bg = 4; bg = 4;
Screen::paintTile(Screen::Pen(c, fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 4 + row); Screen::paintTile(Screen::Pen(c, fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 4 + row);
} }
} }
@ -703,17 +765,17 @@ void viewscreen_unitlaborsst::render()
{ {
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
int x = 1; int x = 1;
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->transname); Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, cur->transname);
x += cur->transname.length(); x += cur->transname.length();
if (cur->transname.length()) if (cur->transname.length())
{ {
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ", "); Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, ", ");
x += 2; x += 2;
} }
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->profession); Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, cur->profession);
x += cur->profession.length(); x += cur->profession.length();
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ": "); Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, ": ");
x += 2; x += 2;
string str; string str;
@ -740,7 +802,7 @@ void viewscreen_unitlaborsst::render()
else else
str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill)); str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill));
} }
Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + height + 2, str); Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + num_rows + 2, str);
canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE); canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE);
} }
@ -779,6 +841,9 @@ void viewscreen_unitlaborsst::render()
case ALTSORT_PROFESSION: case ALTSORT_PROFESSION:
OutputString(15, x, gps->dimy - 2, "Profession"); OutputString(15, x, gps->dimy - 2, "Profession");
break; break;
case ALTSORT_HAPPINESS:
OutputString(15, x, gps->dimy - 2, "Happiness");
break;
default: default:
OutputString(15, x, gps->dimy - 2, "Unknown"); OutputString(15, x, gps->dimy - 2, "Unknown");
break; break;
@ -810,7 +875,7 @@ struct unitlist_hook : df::viewscreen_unitlistst
{ {
int x = 2; int x = 2;
OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key
OutputString(15, x, gps->dimy - 2, ": Manage labors"); OutputString(15, x, gps->dimy - 2, ": Manage labors (DFHack)");
} }
} }
}; };

@ -0,0 +1,237 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <Error.h>
#include <PluginManager.h>
#include <modules/Gui.h>
#include <modules/Screen.h>
#include <modules/Maps.h>
#include <modules/World.h>
#include <TileTypes.h>
#include <vector>
#include <cstdio>
#include <stack>
#include <string>
#include <cmath>
#include <string.h>
#include <VTableInterpose.h>
#include "df/graphic.h"
#include "df/building_trapst.h"
#include "df/builtin_mats.h"
#include "df/world.h"
#include "df/buildings_other_id.h"
#include "df/machine.h"
#include "df/machine_info.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"
using std::vector;
using std::string;
using std::stack;
using namespace DFHack;
using namespace df::enums;
using df::global::gps;
using df::global::world;
using df::global::ui;
using df::global::ui_build_selector;
DFHACK_PLUGIN("power-meter");
static const uint32_t METER_BIT = 0x80000000U;
static void init_plate_info(df::pressure_plate_info &plate_info)
{
plate_info.water_min = 1;
plate_info.water_max = 7;
plate_info.flags.whole = METER_BIT;
plate_info.flags.bits.water = true;
plate_info.flags.bits.resets = true;
}
/*
* Hook for the pressure plate itself. Implements core logic.
*/
struct trap_hook : df::building_trapst {
typedef df::building_trapst interpose_base;
// Engine detection
bool is_power_meter()
{
return trap_type == trap_type::PressurePlate &&
(plate_info.flags.whole & METER_BIT) != 0;
}
inline bool is_fully_built()
{
return getBuildStage() >= getMaxBuildStage();
}
DEFINE_VMETHOD_INTERPOSE(void, getName, (std::string *buf))
{
if (is_power_meter())
{
buf->clear();
*buf += "Power Meter";
return;
}
INTERPOSE_NEXT(getName)(buf);
}
DEFINE_VMETHOD_INTERPOSE(void, updateAction, ())
{
if (is_power_meter())
{
auto pdsgn = Maps::getTileDesignation(centerx,centery,z);
if (pdsgn)
{
bool active = false;
auto &gears = world->buildings.other[buildings_other_id::GEAR_ASSEMBLY];
for (size_t i = 0; i < gears.size(); i++)
{
// Adjacent
auto gear = gears[i];
int deltaxy = abs(centerx - gear->centerx) + abs(centery - gear->centery);
if (gear->z != z || deltaxy != 1)
continue;
// Linked to machine
auto info = gears[i]->getMachineInfo();
if (!info || info->machine_id < 0)
continue;
// an active machine
auto machine = df::machine::find(info->machine_id);
if (!machine || !machine->flags.bits.active)
continue;
// with adequate power?
int power = machine->cur_power - machine->min_power;
if (power < 0 || machine->cur_power <= 0)
continue;
if (power < plate_info.track_min)
continue;
if (power > plate_info.track_max && plate_info.track_max >= 0)
continue;
active = true;
break;
}
if (plate_info.flags.bits.citizens)
active = !active;
// Temporarily set the tile water amount based on power state
auto old_dsgn = *pdsgn;
pdsgn->bits.liquid_type = tile_liquid::Water;
pdsgn->bits.flow_size = (active ? 7 : 0);
INTERPOSE_NEXT(updateAction)();
*pdsgn = old_dsgn;
return;
}
}
INTERPOSE_NEXT(updateAction)();
}
DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, void *unk))
{
INTERPOSE_NEXT(drawBuilding)(db, unk);
if (is_power_meter() && is_fully_built())
{
db->fore[0][0] = 3;
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, getName);
IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, updateAction);
IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, drawBuilding);
static bool enabled = false;
static void enable_hooks(bool enable)
{
enabled = enable;
INTERPOSE_HOOK(trap_hook, getName).apply(enable);
INTERPOSE_HOOK(trap_hook, updateAction).apply(enable);
INTERPOSE_HOOK(trap_hook, drawBuilding).apply(enable);
}
static bool makePowerMeter(df::pressure_plate_info *info, int min_power, int max_power, bool invert)
{
CHECK_NULL_POINTER(info);
if (!enabled)
{
auto pworld = Core::getInstance().getWorld();
auto entry = pworld->GetPersistentData("power-meter/enabled", NULL);
if (!entry.isValid())
return false;
enable_hooks(true);
}
init_plate_info(*info);
info->track_min = min_power;
info->track_max = max_power;
info->flags.bits.citizens = invert;
return true;
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(makePowerMeter),
DFHACK_LUA_END
};
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
{
auto pworld = Core::getInstance().getWorld();
bool enable = pworld->GetPersistentData("power-meter/enabled").isValid();
if (enable)
{
out.print("Enabling the power meter plugin.\n");
enable_hooks(true);
}
}
break;
case SC_MAP_UNLOADED:
enable_hooks(false);
break;
default:
break;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (Core::getInstance().isMapLoaded())
plugin_onstatechange(out, SC_MAP_LOADED);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
enable_hooks(false);
return CR_OK;
}

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

@ -4,8 +4,8 @@ item_trapcomp_steam_engine
[ITEM_TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON] [ITEM_TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON]
[NAME:piston:pistons] [NAME:piston:pistons]
[ADJECTIVE:huge] [ADJECTIVE:heavy]
[SIZE:1600] [SIZE:1800]
[HITS:1] [HITS:1]
[MATERIAL_SIZE:6] [MATERIAL_SIZE:6]
[METAL] [METAL]

@ -3,11 +3,15 @@
#include "Export.h" #include "Export.h"
#include "PluginManager.h" #include "PluginManager.h"
#include <Error.h>
#include <LuaTools.h>
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Translation.h" #include "modules/Translation.h"
#include "modules/Units.h" #include "modules/Units.h"
#include "modules/World.h"
#include "DataDefs.h" #include <VTableInterpose.h>
#include "df/ui.h" #include "df/ui.h"
#include "df/world.h" #include "df/world.h"
#include "df/squad.h" #include "df/squad.h"
@ -18,6 +22,11 @@
#include "df/historical_figure_info.h" #include "df/historical_figure_info.h"
#include "df/assumed_identity.h" #include "df/assumed_identity.h"
#include "df/language_name.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 "RemoteServer.h"
#include "rename.pb.h" #include "rename.pb.h"
@ -36,6 +45,8 @@ using namespace dfproto;
using df::global::ui; using df::global::ui;
using df::global::world; 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); static command_result rename(color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("rename"); DFHACK_PLUGIN("rename");
@ -51,8 +62,32 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" rename unit \"nickname\"\n" " rename unit \"nickname\"\n"
" rename unit-profession \"custom profession\"\n" " rename unit-profession \"custom profession\"\n"
" (a unit must be highlighted in the ui)\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; return CR_OK;
} }
@ -61,6 +96,133 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK; 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) static df::squad *getSquadByIndex(unsigned idx)
{ {
auto entity = df::historical_entity::find(ui->group_id); 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; 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 &) DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
{ {
RPCService *svc = new RPCService(); RPCService *svc = new RPCService();
svc->addFunction("RenameSquad", RenameSquad); svc->addFunction("RenameSquad", RenameSquad);
svc->addFunction("RenameUnit", RenameUnit); svc->addFunction("RenameUnit", RenameUnit);
svc->addFunction("RenameBuilding", RenameBuilding);
return svc; 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) static command_result rename(color_ostream &out, vector <string> &parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;
@ -167,6 +352,20 @@ static command_result rename(color_ostream &out, vector <string> &parameters)
unit->custom_profession = parameters[1]; 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 else
{ {
if (!parameters.empty() && cmd != "?") if (!parameters.empty() && cmd != "?")

@ -34,6 +34,82 @@
#include "MiscUtils.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::vector;
using std::string; using std::string;
using std::stack; using std::stack;
@ -465,9 +541,11 @@ struct workshop_hook : df::building_workshopst {
power_rate = 0.0f; power_rate = 0.0f;
} }
// waste rate: 1-10% depending on piston assembly quality // waste rate: 1-10% depending on piston assembly quality
float waste = 0.1f - 0.015f * get_component_quality(1); 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 // apply rate and waste factor
ticks *= (waste + 0.9f*power_rate)*power_level; ticks *= (waste + 0.9f*power_rate)*power_level*efficiency_coeff;
// end result // end result
return std::max(1, int(ticks)); return std::max(1, int(ticks));
} }

@ -1 +1 @@
Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e Subproject commit 37a823541538023b9f3d0d1e8039cf32851de68d

@ -767,7 +767,7 @@ command_result executePaintJob(color_ostream &out)
} }
// Remove liquid from walls, etc // Remove liquid from walls, etc
if (type != -1 && !DFHack::FlowPassable(type)) if (type != (df::tiletype)-1 && !DFHack::FlowPassable(type))
{ {
des.bits.flow_size = 0; des.bits.flow_size = 0;
//des.bits.liquid_type = DFHack::liquid_water; //des.bits.liquid_type = DFHack::liquid_water;

@ -828,7 +828,7 @@ static void compute_custom_job(ProtectedJob *pj, df::job *job)
using namespace df::enums::reaction_product_item_flags; using namespace df::enums::reaction_product_item_flags;
VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]); VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]);
if (!prod || (prod->item_type < 0 && !prod->flags.is_set(CRAFTS))) if (!prod || (prod->item_type < (df::item_type)0 && !prod->flags.is_set(CRAFTS)))
continue; continue;
MaterialInfo mat(prod); MaterialInfo mat(prod);

@ -792,7 +792,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false)
bool isActivityZone(df::building * building) bool isActivityZone(df::building * building)
{ {
if( building->getType() == building_type::Civzone if( building->getType() == building_type::Civzone
&& building->getSubtype() == civzone_type::ActivityZone) && building->getSubtype() == (short)civzone_type::ActivityZone)
return true; return true;
else else
return false; return false;
@ -1603,7 +1603,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose)
if(building->getType()!= building_type::Civzone) if(building->getType()!= building_type::Civzone)
return; return;
if(building->getSubtype() != civzone_type::ActivityZone) if(building->getSubtype() != (short)civzone_type::ActivityZone)
return; return;
string name; string name;

@ -0,0 +1,3 @@
-- For killing bugged out gui script screens.
dfhack.screen.dismiss(dfhack.gui.getCurViewscreen())

@ -3,6 +3,7 @@
local utils = require 'utils' local utils = require 'utils'
local gui = require 'gui' local gui = require 'gui'
local guidm = require 'gui.dwarfmode' local guidm = require 'gui.dwarfmode'
local dlg = require 'gui.dialogs'
local liquids = require('plugins.liquids') local liquids = require('plugins.liquids')
@ -199,6 +200,42 @@ function LiquidsUI:onRenderBody(dc)
dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint") dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint")
end end
function ensure_blocks(cursor, size, cb)
local cx,cy,cz = pos2xyz(cursor)
local all = true
for x=1,size.x or 1,16 do
for y=1,size.y or 1,16 do
for z=1,size.z do
if not dfhack.maps.getTileBlock(cx+x-1, cy+y-1, cz+z-1) then
all = false
end
end
end
end
if all then
cb()
return
end
dlg.showYesNoPrompt(
'Instantiate Blocks',
'Not all map blocks are allocated - instantiate?\n\nWarning: new untested feature.',
COLOR_YELLOW,
function()
for x=1,size.x or 1,16 do
for y=1,size.y or 1,16 do
for z=1,size.z do
dfhack.maps.ensureTileBlock(cx+x-1, cy+y-1, cz+z-1)
end
end
end
cb()
end,
function()
cb()
end
)
end
function LiquidsUI:onInput(keys) function LiquidsUI:onInput(keys)
local paint = self.paint:get() local paint = self.paint:get()
local liquid = paint.liquid local liquid = paint.liquid
@ -239,13 +276,15 @@ function LiquidsUI:onInput(keys)
else else
guidm.clearSelection() guidm.clearSelection()
end end
liquids.paint( local cb = curry(
liquids.paint,
cursor, cursor,
self.brush:get().tag, self.paint:get().tag, self.brush:get().tag, self.paint:get().tag,
self.amount, size, self.amount, size,
self.set:get().tag, self.flow:get().tag, self.set:get().tag, self.flow:get().tag,
self.permaflow:get().tag self.permaflow:get().tag
) )
ensure_blocks(cursor, size, cb)
elseif self:propagateMoveKeys(keys) then elseif self:propagateMoveKeys(keys) then
return return
elseif keys.D_LOOK_ARENA_WATER then elseif keys.D_LOOK_ARENA_WATER then

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

@ -0,0 +1,116 @@
-- Interface front-end for power-meter plugin.
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local dlg = require 'gui.dialogs'
local plugin = require('plugins.power-meter')
local bselector = df.global.ui_build_selector
PowerMeter = defclass(PowerMeter, guidm.MenuOverlay)
PowerMeter.focus_path = 'power-meter'
function PowerMeter:init()
self:init_fields{
min_power = 0, max_power = -1, invert = false,
}
guidm.MenuOverlay.init(self)
return self
end
function PowerMeter:onShow()
guidm.MenuOverlay.onShow(self)
-- Send an event to update the errors
bselector.plate_info.flags.whole = 0
self:sendInputToParent('BUILDING_TRIGGER_ENABLE_WATER')
end
function PowerMeter:onRenderBody(dc)
dc:fill(0,0,dc.width-1,13,gui.CLEAR_PEN)
dc:seek(1,1):pen(COLOR_WHITE)
dc:string("Power Meter"):newline():newline(1)
dc:string("Placement"):newline():newline(1)
dc:string("Excess power range:")
dc:newline(3):string("as", COLOR_LIGHTGREEN)
dc:string(": Min ")
if self.min_power <= 0 then
dc:string("(any)")
else
dc:string(''..self.min_power)
end
dc:newline(3):string("zx", COLOR_LIGHTGREEN)
dc:string(": Max ")
if self.max_power < 0 then
dc:string("(any)")
else
dc:string(''..self.max_power)
end
dc:newline():newline(1)
dc:string("i",COLOR_LIGHTGREEN):string(": ")
if self.invert then
dc:string("Inverted")
else
dc:string("Not inverted")
end
end
function PowerMeter:onInput(keys)
if keys.CUSTOM_I then
self.invert = not self.invert
elseif keys.BUILDING_TRIGGER_MIN_WATER_UP then
self.min_power = self.min_power + 10
elseif keys.BUILDING_TRIGGER_MIN_WATER_DOWN then
self.min_power = math.max(0, self.min_power - 10)
elseif keys.BUILDING_TRIGGER_MAX_WATER_UP then
if self.max_power < 0 then
self.max_power = 0
else
self.max_power = self.max_power + 10
end
elseif keys.BUILDING_TRIGGER_MAX_WATER_DOWN then
self.max_power = math.max(-1, self.max_power - 10)
elseif keys.LEAVESCREEN then
self:dismiss()
self:sendInputToParent('LEAVESCREEN')
elseif keys.SELECT then
if #bselector.errors == 0 then
if not plugin.makePowerMeter(
bselector.plate_info,
self.min_power, self.max_power, self.invert
)
then
dlg.showMessage(
'Power Meter',
'Could not initialize.', COLOR_LIGHTRED
)
self:dismiss()
self:sendInputToParent('LEAVESCREEN')
return
end
self:sendInputToParent('SELECT')
if bselector.stage ~= 1 then
self:dismiss()
end
end
elseif self:propagateMoveKeys(keys) then
return
end
end
if dfhack.gui.getCurFocus() ~= 'dwarfmode/Build/Position/Trap'
or bselector.building_subtype ~= df.trap_type.PressurePlate
then
qerror("This script requires the main dwarfmode view in build pressure plate mode")
end
local list = mkinstance(PowerMeter):init()
list:show()

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

@ -0,0 +1,490 @@
-- Front-end for the siege engine plugin.
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local dlg = require 'gui.dialogs'
local plugin = require 'plugins.siege-engine'
local wmap = df.global.world.map
local LEGENDARY = df.skill_rating.Legendary
-- Globals kept between script calls
last_target_min = last_target_min or nil
last_target_max = last_target_max or nil
local item_choices = {
{ caption = 'boulders (default)', item_type = df.item_type.BOULDER },
{ caption = 'blocks', item_type = df.item_type.BLOCKS },
{ caption = 'weapons', item_type = df.item_type.WEAPON },
{ caption = 'trap components', item_type = df.item_type.TRAPCOMP },
{ caption = 'bins', item_type = df.item_type.BIN },
{ caption = 'barrels', item_type = df.item_type.BARREL },
{ caption = 'anything', item_type = -1 },
}
local item_choice_idx = {}
for i,v in ipairs(item_choices) do
item_choice_idx[v.item_type] = i
end
SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay)
SiegeEngine.focus_path = 'siege-engine'
function SiegeEngine:init(building)
self:init_fields{
building = building,
center = utils.getBuildingCenter(building),
selected_pile = 1,
}
guidm.MenuOverlay.init(self)
self.mode_main = {
render = self:callback 'onRenderBody_main',
input = self:callback 'onInput_main',
}
self.mode_aim = {
render = self:callback 'onRenderBody_aim',
input = self:callback 'onInput_aim',
}
self.mode_pile = {
render = self:callback 'onRenderBody_pile',
input = self:callback 'onInput_pile',
}
return self
end
function SiegeEngine:onShow()
guidm.MenuOverlay.onShow(self)
self.old_cursor = guidm.getCursorPos()
self.old_viewport = self:getViewport()
self.mode = self.mode_main
self:showCursor(false)
end
function SiegeEngine:onDestroy()
if self.save_profile then
plugin.saveWorkshopProfile(self.building)
end
if not self.no_select_building then
self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10)
end
end
function SiegeEngine:showCursor(enable)
local cursor = guidm.getCursorPos()
if cursor and not enable then
self.cursor = cursor
self.target_select_first = nil
guidm.clearCursorPos()
elseif not cursor and enable then
local view = self:getViewport()
cursor = self.cursor
if not cursor or not view:isVisible(cursor) then
cursor = view:getCenter()
end
self.cursor = nil
guidm.setCursorPos(cursor)
end
end
function SiegeEngine:centerViewOn(pos)
local cursor = guidm.getCursorPos()
if cursor then
guidm.setCursorPos(pos)
else
self.cursor = pos
end
self:getViewport():centerOn(pos):set()
end
function SiegeEngine:zoomToTarget()
local target_min, target_max = plugin.getTargetArea(self.building)
if target_min then
local cx = math.floor((target_min.x + target_max.x)/2)
local cy = math.floor((target_min.y + target_max.y)/2)
local cz = math.floor((target_min.z + target_max.z)/2)
local pos = plugin.adjustToTarget(self.building, xyz2pos(cx,cy,cz))
self:centerViewOn(pos)
end
end
function paint_target_grid(dc, view, origin, p1, p2)
local r1, sz, r2 = guidm.getSelectionRange(p1, p2)
if view.z < r1.z or view.z > r2.z then
return
end
local p1 = view:tileToScreen(r1)
local p2 = view:tileToScreen(r2)
local org = view:tileToScreen(origin)
dc:pen{ fg = COLOR_CYAN, bg = COLOR_CYAN, ch = '+', bold = true }
-- Frame
dc:fill(p1.x,p1.y,p1.x,p2.y)
dc:fill(p1.x,p1.y,p2.x,p1.y)
dc:fill(p2.x,p1.y,p2.x,p2.y)
dc:fill(p1.x,p2.y,p2.x,p2.y)
-- Grid
local gxmin = org.x+10*math.ceil((p1.x-org.x)/10)
local gxmax = org.x+10*math.floor((p2.x-org.x)/10)
local gymin = org.y+10*math.ceil((p1.y-org.y)/10)
local gymax = org.y+10*math.floor((p2.y-org.y)/10)
for x = gxmin,gxmax,10 do
for y = gymin,gymax,10 do
dc:fill(p1.x,y,p2.x,y)
dc:fill(x,p1.y,x,p2.y)
end
end
end
function SiegeEngine:renderTargetView(target_min, target_max)
local view = self:getViewport()
local map = self.df_layout.map
local map_dc = gui.Painter.new(map)
plugin.paintAimScreen(
self.building, view:getPos(),
xy2pos(map.x1, map.y1), view:getSize()
)
if target_min and math.floor(dfhack.getTickCount()/500) % 2 == 0 then
paint_target_grid(map_dc, view, self.center, target_min, target_max)
end
local cursor = guidm.getCursorPos()
if cursor then
local cx, cy, cz = pos2xyz(view:tileToScreen(cursor))
if cz == 0 then
map_dc:seek(cx,cy):char('X', COLOR_YELLOW)
end
end
end
function SiegeEngine:scrollPiles(delta)
local links = plugin.getStockpileLinks(self.building)
if links then
self.selected_pile = 1+(self.selected_pile+delta-1) % #links
return links[self.selected_pile]
end
end
function SiegeEngine:renderStockpiles(dc, links, nlines)
local idx = (self.selected_pile-1) % #links
local page = math.floor(idx/nlines)
for i = page*nlines,math.min(#links,(page+1)*nlines)-1 do
local color = COLOR_BROWN
if i == idx then
color = COLOR_YELLOW
end
dc:newline(2):string(utils.getBuildingName(links[i+1]), color)
end
end
function SiegeEngine:onRenderBody_main(dc)
dc:newline(1):pen(COLOR_WHITE):string("Target: ")
local target_min, target_max = plugin.getTargetArea(self.building)
if target_min then
dc:string(
(target_max.x-target_min.x+1).."x"..
(target_max.y-target_min.y+1).."x"..
(target_max.z-target_min.z+1).." Rect"
)
else
dc:string("None (default)")
end
dc:newline(3):string("r",COLOR_LIGHTGREEN):string(": Rectangle")
if last_target_min then
dc:string(", "):string("p",COLOR_LIGHTGREEN):string(": Paste")
end
dc:newline(3)
if target_min then
dc:string("x",COLOR_LIGHTGREEN):string(": Clear, ")
dc:string("z",COLOR_LIGHTGREEN):string(": Zoom")
end
dc:newline():newline(1)
if self.building.type == df.siegeengine_type.Ballista then
dc:string("Uses ballista arrows")
else
local item = plugin.getAmmoItem(self.building)
dc:string("u",COLOR_LIGHTGREEN):string(": Use ")
if item_choice_idx[item] then
dc:string(item_choices[item_choice_idx[item]].caption)
else
dc:string(df.item_type[item])
end
end
dc:newline():newline(1)
dc:string("t",COLOR_LIGHTGREEN):string(": Take from stockpile"):newline(3)
local links = plugin.getStockpileLinks(self.building)
local bottom = dc.height - 5
if links then
dc:string("d",COLOR_LIGHTGREEN):string(": Delete, ")
dc:string("o",COLOR_LIGHTGREEN):string(": Zoom"):newline()
self:renderStockpiles(dc, links, bottom-2-dc:localY())
dc:newline():newline()
end
local prof = self.building:getWorkshopProfile() or {}
dc:seek(1,math.max(dc:localY(),19)):string('ghjk',COLOR_LIGHTGREEN)dc:string(': ')
dc:string(df.skill_rating.attrs[prof.min_level or 0].caption):string('-')
dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption)
dc:newline():newline()
if self.target_select_first then
self:renderTargetView(self.target_select_first, guidm.getCursorPos())
else
self:renderTargetView(target_min, target_max)
end
end
function SiegeEngine:setTargetArea(p1, p2)
self.target_select_first = nil
if not plugin.setTargetArea(self.building, p1, p2) then
dlg.showMessage(
'Set Target Area',
'Could not set the target area', COLOR_LIGHTRED
)
else
last_target_min = p1
last_target_max = p2
end
end
function SiegeEngine:setAmmoItem(choice)
if self.building.type == df.siegeengine_type.Ballista then
return
end
if not plugin.setAmmoItem(self.building, choice.item_type) then
dlg.showMessage(
'Set Ammo Item',
'Could not set the ammo item', COLOR_LIGHTRED
)
end
end
function SiegeEngine:onInput_main(keys)
if keys.CUSTOM_R then
self:showCursor(true)
self.target_select_first = nil
self.mode = self.mode_aim
elseif keys.CUSTOM_P and last_target_min then
self:setTargetArea(last_target_min, last_target_max)
elseif keys.CUSTOM_U then
local item = plugin.getAmmoItem(self.building)
local idx = 1 + (item_choice_idx[item] or 0) % #item_choices
self:setAmmoItem(item_choices[idx])
elseif keys.CUSTOM_Z then
self:zoomToTarget()
elseif keys.CUSTOM_X then
plugin.clearTargetArea(self.building)
elseif keys.SECONDSCROLL_UP then
self:scrollPiles(-1)
elseif keys.SECONDSCROLL_DOWN then
self:scrollPiles(1)
elseif keys.CUSTOM_D then
local pile = self:scrollPiles(0)
if pile then
plugin.removeStockpileLink(self.building, pile)
end
elseif keys.CUSTOM_O then
local pile = self:scrollPiles(0)
if pile then
self:centerViewOn(utils.getBuildingCenter(pile))
end
elseif keys.CUSTOM_T then
self:showCursor(true)
self.mode = self.mode_pile
self:sendInputToParent('CURSOR_DOWN_Z')
self:sendInputToParent('CURSOR_UP_Z')
elseif keys.CUSTOM_G then
local prof = plugin.saveWorkshopProfile(self.building)
prof.min_level = math.max(0, prof.min_level-1)
plugin.saveWorkshopProfile(self.building)
elseif keys.CUSTOM_H then
local prof = plugin.saveWorkshopProfile(self.building)
prof.min_level = math.min(LEGENDARY, prof.min_level+1)
plugin.saveWorkshopProfile(self.building)
elseif keys.CUSTOM_J then
local prof = plugin.saveWorkshopProfile(self.building)
prof.max_level = math.max(0, math.min(LEGENDARY,prof.max_level)-1)
plugin.saveWorkshopProfile(self.building)
elseif keys.CUSTOM_K then
local prof = plugin.saveWorkshopProfile(self.building)
prof.max_level = math.min(LEGENDARY, prof.max_level+1)
if prof.max_level >= LEGENDARY then prof.max_level = 3000 end
plugin.saveWorkshopProfile(self.building)
elseif self:simulateViewScroll(keys) then
self.cursor = nil
else
return false
end
return true
end
local status_table = {
ok = { pen = COLOR_GREEN, msg = "Target accessible" },
out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" },
blocked = { pen = COLOR_RED, msg = "Target obstructed" },
semi_blocked = { pen = COLOR_BROWN, msg = "Partially obstructed" },
}
function SiegeEngine:onRenderBody_aim(dc)
local cursor = guidm.getCursorPos()
local first = self.target_select_first
dc:newline(1):string('Select target rectangle'):newline()
local info = status_table[plugin.getTileStatus(self.building, cursor)]
if info then
dc:newline(2):string(info.msg, info.pen)
else
dc:newline(2):string('ERROR', COLOR_RED)
end
dc:newline():newline(1):string("Enter",COLOR_LIGHTGREEN)
if first then
dc:string(": Finish rectangle")
else
dc:string(": Start rectangle")
end
dc:newline()
local target_min, target_max = plugin.getTargetArea(self.building)
if target_min then
dc:newline(1):string("z",COLOR_LIGHTGREEN):string(": Zoom to current target")
end
if first then
self:renderTargetView(first, cursor)
else
local target_min, target_max = plugin.getTargetArea(self.building)
self:renderTargetView(target_min, target_max)
end
end
function SiegeEngine:onInput_aim(keys)
if keys.SELECT then
local cursor = guidm.getCursorPos()
if self.target_select_first then
self:setTargetArea(self.target_select_first, cursor)
self.mode = self.mode_main
self:showCursor(false)
else
self.target_select_first = cursor
end
elseif keys.CUSTOM_Z then
self:zoomToTarget()
elseif keys.LEAVESCREEN then
self.mode = self.mode_main
self:showCursor(false)
elseif self:simulateCursorMovement(keys) then
self.cursor = nil
else
return false
end
return true
end
function SiegeEngine:onRenderBody_pile(dc)
dc:newline(1):string('Select pile to take from'):newline():newline(2)
local sel = df.global.world.selected_building
if df.building_stockpilest:is_instance(sel) then
dc:string(utils.getBuildingName(sel), COLOR_GREEN):newline():newline(1)
if plugin.isLinkedToPile(self.building, sel) then
dc:string("Already taking from here"):newline():newline(2)
dc:string("d", COLOR_LIGHTGREEN):string(": Delete link")
else
dc:string("Enter",COLOR_LIGHTGREEN):string(": Take from this pile")
end
elseif sel then
dc:string(utils.getBuildingName(sel), COLOR_DARKGREY)
dc:newline():newline(1)
dc:string("Not a stockpile",COLOR_LIGHTRED)
else
dc:string("No building selected", COLOR_DARKGREY)
end
end
function SiegeEngine:onInput_pile(keys)
if keys.SELECT then
local sel = df.global.world.selected_building
if df.building_stockpilest:is_instance(sel)
and not plugin.isLinkedToPile(self.building, sel) then
plugin.addStockpileLink(self.building, sel)
df.global.world.selected_building = self.building
self.mode = self.mode_main
self:showCursor(false)
end
elseif keys.CUSTOM_D then
local sel = df.global.world.selected_building
if df.building_stockpilest:is_instance(sel) then
plugin.removeStockpileLink(self.building, sel)
end
elseif keys.LEAVESCREEN then
df.global.world.selected_building = self.building
self.mode = self.mode_main
self:showCursor(false)
elseif self:propagateMoveKeys(keys) then
--
else
return false
end
return true
end
function SiegeEngine:onRenderBody(dc)
dc:clear()
dc:seek(1,1):pen(COLOR_WHITE):string(utils.getBuildingName(self.building)):newline()
self.mode.render(dc)
dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE)
dc:string("ESC", COLOR_LIGHTGREEN):string(": Back, ")
dc:string("c", COLOR_LIGHTGREEN):string(": Recenter")
end
function SiegeEngine:onInput(keys)
if self.mode.input(keys) then
--
elseif keys.CUSTOM_C then
self:centerViewOn(self.center)
elseif keys.LEAVESCREEN then
self:dismiss()
elseif keys.LEAVESCREEN_ALL then
self:dismiss()
self.no_select_building = true
guidm.clearCursorPos()
df.global.ui.main.mode = df.ui_sidebar_mode.Default
df.global.world.selected_building = nil
end
end
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/SiegeEngine') then
qerror("This script requires a siege engine selected in 'q' mode")
end
local building = df.global.world.selected_building
if not df.building_siegeenginest:is_instance(building) then
qerror("A siege engine must be selected")
end
local list = mkinstance(SiegeEngine):init(df.global.world.selected_building)
list:show()