Merge branch 'master' of git://github.com/angavrilov/dfhack

develop
jj 2012-09-14 14:30:25 +02:00
commit 5474ccacb6
79 changed files with 6232 additions and 389 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.
@ -764,10 +782,20 @@ Gui module
Adds a regular announcement with given text, color, and brightness. Adds a regular announcement with given text, color, and brightness.
The is_bright boolean actually seems to invert the brightness. The is_bright boolean actually seems to invert the brightness.
* ``dfhack.gui.showZoomAnnouncement(type,pos,text,color[,is_bright])``
Like above, but also specifies a position you can zoom to from the announcement menu.
* ``dfhack.gui.showPopupAnnouncement(text,color[,is_bright])`` * ``dfhack.gui.showPopupAnnouncement(text,color[,is_bright])``
Pops up a titan-style modal announcement window. Pops up a titan-style modal announcement window.
* ``dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])``
Uses the type to look up options from announcements.txt, and calls the
above operations accordingly. If enabled, pauses and zooms to position.
Job module Job module
---------- ----------
@ -840,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.
@ -866,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*.
@ -943,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
----------- -----------
@ -959,10 +1019,18 @@ Maps module
Returns a map block object for given x,y,z in local block coordinates. Returns a map block object for given x,y,z in local block coordinates.
* ``dfhack.maps.isValidTilePos(coords)``, or isValidTilePos(x,y,z)``
Checks if the given df::coord or x,y,z in local tile coordinates are valid.
* ``dfhack.maps.getTileBlock(coords)``, or ``getTileBlock(x,y,z)`` * ``dfhack.maps.getTileBlock(coords)``, or ``getTileBlock(x,y,z)``
Returns a map block object for given df::coord or x,y,z in local tile coordinates. 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.
@ -971,6 +1039,11 @@ Maps module
Enables updates for liquid flow or temperature, unless already active. Enables updates for liquid flow or temperature, unless already active.
* ``dfhack.maps.spawnFlow(pos,type,mat_type,mat_index,dimension)``
Spawns a new flow (i.e. steam/mist/dust/etc) at the given pos, and with
the given parameters. Returns it, or *nil* if unsuccessful.
* ``dfhack.maps.getGlobalInitFeature(index)`` * ``dfhack.maps.getGlobalInitFeature(index)``
Returns the global feature object with the given index. Returns the global feature object with the given index.
@ -1267,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>,
@ -1019,13 +1036,20 @@ the container itself.</p>
<p>Adds a regular announcement with given text, color, and brightness. <p>Adds a regular announcement with given text, color, and brightness.
The is_bright boolean actually seems to invert the brightness.</p> The is_bright boolean actually seems to invert the brightness.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showZoomAnnouncement(type,pos,text,color[,is_bright])</span></tt></p>
<p>Like above, but also specifies a position you can zoom to from the announcement menu.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showPopupAnnouncement(text,color[,is_bright])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showPopupAnnouncement(text,color[,is_bright])</span></tt></p>
<p>Pops up a titan-style modal announcement window.</p> <p>Pops up a titan-style modal announcement window.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])</span></tt></p>
<p>Uses the type to look up options from announcements.txt, and calls the
above operations accordingly. If enabled, pauses and zooms to position.</p>
</li>
</ul> </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>
@ -1062,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>
@ -1082,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>
@ -1102,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>
@ -1123,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>
@ -1163,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>
@ -1177,15 +1230,25 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getBlock(x,y,z)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.maps.getBlock(x,y,z)</tt></p>
<p>Returns a map block object for given x,y,z in local block coordinates.</p> <p>Returns a map block object for given x,y,z in local block coordinates.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.isValidTilePos(coords)</tt>, or isValidTilePos(x,y,z)``</p>
<p>Checks if the given df::coord or x,y,z in local tile coordinates are valid.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBlock(coords)</tt>, or <tt class="docutils literal">getTileBlock(x,y,z)</tt></p> <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>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.maps.enableBlockUpdates(block[,flow,temperature])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.maps.enableBlockUpdates(block[,flow,temperature])</span></tt></p>
<p>Enables updates for liquid flow or temperature, unless already active.</p> <p>Enables updates for liquid flow or temperature, unless already active.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.spawnFlow(pos,type,mat_type,mat_index,dimension)</tt></p>
<p>Spawns a new flow (i.e. steam/mist/dust/etc) at the given pos, and with
the given parameters. Returns it, or <em>nil</em> if unsuccessful.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getGlobalInitFeature(index)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.maps.getGlobalInitFeature(index)</tt></p>
<p>Returns the global feature object with the given index.</p> <p>Returns the global feature object with the given index.</p>
</li> </li>
@ -1207,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>
@ -1242,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.
@ -1386,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
@ -1402,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>
@ -1446,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>
@ -1541,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>
@ -1589,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>
@ -1620,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.
@ -1652,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
@ -1681,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>
@ -1722,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>
@ -1835,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>
@ -1849,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>
@ -1894,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

61
NEWS

@ -0,0 +1,61 @@
DFHack v0.34.11-r2 (UNRELEASED)
Internals:
- full support for Mac OS X.
- a plugin that adds scripting in ruby.
- support for interposing virtual methods in DF from C++ plugins.
- support for creating new interface screens from C++ and lua.
- added various other API functions.
Notable bugfixes:
- better terminal reset after exit on linux.
- seedwatch now works on reclaim.
- the sort plugin won't crash on cages anymore.
Misc improvements:
- autodump: can move items to any walkable tile, not just floors.
- stripcaged: by default keep armor, new dumparmor option.
- zone: allow non-domesticated birds in nestboxes.
- workflow: quality range in constraints.
- cleanplants: new command to remove rain water from plants.
- liquids: can paint permaflow, i.e. what makes rivers power water wheels.
- prospect: pre-embark prospector accounts for caves & magma sea in its estimate.
- rename: supports renaming stockpiles, workshops, traps, siege engines.
New tweaks:
- tweak stable-cursor: keeps exact cursor position between d/k/t/q/v etc menus.
- tweak patrol-duty: makes Train orders reduce patrol timer, like the binary patch does.
- tweak readable-build-plate: fix unreadable truncation in unit pressure plate build ui.
- tweak stable-temp: fixes bug 6012; may improve FPS by 50-100% on a slow item-heavy fort.
- tweak fast-heat: speeds up item heating & cooling, thus making stable-temp act faster.
New scripts:
- fixnaked: removes thoughts about nakedness.
- setfps: set FPS cap at runtime, in case you want slow motion or speed-up.
- fix/population-cap: run after every migrant wave to prevent exceeding the cap.
- fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp.
New GUI scripts:
- gui/mechanisms: browse mechanism links of the current building.
- gui/room-list: browse other rooms owned by the unit when assigning one.
- gui/liquids: a GUI front-end for the liquids plugin.
- gui/rename: renaming stockpiles, workshops and units via an in-game dialog.
- gui/power-meter: front-end for the Power Meter plugin.
- gui/siege-engine: front-end for the Siege Engine plugin.
Autolabor plugin:
- can set nonidle hauler percentage.
- broker excluded from all labors when needed at depot.
- likewise, anybody with a scheduled diplomat meeting.
New Dwarf Manipulator plugin:
Open the unit list, and press 'l' to access a Dwarf Therapist like UI in the game.
New Steam Engine plugin:
Dwarven Water Reactors don't make any sense whatsoever, so this is a potential
replacement for those concerned by it. The plugin detects if a workshop with a
certain name is in the raws used by the current world, and provides the necessary
behavior. See hack/raw/*_steam_engine.txt for the necessary raw definitions.
Note: Stuff like animal treadmills might be more period, but can't be done with dfhack.
New Power Meter plugin:
When activated, implements a pressure plate modification that detects power in gear
boxes built on the four adjacent N/S/W/E tiles. The gui/power-meter script implements
the build configuration UI.
New Siege Engine plugin (INCOMPLETE):
When enabled and configured via gui/siege-engine, allows aiming siege engines
at a designated rectangular area across Z levels. Also supports loading catapults
with non-boulder projectiles, taking from a stockpile, and restricting operator
skill range, like with ordinary workshops.

@ -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 #
############################## ##############################
@ -47,17 +51,31 @@ keybinding add Shift-G "job-material GLASS_GREEN"
keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms
# browse rooms of same owner # browse rooms of same owner
keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list
# 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
# UI logic tweaks # keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter
###################
############################
# UI and game logic tweaks #
############################
# stabilize the cursor of dwarfmode when switching menus # stabilize the cursor of dwarfmode when switching menus
tweak stable-cursor tweak stable-cursor
# stop military from considering training as 'patrol duty' # stop military from considering training as 'patrol duty'
tweak patrol-duty tweak patrol-duty
# display creature weight in build plate menu as ??K, instead of (???df: Max
tweak readable-build-plate
# improve FPS by squashing endless item temperature update loops
tweak stable-temp
# speed up items reaching temp equilibrium with environment by
# capping the rate to no less than 1 degree change per 500 frames
# Note: will also cause stuff to melt faster in magma etc
tweak fast-heat 500

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

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

@ -78,6 +78,9 @@ distribution.
#include "df/burrow.h" #include "df/burrow.h"
#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/unit_misc_trait.h"
#include "df/proj_itemst.h"
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
@ -727,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(); }
@ -738,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),
@ -756,7 +761,9 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getSelectedUnit), WRAPM(Gui, getSelectedUnit),
WRAPM(Gui, getSelectedItem), WRAPM(Gui, getSelectedItem),
WRAPM(Gui, showAnnouncement), WRAPM(Gui, showAnnouncement),
WRAPM(Gui, showZoomAnnouncement),
WRAPM(Gui, showPopupAnnouncement), WRAPM(Gui, showPopupAnnouncement),
WRAPM(Gui, showAutoAnnouncement),
{ NULL, NULL } { NULL, NULL }
}; };
@ -808,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),
@ -871,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),
@ -882,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 }
}; };
@ -912,9 +934,17 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPM(Maps, getGlobalInitFeature), WRAPM(Maps, getGlobalInitFeature),
WRAPM(Maps, getLocalInitFeature), WRAPM(Maps, getLocalInitFeature),
WRAPM(Maps, canWalkBetween), WRAPM(Maps, canWalkBetween),
WRAPM(Maps, spawnFlow),
{ NULL, NULL } { NULL, NULL }
}; };
static int maps_isValidTilePos(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
lua_pushboolean(L, Maps::isValidTilePos(pos));
return 1;
}
static int maps_getTileBlock(lua_State *L) static int maps_getTileBlock(lua_State *L)
{ {
auto pos = CheckCoordXYZ(L, 1, true); auto pos = CheckCoordXYZ(L, 1, true);
@ -922,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);
@ -936,7 +973,9 @@ static int maps_getTileBiomeRgn(lua_State *L)
} }
static const luaL_Reg dfhack_maps_funcs[] = { static const luaL_Reg dfhack_maps_funcs[] = {
{ "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 }
@ -1132,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;
@ -1236,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");

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

@ -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>
@ -126,6 +127,9 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
char permissions[5]; // r/-, w/-, x/-, p/s, 0 char permissions[5]; // r/-, w/-, x/-, p/s, 0
FILE *mapFile = ::fopen("/proc/self/maps", "r"); FILE *mapFile = ::fopen("/proc/self/maps", "r");
if (!mapFile)
return;
size_t start, end, offset, device1, device2, node; size_t start, end, offset, device1, device2, node;
while (fgets(buffer, 1024, mapFile)) while (fgets(buffer, 1024, mapFile))
@ -147,6 +151,8 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
temp.valid = true; temp.valid = true;
ranges.push_back(temp); ranges.push_back(temp);
} }
fclose(mapFile);
} }
uint32_t Process::getBase() uint32_t Process::getBase()
@ -192,6 +198,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);

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

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

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

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

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

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

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

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

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

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

@ -324,7 +324,7 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in
{ {
auto obj = (df::building_trapst*)bld; auto obj = (df::building_trapst*)bld;
if (obj->trap_type == trap_type::PressurePlate) if (obj->trap_type == trap_type::PressurePlate)
obj->unk_cc = 500; obj->ready_timeout = 500;
break; break;
} }
default: default:

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

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

@ -58,6 +58,7 @@ using namespace std;
#include "df/block_square_event_grassst.h" #include "df/block_square_event_grassst.h"
#include "df/z_level_flags.h" #include "df/z_level_flags.h"
#include "df/region_map_entry.h" #include "df/region_map_entry.h"
#include "df/flow_info.h"
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
@ -138,17 +139,57 @@ df::map_block *Maps::getBlock (int32_t blockx, int32_t blocky, int32_t blockz)
return world->map.block_index[blockx][blocky][blockz]; return world->map.block_index[blockx][blocky][blockz];
} }
df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z) bool Maps::isValidTilePos(int32_t x, int32_t y, int32_t z)
{ {
if (!IsValid()) if (!IsValid())
return NULL; return false;
if ((x < 0) || (y < 0) || (z < 0)) if ((x < 0) || (y < 0) || (z < 0))
return NULL; return false;
if ((x >= world->map.x_count) || (y >= world->map.y_count) || (z >= world->map.z_count)) if ((x >= world->map.x_count) || (y >= world->map.y_count) || (z >= world->map.z_count))
return false;
return true;
}
df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z)
{
if (!isValidTilePos(x,y,z))
return NULL; return NULL;
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);
@ -204,6 +245,26 @@ void Maps::enableBlockUpdates(df::map_block *blk, bool flow, bool temperature)
} }
} }
df::flow_info *Maps::spawnFlow(df::coord pos, df::flow_type type, int mat_type, int mat_index, int density)
{
using df::global::flows;
auto block = getTileBlock(pos);
if (!flows || !block)
return NULL;
auto flow = new df::flow_info();
flow->type = type;
flow->mat_type = mat_type;
flow->mat_index = mat_index;
flow->density = std::min(100, density);
flow->pos = pos;
block->flows.push_back(flow);
flows->push_back(flow);
return flow;
}
df::feature_init *Maps::getGlobalInitFeature(int32_t index) df::feature_init *Maps::getGlobalInitFeature(int32_t index)
{ {
auto data = world->world_data; auto data = world->world_data;
@ -246,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;
@ -485,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;
@ -509,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 328a8dbdc7d9e1e838798abf79861cc18a387e3f 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
@ -358,11 +359,11 @@ static const dwarf_state dwarf_states[] = {
OTHER /* DrinkBlood */, OTHER /* DrinkBlood */,
OTHER /* ReportCrime */, OTHER /* ReportCrime */,
OTHER /* ExecuteCriminal */, OTHER /* ExecuteCriminal */,
BUSY /* TrainAnimal */, BUSY /* TrainAnimal */,
BUSY /* CarveTrack */, BUSY /* CarveTrack */,
BUSY /* PushTrackVehicle */, BUSY /* PushTrackVehicle */,
BUSY /* PlaceTrackVehicle */, BUSY /* PlaceTrackVehicle */,
BUSY /* StoreItemInVehicle */ BUSY /* StoreItemInVehicle */
}; };
struct labor_info struct labor_info
@ -397,108 +398,108 @@ static int hauler_pct = 33;
static std::vector<struct labor_info> labor_infos; static std::vector<struct labor_info> labor_infos;
static const struct labor_default default_labor_infos[] = { static const struct labor_default default_labor_infos[] = {
/* MINE */ {AUTOMATIC, true, 2, 200, 0}, /* MINE */ {AUTOMATIC, true, 2, 200, 0},
/* HAUL_STONE */ {HAULERS, false, 1, 200, 0}, /* HAUL_STONE */ {HAULERS, false, 1, 200, 0},
/* HAUL_WOOD */ {HAULERS, false, 1, 200, 0}, /* HAUL_WOOD */ {HAULERS, false, 1, 200, 0},
/* HAUL_BODY */ {HAULERS, false, 1, 200, 0}, /* HAUL_BODY */ {HAULERS, false, 1, 200, 0},
/* HAUL_FOOD */ {HAULERS, false, 1, 200, 0}, /* HAUL_FOOD */ {HAULERS, false, 1, 200, 0},
/* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0}, /* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0},
/* HAUL_ITEM */ {HAULERS, false, 1, 200, 0}, /* HAUL_ITEM */ {HAULERS, false, 1, 200, 0},
/* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0}, /* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0},
/* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0}, /* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0},
/* CLEAN */ {HAULERS, false, 1, 200, 0}, /* CLEAN */ {HAULERS, false, 1, 200, 0},
/* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0}, /* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0},
/* CARPENTER */ {AUTOMATIC, false, 1, 200, 0}, /* CARPENTER */ {AUTOMATIC, false, 1, 200, 0},
/* DETAIL */ {AUTOMATIC, false, 1, 200, 0}, /* DETAIL */ {AUTOMATIC, false, 1, 200, 0},
/* MASON */ {AUTOMATIC, false, 1, 200, 0}, /* MASON */ {AUTOMATIC, false, 1, 200, 0},
/* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0}, /* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0},
/* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0}, /* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0},
/* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0}, /* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0},
/* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0}, /* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0},
/* SURGERY */ {AUTOMATIC, false, 1, 200, 0}, /* SURGERY */ {AUTOMATIC, false, 1, 200, 0},
/* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0}, /* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0},
/* SUTURING */ {AUTOMATIC, false, 1, 200, 0}, /* SUTURING */ {AUTOMATIC, false, 1, 200, 0},
/* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0}, /* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0},
/* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0}, /* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0},
/* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0}, /* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0},
/* BUTCHER */ {AUTOMATIC, false, 1, 200, 0}, /* BUTCHER */ {AUTOMATIC, false, 1, 200, 0},
/* TRAPPER */ {AUTOMATIC, false, 1, 200, 0}, /* TRAPPER */ {AUTOMATIC, false, 1, 200, 0},
/* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0}, /* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0},
/* LEATHER */ {AUTOMATIC, false, 1, 200, 0}, /* LEATHER */ {AUTOMATIC, false, 1, 200, 0},
/* TANNER */ {AUTOMATIC, false, 1, 200, 0}, /* TANNER */ {AUTOMATIC, false, 1, 200, 0},
/* BREWER */ {AUTOMATIC, false, 1, 200, 0}, /* BREWER */ {AUTOMATIC, false, 1, 200, 0},
/* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0}, /* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0},
/* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0}, /* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0},
/* WEAVER */ {AUTOMATIC, false, 1, 200, 0}, /* WEAVER */ {AUTOMATIC, false, 1, 200, 0},
/* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0}, /* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0},
/* MILLER */ {AUTOMATIC, false, 1, 200, 0}, /* MILLER */ {AUTOMATIC, false, 1, 200, 0},
/* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0}, /* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0},
/* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0}, /* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0},
/* MILK */ {AUTOMATIC, false, 1, 200, 0}, /* MILK */ {AUTOMATIC, false, 1, 200, 0},
/* COOK */ {AUTOMATIC, false, 1, 200, 0}, /* COOK */ {AUTOMATIC, false, 1, 200, 0},
/* PLANT */ {AUTOMATIC, false, 1, 200, 0}, /* PLANT */ {AUTOMATIC, false, 1, 200, 0},
/* HERBALIST */ {AUTOMATIC, false, 1, 200, 0}, /* HERBALIST */ {AUTOMATIC, false, 1, 200, 0},
/* FISH */ {AUTOMATIC, false, 1, 1, 0}, /* FISH */ {AUTOMATIC, false, 1, 1, 0},
/* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0}, /* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0},
/* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0}, /* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0},
/* HUNT */ {AUTOMATIC, true, 1, 1, 0}, /* HUNT */ {AUTOMATIC, true, 1, 1, 0},
/* SMELT */ {AUTOMATIC, false, 1, 200, 0}, /* SMELT */ {AUTOMATIC, false, 1, 200, 0},
/* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0}, /* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0},
/* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0}, /* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0},
/* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0}, /* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0},
/* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, /* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0}, /* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0},
/* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0}, /* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0},
/* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, /* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, /* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0}, /* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0},
/* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0}, /* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0},
/* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0}, /* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0},
/* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0}, /* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0}, /* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0},
/* BOWYER */ {AUTOMATIC, false, 1, 200, 0}, /* BOWYER */ {AUTOMATIC, false, 1, 200, 0},
/* MECHANIC */ {AUTOMATIC, false, 1, 200, 0}, /* MECHANIC */ {AUTOMATIC, false, 1, 200, 0},
/* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0}, /* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0},
/* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0}, /* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0},
/* DYER */ {AUTOMATIC, false, 1, 200, 0}, /* DYER */ {AUTOMATIC, false, 1, 200, 0},
/* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0}, /* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0},
/* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0}, /* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0},
/* SHEARER */ {AUTOMATIC, false, 1, 200, 0}, /* SHEARER */ {AUTOMATIC, false, 1, 200, 0},
/* SPINNER */ {AUTOMATIC, false, 1, 200, 0}, /* SPINNER */ {AUTOMATIC, false, 1, 200, 0},
/* POTTERY */ {AUTOMATIC, false, 1, 200, 0}, /* POTTERY */ {AUTOMATIC, false, 1, 200, 0},
/* GLAZING */ {AUTOMATIC, false, 1, 200, 0}, /* GLAZING */ {AUTOMATIC, false, 1, 200, 0},
/* PRESSING */ {AUTOMATIC, false, 1, 200, 0}, /* PRESSING */ {AUTOMATIC, false, 1, 200, 0},
/* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) /* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981)
/* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0}, /* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0},
/* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0} /* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0}
}; };
static const int responsibility_penalties[] = { static const int responsibility_penalties[] = {
0, /* LAW_MAKING */ 0, /* LAW_MAKING */
0, /* LAW_ENFORCEMENT */ 0, /* LAW_ENFORCEMENT */
3000, /* RECEIVE_DIPLOMATS */ 3000, /* RECEIVE_DIPLOMATS */
0, /* MEET_WORKERS */ 0, /* MEET_WORKERS */
1000, /* MANAGE_PRODUCTION */ 1000, /* MANAGE_PRODUCTION */
3000, /* TRADE */ 3000, /* TRADE */
1000, /* ACCOUNTING */ 1000, /* ACCOUNTING */
0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */ 0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */
0, /* MAKE_INTRODUCTIONS */ 0, /* MAKE_INTRODUCTIONS */
0, /* MAKE_PEACE_AGREEMENTS */ 0, /* MAKE_PEACE_AGREEMENTS */
0, /* MAKE_TOPIC_AGREEMENTS */ 0, /* MAKE_TOPIC_AGREEMENTS */
0, /* COLLECT_TAXES */ 0, /* COLLECT_TAXES */
0, /* ESCORT_TAX_COLLECTOR */ 0, /* ESCORT_TAX_COLLECTOR */
0, /* EXECUTIONS */ 0, /* EXECUTIONS */
0, /* TAME_EXOTICS */ 0, /* TAME_EXOTICS */
0, /* RELIGION */ 0, /* RELIGION */
0, /* ATTACK_ENEMIES */ 0, /* ATTACK_ENEMIES */
0, /* PATROL_TERRITORY */ 0, /* PATROL_TERRITORY */
0, /* MILITARY_GOALS */ 0, /* MILITARY_GOALS */
0, /* MILITARY_STRATEGY */ 0, /* MILITARY_STRATEGY */
0, /* UPGRADE_SQUAD_EQUIPMENT */ 0, /* UPGRADE_SQUAD_EQUIPMENT */
0, /* EQUIPMENT_MANIFESTS */ 0, /* EQUIPMENT_MANIFESTS */
0, /* SORT_AMMUNITION */ 0, /* SORT_AMMUNITION */
0, /* BUILD_MORALE */ 0, /* BUILD_MORALE */
5000 /* HEALTH_MANAGEMENT */ 5000 /* HEALTH_MANAGEMENT */
}; };
struct dwarf_info_t struct dwarf_info_t
@ -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,18 +1136,13 @@ 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;
if (job >= 0 && job < ARRAY_COUNT(dwarf_states))
/* dwarf_info[dwarf].state = dwarf_states[job];
assert(job >= 0); else
assert(job < ARRAY_COUNT(dwarf_states)); {
*/ out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job);
if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) dwarf_info[dwarf].state = OTHER;
dwarf_info[dwarf].state = dwarf_states[job]; }
else
{
out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job);
dwarf_info[dwarf].state = OTHER;
}
} }
state_count[dwarf_info[dwarf].state]++; state_count[dwarf_info[dwarf].state]++;
@ -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,6 +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(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

@ -39,8 +39,6 @@ using df::global::ui;
using df::global::gps; using df::global::gps;
using df::global::enabler; using df::global::enabler;
DFHACK_PLUGIN("manipulator");
struct SkillLevel struct SkillLevel
{ {
const char *name; const char *name;
@ -254,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
}; };
@ -277,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)
@ -312,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);
@ -330,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 ();
}; };
@ -376,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)
{
col_widths[DISP_COLUMN_LABORS]--;
if (col_widths[DISP_COLUMN_LABORS] & 1)
col_widths[DISP_COLUMN_NAME]++;
else
col_widths[DISP_COLUMN_PROFESSION]++;
}
for (int i = 0; i < DISP_COLUMN_MAX; i++)
{ {
if (labors_width & 1) if (i == 0)
name_width++; col_offsets[i] = 1;
else else
prof_width++; col_offsets[i] = col_offsets[i - 1] + col_widths[i - 1] + 1;
labors_width--;
} }
// don't adjust scroll position immediately after the window opened // don't adjust scroll position immediately after the window opened
@ -411,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)
@ -455,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--;
@ -491,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))
@ -558,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))
@ -568,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;
} }
@ -605,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)
@ -622,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())
@ -645,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;
@ -652,23 +712,23 @@ 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;
bg = 0; bg = 0;
char c = 0xFA; uint8_t c = 0xFA;
if ((col_offset == sel_column) && (row_offset == sel_row)) if ((col_offset == sel_column) && (row_offset == sel_row))
fg = 9; fg = 9;
if (columns[col_offset].skill != job_skill::NONE) if (columns[col_offset].skill != job_skill::NONE)
@ -695,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);
} }
} }
@ -705,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;
@ -742,12 +802,12 @@ 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);
} }
int x = 1; int x = 2;
OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key
OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, "); OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, ");
@ -760,7 +820,7 @@ void viewscreen_unitlaborsst::render()
OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key
OutputString(15, x, gps->dimy - 3, ": Zoom-Cre"); OutputString(15, x, gps->dimy - 3, ": Zoom-Cre");
x = 1; x = 2;
OutputString(10, x, gps->dimy - 2, "Esc"); // LEAVESCREEN key OutputString(10, x, gps->dimy - 2, "Esc"); // LEAVESCREEN key
OutputString(15, x, gps->dimy - 2, ": Done, "); OutputString(15, x, gps->dimy - 2, ": Done, ");
@ -781,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;
@ -803,23 +866,35 @@ struct unitlist_hook : df::viewscreen_unitlistst
} }
INTERPOSE_NEXT(feed)(input); INTERPOSE_NEXT(feed)(input);
} }
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (units[page].size())
{
int x = 2;
OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key
OutputString(15, x, gps->dimy - 2, ": Manage labors (DFHack)");
}
}
}; };
IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, render);
DFHACK_PLUGIN("manipulator");
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{ {
if (gps) if (!gps || !INTERPOSE_HOOK(unitlist_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_hook, render).apply())
{ out.printerr("Could not insert Dwarf Manipulator hooks!\n");
if (!INTERPOSE_HOOK(unitlist_hook, feed).apply())
out.printerr("Could not interpose viewscreen_unitlistst::feed\n");
}
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{ {
INTERPOSE_HOOK(unitlist_hook, feed).remove(); INTERPOSE_HOOK(unitlist_hook, feed).remove();
INTERPOSE_HOOK(unitlist_hook, render).remove();
return CR_OK; return CR_OK;
} }

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

@ -0,0 +1,92 @@
building_steam_engine
[OBJECT:BUILDING]
[BUILDING_WORKSHOP:STEAM_ENGINE]
[NAME:Steam Engine]
[NAME_COLOR:4:0:1]
[DIM:3:3]
[WORK_LOCATION:2:3]
[BUILD_LABOR:MECHANIC]
[BUILD_KEY:CUSTOM_ALT_S]
[BLOCK:1:1:1:1]
[BLOCK:2:1:1:1]
[BLOCK:3:1:0:1]
[TILE:0:1:240:' ':254]
[TILE:0:2:' ':' ':128]
[TILE:0:3:246:' ':' ']
[COLOR:0:1:6:0:0:0:0:0:7:0:0]
[COLOR:0:2:0:0:0:0:0:0:7:0:0]
[COLOR:0:3:MAT:0:0:0:0:0:0]
[TILE:1:1:246:128:' ']
[TILE:1:2:' ':' ':254]
[TILE:1:3:254:'/':240]
[COLOR:1:1:MAT:7:0:0:0:0:0]
[COLOR:1:2:0:0:0:0:0:0:7:0:0]
[COLOR:1:3:7:0:0:6:0:0:6:0:0]
[TILE:2:1:21:' ':128]
[TILE:2:2:128:' ':246]
[TILE:2:3:177:19:177]
[COLOR:2:1:6:0:0:0:0:0:7:0:0]
[COLOR:2:2:7:0:0:0:0:0:MAT]
[COLOR:2:3:7:0:0:6:0:0:7:0:0]
Tile 15 marks places where machines can connect.
Tile 19 marks the hearth (color changed to reflect power).
[TILE:3:1:15:246:15]
[TILE:3:2:'\':19:'/']
[TILE:3:3:7:' ':7]
Color 1:?:1 water indicator, 4:?:1 magma indicator:
[COLOR:3:1:7:0:0:MAT:7:0:0]
[COLOR:3:2:6:0:0:0:0:1:6:0:0]
[COLOR:3:3:1:7:1:0:0:0:4:7:1]
[BUILD_ITEM:1:BARREL:NONE:INORGANIC:NONE][EMPTY][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:PIPE_SECTION:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:CHAIN:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPPARTS:NONE:NONE:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][FIRE_BUILD_SAFE]
[BUILDING_WORKSHOP:MAGMA_STEAM_ENGINE]
[NAME:Magma Steam Engine]
[NAME_COLOR:4:0:1]
[DIM:3:3]
[WORK_LOCATION:2:3]
[BUILD_LABOR:MECHANIC]
[BUILD_KEY:CUSTOM_ALT_E]
[NEEDS_MAGMA]
[BLOCK:1:1:1:1]
[BLOCK:2:1:1:1]
[BLOCK:3:1:0:1]
[TILE:0:1:240:' ':254]
[TILE:0:2:' ':' ':128]
[TILE:0:3:246:' ':' ']
[COLOR:0:1:6:0:0:0:0:0:7:0:0]
[COLOR:0:2:0:0:0:0:0:0:7:0:0]
[COLOR:0:3:MAT:0:0:0:0:0:0]
[TILE:1:1:246:128:' ']
[TILE:1:2:' ':' ':254]
[TILE:1:3:254:'/':240]
[COLOR:1:1:MAT:7:0:0:0:0:0]
[COLOR:1:2:0:0:0:0:0:0:7:0:0]
[COLOR:1:3:7:0:0:6:0:0:6:0:0]
[TILE:2:1:21:' ':128]
[TILE:2:2:128:' ':246]
[TILE:2:3:177:19:177]
[COLOR:2:1:6:0:0:0:0:0:7:0:0]
[COLOR:2:2:7:0:0:0:0:0:MAT]
[COLOR:2:3:7:0:0:6:0:0:7:0:0]
Tile 15 marks places where machines can connect.
Tile 19 marks the hearth (color changed to reflect power).
[TILE:3:1:15:246:15]
[TILE:3:2:'\':19:'/']
[TILE:3:3:7:' ':7]
Color 1:?:1 water indicator, 4:?:1 magma indicator:
[COLOR:3:1:7:0:0:MAT:7:0:0]
[COLOR:3:2:6:0:0:0:0:1:6:0:0]
[COLOR:3:3:1:7:1:0:0:0:4:7:1]
[BUILD_ITEM:1:BARREL:NONE:INORGANIC:NONE][EMPTY][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:PIPE_SECTION:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:CHAIN:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPPARTS:NONE:NONE:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][MAGMA_BUILD_SAFE]

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

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

@ -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 != "?")

File diff suppressed because it is too large Load Diff

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

@ -32,6 +32,8 @@
#include "df/squad_order_trainst.h" #include "df/squad_order_trainst.h"
#include "df/ui_build_selector.h" #include "df/ui_build_selector.h"
#include "df/building_trapst.h" #include "df/building_trapst.h"
#include "df/item_actual.h"
#include "df/contaminant.h"
#include <stdlib.h> #include <stdlib.h>
@ -85,6 +87,12 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" when soldiers go off-duty (i.e. civilian).\n" " when soldiers go off-duty (i.e. civilian).\n"
" tweak readable-build-plate [disable]\n" " tweak readable-build-plate [disable]\n"
" Fixes rendering of creature weight limits in pressure plate build menu.\n" " Fixes rendering of creature weight limits in pressure plate build menu.\n"
" tweak stable-temp [disable]\n"
" Fixes performance bug 6012 by squashing jitter in temperature updates.\n"
" tweak fast-heat <max-ticks>\n"
" Further improves temperature updates by ensuring that 1 degree of\n"
" item temperature is crossed in no more than specified number of frames\n"
" when updating from the environment temperature. Use 0 to disable.\n"
)); ));
return CR_OK; return CR_OK;
} }
@ -211,9 +219,6 @@ struct patrol_duty_hook : df::squad_order_trainst
IMPLEMENT_VMETHOD_INTERPOSE(patrol_duty_hook, isPatrol); IMPLEMENT_VMETHOD_INTERPOSE(patrol_duty_hook, isPatrol);
static const int AREA_MAP_WIDTH = 23;
static const int MENU_WIDTH = 30;
struct readable_build_plate_hook : df::viewscreen_dwarfmodest struct readable_build_plate_hook : df::viewscreen_dwarfmodest
{ {
typedef df::viewscreen_dwarfmodest interpose_base; typedef df::viewscreen_dwarfmodest interpose_base;
@ -228,10 +233,8 @@ struct readable_build_plate_hook : df::viewscreen_dwarfmodest
ui_build_selector->building_subtype == trap_type::PressurePlate && ui_build_selector->building_subtype == trap_type::PressurePlate &&
ui_build_selector->plate_info.flags.bits.units) ui_build_selector->plate_info.flags.bits.units)
{ {
auto wsize = Screen::getWindowSize(); auto dims = Gui::getDwarfmodeViewDims();
int x = wsize.x - MENU_WIDTH - 1; int x = dims.menu_x1;
if (*ui_menu_width == 1 || *ui_area_map_width == 2)
x -= AREA_MAP_WIDTH + 1;
Screen::Pen pen(' ',COLOR_WHITE); Screen::Pen pen(' ',COLOR_WHITE);
@ -248,6 +251,98 @@ struct readable_build_plate_hook : df::viewscreen_dwarfmodest
IMPLEMENT_VMETHOD_INTERPOSE(readable_build_plate_hook, render); IMPLEMENT_VMETHOD_INTERPOSE(readable_build_plate_hook, render);
struct stable_temp_hook : df::item_actual {
typedef df::item_actual interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t rate_mult))
{
if (temperature != temp)
{
// Bug 6012 is caused by fixed-point precision mismatch jitter
// when an item is being pushed by two sources at N and N+1.
// This check suppresses it altogether.
if (temp == temperature+1 ||
(temp == temperature-1 && temperature_fraction == 0))
temp = temperature;
// When SPEC_HEAT is NONE, the original function seems to not
// change the temperature, yet return true, which is silly.
else if (getSpecHeat() == 60001)
temp = temperature;
}
return INTERPOSE_NEXT(adjustTemperature)(temp, rate_mult);
}
DEFINE_VMETHOD_INTERPOSE(bool, updateContaminants, ())
{
if (contaminants)
{
// Force 1-degree difference in contaminant temperature to 0
for (size_t i = 0; i < contaminants->size(); i++)
{
auto obj = (*contaminants)[i];
if (abs(obj->temperature - temperature) == 1)
{
obj->temperature = temperature;
obj->temperature_fraction = temperature_fraction;
}
}
}
return INTERPOSE_NEXT(updateContaminants)();
}
};
IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, adjustTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, updateContaminants);
static int map_temp_mult = -1;
static int max_heat_ticks = 0;
struct fast_heat_hook : df::item_actual {
typedef df::item_actual interpose_base;
DEFINE_VMETHOD_INTERPOSE(
bool, updateTempFromMap,
(bool local, bool contained, bool adjust, int32_t rate_mult)
) {
int cmult = map_temp_mult;
map_temp_mult = rate_mult;
bool rv = INTERPOSE_NEXT(updateTempFromMap)(local, contained, adjust, rate_mult);
map_temp_mult = cmult;
return rv;
}
DEFINE_VMETHOD_INTERPOSE(
bool, updateTemperature,
(uint16_t temp, bool local, bool contained, bool adjust, int32_t rate_mult)
) {
// Some items take ages to cross the last degree, so speed them up
if (map_temp_mult > 0 && temp != temperature && max_heat_ticks > 0)
{
int spec = getSpecHeat();
if (spec != 60001)
rate_mult = std::max(map_temp_mult, spec/max_heat_ticks/abs(temp - temperature));
}
return INTERPOSE_NEXT(updateTemperature)(temp, local, contained, adjust, rate_mult);
}
DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t rate_mult))
{
if (map_temp_mult > 0)
rate_mult = map_temp_mult;
return INTERPOSE_NEXT(adjustTemperature)(temp, rate_mult);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, updateTempFromMap);
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, updateTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, adjustTemperature);
static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector <string> &parameters) static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector <string> &parameters)
{ {
if (vector_get(parameters, 1) == "disable") if (vector_get(parameters, 1) == "disable")
@ -380,6 +475,22 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
enable_hook(out, INTERPOSE_HOOK(readable_build_plate_hook, render), parameters); enable_hook(out, INTERPOSE_HOOK(readable_build_plate_hook, render), parameters);
} }
else if (cmd == "stable-temp")
{
enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, adjustTemperature), parameters);
enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, updateContaminants), parameters);
}
else if (cmd == "fast-heat")
{
if (parameters.size() < 2)
return CR_WRONG_USAGE;
max_heat_ticks = atoi(parameters[1].c_str());
if (max_heat_ticks <= 0)
parameters[1] = "disable";
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, updateTempFromMap), parameters);
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, updateTemperature), parameters);
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, adjustTemperature), parameters);
}
else else
return CR_WRONG_USAGE; return CR_WRONG_USAGE;

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

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

@ -1,5 +1,9 @@
-- Reset item temperature to the value of their tile. -- Reset item temperature to the value of their tile.
local args = {...}
local apply = (args[1] == 'apply')
local count = 0 local count = 0
local types = {} local types = {}
@ -9,13 +13,16 @@ local function update_temp(item,btemp)
local tid = item:getType() local tid = item:getType()
types[tid] = (types[tid] or 0) + 1 types[tid] = (types[tid] or 0) + 1
end end
item.temperature = btemp
item.temperature_fraction = 0
if item.contaminants then if apply then
for _,c in ipairs(item.contaminants) do item.temperature = btemp
c.temperature = btemp item.temperature_fraction = 0
c.temperature_fraction = 0
if item.contaminants then
for _,c in ipairs(item.contaminants) do
c.temperature = btemp
c.temperature_fraction = 0
end
end end
end end
@ -23,7 +30,9 @@ local function update_temp(item,btemp)
update_temp(sub,btemp) update_temp(sub,btemp)
end end
item:checkTemperatureDamage() if apply then
item:checkTemperatureDamage()
end
end end
local last_frame = df.global.world.frame_counter-1 local last_frame = df.global.world.frame_counter-1
@ -39,7 +48,11 @@ for _,item in ipairs(df.global.world.items.all) do
end end
end end
print('Items updated: '..count) if apply then
print('Items updated: '..count)
else
print('Items not in equilibrium: '..count)
end
local tlist = {} local tlist = {}
for k,_ in pairs(types) do tlist[#tlist+1] = k end for k,_ in pairs(types) do tlist[#tlist+1] = k end
@ -47,3 +60,7 @@ table.sort(tlist, function(a,b) return types[a] > types[b] end)
for _,k in ipairs(tlist) do for _,k in ipairs(tlist) do
print(' '..df.item_type[k]..':', types[k]) print(' '..df.item_type[k]..':', types[k])
end end
if not apply then
print("Use 'fix/stable-temp apply' to force-change temperature.")
end

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

@ -0,0 +1,10 @@
-- Set the FPS cap at runtime.
local cap = ...
local capnum = tonumber(cap)
if not capnum or capnum < 1 then
qerror('Invalid FPS cap value: '..cap)
end
df.global.enabler.fps = capnum