Digging Invaders: merged eventManager and recent.

develop
expwnent 2013-01-02 11:26:30 -05:00
parent 46b9148277
commit 144e0b4dcb
236 changed files with 10121 additions and 6962 deletions

@ -172,6 +172,7 @@ IF(BUILD_LIBRARY)
add_subdirectory (library) add_subdirectory (library)
## install the default documentation files ## install the default documentation files
install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION})
install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif() endif()
#build the plugins #build the plugins

@ -404,7 +404,10 @@ ul.auto-toc {
<li><a class="reference internal" href="#sort" id="id54">sort</a></li> <li><a class="reference internal" href="#sort" id="id54">sort</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#scripts" id="id55">Scripts</a></li> <li><a class="reference internal" href="#scripts" id="id55">Scripts</a><ul>
<li><a class="reference internal" href="#save-init-script" id="id56">Save init script</a></li>
</ul>
</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
@ -1034,6 +1037,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.getSavePath()</tt></p>
<p>Returns the path to the current save directory, or <em>nil</em> if no save loaded.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.getTickCount()</tt></p> <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> <p>Returns the tick count in ms, exactly as DF ui uses.</p>
</li> </li>
@ -1109,6 +1115,12 @@ above operations accordingly. If enabled, pauses and zooms to position.</p>
<li><p class="first"><tt class="docutils literal">dfhack.job.printItemDetails(jobitem,idx)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.job.printItemDetails(jobitem,idx)</tt></p>
<p>Prints info about the job item.</p> <p>Prints info about the job item.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.job.getGeneralRef(job, type)</tt></p>
<p>Searches for a general_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.getSpecificRef(job, type)</tt></p>
<p>Searches for a specific_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.getHolder(job)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.job.getHolder(job)</tt></p>
<p>Returns the building holding the job.</p> <p>Returns the building holding the job.</p>
</li> </li>
@ -1147,6 +1159,12 @@ the flags in the job item.</p>
<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>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getGeneralRef(unit, type)</tt></p>
<p>Searches for a general_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getSpecificRef(unit, type)</tt></p>
<p>Searches for a specific_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getContainer(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.getContainer(unit)</tt></p>
<p>Returns the container (cage) item or <em>nil</em>.</p> <p>Returns the container (cage) item or <em>nil</em>.</p>
</li> </li>
@ -1209,6 +1227,9 @@ is <em>true</em>, subtracts the rust penalty.</p>
<li><p class="first"><tt class="docutils literal">dfhack.units.getEffectiveSkill(unit, skill)</tt></p> <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> <p>Computes the effective rating for the given skill, taking into account exhaustion, pain etc.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getExperience(unit, skill[, total])</tt></p>
<p>Returns the experience value for the given skill. If <tt class="docutils literal">total</tt> is true, adds experience implied by the current rating.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.computeMovementSpeed(unit)</tt></p> <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> <p>Computes number of frames * 100 it takes the unit to move in its current state of mind and body.</p>
</li> </li>
@ -1403,6 +1424,12 @@ burrows, or the presence of invaders.</p>
<div class="section" id="buildings-module"> <div class="section" id="buildings-module">
<h3><a class="toc-backref" href="#id25">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.getGeneralRef(building, type)</tt></p>
<p>Searches for a general_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getSpecificRef(building, type)</tt></p>
<p>Searches for a specific_ref with the given type.</p>
</li>
<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.
Returns <em>false</em> in case of error.</p> Returns <em>false</em> in case of error.</p>
@ -1728,7 +1755,10 @@ options; if multiple interpretations exist, the table will contain multiple keys
<dd><p class="first last">Maps to an integer in range 0-255. Duplicates a separate &quot;STRING_A???&quot; code for convenience.</p> <dd><p class="first last">Maps to an integer in range 0-255. Duplicates a separate &quot;STRING_A???&quot; code for convenience.</p>
</dd> </dd>
<dt><tt class="docutils literal">_MOUSE_L, _MOUSE_R</tt></dt> <dt><tt class="docutils literal">_MOUSE_L, _MOUSE_R</tt></dt>
<dd><p class="first last">If the left or right mouse button is pressed.</p> <dd><p class="first last">If the left or right mouse button is being pressed.</p>
</dd>
<dt><tt class="docutils literal">_MOUSE_L_DOWN, _MOUSE_R_DOWN</tt></dt>
<dd><p class="first last">If the left or right mouse button was just pressed.</p>
</dd> </dd>
</dl> </dl>
<p>If this method is omitted, the screen is dismissed on receival of the <tt class="docutils literal">LEAVESCREEN</tt> key.</p> <p>If this method is omitted, the screen is dismissed on receival of the <tt class="docutils literal">LEAVESCREEN</tt> key.</p>
@ -1763,9 +1793,17 @@ global environment, persistent between calls to the script.</p>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getVTable(name)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.internal.getVTable(name)</tt></p>
<p>Returns the pre-extracted vtable address <tt class="docutils literal">name</tt>, or <em>nil</em>.</p> <p>Returns the pre-extracted vtable address <tt class="docutils literal">name</tt>, or <em>nil</em>.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getImageBase()</tt></p>
<p>Returns the mmap base of the executable.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getRebaseDelta()</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.internal.getRebaseDelta()</tt></p>
<p>Returns the ASLR rebase offset of the DF executable.</p> <p>Returns the ASLR rebase offset of the DF executable.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.internal.adjustOffset(offset[,to_file])</span></tt></p>
<p>Returns the re-aligned offset, or <em>nil</em> if invalid.
If <tt class="docutils literal">to_file</tt> is true, the offset is adjusted from memory to file.
This function returns the original value everywhere except windows.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getMemRanges()</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.internal.getMemRanges()</tt></p>
<p>Returns a sequence of tables describing virtual memory ranges of the process.</p> <p>Returns a sequence of tables describing virtual memory ranges of the process.</p>
</li> </li>
@ -2758,6 +2796,14 @@ before rendering the token.</p>
<li><p class="first"><tt class="docutils literal">token.tile = pen</tt></p> <li><p class="first"><tt class="docutils literal">token.tile = pen</tt></p>
<p>Specifies a pen to paint as one tile before the main part of the token.</p> <p>Specifies a pen to paint as one tile before the main part of the token.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">token.width = ...</tt></p>
<p>If specified either as a value or a callback, the text field is padded
or truncated to the specified number.</p>
</li>
<li><p class="first"><tt class="docutils literal">token.pad_char = <span class="pre">'?'</span></tt></p>
<p>If specified together with <tt class="docutils literal">width</tt>, the padding area is filled with
this character instead of just being skipped over.</p>
</li>
<li><p class="first"><tt class="docutils literal">token.key = <span class="pre">'...'</span></tt></p> <li><p class="first"><tt class="docutils literal">token.key = <span class="pre">'...'</span></tt></p>
<p>Specifies the keycode associated with the token. The string description <p>Specifies the keycode associated with the token. The string description
of the key binding is added to the text content of the token.</p> of the key binding is added to the text content of the token.</p>
@ -2819,11 +2865,16 @@ this may be extended with mouse click support.</p>
</tr> </tr>
<tr class="field"><th class="field-name">icon_pen:</th><td class="field-body">Default pen for icons.</td> <tr class="field"><th class="field-name">icon_pen:</th><td class="field-body">Default pen for icons.</td>
</tr> </tr>
<tr class="field"><th class="field-name">on_select:</th><td class="field-body">Selection change callback; called as <tt class="docutils literal">on_select(index,choice)</tt>.</td> <tr class="field"><th class="field-name">on_select:</th><td class="field-body">Selection change callback; called as <tt class="docutils literal">on_select(index,choice)</tt>.
This is also called with <em>nil</em> arguments if <tt class="docutils literal">setChoices</tt> is called
with an empty list.</td>
</tr> </tr>
<tr class="field"><th class="field-name">on_submit:</th><td class="field-body">Enter key callback; if specified, the list reacts to the key <tr class="field"><th class="field-name">on_submit:</th><td class="field-body">Enter key callback; if specified, the list reacts to the key
and calls it as <tt class="docutils literal">on_submit(index,choice)</tt>.</td> and calls it as <tt class="docutils literal">on_submit(index,choice)</tt>.</td>
</tr> </tr>
<tr class="field"><th class="field-name">on_submit2:</th><td class="field-body">Shift-Enter key callback; if specified, the list reacts to the key
and calls it as <tt class="docutils literal">on_submit2(index,choice)</tt>.</td>
</tr>
<tr class="field"><th class="field-name">row_height:</th><td class="field-body">Height of every row in text lines.</td> <tr class="field"><th class="field-name">row_height:</th><td class="field-body">Height of every row in text lines.</td>
</tr> </tr>
<tr class="field"><th class="field-name">icon_width:</th><td class="field-body">If not <em>nil</em>, the specified number of character columns <tr class="field"><th class="field-name">icon_width:</th><td class="field-body">If not <em>nil</em>, the specified number of character columns
@ -2879,6 +2930,9 @@ with the following fields:</p>
<li><p class="first"><tt class="docutils literal">list:submit()</tt></p> <li><p class="first"><tt class="docutils literal">list:submit()</tt></p>
<p>Call the <tt class="docutils literal">on_submit</tt> callback, as if the Enter key was handled.</p> <p>Call the <tt class="docutils literal">on_submit</tt> callback, as if the Enter key was handled.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">list:submit2()</tt></p>
<p>Call the <tt class="docutils literal">on_submit2</tt> callback, as if the Shift-Enter key was handled.</p>
</li>
</ul> </ul>
</div> </div>
<div class="section" id="filteredlist-class"> <div class="section" id="filteredlist-class">
@ -2893,6 +2947,8 @@ supports:</p>
<tbody valign="top"> <tbody valign="top">
<tr class="field"><th class="field-name">edit_pen:</th><td class="field-body">If specified, used instead of <tt class="docutils literal">cursor_pen</tt> for the edit field.</td> <tr class="field"><th class="field-name">edit_pen:</th><td class="field-body">If specified, used instead of <tt class="docutils literal">cursor_pen</tt> for the edit field.</td>
</tr> </tr>
<tr class="field"><th class="field-name">edit_below:</th><td class="field-body">If true, the edit field is placed below the list instead of above.</td>
</tr>
<tr class="field"><th class="field-name" colspan="2">not_found_label:</th></tr> <tr class="field"><th class="field-name" colspan="2">not_found_label:</th></tr>
<tr class="field"><td>&nbsp;</td><td class="field-body">Specifies the text of the label shown when no items match the filter.</td> <tr class="field"><td>&nbsp;</td><td class="field-body">Specifies the text of the label shown when no items match the filter.</td>
</tr> </tr>
@ -3014,6 +3070,25 @@ The <tt class="docutils literal">name</tt> argument should be the name stem, as
</li> </li>
</ul> </ul>
<p>Note that this function lets errors propagate to the caller.</p> <p>Note that this function lets errors propagate to the caller.</p>
<div class="section" id="save-init-script">
<h2><a class="toc-backref" href="#id56">Save init script</a></h2>
<p>If a save directory contains a file called <tt class="docutils literal">raw/init.lua</tt>, it is
automatically loaded and executed every time the save is loaded. It
can also define the following functions to be called by dfhack:</p>
<ul>
<li><p class="first"><tt class="docutils literal">function onStateChange(op) ... end</tt></p>
<p>Automatically called from the regular onStateChange event as long
as the save is still loaded. This avoids the need to install a hook
into the global <tt class="docutils literal">dfhack.onStateChange</tt> table, with associated
cleanup concerns.</p>
</li>
<li><p class="first"><tt class="docutils literal">function onUnload() ... end</tt></p>
<p>Called when the save containing the script is unloaded. This function
should clean up any global hooks installed by the script.</p>
</li>
</ul>
<p>Within the init script, the path to the save directory is available as <tt class="docutils literal">SAVE_PATH</tt>.</p>
</div>
</div> </div>
</div> </div>
</body> </body>

@ -741,6 +741,10 @@ can be omitted.
Returns the dfhack directory path, i.e. ``".../df/hack/"``. Returns the dfhack directory path, i.e. ``".../df/hack/"``.
* ``dfhack.getSavePath()``
Returns the path to the current save directory, or *nil* if no save loaded.
* ``dfhack.getTickCount()`` * ``dfhack.getTickCount()``
Returns the tick count in ms, exactly as DF ui uses. Returns the tick count in ms, exactly as DF ui uses.
@ -833,6 +837,14 @@ Job module
Prints info about the job item. Prints info about the job item.
* ``dfhack.job.getGeneralRef(job, type)``
Searches for a general_ref with the given type.
* ``dfhack.job.getSpecificRef(job, type)``
Searches for a specific_ref with the given type.
* ``dfhack.job.getHolder(job)`` * ``dfhack.job.getHolder(job)``
Returns the building holding the job. Returns the building holding the job.
@ -879,6 +891,14 @@ Units module
Returns true *x,y,z* of the unit, or *nil* if invalid; may be not equal to unit.pos if caged. Returns true *x,y,z* of the unit, or *nil* if invalid; may be not equal to unit.pos if caged.
* ``dfhack.units.getGeneralRef(unit, type)``
Searches for a general_ref with the given type.
* ``dfhack.units.getSpecificRef(unit, type)``
Searches for a specific_ref with the given type.
* ``dfhack.units.getContainer(unit)`` * ``dfhack.units.getContainer(unit)``
Returns the container (cage) item or *nil*. Returns the container (cage) item or *nil*.
@ -954,6 +974,10 @@ Units module
Computes the effective rating for the given skill, taking into account exhaustion, pain etc. Computes the effective rating for the given skill, taking into account exhaustion, pain etc.
* ``dfhack.units.getExperience(unit, skill[, total])``
Returns the experience value for the given skill. If ``total`` is true, adds experience implied by the current rating.
* ``dfhack.units.computeMovementSpeed(unit)`` * ``dfhack.units.computeMovementSpeed(unit)``
Computes number of frames * 100 it takes the unit to move in its current state of mind and body. Computes number of frames * 100 it takes the unit to move in its current state of mind and body.
@ -1198,6 +1222,14 @@ Burrows module
Buildings module Buildings module
---------------- ----------------
* ``dfhack.buildings.getGeneralRef(building, type)``
Searches for a general_ref with the given type.
* ``dfhack.buildings.getSpecificRef(building, type)``
Searches for a specific_ref with the given type.
* ``dfhack.buildings.setOwner(item,unit)`` * ``dfhack.buildings.setOwner(item,unit)``
Replaces the owner of the building. If unit is *nil*, removes ownership. Replaces the owner of the building. If unit is *nil*, removes ownership.
@ -1582,7 +1614,10 @@ Supported callbacks and fields are:
Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience. Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience.
``_MOUSE_L, _MOUSE_R`` ``_MOUSE_L, _MOUSE_R``
If the left or right mouse button is pressed. If the left or right mouse button is being pressed.
``_MOUSE_L_DOWN, _MOUSE_R_DOWN``
If the left or right mouse button was just pressed.
If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key. If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key.
@ -1618,10 +1653,20 @@ and are only documented here for completeness:
Returns the pre-extracted vtable address ``name``, or *nil*. Returns the pre-extracted vtable address ``name``, or *nil*.
* ``dfhack.internal.getImageBase()``
Returns the mmap base of the executable.
* ``dfhack.internal.getRebaseDelta()`` * ``dfhack.internal.getRebaseDelta()``
Returns the ASLR rebase offset of the DF executable. Returns the ASLR rebase offset of the DF executable.
* ``dfhack.internal.adjustOffset(offset[,to_file])``
Returns the re-aligned offset, or *nil* if invalid.
If ``to_file`` is true, the offset is adjusted from memory to file.
This function returns the original value everywhere except windows.
* ``dfhack.internal.getMemRanges()`` * ``dfhack.internal.getMemRanges()``
Returns a sequence of tables describing virtual memory ranges of the process. Returns a sequence of tables describing virtual memory ranges of the process.
@ -2672,6 +2717,16 @@ containing newlines, or a table with the following possible fields:
Specifies a pen to paint as one tile before the main part of the token. Specifies a pen to paint as one tile before the main part of the token.
* ``token.width = ...``
If specified either as a value or a callback, the text field is padded
or truncated to the specified number.
* ``token.pad_char = '?'``
If specified together with ``width``, the padding area is filled with
this character instead of just being skipped over.
* ``token.key = '...'`` * ``token.key = '...'``
Specifies the keycode associated with the token. The string description Specifies the keycode associated with the token. The string description
@ -2737,8 +2792,12 @@ It has the following attributes:
:inactive_pen: If specified, used for the cursor when the widget is not active. :inactive_pen: If specified, used for the cursor when the widget is not active.
:icon_pen: Default pen for icons. :icon_pen: Default pen for icons.
:on_select: Selection change callback; called as ``on_select(index,choice)``. :on_select: Selection change callback; called as ``on_select(index,choice)``.
This is also called with *nil* arguments if ``setChoices`` is called
with an empty list.
:on_submit: Enter key callback; if specified, the list reacts to the key :on_submit: Enter key callback; if specified, the list reacts to the key
and calls it as ``on_submit(index,choice)``. and calls it as ``on_submit(index,choice)``.
:on_submit2: Shift-Enter key callback; if specified, the list reacts to the key
and calls it as ``on_submit2(index,choice)``.
:row_height: Height of every row in text lines. :row_height: Height of every row in text lines.
:icon_width: If not *nil*, the specified number of character columns :icon_width: If not *nil*, the specified number of character columns
are reserved to the left of the list item for the icons. are reserved to the left of the list item for the icons.
@ -2788,6 +2847,10 @@ The list supports the following methods:
Call the ``on_submit`` callback, as if the Enter key was handled. Call the ``on_submit`` callback, as if the Enter key was handled.
* ``list:submit2()``
Call the ``on_submit2`` callback, as if the Shift-Enter key was handled.
FilteredList class FilteredList class
------------------ ------------------
@ -2798,6 +2861,7 @@ In addition to passing through all attributes supported by List, it
supports: supports:
:edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. :edit_pen: If specified, used instead of ``cursor_pen`` for the edit field.
:edit_below: If true, the edit field is placed below the list instead of above.
:not_found_label: Specifies the text of the label shown when no items match the filter. :not_found_label: Specifies the text of the label shown when no items match the filter.
The list choices may include the following attributes: The list choices may include the following attributes:
@ -2933,3 +2997,24 @@ from other scripts) in any context, via the same function the core uses:
The ``name`` argument should be the name stem, as would be used on the command line. The ``name`` argument should be the name stem, as would be used on the command line.
Note that this function lets errors propagate to the caller. Note that this function lets errors propagate to the caller.
Save init script
================
If a save directory contains a file called ``raw/init.lua``, it is
automatically loaded and executed every time the save is loaded. It
can also define the following functions to be called by dfhack:
* ``function onStateChange(op) ... end``
Automatically called from the regular onStateChange event as long
as the save is still loaded. This avoids the need to install a hook
into the global ``dfhack.onStateChange`` table, with associated
cleanup concerns.
* ``function onUnload() ... end``
Called when the save containing the script is unloaded. This function
should clean up any global hooks installed by the script.
Within the init script, the path to the save directory is available as ``SAVE_PATH``.

37
NEWS

@ -10,24 +10,55 @@ DFHack future
- fastdwarf: new mode using debug flags, and some internal consistency fixes. - fastdwarf: new mode using debug flags, and some internal consistency fixes.
- added a small stand-alone utility for applying and removing binary patches. - added a small stand-alone utility for applying and removing binary patches.
- removebadthoughts: add --dry-run option - removebadthoughts: add --dry-run option
- superdwarf: work in adventure mode too
- tweak stable-cursor: carries cursor location from/to Build menu.
- deathcause: allow selection from the unitlist screen
- slayrace: allow targetting undeads
New tweaks:
- tweak military-training: speed up melee squad training up to 10x (normally 3-5x).
New scripts: New scripts:
- binpatch: the same as the stand-alone binpatch.exe, but works at runtime.
- region-pops: displays animal populations of the region and allows tweaking them. - region-pops: displays animal populations of the region and allows tweaking them.
- lua: lua interpreter front-end converted to a script from a native command.
- dfusion: misc scripts with a text based menu.
- embark: lets you embark anywhere.
- lever: list and pull fort levers from the dfhack console.
- stripcaged: mark items inside cages for dumping, eg caged goblin weapons.
- soundsense-season: writes the correct season to gamelog.txt on world load.
- create-items: spawn items
New GUI scripts: New GUI scripts:
- gui/guide-path: displays the cached path for minecart Guide orders. - gui/guide-path: displays the cached path for minecart Guide orders.
- gui/workshop-job: displays inputs of a workshop job and allows tweaking them. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them.
- gui/workflow: a front-end for the workflow plugin. - gui/workflow: a front-end for the workflow plugin (part inspired by falconne).
- gui/assign-rack: works together with a binary patch to fix weapon racks. - gui/assign-rack: works together with a binary patch to fix weapon racks.
- gui/gm-editor: an universal editor for lots of dfhack things.
- gui/companion-order: a adventure mode command interface for your companions.
New binary patches (for use with binpatch):
- armorstand-capacity: doubles the capacity of armor stands.
- custom-reagent-size: lets custom reactions use small amounts of inputs.
- deconstruct-heapfall: stops some items still falling on head when deconstructing.
- deconstruct-teleport: stops items from 16x16 block teleporting when deconstructing.
- hospital-overstocking: stops hospital overstocking with supplies.
- training-ammo: lets dwarves with quiver full of combat-only ammo train.
- weaponrack-unassign: fixes bug that negates work done by gui/assign-rack.
Workflow plugin: Workflow plugin:
- properly considers minecarts assigned to routes busy. - properly considers minecarts assigned to routes busy.
- code for deducing job outputs rewritten in lua for flexibility. - code for deducing job outputs rewritten in lua for flexibility.
- logic fix: collecting webs produces silk, and ungathered webs are not thread. - logic fix: collecting webs produces silk, and ungathered webs are not thread.
- items assigned to squads are considered busy, even if not in inventory.
- shearing and milking jobs are supported, but only with generic MILK or YARN outputs.
- workflow announces when the stock level gets very low once a season.
New Fix Armory plugin: New Fix Armory plugin:
Together with a couple of binary patches and the gui/assign-rack script, Together with a couple of binary patches and the gui/assign-rack script,
this plugin makes weapon racks, armor stands, chests and cabinets in this plugin makes weapon racks, armor stands, chests and cabinets in
properly designated barracks be used again for storage of squad equipment. properly designated barracks be used again for storage of squad equipment.
New Search plugin by falconne: New Search plugin by falconne:
Adds an incremental search function to the Stocks, Trading and Unit List screens. Adds an incremental search function to the Stocks, Trading, Stockpile and Unit List screens.
New AutoMaterial plugin by falconne:
Makes building constructions (walls, floors, fortifications, etc) a little bit easier by
saving you from having to trawl through long lists of materials each time you place one.
Dfusion plugin:
Reworked to make use of lua modules, now all the scripts can be used from other scripts.
DFHack v0.34.11-r2 DFHack v0.34.11-r2

File diff suppressed because it is too large Load Diff

@ -58,9 +58,35 @@ The stonesense plugin might require some additional libraries on Linux.
If any of the plugins or dfhack itself refuses to load, check the stderr.log If any of the plugins or dfhack itself refuses to load, check the stderr.log
file created in your DF folder. file created in your DF folder.
Getting started
===============
If DFHack is installed correctly, it will automatically pop up a console
window once DF is started as usual on windows. Linux and Mac OS X require
running the dfhack script from the terminal, and will use that terminal for
the console.
**NOTE**: The dfhack-run executable is there for calling DFHack commands in
an already running DF+DFHack instance from external OS scripts and programs,
and is *not* the way how you use DFHack normally.
DFHack has a lot of features, which can be accessed by typing commands in the
console, or by mapping them to keyboard shortcuts. Most of the newer and more
user-friendly tools are designed to be at least partially used via the latter
way.
In order to set keybindings, you have to create a text configuration file
called ``dfhack.init``; the installation comes with an example version called
``dfhack.init-example``, which is fully functional, covers all of the recent
features and can be simply renamed to ``dfhack.init``. You are encouraged to look
through it to learn which features it makes available under which key combinations.
For more information, refer to the rest of this document.
============ ============
Using DFHack Using DFHack
============ ============
DFHack basically extends what DF can do with something similar to the drop-down DFHack basically extends what DF can do with something similar to the drop-down
console found in Quake engine games. On Windows, this is a separate command line console found in Quake engine games. On Windows, this is a separate command line
window. On linux, the terminal used to launch the dfhack script is taken over window. On linux, the terminal used to launch the dfhack script is taken over
@ -118,6 +144,16 @@ system console:
The patches are expected to be encoded in text format used by IDA. The patches are expected to be encoded in text format used by IDA.
Live patching
-------------
As an alternative, you can use the ``binpatch`` dfhack command to apply/remove
patches live in memory during a DF session.
In this case, updating symbols.xml is not necessary.
============================= =============================
Something doesn't work, help! Something doesn't work, help!
============================= =============================
@ -1051,6 +1087,9 @@ Subcommands that persist until disabled or DF quit:
:patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts. :patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts.
Does NOT fix the problem when soldiers go off-duty (i.e. civilian). Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
:readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu. :readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu.
.. image:: images/tweak-plate.png
:stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates. :stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates.
In very item-heavy forts with big stockpiles this can improve FPS by 50-100% In very item-heavy forts with big stockpiles this can improve FPS by 50-100%
:fast-heat: Further improves temperature update performance by ensuring that 1 degree :fast-heat: Further improves temperature update performance by ensuring that 1 degree
@ -1069,15 +1108,22 @@ Subcommands that persist until disabled or DF quit:
:military-stable-assign: Preserve list order and cursor position when assigning to squad, :military-stable-assign: Preserve list order and cursor position when assigning to squad,
i.e. stop the rightmost list of the Positions page of the military i.e. stop the rightmost list of the Positions page of the military
screen from constantly resetting to the top. screen from constantly resetting to the top.
:military-color-assigned: Color squad candidates already assigned to other squads in brown/green :military-color-assigned: Color squad candidates already assigned to other squads in yellow/green
to make them stand out more in the list. to make them stand out more in the list.
.. image:: images/tweak-mil-color.png
:military-training: Speeds up melee squad training by removing an almost certainly
unintended inverse dependency of training speed on unit count
(i.e. the more units you have, the slower it becomes), and making
the units spar more.
fix-armory fix-armory
---------- ----------
Enables a fix for storage of squad equipment in barracks. Enables a fix for storage of squad equipment in barracks.
Specifically, it prevents your haulers from moving that equipment Specifically, it prevents your haulers from moving squad equipment
to stockpiles, and instead queues jobs to store it on weapon racks, to stockpiles, and instead queues jobs to store it on weapon racks,
armor stands, and in containers. armor stands, and in containers.
@ -1087,9 +1133,10 @@ armor stands, and in containers.
manually assigned to a squad. See documentation for ``gui/assign-rack`` manually assigned to a squad. See documentation for ``gui/assign-rack``
below. below.
Also, the default capacity of armor stands is way too low, so check out Also, the default capacity of armor stands is way too low, so you
may want to also apply the ``armorstand-capacity`` patch. Check out
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
for a patch addressing that too. for more information about the bugs.
Note that the buildings in the armory are used as follows: Note that the buildings in the armory are used as follows:
@ -1258,10 +1305,18 @@ Usage:
List workflow-controlled jobs (if in a workshop, filtered by it). List workflow-controlled jobs (if in a workshop, filtered by it).
``workflow list`` ``workflow list``
List active constraints, and their job counts. List active constraints, and their job counts.
``workflow count <constraint-spec> <cnt-limit> [cnt-gap], workflow amount <constraint-spec> <cnt-limit> [cnt-gap]`` ``workflow list-commands``
Set a constraint. The first form counts each stack as only 1 item. List active constraints as workflow commands that re-create them;
this list can be copied to a file, and then reloaded using the
``script`` built-in command.
``workflow count <constraint-spec> <cnt-limit> [cnt-gap]``
Set a constraint, counting every stack as 1 item.
``workflow amount <constraint-spec> <cnt-limit> [cnt-gap]``
Set a constraint, counting all items within stacks.
``workflow unlimit <constraint-spec>`` ``workflow unlimit <constraint-spec>``
Delete a constraint. Delete a constraint.
``workflow unlimit-all``
Delete all constraints.
Function Function
........ ........
@ -1279,6 +1334,34 @@ the frequency of jobs being toggled.
Check out the ``gui/workflow`` script below for a simple front-end integrated Check out the ``gui/workflow`` script below for a simple front-end integrated
in the game UI. in the game UI.
Constraint format
.................
The contstraint spec consists of 4 parts, separated with '/' characters::
ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,<quality>]
The first part is mandatory and specifies the item type and subtype,
using the raw tokens for items, in the same syntax you would e.g. use
for a custom reaction input. See this list for more info: http://dwarffortresswiki.org/index.php/Item_token
The subsequent parts are optional:
- A generic material spec constrains the item material to one of
the hard-coded generic classes, which currently include::
PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN
METAL STONE SAND GLASS CLAY MILK
- A specific material spec chooses the material exactly, using the
raw syntax for reaction input materials, e.g. INORGANIC:IRON,
although for convenience it also allows just IRON, or ACACIA:WOOD etc.
See this page for more details on the unabbreviated raw syntax:
http://dwarffortresswiki.org/index.php/Material_token
- A comma-separated list of miscellaneous flags, which currently can
be used to ignore imported items or items below a certain quality.
Constraint examples Constraint examples
................... ...................
@ -1308,6 +1391,11 @@ Make sure there are always 15-20 coal and 25-30 copper bars.
workflow count BAR//COAL 20 workflow count BAR//COAL 20
workflow count BAR//COPPER 30 workflow count BAR//COPPER 30
Produce 15-20 gold crafts.
::
workflow count CRAFTS//GOLD 20
Collect 15-20 sand bags and clay boulders. Collect 15-20 sand bags and clay boulders.
:: ::
@ -1319,9 +1407,16 @@ Make sure there are always 80-100 units of dimple dye.
workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20 workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20
.. note::
In order for this to work, you have to set the material of the PLANT input In order for this to work, you have to set the material of the PLANT input
on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material' on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material'
command. command. Otherwise the plugin won't be able to deduce the output material.
Maintain 10-100 locally-made crafts of exceptional quality.
::
workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
Fortress activity management Fortress activity management
@ -1611,19 +1706,17 @@ twice.
dfusion dfusion
------- -------
This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin. This is the DFusion lua plugin system by Warmist, running as a DFHack plugin. There are two parts to this plugin: an interactive script that shows a text based menu and lua modules. Some of the functionality of is intentionaly left out of the menu:
:Friendship: a binary plugin that allows multi race forts (to use make a script that imports plugins.dfusion.friendship and use Friendship:install{table} table should contain list of race names.)
:Embark: a binary plugin that allows multi race embark (to use make a script that imports plugins.dfusion.embark and use Embark:install{table} table should contain list of race names or list of pairs (race-name, caste_id)).
See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15 See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=93317.0
Confirmed working DFusion plugins:
:simple_embark: allows changing the number of dwarves available on embark.
.. note:: .. note::
* Some of the DFusion plugins aren't completely ported yet. This can lead to crashes. * Some of the DFusion plugins aren't completely ported yet. This can lead to crashes.
* This is currently working only on Windows. * The game will be suspended while you're using dfusion. Don't panic when it doesn't respond.
* The game will be suspended while you're using dfusion. Don't panic when it doen't respond.
misery misery
------ ------
@ -1687,6 +1780,17 @@ gui/*
Scripts that implement dialogs inserted into the main game window are put in this Scripts that implement dialogs inserted into the main game window are put in this
directory. directory.
binpatch
========
Checks, applies or removes binary patches directly in memory at runtime::
binpatch check/apply/remove <patchname>
If the name of the patch has no extension or directory separators, the
script uses ``hack/patches/<df-version>/<name>.dif``, thus auto-selecting
the version appropriate for the currently loaded executable.
quicksave quicksave
========= =========
@ -1746,13 +1850,16 @@ slayrace
======== ========
Kills any unit of a given race. Kills any unit of a given race.
With no argument, lists the available races. With no argument, lists the available races and count eligible targets.
With the special argument ``him``, targets only the selected creature. With the special argument ``him``, targets only the selected creature.
With the special argument ``undead``, targets all undeads on the map,
regardless of their race.
Any non-dead non-caged unit of the specified race gets its ``blood_count`` Any non-dead non-caged unit of the specified race gets its ``blood_count``
set to 0, which means immediate death at the next game tick. For creatures set to 0, which means immediate death at the next game tick. For creatures
such as vampires, also set animal.vanish_countdown to 2. such as vampires, it also sets animal.vanish_countdown to 2.
An alternate mode is selected by adding a 2nd argument to the command, An alternate mode is selected by adding a 2nd argument to the command,
``magma``. In this case, a column of 7/7 magma is generated on top of the ``magma``. In this case, a column of 7/7 magma is generated on top of the
@ -1835,7 +1942,95 @@ deathcause
========== ==========
Focus a body part ingame, and this script will display the cause of death of Focus a body part ingame, and this script will display the cause of death of
the creature. the creature.
Also works when selecting units from the 'u'nitlist viewscreen.
lua
===
There are the following ways to invoke this command:
1. ``lua`` (without any parameters)
This starts an interactive lua interpreter.
2. ``lua -f "filename"`` or ``lua --file "filename"``
This loads and runs the file indicated by filename.
3. ``lua -s ["filename"]`` or ``lua --save ["filename"]``
This loads and runs the file indicated by filename from the save
directory. If the filename is not supplied, it loads "dfhack.lua".
4. ``:lua`` *lua statement...*
Parses and executes the lua statement like the interactive interpreter would.
embark
======
Allows to embark anywhere. Currently windows only.
lever
=====
Allow manipulation of in-game levers from the dfhack console.
Can list levers, including state and links, with::
lever list
To queue a job so that a dwarf will pull the lever 42, use ``lever pull 42``.
This is the same as 'q'uerying the building and queue a 'P'ull request.
To magically toggle the lever immediately, use::
lever pull 42 --now
stripcaged
==========
For dumping items inside cages. Will mark selected items for dumping, then
a dwarf may come and actually dump it. See also ``autodump``.
With the ``items`` argument, only dumps items laying in the cage, excluding
stuff worn by caged creatures. ``weapons`` will dump worn weapons, ``armor``
will dump everything worn by caged creatures (including armor and clothing),
and ``all`` will dump everything, on a creature or not.
``stripcaged list`` will display on the dfhack console the list of all cages
and their item content.
Without further arguments, all commands work on all cages and animal traps on
the map. With the ``here`` argument, considers only the in-game selected cage
(or the cage under the game cursor). To target only specific cages, you can
alternatively pass cage IDs as arguments::
stripcaged weapons 25321 34228
create-items
============
Spawn arbitrary items under the cursor.
The first argument gives the item category, the second gives the material,
and the optionnal third gives the number of items to create (defaults to 20).
Currently supported item categories: ``boulder``, ``bar``, ``plant``, ``log``,
``web``.
Instead of material, using ``list`` makes the script list eligible materials.
The ``web`` item category will create an uncollected cobweb on the floor.
Note that the script does not enforce anything, and will let you create
boulders of toad blood and stuff like that.
However the ``list`` mode will only show 'normal' materials.
Exemples::
create-items boulders COAL_BITUMINOUS 12
create-items plant tail_pig
create-items log list
create-items web CREATURE:SPIDER_CAVE_GIANT:SILK
create-items bar CREATURE:CAT:SOAP
create-items bar adamantine
======================= =======================
In-game interface tools In-game interface tools
@ -1849,6 +2044,9 @@ are mostly implemented by lua scripts.
In order to avoid user confusion, as a matter of policy all these tools In order to avoid user confusion, as a matter of policy all these tools
display the word "DFHack" on the screen somewhere while active. display the word "DFHack" on the screen somewhere while active.
When that is not appropriate because they merely add keybinding hints to
existing DF screens, they deliberately use red instead of green for the key.
As an exception, the tweak plugin described above does not follow this As an exception, the tweak plugin described above does not follow this
guideline because it arguably just fixes small usability bugs in the game UI. guideline because it arguably just fixes small usability bugs in the game UI.
@ -1859,12 +2057,18 @@ Dwarf Manipulator
Implemented by the manipulator plugin. To activate, open the unit screen and Implemented by the manipulator plugin. To activate, open the unit screen and
press 'l'. press 'l'.
.. image:: images/manipulator.png
This tool implements a Dwarf Therapist-like interface within the game UI. The This tool implements a Dwarf Therapist-like interface within the game UI. The
far left column displays the unit's Happiness (color-coded based on its far left column displays the unit's Happiness (color-coded based on its
value), and the right half of the screen displays each dwarf's labor settings value), and the right half of the screen displays each dwarf's labor settings
and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand
Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds Master, and U-Z for Legendary thru Legendary+5).
denote skills not controlled by labors.
Cells with teal backgrounds denote skills not controlled by labors, e.g.
military and social skills.
.. image:: images/manipulator2.png
Use the arrow keys or number pad to move the cursor around, holding Shift to Use the arrow keys or number pad to move the cursor around, holding Shift to
move 10 tiles at a time. move 10 tiles at a time.
@ -1901,7 +2105,9 @@ directly to the main dwarf mode screen.
Search Search
====== ======
The search plugin adds search to the Stocks, Trading and Unit List screens. The search plugin adds search to the Stocks, Trading, Stockpile and Unit List screens.
.. image:: images/search.png
Searching works the same way as the search option in "Move to Depot" does. Searching works the same way as the search option in "Move to Depot" does.
You will see the Search option displayed on screen with a hotkey (usually 's'). You will see the Search option displayed on screen with a hotkey (usually 's').
@ -1922,11 +2128,59 @@ are actually visible in the list; the same effect applies to the Trade
Value numbers displayed by the screen. Because of this, pressing the 't' Value numbers displayed by the screen. Because of this, pressing the 't'
key while search is active clears the search instead of executing the trade. key while search is active clears the search instead of executing the trade.
In the stockpile screen the option only appears if the cursor is in the
rightmost list:
.. image:: images/search-stockpile.png
Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only
on items actually shown in the rightmost list, so it is possible to select
only fat or tallow by forbidding fats, then searching for fat/tallow, and
using Permit Fats again while the list is filtered.
AutoMaterial
============
The automaterial plugin makes building constructions (walls, floors, fortifications,
etc) a little bit easier by saving you from having to trawl through long lists of
materials each time you place one.
Firstly, it moves the last used material for a given construction type to the top of
the list, if there are any left. So if you build a wall with chalk blocks, the next
time you place a wall the chalk blocks will be at the top of the list, regardless of
distance (it only does this in "grouped" mode, as individual item lists could be huge).
This should mean you can place most constructions without having to search for your
preferred material type.
.. image:: images/automaterial-mat.png
Pressing 'a' while highlighting any material will enable that material for "auto select"
for this construction type. You can enable multiple materials as autoselect. Now the next
time you place this type of construction, the plugin will automatically choose materials
for you from the kinds you enabled. If there is enough to satisfy the whole placement,
you won't be prompted with the material screen - the construction will be placed and you
will be back in the construction menu as if you did it manually.
When choosing the construction placement, you will see a couple of options:
.. image:: images/automaterial-pos.png
Use 'a' here to temporarily disable the material autoselection, e.g. if you need
to go to the material selection screen so you can toggle some materials on or off.
The other option (auto type selection, off by default) can be toggled on with 't'. If you
toggle this option on, instead of returning you to the main construction menu after selecting
materials, it returns you back to this screen. If you use this along with several autoselect
enabled materials, you should be able to place complex constructions more conveniently.
gui/liquids gui/liquids
=========== ===========
To use, bind to a key and activate in the 'k' mode. To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.
.. image:: images/liquids.png
While active, use the suggested keys to switch the usual liquids parameters, and Enter While active, use the suggested keys to switch the usual liquids parameters, and Enter
to select the target area and apply changes. to select the target area and apply changes.
@ -1935,7 +2189,9 @@ to select the target area and apply changes.
gui/mechanisms gui/mechanisms
============== ==============
To use, bind to a key and activate in the 'q' mode. To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.
.. image:: images/mechanisms.png
Lists mechanisms connected to the building, and their links. Navigating the list centers Lists mechanisms connected to the building, and their links. Navigating the list centers
the view on the relevant linked buildings. the view on the relevant linked buildings.
@ -1953,21 +2209,35 @@ via a simple dialog in the game ui.
* ``gui/rename [building]`` in 'q' mode changes the name of a building. * ``gui/rename [building]`` in 'q' mode changes the name of a building.
.. image:: images/rename-bld.png
The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. The selected building must be one of stockpile, workshop, furnace, trap, or siege engine.
It is also possible to rename zones from the 'i' menu. It is also possible to rename zones from the 'i' menu.
* ``gui/rename [unit]`` with a unit selected changes the nickname. * ``gui/rename [unit]`` with a unit selected changes the nickname.
Unlike the built-in interface, this works even on enemies and animals.
* ``gui/rename unit-profession`` changes the selected unit's custom profession name. * ``gui/rename unit-profession`` changes the selected unit's custom profession name.
.. image:: images/rename-prof.png
Likewise, this can be applied to any unit, and when used on animals it overrides
their species string.
The ``building`` or ``unit`` options are automatically assumed when in relevant ui state. The ``building`` or ``unit`` options are automatically assumed when in relevant ui state.
The example config binds building/unit rename to Ctrl-Shift-N, and
unit profession change to Ctrl-Shift-T.
gui/room-list gui/room-list
============= =============
To use, bind to a key and activate in the 'q' mode, either immediately or after opening To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode,
the assign owner page. either immediately or after opening the assign owner page.
.. image:: images/room-list.png
The script lists other rooms owned by the same owner, or by the unit selected in the assign The script lists other rooms owned by the same owner, or by the unit selected in the assign
list, and allows unassigning them. list, and allows unassigning them.
@ -1976,7 +2246,8 @@ list, and allows unassigning them.
gui/choose-weapons gui/choose-weapons
================== ==================
Bind to a key, and activate in the Equip->View/Customize page of the military screen. Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize
page of the military screen.
Depending on the cursor location, it rewrites all 'individual choice weapon' entries Depending on the cursor location, it rewrites all 'individual choice weapon' entries
in the selected squad or position to use a specific weapon type matching the assigned in the selected squad or position to use a specific weapon type matching the assigned
@ -1990,7 +2261,10 @@ and may lead to inappropriate weapons being selected.
gui/guide-path gui/guide-path
============== ==============
Bind to a key, and activate in the Hauling menu with the cursor over a Guide order. Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with
the cursor over a Guide order.
.. image:: images/guide-path.png
The script displays the cached path that will be used by the order; the game The script displays the cached path that will be used by the order; the game
computes it when the order is executed for the first time. computes it when the order is executed for the first time.
@ -1999,15 +2273,28 @@ computes it when the order is executed for the first time.
gui/workshop-job gui/workshop-job
================ ================
Bind to a key, and activate with a job selected in a workshop in the 'q' mode. Bind to a key (the example config uses Alt-A), and activate with a job selected in
a workshop in the 'q' mode.
.. image:: images/workshop-job.png
The script shows a list of the input reagents of the selected job, and allows changing The script shows a list of the input reagents of the selected job, and allows changing
them like the ``job item-type`` and ``job item-material`` commands. them like the ``job item-type`` and ``job item-material`` commands.
Specifically, pressing the 'i' key pops up a dialog that lets you select an item Specifically, pressing the 'i' key pops up a dialog that lets you select an item
type from a list. Pressing 'm', unless the item type does not allow a material, type from a list.
.. image:: images/workshop-job-item.png
Pressing 'm', unless the item type does not allow a material,
lets you choose a material. lets you choose a material.
.. image:: images/workshop-job-material.png
Since there are a lot more materials than item types, this dialog is more complex
and uses a hierarchy of sub-menus. List choices that open a sub-menu are marked
with an arrow on the left.
.. warning:: .. warning::
Due to the way input reagent matching works in DF, you must select an item type Due to the way input reagent matching works in DF, you must select an item type
@ -2034,7 +2321,10 @@ you have to unset the material first.
gui/workflow gui/workflow
============ ============
Bind to a key, and activate with a job selected in a workshop in the 'q' mode. Bind to a key (the example config uses Alt-W), and activate with a job selected
in a workshop in the 'q' mode.
.. image:: images/workflow.png
This script provides a simple interface to constraints managed by the workflow This script provides a simple interface to constraints managed by the workflow
plugin. When active, it displays a list of all constraints applicable to the plugin. When active, it displays a list of all constraints applicable to the
@ -2046,23 +2336,58 @@ current count is below the lower bound of the range, the job is resumed; if it
is above or equal to the top bound, it will be suspended. Within the range, the is above or equal to the top bound, it will be suspended. Within the range, the
specific constraint has no effect on the job; others may still affect it. specific constraint has no effect on the job; others may still affect it.
Pressing 'c' switches the current constraint between counting stacks or items. Pressing 'I' switches the current constraint between counting stacks or items.
Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the Pressing 'R' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the
bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting bounds by 5, 10, or 20 depending on the direction and the 'I' setting (counting
items and expanding the range each gives a 5x bonus). items and expanding the range each gives a 2x bonus).
Pressing 'n' produces a list of possible outputs of this job as guessed by Pressing 'A' produces a list of possible outputs of this job as guessed by
workflow, and lets you create a new constraint by just choosing one. If you workflow, and lets you create a new constraint by choosing one as template. If you
don't see the choice you want in the list, it likely means you have to adjust don't see the choice you want in the list, it likely means you have to adjust
the job material first using ``job item-material`` or ``gui/workshop-job``, the job material first using ``job item-material`` or ``gui/workshop-job``,
as described in ``workflow`` documentation above. In this manner, this feature as described in ``workflow`` documentation above. In this manner, this feature
can be used for troubleshooting jobs that don't match the right constraints. can be used for troubleshooting jobs that don't match the right constraints.
.. image:: images/workflow-new1.png
If you select one of the outputs with Enter, the matching constraint is simply
added to the list. If you use Shift-Enter, the interface proceeds to the
next dialog, which allows you to edit the suggested constraint parameters to
suit your need, and set the item count range.
.. image:: images/workflow-new2.png
Pressing 'S' (or, with the example config, Alt-W in the 'z' stocks screen)
opens the overall status screen, which was copied from the C++ implementation
by falconne for better integration with the rest of the lua script:
.. image:: images/workflow-status.png
This screen shows all currently existing workflow constraints, and allows
monitoring and/or changing them from one screen. The constraint list can
be filtered by typing text in the field below.
The color of the stock level number indicates how "healthy" the stock level
is, based on current count and trend. Bright green is very good, green is good,
red is bad, bright red is very bad.
The limit number is also color-coded. Red means that there are currently no
workshops producing that item (i.e. no jobs). If it's yellow, that means the
production has been delayed, possibly due to lack of input materials.
The chart on the right is a plot of the last 14 days (28 half day plots) worth
of stock history for the selected item, with the rightmost point representing
the current stock value. The bright green dashed line is the target
limit (maximum) and the dark green line is that minus the gap (minimum).
gui/assign-rack gui/assign-rack
=============== ===============
Bind to a key, and activate when viewing a weapon rack in the 'q' mode. Bind to a key (the example config uses P), and activate when viewing a weapon
rack in the 'q' mode.
.. image:: images/assign-rack.png
This script is part of a group of related fixes to make the armory storage This script is part of a group of related fixes to make the armory storage
work again. The existing issues are: work again. The existing issues are:
@ -2072,7 +2397,9 @@ work again. The existing issues are:
the game does this. This issue is what this script addresses. the game does this. This issue is what this script addresses.
* Even if assigned by the script, **the game will unassign the racks again without a binary patch**. * Even if assigned by the script, **the game will unassign the racks again without a binary patch**.
Check the comments for this bug to get it: This patch is called ``weaponrack-unassign``, and can be applied via
the binpatch program, or the matching script. See this for more info
about the bug:
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
* Haulers still take equpment stored in the armory away to the stockpiles, * Haulers still take equpment stored in the armory away to the stockpiles,
@ -2080,7 +2407,8 @@ work again. The existing issues are:
The script interface simply lets you designate one of the squads that The script interface simply lets you designate one of the squads that
are assigned to the barracks/armory containing the selected stand as are assigned to the barracks/armory containing the selected stand as
the intended user. the intended user. In order to aid in the choice, it shows the number
of currently assigned racks for every valid squad.
============= =============
@ -2118,7 +2446,10 @@ Configuration UI
---------------- ----------------
The configuration front-end to the plugin is implemented by the gui/siege-engine The configuration front-end to the plugin is implemented by the gui/siege-engine
script. Bind it to a key and activate after selecting a siege engine in 'q' mode. script. Bind it to a key (the example config uses Alt-A) and activate after selecting
a siege engine in 'q' mode.
.. image:: images/siege-engine.png
The main mode displays the current target, selected ammo item type, linked stockpiles and The main mode displays the current target, selected ammo item type, linked stockpiles and
the allowed operator skill range. The map tile color is changed to signify if it can be the allowed operator skill range. The map tile color is changed to signify if it can be
@ -2148,7 +2479,10 @@ The power-meter plugin implements a modified pressure plate that detects power b
supplied to gear boxes built in the four adjacent N/S/W/E tiles. supplied to gear boxes built in the four adjacent N/S/W/E tiles.
The configuration front-end is implemented by the gui/power-meter script. Bind it to a The configuration front-end is implemented by the gui/power-meter script. Bind it to a
key and activate after selecting Pressure Plate in the build menu. key (the example config uses Ctrl-Shift-M) and activate after selecting Pressure Plate
in the build menu.
.. image:: images/power-meter.png
The script follows the general look and feel of the regular pressure plate build The script follows the general look and feel of the regular pressure plate build
configuration page, but configures parameters relevant to the modded power meter building. configuration page, but configures parameters relevant to the modded power meter building.

@ -2,21 +2,32 @@
# Generic dwarfmode bindings # # Generic dwarfmode bindings #
############################## ##############################
# toggle the display of water level as 1-7 tiles
keybinding add Ctrl-W twaterlvl keybinding add Ctrl-W twaterlvl
# with cursor: # with cursor:
# designate the whole vein for digging
keybinding add Ctrl-V digv keybinding add Ctrl-V digv
keybinding add Ctrl-Shift-V "digv x" keybinding add Ctrl-Shift-V "digv x"
# clean the selected tile of blood etc
keybinding add Ctrl-C spotclean keybinding add Ctrl-C spotclean
# destroy items designated for dump in the selected tile
keybinding add Ctrl-Shift-K autodump-destroy-here keybinding add Ctrl-Shift-K autodump-destroy-here
# any item: # with an item selected:
# destroy the selected item
keybinding add Ctrl-K autodump-destroy-item keybinding add Ctrl-K autodump-destroy-item
# scripts:
# 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 # gui/rename script - rename units and buildings
keybinding add Ctrl-Shift-N gui/rename keybinding add Ctrl-Shift-N gui/rename
keybinding add Ctrl-Shift-T "gui/rename unit-profession" keybinding add Ctrl-Shift-T "gui/rename unit-profession"
@ -31,10 +42,10 @@ keybinding add Ctrl-Shift-B "adv-bodyswap force"
# Context-specific bindings # # Context-specific bindings #
############################# #############################
# q->stockpile; p # q->stockpile; p - copy & paste stockpiles
keybinding add Alt-P copystock keybinding add Alt-P copystock
# q->workshop # q->workshop - duplicate the selected job
keybinding add Ctrl-D job-duplicate keybinding add Ctrl-D job-duplicate
# materials: q->workshop; b->select items # materials: q->workshop; b->select items
@ -48,7 +59,7 @@ keybinding add Shift-O "job-material OBSIDIAN"
keybinding add Shift-T "job-material ORTHOCLASE" keybinding add Shift-T "job-material ORTHOCLASE"
keybinding add Shift-G "job-material GLASS_GREEN" keybinding add Shift-G "job-material GLASS_GREEN"
# sort units and items # sort units and items in the on-screen list
keybinding add Alt-Shift-N "sort-units name" "sort-items description" keybinding add Alt-Shift-N "sort-units name" "sort-items description"
keybinding add Alt-Shift-R "sort-units arrival" keybinding add Alt-Shift-R "sort-units arrival"
keybinding add Alt-Shift-T "sort-units profession" "sort-items type material" keybinding add Alt-Shift-T "sort-units profession" "sort-items type material"
@ -60,7 +71,7 @@ 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 keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list
# interface for the liquids plugin # interface for the liquids plugin - spawn water/magma/obsidian
keybinding add Alt-L@dwarfmode/LookAround gui/liquids keybinding add Alt-L@dwarfmode/LookAround gui/liquids
# machine power sensitive pressure plate construction # machine power sensitive pressure plate construction
@ -80,6 +91,7 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job
# workflow front-end # workflow front-end
keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
keybinding add Alt-W@overallstatus "gui/workflow status"
# assign weapon racks to squads so that they can be used # assign weapon racks to squads so that they can be used
keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack
@ -121,3 +133,36 @@ tweak fast-trade
tweak military-stable-assign tweak military-stable-assign
# in same list, color units already assigned to squads in brown & green # in same list, color units already assigned to squads in brown & green
tweak military-color-assigned tweak military-color-assigned
# remove inverse dependency of squad training speed on unit list size and use more sparring
tweak military-training
###########
# Scripts #
###########
# write the correct season to gamelog on world load
soundsense-season
#######################################################
# Apply binary patches at runtime #
# #
# Commented out by default; enable the ones you want. #
#######################################################
# Bug 5994 - items teleported when removing a construction
#binpatch apply deconstruct-teleport
#binpatch apply deconstruct-heapfall
# Bug 4406 - hospital overstocking on all items
#binpatch apply hospital-overstocking
# Bug 808 - custom reactions completely using up all of their reagents
#binpatch apply custom-reagent-size
# Bug 4530 - marksdwarves not training when quiver full of combat-only ammo
#binpatch apply training-ammo
# Bug 1445 - weapon racks broken, armor stand capacity too low
#binpatch apply weaponrack-unassign
#binpatch apply armorstand-capacity

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

@ -122,7 +122,6 @@ include/modules/Materials.h
include/modules/Notes.h include/modules/Notes.h
include/modules/Screen.h include/modules/Screen.h
include/modules/Translation.h include/modules/Translation.h
include/modules/Vegetation.h
include/modules/Vermin.h include/modules/Vermin.h
include/modules/World.h include/modules/World.h
include/modules/Graphic.h include/modules/Graphic.h
@ -144,7 +143,6 @@ modules/Materials.cpp
modules/Notes.cpp modules/Notes.cpp
modules/Screen.cpp modules/Screen.cpp
modules/Translation.cpp modules/Translation.cpp
modules/Vegetation.cpp
modules/Vermin.cpp modules/Vermin.cpp
modules/World.cpp modules/World.cpp
modules/Graphic.cpp modules/Graphic.cpp
@ -348,6 +346,10 @@ install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts
PATTERN "*.rb" PATTERN "*.rb"
) )
install(DIRECTORY ${dfhack_SOURCE_DIR}/patches
DESTINATION ${DFHACK_DATA_DESTINATION}
FILES_MATCHING PATTERN "*.dif")
# Unused for so long that it's not even relevant now... # Unused for so long that it's not even relevant now...
if(BUILD_DEVEL) if(BUILD_DEVEL)
if(WIN32) if(WIN32)

@ -317,7 +317,7 @@ static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr,
rbcmd += "'" + args[i] + "', "; rbcmd += "'" + args[i] + "', ";
rbcmd += "]\n"; rbcmd += "]\n";
rbcmd += "load './hack/scripts/" + name + ".rb'"; rbcmd += "catch(:script_finished) { load './hack/scripts/" + name + ".rb' }";
return plug_mgr->eval_ruby(out, rbcmd.c_str()); return plug_mgr->eval_ruby(out, rbcmd.c_str());
} }
@ -344,6 +344,50 @@ command_result Core::runCommand(color_ostream &out, const std::string &command)
return CR_NOT_IMPLEMENTED; return CR_NOT_IMPLEMENTED;
} }
static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed)
{
std::vector<std::string> possible;
auto plug_mgr = Core::getInstance().getPluginManager();
for(size_t i = 0; i < plug_mgr->size(); i++)
{
const Plugin * plug = (plug_mgr->operator[](i));
for (size_t j = 0; j < plug->size(); j++)
{
const PluginCommand &pcmd = plug->operator[](j);
if (pcmd.isHotkeyCommand())
continue;
if (pcmd.name.substr(0, first.size()) == first)
possible.push_back(pcmd.name);
}
}
bool all = (first.find('/') != std::string::npos);
std::map<string, string> scripts;
listScripts(plug_mgr, scripts, Core::getInstance().getHackPath() + "scripts/", all);
for (auto iter = scripts.begin(); iter != scripts.end(); ++iter)
if (iter->first.substr(0, first.size()) == first)
possible.push_back(iter->first);
if (possible.size() == 1)
{
completed = possible[0];
fprintf(stderr, "Autocompleted %s to %s\n", first.c_str(), completed.c_str());
return true;
}
if (possible.size() > 1 && possible.size() < 8)
{
std::string out;
for (size_t i = 0; i < possible.size(); i++)
out += " " + possible[i];
con.print("Possible completions:%s\n", out.c_str());
}
return false;
}
command_result Core::runCommand(color_ostream &con, const std::string &first, vector<string> &parts) command_result Core::runCommand(color_ostream &con, const std::string &first, vector<string> &parts)
{ {
if (!first.empty()) if (!first.empty())
@ -666,10 +710,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
if(res == CR_NOT_IMPLEMENTED) if(res == CR_NOT_IMPLEMENTED)
{ {
auto filename = getHackPath() + "scripts/" + first; auto filename = getHackPath() + "scripts/" + first;
std::string completed;
if (fileExists(filename + ".lua")) if (fileExists(filename + ".lua"))
res = runLuaScript(con, first, parts); res = runLuaScript(con, first, parts);
else if (plug_mgr->eval_ruby && fileExists(filename + ".rb")) else if (plug_mgr->eval_ruby && fileExists(filename + ".rb"))
res = runRubyScript(con, plug_mgr, first, parts); res = runRubyScript(con, plug_mgr, first, parts);
else if (try_autocomplete(con, first, completed))
return runCommand(con, completed, parts);
else else
con.printerr("%s is not a recognized command.\n", first.c_str()); con.printerr("%s is not a recognized command.\n", first.c_str());
} }
@ -734,7 +782,6 @@ void fIOthread(void * iodata)
{ {
string command = ""; string command = "";
int ret = con.lineedit("[DFHack]# ",command, main_history); int ret = con.lineedit("[DFHack]# ",command, main_history);
fprintf(stderr,"Command: [%s]\n",command.c_str());
if(ret == -2) if(ret == -2)
{ {
cerr << "Console is shutting down properly." << endl; cerr << "Console is shutting down properly." << endl;
@ -748,14 +795,10 @@ void fIOthread(void * iodata)
else if(ret) else if(ret)
{ {
// a proper, non-empty command was entered // a proper, non-empty command was entered
fprintf(stderr,"Adding command to history\n");
main_history.add(command); main_history.add(command);
fprintf(stderr,"Saving history\n");
main_history.save("dfhack.history"); main_history.save("dfhack.history");
} }
fprintf(stderr,"Running command\n");
auto rv = core->runCommand(con, command); auto rv = core->runCommand(con, command);
if (rv == CR_NOT_IMPLEMENTED) if (rv == CR_NOT_IMPLEMENTED)

@ -1121,6 +1121,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,cloneJobStruct), WRAPM(Job,cloneJobStruct),
WRAPM(Job,printItemDetails), WRAPM(Job,printItemDetails),
WRAPM(Job,printJobDetails), WRAPM(Job,printJobDetails),
WRAPM(Job,getGeneralRef),
WRAPM(Job,getSpecificRef),
WRAPM(Job,getHolder), WRAPM(Job,getHolder),
WRAPM(Job,getWorker), WRAPM(Job,getWorker),
WRAPM(Job,checkBuildingsNow), WRAPM(Job,checkBuildingsNow),
@ -1157,6 +1159,8 @@ static const luaL_Reg dfhack_job_funcs[] = {
/***** Units module *****/ /***** Units module *****/
static const LuaWrapper::FunctionReg dfhack_units_module[] = { static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getGeneralRef),
WRAPM(Units, getSpecificRef),
WRAPM(Units, getContainer), WRAPM(Units, getContainer),
WRAPM(Units, setNickname), WRAPM(Units, setNickname),
WRAPM(Units, getVisibleName), WRAPM(Units, getVisibleName),
@ -1176,6 +1180,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getAge), WRAPM(Units, getAge),
WRAPM(Units, getNominalSkill), WRAPM(Units, getNominalSkill),
WRAPM(Units, getEffectiveSkill), WRAPM(Units, getEffectiveSkill),
WRAPM(Units, getExperience),
WRAPM(Units, computeMovementSpeed), WRAPM(Units, computeMovementSpeed),
WRAPM(Units, getProfessionName), WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName), WRAPM(Units, getCasteProfessionName),
@ -1427,6 +1432,8 @@ static bool buildings_containsTile(df::building *bld, int x, int y, bool room) {
} }
static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
WRAPM(Buildings, getGeneralRef),
WRAPM(Buildings, getSpecificRef),
WRAPM(Buildings, setOwner), WRAPM(Buildings, setOwner),
WRAPM(Buildings, allocInstance), WRAPM(Buildings, allocInstance),
WRAPM(Buildings, checkFreeTiles), WRAPM(Buildings, checkFreeTiles),
@ -1718,9 +1725,11 @@ static void *checkaddr(lua_State *L, int idx, bool allow_null = false)
return rv; return rv;
} }
static uint32_t getImageBase() { return Core::getInstance().p->getBase(); }
static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); } static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); }
static const LuaWrapper::FunctionReg dfhack_internal_module[] = { static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
WRAP(getImageBase),
WRAP(getRebaseDelta), WRAP(getRebaseDelta),
{ NULL, NULL } { NULL, NULL }
}; };
@ -1774,6 +1783,18 @@ static int internal_getVTable(lua_State *L)
return 1; return 1;
} }
static int internal_adjustOffset(lua_State *L)
{
lua_settop(L, 2);
int off = luaL_checkint(L, 1);
int rv = Core::getInstance().p->adjustOffset(off, lua_toboolean(L, 2));
if (rv >= 0)
lua_pushinteger(L, rv);
else
lua_pushnil(L);
return 1;
}
static int internal_getMemRanges(lua_State *L) static int internal_getMemRanges(lua_State *L)
{ {
std::vector<DFHack::t_memrange> ranges; std::vector<DFHack::t_memrange> ranges;
@ -1981,6 +2002,7 @@ static const luaL_Reg dfhack_internal_funcs[] = {
{ "getAddress", internal_getAddress }, { "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress }, { "setAddress", internal_setAddress },
{ "getVTable", internal_getVTable }, { "getVTable", internal_getVTable },
{ "adjustOffset", internal_adjustOffset },
{ "getMemRanges", internal_getMemRanges }, { "getMemRanges", internal_getMemRanges },
{ "patchMemory", internal_patchMemory }, { "patchMemory", internal_patchMemory },
{ "patchBytes", internal_patchBytes }, { "patchBytes", internal_patchBytes },

@ -1551,6 +1551,10 @@ void DFHack::Lua::Notification::bind(lua_State *state, const char *name)
void OpenDFHackApi(lua_State *state); void OpenDFHackApi(lua_State *state);
namespace DFHack { namespace Lua { namespace Core {
static void InitCoreContext();
}}}
lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
{ {
if (!state) if (!state)
@ -1654,6 +1658,10 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_dup(state); lua_dup(state);
lua_rawseti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); lua_rawseti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
// Init core-context specific stuff before loading dfhack.lua
if (IsCoreContext(state))
Lua::Core::InitCoreContext();
// load dfhack.lua // load dfhack.lua
Require(out, state, "dfhack"); Require(out, state, "dfhack");
@ -1829,8 +1837,12 @@ void DFHack::Lua::Core::Init(color_ostream &out)
State = luaL_newstate(); State = luaL_newstate();
// Calls InitCoreContext after checking IsCoreContext
Lua::Open(out, State); Lua::Open(out, State);
}
static void Lua::Core::InitCoreContext()
{
lua_newtable(State); lua_newtable(State);
lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);

@ -220,9 +220,14 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
}*/ }*/
} }
uint32_t Process::getBase() uintptr_t Process::getBase()
{ {
return 0; return 0x1000000;
}
int Process::adjustOffset(int offset, bool /*to_file*/)
{
return offset;
} }
static int getdir (string dir, vector<string> &files) static int getdir (string dir, vector<string> &files)
@ -300,3 +305,28 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
return result==0; return result==0;
} }
// returns -1 on error
void* Process::memAlloc(const int length)
{
return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
}
int Process::memDealloc(const void *ptr, const int length)
{
return munmap(ptr, length);
}
int Process::memProtect(const void *ptr, const int length, const int prot)
{
int prot_native = 0;
if (prot & Process::MemProt::READ)
prot_native |= PROT_READ;
if (prot & Process::MemProt::WRITE)
prot_native |= PROT_WRITE;
if (prot & Process::MemProt::EXEC)
prot_native |= PROT_EXEC;
return mprotect(ptr, length, prot_native);
}

@ -155,9 +155,14 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
fclose(mapFile); fclose(mapFile);
} }
uint32_t Process::getBase() uintptr_t Process::getBase()
{ {
return 0; return 0x8048000;
}
int Process::adjustOffset(int offset, bool /*to_file*/)
{
return offset;
} }
static int getdir (string dir, vector<string> &files) static int getdir (string dir, vector<string> &files)
@ -231,3 +236,28 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
return result==0; return result==0;
} }
// returns -1 on error
void* Process::memAlloc(const int length)
{
return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
}
int Process::memDealloc(void *ptr, const int length)
{
return munmap(ptr, length);
}
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;
if (prot & Process::MemProt::READ)
prot_native |= PROT_READ;
if (prot & Process::MemProt::WRITE)
prot_native |= PROT_WRITE;
if (prot & Process::MemProt::EXEC)
prot_native |= PROT_EXEC;
return mprotect(ptr, length, prot_native);
}

@ -160,7 +160,7 @@ Process::Process(VersionInfoFactory * factory)
identified = true; identified = true;
// give the process a data model and memory layout fixed for the base of first module // give the process a data model and memory layout fixed for the base of first module
my_descriptor = new VersionInfo(*vinfo); my_descriptor = new VersionInfo(*vinfo);
my_descriptor->rebaseTo((uint32_t)d->base); my_descriptor->rebaseTo(getBase());
for(size_t i = 0; i < threads_ids.size();i++) for(size_t i = 0; i < threads_ids.size();i++)
{ {
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD) threads_ids[i]); HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD) threads_ids[i]);
@ -394,13 +394,46 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
} }
} }
uint32_t Process::getBase() uintptr_t Process::getBase()
{ {
if(d) if(d)
return (uint32_t) d->base; return (uintptr_t) d->base;
return 0x400000; return 0x400000;
} }
int Process::adjustOffset(int offset, bool to_file)
{
if (!d)
return -1;
for(int i = 0; i < d->pe_header.FileHeader.NumberOfSections; i++)
{
auto &section = d->sections[i];
if (to_file)
{
unsigned delta = offset - section.VirtualAddress;
if (delta >= section.Misc.VirtualSize)
continue;
if (!section.PointerToRawData || delta >= section.SizeOfRawData)
return -1;
return (int)(section.PointerToRawData + delta);
}
else
{
unsigned delta = offset - section.PointerToRawData;
if (!section.PointerToRawData || delta >= section.SizeOfRawData)
continue;
if (delta >= section.Misc.VirtualSize)
return -1;
return (int)(section.VirtualAddress + delta);
}
}
return -1;
}
string Process::doReadClassName (void * vptr) string Process::doReadClassName (void * vptr)
{ {
char * rtti = readPtr((char *)vptr - 0x4); char * rtti = readPtr((char *)vptr - 0x4);
@ -440,3 +473,42 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
return result; return result;
} }
void* Process::memAlloc(const int length)
{
void *ret;
// returns 0 on error
ret = VirtualAlloc(0, length, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if (!ret)
ret = (void*)-1;
return ret;
}
int Process::memDealloc(void *ptr, const int length)
{
// can only free the whole region at once
// vfree returns 0 on error
return !VirtualFree(ptr, 0, MEM_RELEASE);
}
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;
DWORD old_prot = 0;
// only support a few constant combinations
if (prot == 0)
prot_native = PAGE_NOACCESS;
else if (prot == Process::MemProt::READ)
prot_native = PAGE_READONLY;
else if (prot == (Process::MemProt::READ | Process::MemProt::WRITE))
prot_native = PAGE_READWRITE;
else if (prot == (Process::MemProt::READ | Process::MemProt::WRITE | Process::MemProt::EXEC))
prot_native = PAGE_EXECUTE_READWRITE;
else if (prot == (Process::MemProt::READ | Process::MemProt::EXEC))
prot_native = PAGE_EXECUTE_READ;
else
return -1;
return !VirtualProtect(ptr, length, prot_native, &old_prot);
}

@ -100,6 +100,24 @@ bool DFHack::removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type
return false; return false;
} }
df::item *DFHack::findItemRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{
auto ref = findRef(vec, type);
return ref ? ref->getItem() : NULL;
}
df::building *DFHack::findBuildingRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{
auto ref = findRef(vec, type);
return ref ? ref->getBuilding() : NULL;
}
df::unit *DFHack::findUnitRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{
auto ref = findRef(vec, type);
return ref ? ref->getUnit() : NULL;
}
df::specific_ref *DFHack::findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type) df::specific_ref *DFHack::findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type)
{ {
for (int i = vec.size()-1; i >= 0; i--) for (int i = vec.size()-1; i >= 0; i--)

@ -103,13 +103,13 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
{ {
mem->setOS(OS_LINUX); mem->setOS(OS_LINUX);
// this is wrong... I'm not going to do base image relocation on linux though. // this is wrong... I'm not going to do base image relocation on linux though.
mem->setBase(0x0); mem->setBase(0x8048000);
} }
else if(os == "darwin") else if(os == "darwin")
{ {
mem->setOS(OS_APPLE); mem->setOS(OS_APPLE);
// this is wrong... I'm not going to do base image relocation on linux though. // this is wrong... I'm not going to do base image relocation on linux though.
mem->setBase(0x0); mem->setBase(0x1000000);
} }
else else
{ {

@ -61,7 +61,6 @@ distribution.
#include "modules/Translation.h" #include "modules/Translation.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Items.h" #include "modules/Items.h"
#include "modules/Vegetation.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Gui.h" #include "modules/Gui.h"

@ -480,3 +480,18 @@ namespace DFHack {namespace Lua {
name##_event.invoke(out, 5); \ name##_event.invoke(out, 5); \
} \ } \
} }
#define DEFINE_LUA_EVENT_6(name, handler, arg_type1, arg_type2, arg_type3, arg_type4, arg_type5,arg_type6) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4,arg_type5 arg5, arg_type6 arg6) { \
handler(out, arg1, arg2, arg3, arg4, arg5, arg6); \
if (auto state = name##_event.state_if_count()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
DFHack::Lua::Push(state, arg3); \
DFHack::Lua::Push(state, arg4); \
DFHack::Lua::Push(state, arg5); \
DFHack::Lua::Push(state, arg6); \
name##_event.invoke(out, 6); \
} \
}

@ -275,11 +275,13 @@ namespace DFHack
{ {
return my_descriptor; return my_descriptor;
}; };
uint32_t getBase(); uintptr_t getBase();
/// get the DF Process ID /// get the DF Process ID
int getPID(); int getPID();
/// get the DF Process FilePath /// get the DF Process FilePath
std::string getPath(); std::string getPath();
/// Adjust between in-memory and in-file image offset
int adjustOffset(int offset, bool to_file = false);
/// millisecond tick count, exactly as DF uses /// millisecond tick count, exactly as DF uses
uint32_t getTickCount(); uint32_t getTickCount();
@ -289,6 +291,27 @@ namespace DFHack
/// write a possibly read-only memory area /// write a possibly read-only memory area
bool patchMemory(void *target, const void* src, size_t count); bool patchMemory(void *target, const void* src, size_t count);
/// allocate new memory pages for code or stuff
/// returns -1 on error (0 is a valid address)
void* memAlloc(const int length);
/// free memory pages from memAlloc
/// should have length = alloced length for portability
/// returns 0 on success
int memDealloc(void *ptr, const int length);
/// change memory page permissions
/// prot is a bitwise OR of the MemProt enum
/// returns 0 on success
int memProtect(void *ptr, const int length, const int prot);
enum MemProt {
READ = 1,
WRITE = 2,
EXEC = 4
};
private: private:
VersionInfo * my_descriptor; VersionInfo * my_descriptor;
PlatformSpecific *d; PlatformSpecific *d;

@ -33,7 +33,6 @@ namespace DFHack
Module* createGui(); Module* createGui();
Module* createWorld(); Module* createWorld();
Module* createMaterials(); Module* createMaterials();
Module* createVegetation();
Module* createNotes(); Module* createNotes();
Module* createGraphic(); Module* createGraphic();
} }

@ -203,6 +203,12 @@ namespace DFHack
return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype)); return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype));
} }
inline
bool FlowPassableDown(df::tiletype tiletype)
{
return ENUM_ATTR(tiletype_shape, passable_flow_down, tileShape(tiletype));
}
inline inline
bool isWalkable(df::tiletype tiletype) bool isWalkable(df::tiletype tiletype)
{ {

@ -74,12 +74,38 @@ namespace DFHack
uint32_t xpNxtLvl; uint32_t xpNxtLvl;
}; };
typedef std::pair<df::coord2d, df::coord2d> rect2d;
inline rect2d intersect(rect2d a, rect2d b) {
df::coord2d g1 = a.first, g2 = a.second;
df::coord2d c1 = b.first, c2 = b.second;
df::coord2d rc1 = df::coord2d(std::max(g1.x, c1.x), std::max(g1.y, c1.y));
df::coord2d rc2 = df::coord2d(std::min(g2.x, c2.x), std::min(g2.y, c2.y));
return rect2d(rc1, rc2);
}
inline rect2d mkrect_xy(int x1, int y1, int x2, int y2) {
return rect2d(df::coord2d(x1, y1), df::coord2d(x2, y2));
}
inline rect2d mkrect_wh(int x, int y, int w, int h) {
return rect2d(df::coord2d(x, y), df::coord2d(x+w-1, y+h-1));
}
inline df::coord2d rect_size(const rect2d &rect) {
return rect.second - rect.first + df::coord2d(1,1);
}
DFHACK_EXPORT int getdir(std::string dir, std::vector<std::string> &files); DFHACK_EXPORT int getdir(std::string dir, std::vector<std::string> &files);
DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending); DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending);
DFHACK_EXPORT df::general_ref *findRef(std::vector<df::general_ref*> &vec, df::general_ref_type type); DFHACK_EXPORT df::general_ref *findRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT bool removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type type, int id); DFHACK_EXPORT bool removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type type, int id);
DFHACK_EXPORT df::item *findItemRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT df::building *findBuildingRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT df::unit *findUnitRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type); DFHACK_EXPORT df::specific_ref *findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type);
DFHACK_EXPORT bool removeRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type, void *ptr); DFHACK_EXPORT bool removeRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type, void *ptr);
}// namespace DFHack }// namespace DFHack

@ -27,7 +27,7 @@ distribution.
#include "Pragma.h" #include "Pragma.h"
#include "Export.h" #include "Export.h"
#include "Types.h" /* #include "Types.h" */
#include <map> #include <map>
#include <sys/types.h> #include <sys/types.h>
#include <vector> #include <vector>

@ -92,6 +92,9 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building);
*/ */
DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map <uint32_t, std::string> & btypes); DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map <uint32_t, std::string> & btypes);
DFHACK_EXPORT df::general_ref *getGeneralRef(df::building *building, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *getSpecificRef(df::building *building, df::specific_ref_type type);
/** /**
* Sets the owner unit for the building. * Sets the owner unit for the building.
*/ */

@ -29,6 +29,8 @@ distribution.
#include "ColorText.h" #include "ColorText.h"
#include <string> #include <string>
#include "Types.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "df/init.h" #include "df/init.h"
#include "df/ui.h" #include "df/ui.h"
@ -116,6 +118,9 @@ namespace DFHack
int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2; int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2;
int y1, y2; int y1, y2;
bool menu_on, area_on, menu_forced; bool menu_on, area_on, menu_forced;
rect2d map() { return mkrect_xy(map_x1, y1, map_x2, y2); }
rect2d menu() { return mkrect_xy(menu_x1, y1, menu_x2, y2); }
}; };
DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims(); DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims();

@ -28,6 +28,8 @@ distribution.
#include "Export.h" #include "Export.h"
#include "Module.h" #include "Module.h"
#include "Types.h"
#include <ostream> #include <ostream>
#include "DataDefs.h" #include "DataDefs.h"
@ -55,6 +57,9 @@ namespace DFHack
DFHACK_EXPORT void printItemDetails(color_ostream &out, df::job_item *item, int idx); DFHACK_EXPORT void printItemDetails(color_ostream &out, df::job_item *item, int idx);
DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job); DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job);
DFHACK_EXPORT df::general_ref *getGeneralRef(df::job *job, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *getSpecificRef(df::job *job, df::specific_ref_type type);
DFHACK_EXPORT df::building *getHolder(df::job *job); DFHACK_EXPORT df::building *getHolder(df::job *job);
DFHACK_EXPORT df::unit *getWorker(df::job *job); DFHACK_EXPORT df::unit *getWorker(df::job *job);

@ -111,8 +111,8 @@ public:
{ {
if (!basemats) init_tiles(true); if (!basemats) init_tiles(true);
return t_matpair( return t_matpair(
index_tile<int16_t>(basemats->mattype,p), index_tile<int16_t>(basemats->mat_type,p),
index_tile<int16_t>(basemats->matindex,p) index_tile<int16_t>(basemats->mat_index,p)
); );
} }
bool isVeinAt(df::coord2d p) bool isVeinAt(df::coord2d p)
@ -151,8 +151,8 @@ public:
if (!basemats) init_tiles(true); if (!basemats) init_tiles(true);
if (tiles->con_info) if (tiles->con_info)
return t_matpair( return t_matpair(
index_tile<int16_t>(tiles->con_info->mattype,p), index_tile<int16_t>(tiles->con_info->mat_type,p),
index_tile<int16_t>(tiles->con_info->matindex,p) index_tile<int16_t>(tiles->con_info->mat_index,p)
); );
return baseMaterialAt(p); return baseMaterialAt(p);
} }
@ -284,8 +284,8 @@ private:
struct ConInfo { struct ConInfo {
df::tile_bitmask constructed; df::tile_bitmask constructed;
df::tiletype tiles[16][16]; df::tiletype tiles[16][16];
t_blockmaterials mattype; t_blockmaterials mat_type;
t_blockmaterials matindex; t_blockmaterials mat_index;
}; };
struct TileInfo { struct TileInfo {
df::tile_bitmask frozen; df::tile_bitmask frozen;
@ -304,8 +304,8 @@ private:
}; };
struct BasematInfo { struct BasematInfo {
df::tile_bitmask dirty; df::tile_bitmask dirty;
t_blockmaterials mattype; t_blockmaterials mat_type;
t_blockmaterials matindex; t_blockmaterials mat_index;
t_blockmaterials layermat; t_blockmaterials layermat;
BasematInfo(); BasematInfo();

@ -32,7 +32,6 @@ distribution.
#include "Export.h" #include "Export.h"
#include "Module.h" #include "Module.h"
#include "modules/Vegetation.h"
#include <vector> #include <vector>
#include "BitArray.h" #include "BitArray.h"
#include "modules/Materials.h" #include "modules/Materials.h"

@ -338,10 +338,10 @@ namespace DFHack
*/ */
struct t_material struct t_material
{ {
t_itemType itemType; t_itemType item_type;
t_itemSubtype subType; t_itemSubtype item_subtype;
t_materialType material; t_materialType mat_type;
t_materialIndex index; t_materialIndex mat_index;
uint32_t flags; uint32_t flags;
}; };
/** /**

@ -27,7 +27,10 @@ distribution.
#include "Module.h" #include "Module.h"
#include "BitArray.h" #include "BitArray.h"
#include "ColorText.h" #include "ColorText.h"
#include "Types.h"
#include <string> #include <string>
#include <set>
#include "DataDefs.h" #include "DataDefs.h"
#include "df/graphic.h" #include "df/graphic.h"
@ -50,6 +53,8 @@ namespace DFHack
{ {
class Core; class Core;
typedef std::set<df::interface_key> interface_key_set;
/** /**
* The Screen module * The Screen module
* \ingroup grp_modules * \ingroup grp_modules
@ -94,11 +99,67 @@ namespace DFHack
: ch(ch), fg(fg), bg(bg), bold(bold), : ch(ch), fg(fg), bg(bg), bold(bold),
tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg) tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg)
{} {}
void adjust(int8_t nfg) { fg = nfg&7; bold = !!(nfg&8); }
void adjust(int8_t nfg, bool nbold) { fg = nfg; bold = nbold; }
void adjust(int8_t nfg, int8_t nbg) { adjust(nfg); bg = nbg; }
void adjust(int8_t nfg, bool nbold, int8_t nbg) { adjust(nfg, nbold); bg = nbg; }
Pen color(int8_t nfg) const { Pen cp(*this); cp.adjust(nfg); return cp; }
Pen color(int8_t nfg, bool nbold) const { Pen cp(*this); cp.adjust(nfg, nbold); return cp; }
Pen color(int8_t nfg, int8_t nbg) const { Pen cp(*this); cp.adjust(nfg, nbg); return cp; }
Pen color(int8_t nfg, bool nbold, int8_t nbg) const { Pen cp(*this); cp.adjust(nfg, nbold, nbg); return cp; }
Pen chtile(char ch) { Pen cp(*this); cp.ch = ch; return cp; }
Pen chtile(char ch, int tile) { Pen cp(*this); cp.ch = ch; cp.tile = tile; return cp; }
};
struct DFHACK_EXPORT ViewRect {
rect2d view, clip;
ViewRect(rect2d area) : view(area), clip(area) {}
ViewRect(rect2d area, rect2d clip) : view(area), clip(clip) {}
bool isDefunct() const {
return clip.first.x > clip.second.x || clip.first.y > clip.second.y;
}
int width() const { return view.second.x-view.first.x+1; }
int height() const { return view.second.y-view.first.y+1; }
df::coord2d local(df::coord2d pos) const {
return df::coord2d(pos.x - view.first.x, pos.y - view.first.y);
}
df::coord2d global(df::coord2d pos) const {
return df::coord2d(pos.x + view.first.x, pos.y + view.first.y);
}
df::coord2d global(int x, int y) const {
return df::coord2d(x + view.first.x, y + view.first.y);
}
bool inClipGlobal(int x, int y) const {
return x >= clip.first.x && x <= clip.second.x &&
y >= clip.first.y && y <= clip.second.y;
}
bool inClipGlobal(df::coord2d pos) const {
return inClipGlobal(pos.x, pos.y);
}
bool inClipLocal(int x, int y) const {
return inClipGlobal(x + view.first.x, y + view.first.y);
}
bool inClipLocal(df::coord2d pos) const {
return inClipLocal(pos.x, pos.y);
}
ViewRect viewport(rect2d area) const {
rect2d nview(global(area.first), global(area.second));
return ViewRect(nview, intersect(nview, clip));
}
}; };
DFHACK_EXPORT df::coord2d getMousePos(); DFHACK_EXPORT df::coord2d getMousePos();
DFHACK_EXPORT df::coord2d getWindowSize(); DFHACK_EXPORT df::coord2d getWindowSize();
inline rect2d getScreenRect() {
return rect2d(df::coord2d(0,0), getWindowSize()-df::coord2d(1,1));
}
/// Returns the state of [GRAPHICS:YES/NO] /// Returns the state of [GRAPHICS:YES/NO]
DFHACK_EXPORT bool inGraphicsMode(); DFHACK_EXPORT bool inGraphicsMode();
@ -133,6 +194,77 @@ namespace DFHack
/// Retrieve the string representation of the bound key. /// Retrieve the string representation of the bound key.
DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key); DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key);
/// A painter class that implements a clipping area and cursor/pen state
struct DFHACK_EXPORT Painter : ViewRect {
df::coord2d gcursor;
Pen cur_pen, cur_key_pen;
static const Pen default_pen;
static const Pen default_key_pen;
Painter(const ViewRect &area, const Pen &pen = default_pen, const Pen &kpen = default_key_pen)
: ViewRect(area), gcursor(area.view.first), cur_pen(pen), cur_key_pen(kpen)
{}
df::coord2d cursor() const { return local(gcursor); }
int cursorX() const { return gcursor.x - view.first.x; }
int cursorY() const { return gcursor.y - view.first.y; }
bool isValidPos() const { return inClipGlobal(gcursor); }
Painter viewport(rect2d area) const {
return Painter(ViewRect::viewport(area), cur_pen, cur_key_pen);
}
Painter &seek(df::coord2d pos) { gcursor = global(pos); return *this; }
Painter &seek(int x, int y) { gcursor = global(x,y); return *this; }
Painter &advance(int dx) { gcursor.x += dx; return *this; }
Painter &advance(int dx, int dy) { gcursor.x += dx; gcursor.y += dy; return *this; }
Painter &newline(int dx = 0) { gcursor.y++; gcursor.x = view.first.x + dx; return *this; }
const Pen &pen() const { return cur_pen; }
Painter &pen(const Pen &np) { cur_pen = np; return *this; }
Painter &pen(int8_t fg) { cur_pen.adjust(fg); return *this; }
const Pen &key_pen() const { return cur_key_pen; }
Painter &key_pen(const Pen &np) { cur_key_pen = np; return *this; }
Painter &key_pen(int8_t fg) { cur_key_pen.adjust(fg); return *this; }
Painter &clear() {
fillRect(Pen(' ',0,0,false), clip.first.x, clip.first.y, clip.second.x, clip.second.y);
return *this;
}
Painter &fill(const rect2d &area, const Pen &pen) {
rect2d irect = intersect(area, clip);
fillRect(pen, irect.first.x, irect.first.y, irect.second.x, irect.second.y);
return *this;
}
Painter &fill(const rect2d &area) { return fill(area, cur_pen); }
Painter &tile(const Pen &pen) {
if (isValidPos()) paintTile(pen, gcursor.x, gcursor.y);
return advance(1);
}
Painter &tile() { return tile(cur_pen); }
Painter &tile(char ch) { return tile(cur_pen.chtile(ch)); }
Painter &tile(char ch, int tileid) { return tile(cur_pen.chtile(ch, tileid)); }
Painter &string(const std::string &str, const Pen &pen) {
do_paint_string(str, pen); return advance(str.size());
}
Painter &string(const std::string &str) { return string(str, cur_pen); }
Painter &string(const std::string &str, int8_t fg) { return string(str, cur_pen.color(fg)); }
Painter &key(df::interface_key kc, const Pen &pen) {
return string(getKeyDisplay(kc), pen);
}
Painter &key(df::interface_key kc) { return key(kc, cur_key_pen); }
private:
void do_paint_string(const std::string &str, const Pen &pen);
};
} }
class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen { class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen {

@ -205,6 +205,9 @@ DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target);
/// Returns the true position of the unit (non-trivial in case of caged). /// Returns the true position of the unit (non-trivial in case of caged).
DFHACK_EXPORT df::coord getPosition(df::unit *unit); DFHACK_EXPORT df::coord getPosition(df::unit *unit);
DFHACK_EXPORT df::general_ref *getGeneralRef(df::unit *unit, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *getSpecificRef(df::unit *unit, df::specific_ref_type type);
DFHACK_EXPORT df::item *getContainer(df::unit *unit); DFHACK_EXPORT df::item *getContainer(df::unit *unit);
DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick);
@ -235,6 +238,8 @@ DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false);
DFHACK_EXPORT int getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust = false); DFHACK_EXPORT int getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust = false);
DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id);
DFHACK_EXPORT int getExperience(df::unit *unit, df::job_skill skill_id, bool total = false);
DFHACK_EXPORT int computeMovementSpeed(df::unit *unit); DFHACK_EXPORT int computeMovementSpeed(df::unit *unit);
struct NoblePosition { struct NoblePosition {

@ -1,70 +0,0 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#ifndef CL_MOD_VEGETATION
#define CL_MOD_VEGETATION
/**
* \defgroup grp_vegetation Vegetation : stuff that grows and gets cut down or trampled by dwarves
* @ingroup grp_modules
*/
#include "Export.h"
#include "DataDefs.h"
#include "df/plant.h"
namespace DFHack
{
namespace Vegetation
{
const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years
// "Simplified" copy of plant
struct t_plant {
df::language_name name;
df::plant_flags flags;
int16_t material;
df::coord pos;
int32_t grow_counter;
uint16_t temperature_1;
uint16_t temperature_2;
int32_t is_burning;
int32_t hitpoints;
int16_t update_order;
//std::vector<void *> unk1;
//int32_t unk2;
//uint16_t temperature_3;
//uint16_t temperature_4;
//uint16_t temperature_5;
// Pointer to original object, in case you want to modify it
df::plant *origin;
};
DFHACK_EXPORT bool isValid();
DFHACK_EXPORT uint32_t getCount();
DFHACK_EXPORT df::plant * getPlant(const int32_t index);
DFHACK_EXPORT bool copyPlant (const int32_t index, t_plant &out);
}
}
#endif

@ -81,6 +81,55 @@ namespace DFHack
int &ival(int i) { return int_values[i]; } int &ival(int i) { return int_values[i]; }
int ival(int i) const { return int_values[i]; } int ival(int i) const { return int_values[i]; }
// Pack binary data into string field.
// Since DF serialization chokes on NUL bytes,
// use bit magic to ensure none of the bytes is 0.
// Choose the lowest bit for padding so that
// sign-extend can be used normally.
size_t data_size() const { return str_value->size(); }
bool check_data(size_t off, size_t sz = 1) {
return (str_value->size() >= off+sz);
}
void ensure_data(size_t off, size_t sz = 0) {
if (str_value->size() < off+sz) str_value->resize(off+sz, '\x01');
}
uint8_t *pdata(size_t off) { return (uint8_t*)&(*str_value)[off]; }
static const size_t int7_size = 1;
uint8_t get_uint7(size_t off) {
uint8_t *p = pdata(off);
return p[0]>>1;
}
int8_t get_int7(size_t off) {
uint8_t *p = pdata(off);
return int8_t(p[0])>>1;
}
void set_uint7(size_t off, uint8_t val) {
uint8_t *p = pdata(off);
p[0] = uint8_t((val<<1) | 1);
}
void set_int7(size_t off, int8_t val) { set_uint7(off, val); }
static const size_t int28_size = 4;
uint32_t get_uint28(size_t off) {
uint8_t *p = pdata(off);
return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((p[3]&~1U)<<20);
}
int32_t get_int28(size_t off) {
uint8_t *p = pdata(off);
return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((int8_t(p[3])&~1)<<20);
}
void set_uint28(size_t off, uint32_t val) {
uint8_t *p = pdata(off);
p[0] = uint8_t((val<<1) | 1);
p[1] = uint8_t((val>>6) | 1);
p[2] = uint8_t((val>>13) | 1);
p[3] = uint8_t((val>>20) | 1);
}
void set_int28(size_t off, int32_t val) { set_uint28(off, val); }
PersistentDataItem() : id(0), str_value(0), int_values(0) {} PersistentDataItem() : id(0), str_value(0), int_values(0) {}
PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv) PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv)
: id(id), key_value(key), str_value(sv), int_values(iv) {} : id(id), key_value(key), str_value(sv), int_values(iv) {}

@ -0,0 +1,121 @@
-- Simple binary patch with IDA dif file support.
local function load_patch(name)
local filename = name
if not string.match(filename, '[./\\]') then
filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif'
end
local file, err = io.open(filename, 'r')
if not file then
if string.match(err, ': No such file or directory') then
return nil, 'patch not found'
end
end
local old_bytes = {}
local new_bytes = {}
for line in file:lines() do
if string.match(line, '^%x+:') then
local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$')
if not offset then
file:close()
return nil, 'could not parse: '..line
end
offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16)
if oldv > 255 or newv > 255 then
file:close()
return nil, 'invalid byte values: '..line
end
old_bytes[offset] = oldv
new_bytes[offset] = newv
end
end
return { name = name, old_bytes = old_bytes, new_bytes = new_bytes }
end
local function rebase_table(input)
local output = {}
local base = dfhack.internal.getImageBase()
for k,v in pairs(input) do
local offset = dfhack.internal.adjustOffset(k)
if not offset then
return nil, string.format('invalid offset: %x', k)
end
output[base + offset] = v
end
return output
end
local function rebase_patch(patch)
local nold, err = rebase_table(patch.old_bytes)
if not nold then return nil, err end
local nnew, err = rebase_table(patch.new_bytes)
if not nnew then return nil, err end
return { name = patch.name, old_bytes = nold, new_bytes = nnew }
end
BinaryPatch = defclass(BinaryPatch)
BinaryPatch.ATTRS {
name = DEFAULT_NIL,
old_bytes = DEFAULT_NIL,
new_bytes = DEFAULT_NIL,
}
function load_dif_file(name)
local patch, err = load_patch(name)
if not patch then return nil, err end
local rpatch, err = rebase_patch(patch)
if not rpatch then return nil, err end
return BinaryPatch(rpatch)
end
function BinaryPatch:status()
local old_ok, err, addr = dfhack.internal.patchBytes({}, self.old_bytes)
if old_ok then
return 'removed'
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
return 'applied'
else
return 'conflict', addr
end
end
function BinaryPatch:isApplied()
return dfhack.internal.patchBytes({}, self.new_bytes)
end
function BinaryPatch:apply()
local ok, err, addr = dfhack.internal.patchBytes(self.new_bytes, self.old_bytes)
if ok then
return true, 'applied the patch'
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
return true, 'patch is already applied'
else
return false, string.format('conflict at address %x', addr)
end
end
function BinaryPatch:isRemoved()
return dfhack.internal.patchBytes({}, self.old_bytes)
end
function BinaryPatch:remove()
local ok, err, addr = dfhack.internal.patchBytes(self.old_bytes, self.new_bytes)
if ok then
return true, 'removed the patch'
elseif dfhack.internal.patchBytes({}, self.old_bytes) then
return true, 'patch is already removed'
else
return false, string.format('conflict at address %x', addr)
end
end
return _ENV

@ -328,9 +328,11 @@ end
-- Command scripts -- Command scripts
dfhack.internal.scripts = dfhack.internal.scripts or {} local internal = dfhack.internal
local scripts = dfhack.internal.scripts internal.scripts = internal.scripts or {}
local scripts = internal.scripts
local hack_path = dfhack.getHackPath() local hack_path = dfhack.getHackPath()
function dfhack.run_script(name,...) function dfhack.run_script(name,...)
@ -349,5 +351,42 @@ function dfhack.run_script(name,...)
return f(...) return f(...)
end end
-- Per-save init file
function dfhack.getSavePath()
if dfhack.isWorldLoaded() then
return dfhack.getDFPath() .. '/data/save/' .. df.global.world.cur_savegame.save_dir
end
end
if dfhack.is_core_context then
dfhack.onStateChange.DFHACK_PER_SAVE = function(op)
if op == SC_WORLD_LOADED or op == SC_WORLD_UNLOADED then
if internal.save_init then
if internal.save_init.onUnload then
safecall(internal.save_init.onUnload)
end
internal.save_init = nil
end
local path = dfhack.getSavePath()
if path and op == SC_WORLD_LOADED then
local env = setmetatable({ SAVE_PATH = path }, { __index = base_env })
local f,perr = loadfile(path..'/raw/init.lua', 't', env)
if f == nil then
if not string.match(perr, 'No such file or directory') then
dfhack.printerr(perr)
end
elseif safecall(f) then
internal.save_init = env
end
end
elseif internal.save_init and internal.save_init.onStateChange then
safecall(internal.save_init.onStateChange, op)
end
end
end
-- Feed the table back to the require() mechanism. -- Feed the table back to the require() mechanism.
return dfhack return dfhack

@ -10,13 +10,21 @@ local to_pen = dfhack.pen.parse
CLEAR_PEN = to_pen{ch=32,fg=0,bg=0} CLEAR_PEN = to_pen{ch=32,fg=0,bg=0}
local FAKE_INPUT_KEYS = {
_MOUSE_L = true,
_MOUSE_R = true,
_MOUSE_L_DOWN = true,
_MOUSE_R_DOWN = true,
_STRING = true,
}
function simulateInput(screen,...) function simulateInput(screen,...)
local keys = {} local keys = {}
local function push_key(arg) local function push_key(arg)
local kv = arg local kv = arg
if type(arg) == 'string' then if type(arg) == 'string' then
kv = df.interface_key[arg] kv = df.interface_key[arg]
if kv == nil then if kv == nil and not FAKE_INPUT_KEYS[arg] then
error('Invalid keycode: '..arg) error('Invalid keycode: '..arg)
end end
end end
@ -106,10 +114,14 @@ function inset_frame(rect, inset, gap)
return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
end end
function compute_frame_body(wavail, havail, spec, inset, gap) function compute_frame_body(wavail, havail, spec, inset, gap, inner_frame)
gap = gap or 0 gap = gap or 0
local l,t,r,b = parse_inset(inset) local l,t,r,b = parse_inset(inset)
local rect = compute_frame_rect(wavail, havail, spec, gap*2+l+r, gap*2+t+b) local xgap,ygap = 0,0
if inner_frame then
xgap,ygap = gap*2+l+r, gap*2+t+b
end
local rect = compute_frame_rect(wavail, havail, spec, xgap, ygap)
local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
return rect, body return rect, body
end end
@ -617,7 +629,7 @@ end
function FramedScreen:computeFrame(parent_rect) function FramedScreen:computeFrame(parent_rect)
local sw, sh = parent_rect.width, parent_rect.height local sw, sh = parent_rect.width, parent_rect.height
local fw, fh = self:getWantedFrameSize(parent_rect) local fw, fh = self:getWantedFrameSize(parent_rect)
return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1, true)
end end
function FramedScreen:onRenderFrame(dc, rect) function FramedScreen:onRenderFrame(dc, rect)

@ -152,7 +152,9 @@ ListBox.ATTRS{
with_filter = false, with_filter = false,
cursor_pen = DEFAULT_NIL, cursor_pen = DEFAULT_NIL,
select_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL,
on_select = DEFAULT_NIL on_select = DEFAULT_NIL,
on_select2 = DEFAULT_NIL,
select2_hint = DEFAULT_NIL,
} }
function ListBox:preinit(info) function ListBox:preinit(info)
@ -168,6 +170,16 @@ function ListBox:init(info)
list_widget = widgets.FilteredList list_widget = widgets.FilteredList
end end
local on_submit2
if self.select2_hint or self.on_select2 then
on_submit2 = function(sel, obj)
self:dismiss()
if self.on_select2 then self.on_select2(sel, obj) end
local cb = obj.on_select2
if cb then cb(obj, sel) end
end
end
self:addviews{ self:addviews{
list_widget{ list_widget{
view_id = 'list', view_id = 'list',
@ -182,11 +194,19 @@ function ListBox:init(info)
local cb = obj.on_select or obj[2] local cb = obj.on_select or obj[2]
if cb then cb(obj, sel) end if cb then cb(obj, sel) end
end, end,
on_submit2 = on_submit2,
frame = { l = 0, r = 0 }, frame = { l = 0, r = 0 },
} }
} }
end end
function ListBox:onRenderFrame(dc,rect)
ListBox.super.onRenderFrame(self,dc,rect)
if self.select2_hint then
dc:seek(rect.x1+2,rect.y2):key('SEC_SELECT'):string(': '..self.select2_hint,COLOR_DARKGREY)
end
end
function ListBox:getWantedFrameSize() function ListBox:getWantedFrameSize()
local mw, mh = InputBox.super.getWantedFrameSize(self) local mw, mh = InputBox.super.getWantedFrameSize(self)
local list = self.subviews.list local list = self.subviews.list

@ -23,6 +23,7 @@ MaterialDialog.ATTRS{
frame_title = 'Select Material', frame_title = 'Select Material',
-- new attrs -- new attrs
none_caption = 'none', none_caption = 'none',
hide_none = false,
use_inorganic = true, use_inorganic = true,
use_creature = true, use_creature = true,
use_plant = true, use_plant = true,
@ -68,7 +69,7 @@ function MaterialDialog:init(info)
end end
function MaterialDialog:getWantedFrameSize(rect) function MaterialDialog:getWantedFrameSize(rect)
return math.max(40, #self.prompt), math.min(28, rect.height-8) return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
end end
function MaterialDialog:onDestroy() function MaterialDialog:onDestroy()
@ -78,9 +79,10 @@ function MaterialDialog:onDestroy()
end end
function MaterialDialog:initBuiltinMode() function MaterialDialog:initBuiltinMode()
local choices = { local choices = {}
{ text = self.none_caption, mat_type = -1, mat_index = -1 }, if not self.hide_none then
} table.insert(choices, { text = self.none_caption, mat_type = -1, mat_index = -1 })
end
if self.use_inorganic then if self.use_inorganic then
table.insert(choices, { table.insert(choices, {
@ -281,9 +283,15 @@ function ItemTypeDialog(args)
args.with_filter = true args.with_filter = true
args.icon_width = 2 args.icon_width = 2
local choices = { { local choices = {}
icon = '?', text = args.none_caption or 'none', item_type = -1, item_subtype = -1
} } if not args.hide_none then
table.insert(choices, {
icon = '?', text = args.none_caption or 'none',
item_type = -1, item_subtype = -1
})
end
local filter = args.item_filter local filter = args.item_filter
for itype = 0,df.item_type._last_item do for itype = 0,df.item_type._last_item do

@ -60,6 +60,7 @@ Panel = defclass(Panel, Widget)
Panel.ATTRS { Panel.ATTRS {
on_render = DEFAULT_NIL, on_render = DEFAULT_NIL,
on_layout = DEFAULT_NIL,
} }
function Panel:init(args) function Panel:init(args)
@ -70,6 +71,10 @@ function Panel:onRenderBody(dc)
if self.on_render then self.on_render(dc) end if self.on_render then self.on_render(dc) end
end end
function Panel:postComputeFrame(body)
if self.on_layout then self.on_layout(body) end
end
----------- -----------
-- Pages -- -- Pages --
----------- -----------
@ -242,7 +247,7 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
end end
if token.text or token.key then if token.text or token.key then
local text = getval(token.text) or '' local text = ''..(getval(token.text) or '')
local keypen local keypen
if dc then if dc then
@ -256,7 +261,23 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
end end
end end
local width = getval(token.width)
local padstr
if width then
x = x + width
if #text > width then
text = string.sub(text,1,width)
else
if token.pad_char then
padstr = string.rep(token.pad_char,width-#text)
end
if dc and token.rjustify then
if padstr then dc:string(padstr) else dc:advance(width-#text) end
end
end
else
x = x + #text x = x + #text
end
if token.key then if token.key then
local keystr = gui.getKeyDisplay(token.key) local keystr = gui.getKeyDisplay(token.key)
@ -281,6 +302,10 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
dc:string(text) dc:string(text)
end end
end end
if width and dc and not token.rjustify then
if padstr then dc:string(padstr) else dc:advance(width-#text) end
end
end end
token.x2 = x token.x2 = x
@ -384,6 +409,7 @@ List.ATTRS{
inactive_pen = DEFAULT_NIL, inactive_pen = DEFAULT_NIL,
on_select = DEFAULT_NIL, on_select = DEFAULT_NIL,
on_submit = DEFAULT_NIL, on_submit = DEFAULT_NIL,
on_submit2 = DEFAULT_NIL,
row_height = 1, row_height = 1,
scroll_keys = STANDARDSCROLL, scroll_keys = STANDARDSCROLL,
icon_width = DEFAULT_NIL, icon_width = DEFAULT_NIL,
@ -392,7 +418,13 @@ List.ATTRS{
function List:init(info) function List:init(info)
self.page_top = 1 self.page_top = 1
self.page_size = 1 self.page_size = 1
if info.choices then
self:setChoices(info.choices, info.selected) self:setChoices(info.choices, info.selected)
else
self.choices = {}
self.selected = 1
end
end end
function List:setChoices(choices, selected) function List:setChoices(choices, selected)
@ -455,6 +487,9 @@ function List:moveCursor(delta, force_cb)
if cnt < 1 then if cnt < 1 then
self.page_top = 1 self.page_top = 1
self.selected = 1 self.selected = 1
if force_cb and self.on_select then
self.on_select(nil,nil)
end
return return
end end
@ -488,6 +523,18 @@ function List:onRenderBody(dc)
local iend = math.min(#choices, top+self.page_size-1) local iend = math.min(#choices, top+self.page_size-1)
local iw = self.icon_width local iw = self.icon_width
local function paint_icon(icon, obj)
if type(icon) ~= 'string' then
dc:char(nil,icon)
else
if current then
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
else
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
end
end
end
for i = top,iend do for i = top,iend do
local obj = choices[i] local obj = choices[i]
local current = (i == self.selected) local current = (i == self.selected)
@ -499,30 +546,28 @@ function List:onRenderBody(dc)
end end
local y = (i - top)*self.row_height local y = (i - top)*self.row_height
if iw and obj.icon then
local icon = getval(obj.icon) local icon = getval(obj.icon)
if icon then
if iw and icon then
dc:seek(0, y) dc:seek(0, y)
if type(icon) ~= 'string' then paint_icon(icon, obj)
dc:char(nil,icon)
else
if current then
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
else
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
end
end
end
end end
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current) render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current)
local ip = dc.width
if obj.key then if obj.key then
local keystr = gui.getKeyDisplay(obj.key) local keystr = gui.getKeyDisplay(obj.key)
dc:seek(dc.width-2-#keystr,y):pen(self.text_pen) ip = ip-2-#keystr
dc:seek(ip,y):pen(self.text_pen)
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')') dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
end end
if icon and not iw then
dc:seek(ip-1,y)
paint_icon(icon, obj)
end
end end
end end
@ -532,10 +577,19 @@ function List:submit()
end end
end end
function List:submit2()
if self.on_submit2 and #self.choices > 0 then
self.on_submit2(self:getSelected())
end
end
function List:onInput(keys) function List:onInput(keys)
if self.on_submit and keys.SELECT then if self.on_submit and keys.SELECT then
self:submit() self:submit()
return true return true
elseif self.on_submit2 and keys.SEC_SELECT then
self:submit2()
return true
else else
for k,v in pairs(self.scroll_keys) do for k,v in pairs(self.scroll_keys) do
if keys[k] then if keys[k] then
@ -571,10 +625,14 @@ end
FilteredList = defclass(FilteredList, Widget) FilteredList = defclass(FilteredList, Widget)
FilteredList.ATTRS {
edit_below = false,
}
function FilteredList:init(info) function FilteredList:init(info)
self.edit = EditField{ self.edit = EditField{
text_pen = info.edit_pen or info.cursor_pen, text_pen = info.edit_pen or info.cursor_pen,
frame = { l = info.icon_width, t = 0 }, frame = { l = info.icon_width, t = 0, h = 1 },
on_change = self:callback('onFilterChange'), on_change = self:callback('onFilterChange'),
on_char = self:callback('onFilterChar'), on_char = self:callback('onFilterChar'),
} }
@ -588,6 +646,10 @@ function FilteredList:init(info)
scroll_keys = info.scroll_keys, scroll_keys = info.scroll_keys,
icon_width = info.icon_width, icon_width = info.icon_width,
} }
if self.edit_below then
self.edit.frame = { l = info.icon_width, b = 0, h = 1 }
self.list.frame = { t = 0, b = 2 }
end
if info.on_select then if info.on_select then
self.list.on_select = function() self.list.on_select = function()
return info.on_select(self:getSelected()) return info.on_select(self:getSelected())
@ -598,14 +660,23 @@ function FilteredList:init(info)
return info.on_submit(self:getSelected()) return info.on_submit(self:getSelected())
end end
end end
if info.on_submit2 then
self.list.on_submit2 = function()
return info.on_submit2(self:getSelected())
end
end
self.not_found = Label{ self.not_found = Label{
visible = false, visible = true,
text = info.not_found_label or 'No matches', text = info.not_found_label or 'No matches',
text_pen = COLOR_LIGHTRED, text_pen = COLOR_LIGHTRED,
frame = { l = info.icon_width, t = 2 }, frame = { l = info.icon_width, t = self.list.frame.t },
} }
self:addviews{ self.edit, self.list, self.not_found } self:addviews{ self.edit, self.list, self.not_found }
if info.choices then
self:setChoices(info.choices, info.selected) self:setChoices(info.choices, info.selected)
else
self.choices = {}
end
end end
function FilteredList:getChoices() function FilteredList:getChoices()
@ -624,6 +695,10 @@ function FilteredList:submit()
return self.list:submit() return self.list:submit()
end end
function FilteredList:submit2()
return self.list:submit2()
end
function FilteredList:canSubmit() function FilteredList:canSubmit()
return not self.not_found.visible return not self.not_found.visible
end end

@ -24,7 +24,7 @@ function CheckedArray:__len()
return self.count return self.count
end end
function CheckedArray:__index(idx) function CheckedArray:__index(idx)
if type(idx) == number then if type(idx) == "number" then
if idx >= self.count then if idx >= self.count then
error('Index out of bounds: '..tostring(idx)) error('Index out of bounds: '..tostring(idx))
end end
@ -195,6 +195,26 @@ function MemoryArea:delete()
for k,v in pairs(self) do self[k] = nil end for k,v in pairs(self) do self[k] = nil end
end end
-- Static code segment search
function get_code_segment()
local cstart, cend
for i,mem in ipairs(dfhack.internal.getMemRanges()) do
if mem.read and mem.execute
and (string.match(mem.name,'/dwarfort%.exe$')
or string.match(mem.name,'/Dwarf_Fortress$')
or string.match(mem.name,'Dwarf Fortress%.exe'))
then
cstart = mem.start_addr
cend = mem.end_addr
end
end
if cstart and cend then
return MemoryArea.new(cstart, cend)
end
end
-- Static data segment search -- Static data segment search
local function find_data_segment() local function find_data_segment()

@ -178,6 +178,20 @@ bool Buildings::ReadCustomWorkshopTypes(map <uint32_t, string> & btypes)
return true; return true;
} }
df::general_ref *Buildings::getGeneralRef(df::building *building, df::general_ref_type type)
{
CHECK_NULL_POINTER(building);
return findRef(building->general_refs, type);
}
df::specific_ref *Buildings::getSpecificRef(df::building *building, df::specific_ref_type type)
{
CHECK_NULL_POINTER(building);
return findRef(building->specific_refs, type);
}
bool Buildings::setOwner(df::building *bld, df::unit *unit) bool Buildings::setOwner(df::building *bld, df::unit *unit)
{ {
CHECK_NULL_POINTER(bld); CHECK_NULL_POINTER(bld);
@ -895,7 +909,7 @@ static bool linkForConstruct(df::job* &job, df::building *bld)
job = new df::job(); job = new df::job();
job->job_type = df::job_type::ConstructBuilding; job->job_type = df::job_type::ConstructBuilding;
job->pos = df::coord(bld->centerx, bld->centery, bld->z); job->pos = df::coord(bld->centerx, bld->centery, bld->z);
job->references.push_back(ref); job->general_refs.push_back(ref);
bld->jobs.push_back(job); bld->jobs.push_back(job);

@ -495,10 +495,10 @@ bool Items::copyItem(df::item * itembase, DFHack::dfh_item &item)
item.id = itreal->id; item.id = itreal->id;
item.age = itreal->age; item.age = itreal->age;
item.flags = itreal->flags; item.flags = itreal->flags;
item.matdesc.itemType = itreal->getType(); item.matdesc.item_type = itreal->getType();
item.matdesc.subType = itreal->getSubtype(); item.matdesc.item_subtype = itreal->getSubtype();
item.matdesc.material = itreal->getMaterial(); item.matdesc.mat_type = itreal->getMaterial();
item.matdesc.index = itreal->getMaterialIndex(); item.matdesc.mat_index = itreal->getMaterialIndex();
item.wear_level = itreal->getWear(); item.wear_level = itreal->getWear();
item.quality = itreal->getQuality(); item.quality = itreal->getQuality();
item.quantity = itreal->getStackSize(); item.quantity = itreal->getStackSize();
@ -509,7 +509,7 @@ df::general_ref *Items::getGeneralRef(df::item *item, df::general_ref_type type)
{ {
CHECK_NULL_POINTER(item); CHECK_NULL_POINTER(item);
return findRef(item->itemrefs, type); return findRef(item->general_refs, type);
} }
df::specific_ref *Items::getSpecificRef(df::item *item, df::specific_ref_type type) df::specific_ref *Items::getSpecificRef(df::item *item, df::specific_ref_type type)
@ -530,9 +530,9 @@ bool Items::setOwner(df::item *item, df::unit *unit)
{ {
CHECK_NULL_POINTER(item); CHECK_NULL_POINTER(item);
for (int i = item->itemrefs.size()-1; i >= 0; i--) for (int i = item->general_refs.size()-1; i >= 0; i--)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
if (!strict_virtual_cast<df::general_ref_unit_itemownerst>(ref)) if (!strict_virtual_cast<df::general_ref_unit_itemownerst>(ref))
continue; continue;
@ -546,7 +546,7 @@ bool Items::setOwner(df::item *item, df::unit *unit)
} }
delete ref; delete ref;
vector_erase_at(item->itemrefs, i); vector_erase_at(item->general_refs, i);
} }
item->flags.bits.owned = false; item->flags.bits.owned = false;
@ -561,7 +561,7 @@ bool Items::setOwner(df::item *item, df::unit *unit)
ref->unit_id = unit->id; ref->unit_id = unit->id;
insert_into_vector(unit->owned_items, item->id); insert_into_vector(unit->owned_items, item->id);
item->itemrefs.push_back(ref); item->general_refs.push_back(ref);
} }
return true; return true;
@ -580,9 +580,9 @@ void Items::getContainedItems(df::item *item, std::vector<df::item*> *items)
items->clear(); items->clear();
for (size_t i = 0; i < item->itemrefs.size(); i++) for (size_t i = 0; i < item->general_refs.size(); i++)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
if (ref->getType() != general_ref_type::CONTAINS_ITEM) if (ref->getType() != general_ref_type::CONTAINS_ITEM)
continue; continue;
@ -617,9 +617,9 @@ df::coord Items::getPosition(df::item *item)
if (item->flags.bits.in_inventory) if (item->flags.bits.in_inventory)
{ {
for (size_t i = 0; i < item->itemrefs.size(); i++) for (size_t i = 0; i < item->general_refs.size(); i++)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
switch (ref->getType()) switch (ref->getType())
{ {
@ -716,9 +716,9 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
if (item->world_data_id != -1) if (item->world_data_id != -1)
return false; return false;
for (size_t i = 0; i < item->itemrefs.size(); i++) for (size_t i = 0; i < item->general_refs.size(); i++)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
switch (ref->getType()) switch (ref->getType())
{ {
@ -748,9 +748,9 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
{ {
bool found = false; bool found = false;
for (int i = item->itemrefs.size()-1; i >= 0; i--) for (int i = item->general_refs.size()-1; i >= 0; i--)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
switch (ref->getType()) switch (ref->getType())
{ {
@ -767,7 +767,7 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
item2->flags.bits.weight_computed = false; item2->flags.bits.weight_computed = false;
removeRef(item2->itemrefs, general_ref_type::CONTAINS_ITEM, item->id); removeRef(item2->general_refs, general_ref_type::CONTAINS_ITEM, item->id);
} }
break; break;
@ -799,7 +799,7 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
} }
found = true; found = true;
vector_erase_at(item->itemrefs, i); vector_erase_at(item->general_refs, i);
delete ref; delete ref;
} }
@ -878,10 +878,10 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
container->flags.bits.weight_computed = false; container->flags.bits.weight_computed = false;
ref1->item_id = item->id; ref1->item_id = item->id;
container->itemrefs.push_back(ref1); container->general_refs.push_back(ref1);
ref2->item_id = container->id; ref2->item_id = container->id;
item->itemrefs.push_back(ref2); item->general_refs.push_back(ref2);
return true; return true;
} }
@ -912,7 +912,7 @@ bool DFHack::Items::moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::
item->flags.bits.in_building=true; item->flags.bits.in_building=true;
ref->building_id=building->id; ref->building_id=building->id;
item->itemrefs.push_back(ref); item->general_refs.push_back(ref);
auto con=new df::building_actual::T_contained_items; auto con=new df::building_actual::T_contained_items;
con->item=item; con->item=item;
@ -955,7 +955,7 @@ bool DFHack::Items::moveToInventory(
unit->inventory.push_back(newInventoryItem); unit->inventory.push_back(newInventoryItem);
holderReference->unit_id = unit->id; holderReference->unit_id = unit->id;
item->itemrefs.push_back(holderReference); item->general_refs.push_back(holderReference);
resetUnitInvFlags(unit, newInventoryItem); resetUnitInvFlags(unit, newInventoryItem);
@ -1016,7 +1016,7 @@ df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item)
proj->item = item; proj->item = item;
ref->projectile_id = proj->id; ref->projectile_id = proj->id;
item->itemrefs.push_back(ref); item->general_refs.push_back(ref);
linked_list_append(&world->proj_list, proj->link); linked_list_append(&world->proj_list, proj->link);

@ -35,6 +35,7 @@ using namespace std;
#include "Error.h" #include "Error.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "MiscUtils.h" #include "MiscUtils.h"
#include "Types.h"
#include "modules/Job.h" #include "modules/Job.h"
#include "modules/Materials.h" #include "modules/Materials.h"
@ -71,14 +72,14 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepWorkerData)
pnew->specific_refs.clear(); pnew->specific_refs.clear();
// Clone refs // Clone refs
for (int i = pnew->references.size()-1; i >= 0; i--) for (int i = pnew->general_refs.size()-1; i >= 0; i--)
{ {
df::general_ref *ref = pnew->references[i]; df::general_ref *ref = pnew->general_refs[i];
if (!keepWorkerData && virtual_cast<df::general_ref_unit_workerst>(ref)) if (!keepWorkerData && virtual_cast<df::general_ref_unit_workerst>(ref))
vector_erase_at(pnew->references, i); vector_erase_at(pnew->general_refs, i);
else else
pnew->references[i] = ref->clone(); pnew->general_refs[i] = ref->clone();
} }
// Clone items // Clone items
@ -96,8 +97,8 @@ void DFHack::Job::deleteJobStruct(df::job *job)
// Only allow free-floating job structs // Only allow free-floating job structs
assert(!job->list_link && job->items.empty() && job->specific_refs.empty()); assert(!job->list_link && job->items.empty() && job->specific_refs.empty());
for (int i = job->references.size()-1; i >= 0; i--) for (int i = job->general_refs.size()-1; i >= 0; i--)
delete job->references[i]; delete job->general_refs[i];
for (int i = job->job_items.size()-1; i >= 0; i--) for (int i = job->job_items.size()-1; i >= 0; i--)
delete job->job_items[i]; delete job->job_items[i];
@ -228,13 +229,27 @@ void DFHack::Job::printJobDetails(color_ostream &out, df::job *job)
printItemDetails(out, job->job_items[i], i); printItemDetails(out, job->job_items[i], i);
} }
df::general_ref *Job::getGeneralRef(df::job *job, df::general_ref_type type)
{
CHECK_NULL_POINTER(job);
return findRef(job->general_refs, type);
}
df::specific_ref *Job::getSpecificRef(df::job *job, df::specific_ref_type type)
{
CHECK_NULL_POINTER(job);
return findRef(job->specific_refs, type);
}
df::building *DFHack::Job::getHolder(df::job *job) df::building *DFHack::Job::getHolder(df::job *job)
{ {
CHECK_NULL_POINTER(job); CHECK_NULL_POINTER(job);
for (size_t i = 0; i < job->references.size(); i++) for (size_t i = 0; i < job->general_refs.size(); i++)
{ {
VIRTUAL_CAST_VAR(ref, df::general_ref_building_holderst, job->references[i]); VIRTUAL_CAST_VAR(ref, df::general_ref_building_holderst, job->general_refs[i]);
if (ref) if (ref)
return ref->getBuilding(); return ref->getBuilding();
} }
@ -246,9 +261,9 @@ df::unit *DFHack::Job::getWorker(df::job *job)
{ {
CHECK_NULL_POINTER(job); CHECK_NULL_POINTER(job);
for (size_t i = 0; i < job->references.size(); i++) for (size_t i = 0; i < job->general_refs.size(); i++)
{ {
VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->references[i]); VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->general_refs[i]);
if (ref) if (ref)
return ref->getUnit(); return ref->getUnit();
} }

@ -62,6 +62,7 @@ using namespace std;
#include "df/region_map_entry.h" #include "df/region_map_entry.h"
#include "df/flow_info.h" #include "df/flow_info.h"
#include "df/building_type.h" #include "df/building_type.h"
#include "df/plant.h"
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
@ -776,15 +777,15 @@ void MapExtras::Block::TileInfo::init_coninfo()
con_info = new ConInfo(); con_info = new ConInfo();
con_info->constructed.clear(); con_info->constructed.clear();
COPY(con_info->tiles, base_tiles); COPY(con_info->tiles, base_tiles);
memset(con_info->mattype, -1, sizeof(con_info->mattype)); memset(con_info->mat_type, -1, sizeof(con_info->mat_type));
memset(con_info->matindex, -1, sizeof(con_info->matindex)); memset(con_info->mat_index, -1, sizeof(con_info->mat_index));
} }
MapExtras::Block::BasematInfo::BasematInfo() MapExtras::Block::BasematInfo::BasematInfo()
{ {
dirty.clear(); dirty.clear();
memset(mattype,0,sizeof(mattype)); memset(mat_type,0,sizeof(mat_type));
memset(matindex,-1,sizeof(matindex)); memset(mat_index,-1,sizeof(mat_index));
memset(layermat,-1,sizeof(layermat)); memset(layermat,-1,sizeof(layermat));
} }
@ -845,8 +846,8 @@ void MapExtras::Block::ParseTiles(TileInfo *tiles)
is_con = true; is_con = true;
tiles->con_info->constructed.setassignment(x,y,true); tiles->con_info->constructed.setassignment(x,y,true);
tiles->con_info->tiles[x][y] = tt; tiles->con_info->tiles[x][y] = tt;
tiles->con_info->mattype[x][y] = con->mat_type; tiles->con_info->mat_type[x][y] = con->mat_type;
tiles->con_info->matindex[x][y] = con->mat_index; tiles->con_info->mat_index[x][y] = con->mat_index;
tt = con->original_tile; tt = con->original_tile;
} }
@ -879,14 +880,14 @@ void MapExtras::Block::ParseBasemats(TileInfo *tiles, BasematInfo *bmats)
auto tt = tiles->base_tiles[x][y]; auto tt = tiles->base_tiles[x][y];
auto mat = info.getBaseMaterial(tt, df::coord2d(x,y)); auto mat = info.getBaseMaterial(tt, df::coord2d(x,y));
bmats->mattype[x][y] = mat.mat_type; bmats->mat_type[x][y] = mat.mat_type;
bmats->matindex[x][y] = mat.mat_index; bmats->mat_index[x][y] = mat.mat_index;
// Copy base info back to construction layer // Copy base info back to construction layer
if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y)) if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y))
{ {
tiles->con_info->mattype[x][y] = mat.mat_type; tiles->con_info->mat_type[x][y] = mat.mat_type;
tiles->con_info->matindex[x][y] = mat.mat_index; tiles->con_info->mat_index[x][y] = mat.mat_index;
} }
} }
} }

@ -425,6 +425,8 @@ bool MaterialInfo::matches(const df::dfhack_material_category &cat)
TEST(glass, IS_GLASS); TEST(glass, IS_GLASS);
if (cat.bits.clay && linear_index(material->reaction_product.id, std::string("FIRED_MAT")) >= 0) if (cat.bits.clay && linear_index(material->reaction_product.id, std::string("FIRED_MAT")) >= 0)
return true; return true;
if (cat.bits.milk && linear_index(material->reaction_product.id, std::string("CHEESE_MAT")) >= 0)
return true;
return false; return false;
} }
@ -838,7 +840,7 @@ bool Materials::ReadAllMaterials(void)
std::string Materials::getDescription(const t_material & mat) std::string Materials::getDescription(const t_material & mat)
{ {
MaterialInfo mi(mat.material, mat.index); MaterialInfo mi(mat.mat_type, mat.mat_index);
if (mi.creature) if (mi.creature)
return mi.creature->creature_id + " " + mi.material->id; return mi.creature->creature_id + " " + mi.material->id;
else if (mi.plant) else if (mi.plant)
@ -851,7 +853,7 @@ std::string Materials::getDescription(const t_material & mat)
// This is completely worthless now // This is completely worthless now
std::string Materials::getType(const t_material & mat) std::string Materials::getType(const t_material & mat)
{ {
MaterialInfo mi(mat.material, mat.index); MaterialInfo mi(mat.mat_type, mat.mat_index);
switch (mi.mode) switch (mi.mode)
{ {
case MaterialInfo::Builtin: case MaterialInfo::Builtin:

@ -110,10 +110,10 @@ bool Screen::paintTile(const Pen &pen, int x, int y)
{ {
if (!gps || !pen.valid()) return false; if (!gps || !pen.valid()) return false;
int dimx = gps->dimx, dimy = gps->dimy; auto dim = getWindowSize();
if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false; if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) return false;
doSetTile(pen, x*dimy + y); doSetTile(pen, x*dim.y + y);
return true; return true;
} }
@ -121,11 +121,11 @@ Pen Screen::readTile(int x, int y)
{ {
if (!gps) return Pen(0,0,0,-1); if (!gps) return Pen(0,0,0,-1);
int dimx = gps->dimx, dimy = gps->dimy; auto dim = getWindowSize();
if (x < 0 || x >= dimx || y < 0 || y >= dimy) if (x < 0 || x >= dim.x || y < 0 || y >= dim.y)
return Pen(0,0,0,-1); return Pen(0,0,0,-1);
int index = x*dimy + y; int index = x*dim.y + y;
auto screen = gps->screen + index*4; auto screen = gps->screen + index*4;
if (screen[3] & 0x80) if (screen[3] & 0x80)
return Pen(0,0,0,-1); return Pen(0,0,0,-1);
@ -154,14 +154,15 @@ Pen Screen::readTile(int x, int y)
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; auto dim = getWindowSize();
if (!gps || y < 0 || y >= dim.y) return false;
Pen tmp(pen); Pen tmp(pen);
bool ok = false; bool ok = false;
for (size_t i = -std::min(0,x); i < text.size(); i++) for (size_t i = -std::min(0,x); i < text.size(); i++)
{ {
if (x + i >= size_t(gps->dimx)) if (x + i >= size_t(dim.x))
break; break;
tmp.ch = text[i]; tmp.ch = text[i];
@ -175,17 +176,18 @@ 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)
{ {
auto dim = getWindowSize();
if (!gps || !pen.valid()) 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;
if (x2 >= gps->dimx) x2 = gps->dimx-1; if (x2 >= dim.x) x2 = dim.x-1;
if (y2 >= gps->dimy) y2 = gps->dimy-1; if (y2 >= dim.y) y2 = dim.y-1;
if (x1 > x2 || y1 > y2) return false; if (x1 > x2 || y1 > y2) return false;
for (int x = x1; x <= x2; x++) for (int x = x1; x <= x2; x++)
{ {
int index = x*gps->dimy; int index = x*dim.y;
for (int y = y1; y <= y2; y++) for (int y = y1; y <= y2; y++)
doSetTile(pen, index+y); doSetTile(pen, index+y);
@ -198,32 +200,33 @@ bool Screen::drawBorder(const std::string &title)
{ {
if (!gps) return false; if (!gps) return false;
int dimx = gps->dimx, dimy = gps->dimy; auto dim = getWindowSize();
Pen border('\xDB', 8); Pen border('\xDB', 8);
Pen text(0, 0, 7); Pen text(0, 0, 7);
Pen signature(0, 0, 8); Pen signature(0, 0, 8);
for (int x = 0; x < dimx; x++) for (int x = 0; x < dim.x; x++)
{ {
doSetTile(border, x * dimy + 0); doSetTile(border, x * dim.y + 0);
doSetTile(border, x * dimy + dimy - 1); doSetTile(border, x * dim.y + dim.y - 1);
} }
for (int y = 0; y < dimy; y++) for (int y = 0; y < dim.y; y++)
{ {
doSetTile(border, 0 * dimy + y); doSetTile(border, 0 * dim.y + y);
doSetTile(border, (dimx - 1) * dimy + y); doSetTile(border, (dim.x - 1) * dim.y + y);
} }
paintString(signature, dimx-8, dimy-1, "DFHack"); paintString(signature, dim.x-8, dim.y-1, "DFHack");
return paintString(text, (dimx - title.length()) / 2, 0, title); return paintString(text, (dim.x - title.length()) / 2, 0, title);
} }
bool Screen::clear() bool Screen::clear()
{ {
if (!gps) return false; if (!gps) return false;
return fillRect(Pen(' ',0,0,false), 0, 0, gps->dimx-1, gps->dimy-1); auto dim = getWindowSize();
return fillRect(Pen(' ',0,0,false), 0, 0, dim.x-1, dim.y-1);
} }
bool Screen::invalidate() bool Screen::invalidate()
@ -234,6 +237,21 @@ bool Screen::invalidate()
return true; return true;
} }
const Pen Screen::Painter::default_pen(0,COLOR_GREY,0);
const Pen Screen::Painter::default_key_pen(0,COLOR_LIGHTGREEN,0);
void Screen::Painter::do_paint_string(const std::string &str, const Pen &pen)
{
if (gcursor.y < clip.first.y || gcursor.y > clip.second.y)
return;
int dx = std::max(0, int(clip.first.x - gcursor.x));
int len = std::min((int)str.size(), int(clip.second.x - gcursor.x + 1));
if (len > dx)
paintString(pen, gcursor.x + dx, gcursor.y, str.substr(dx, len-dx));
}
bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs) bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs)
{ {
if (!gps || !texture || x < 0 || y < 0) return false; if (!gps || !texture || x < 0 || y < 0) return false;
@ -646,14 +664,24 @@ int dfhack_lua_viewscreen::do_input(lua_State *L)
if (enabler && enabler->tracking_on) if (enabler && enabler->tracking_on)
{ {
if (enabler->mouse_lbut) { if (enabler->mouse_lbut_down) {
lua_pushboolean(L, true); lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_L"); lua_setfield(L, -2, "_MOUSE_L");
} }
if (enabler->mouse_rbut) { if (enabler->mouse_rbut_down) {
lua_pushboolean(L, true); lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_R"); lua_setfield(L, -2, "_MOUSE_R");
} }
if (enabler->mouse_lbut) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_L_DOWN");
enabler->mouse_lbut = 0;
}
if (enabler->mouse_rbut) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_R_DOWN");
enabler->mouse_rbut = 0;
}
} }
lua_call(L, 2, 0); lua_call(L, 2, 0);

@ -115,6 +115,9 @@ void Translation::setNickname(df::language_name *name, std::string nick)
if (!name->has_name) if (!name->has_name)
{ {
if (nick.empty())
return;
*name = df::language_name(); *name = df::language_name();
name->language = 0; name->language = 0;
@ -122,6 +125,18 @@ void Translation::setNickname(df::language_name *name, std::string nick)
} }
name->nickname = nick; name->nickname = nick;
// If the nick is empty, check if this made the whole name empty
if (name->nickname.empty() && name->first_name.empty())
{
bool has_words = false;
for (int i = 0; i < 7; i++)
if (name->words[i] >= 0)
has_words = true;
if (!has_words)
name->has_name = false;
}
} }
string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart) string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart)

@ -405,7 +405,7 @@ bool Creatures::WriteJob(const t_creature * furball, std::vector<t_material> con
for(i=0;i<cmats.size();i++) for(i=0;i<cmats.size();i++)
{ {
p->writeWord(cmats[i] + off.job_material_itemtype_o, mat[i].itemType); p->writeWord(cmats[i] + off.job_material_itemtype_o, mat[i].itemType);
p->writeWord(cmats[i] + off.job_material_subtype_o, mat[i].subType); p->writeWord(cmats[i] + off.job_material_subtype_o, mat[i].itemSubtype);
p->writeWord(cmats[i] + off.job_material_subindex_o, mat[i].subIndex); p->writeWord(cmats[i] + off.job_material_subindex_o, mat[i].subIndex);
p->writeDWord(cmats[i] + off.job_material_index_o, mat[i].index); p->writeDWord(cmats[i] + off.job_material_index_o, mat[i].index);
p->writeDWord(cmats[i] + off.job_material_flags_o, mat[i].flags); p->writeDWord(cmats[i] + off.job_material_flags_o, mat[i].flags);
@ -475,7 +475,7 @@ bool Creatures::ReadJob(const t_creature * furball, vector<t_material> & mat)
for(i=0;i<cmats.size();i++) for(i=0;i<cmats.size();i++)
{ {
mat[i].itemType = p->readWord(cmats[i] + off.job_material_itemtype_o); mat[i].itemType = p->readWord(cmats[i] + off.job_material_itemtype_o);
mat[i].subType = p->readWord(cmats[i] + off.job_material_subtype_o); mat[i].itemSubtype = p->readWord(cmats[i] + off.job_material_subtype_o);
mat[i].subIndex = p->readWord(cmats[i] + off.job_material_subindex_o); mat[i].subIndex = p->readWord(cmats[i] + off.job_material_subindex_o);
mat[i].index = p->readDWord(cmats[i] + off.job_material_index_o); mat[i].index = p->readDWord(cmats[i] + off.job_material_index_o);
mat[i].flags = p->readDWord(cmats[i] + off.job_material_flags_o); mat[i].flags = p->readDWord(cmats[i] + off.job_material_flags_o);
@ -519,18 +519,25 @@ df::coord Units::getPosition(df::unit *unit)
return unit->pos; return unit->pos;
} }
df::item *Units::getContainer(df::unit *unit) df::general_ref *Units::getGeneralRef(df::unit *unit, df::general_ref_type type)
{ {
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);
for (size_t i = 0; i < unit->refs.size(); i++) return findRef(unit->general_refs, type);
{ }
df::general_ref *ref = unit->refs[i];
if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM)
return ref->getItem();
}
return NULL; df::specific_ref *Units::getSpecificRef(df::unit *unit, df::specific_ref_type type)
{
CHECK_NULL_POINTER(unit);
return findRef(unit->specific_refs, type);
}
df::item *Units::getContainer(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
return findItemRef(unit->general_refs, general_ref_type::CONTAINED_IN_ITEM);
} }
static df::assumed_identity *getFigureIdentity(df::historical_figure *figure) static df::assumed_identity *getFigureIdentity(df::historical_figure *figure)
@ -607,9 +614,9 @@ df::nemesis_record *Units::getNemesis(df::unit *unit)
if (!unit) if (!unit)
return NULL; return NULL;
for (unsigned i = 0; i < unit->refs.size(); i++) for (unsigned i = 0; i < unit->general_refs.size(); i++)
{ {
df::nemesis_record *rv = unit->refs[i]->getNemesis(); df::nemesis_record *rv = unit->general_refs[i]->getNemesis();
if (rv && rv->unit == unit) if (rv && rv->unit == unit)
return rv; return rv;
} }
@ -778,10 +785,10 @@ bool DFHack::Units::isSane(df::unit *unit)
if (unit->flags1.bits.dead || if (unit->flags1.bits.dead ||
unit->flags3.bits.ghostly || unit->flags3.bits.ghostly ||
isOpposedToLife(unit) || isOpposedToLife(unit) ||
unit->unknown8.unk2) unit->enemy.undead)
return false; return false;
if (unit->unknown8.normal_race == unit->unknown8.were_race && isCrazed(unit)) if (unit->enemy.normal_race == unit->enemy.were_race && isCrazed(unit))
return false; return false;
switch (unit->mood) switch (unit->mood)
@ -832,7 +839,7 @@ bool DFHack::Units::isDwarf(df::unit *unit)
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);
return unit->race == ui->race_id || return unit->race == ui->race_id ||
unit->unknown8.normal_race == ui->race_id; unit->enemy.normal_race == ui->race_id;
} }
double DFHack::Units::getAge(df::unit *unit, bool true_age) double DFHack::Units::getAge(df::unit *unit, bool true_age)
@ -902,6 +909,24 @@ int Units::getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust
return 0; return 0;
} }
int Units::getExperience(df::unit *unit, df::job_skill skill_id, bool total)
{
CHECK_NULL_POINTER(unit);
if (!unit->status.current_soul)
return 0;
auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, skill_id);
if (!skill)
return 0;
int xp = skill->experience;
// exact formula used by the game:
if (total && skill->rating > 0)
xp += 500*skill->rating + 100*skill->rating*(skill->rating - 1)/2;
return xp;
}
int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id)
{ {
/* /*
@ -1200,12 +1225,12 @@ int Units::computeMovementSpeed(df::unit *unit)
// Stance // Stance
if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) if (!unit->flags1.bits.on_ground && unit->status2.limbs_stand_max > 2)
{ {
// WTF // WTF
int as = unit->status2.able_stand; int as = unit->status2.limbs_stand_max;
int x = (as-1) - (as>>1); int x = (as-1) - (as>>1);
int y = as - unit->status2.able_stand_impair; int y = as - unit->status2.limbs_stand_count;
if (unit->flags3.bits.on_crutch) y--; if (unit->flags3.bits.on_crutch) y--;
y = y * 500 / x; y = y * 500 / x;
if (y > 0) speed += y; if (y > 0) speed += y;

@ -1,85 +0,0 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#include "Internal.h"
#include <string>
#include <vector>
#include <map>
using namespace std;
#include "VersionInfo.h"
#include "MemAccess.h"
#include "Types.h"
#include "Core.h"
using namespace DFHack;
#include "modules/Vegetation.h"
#include "df/world.h"
using namespace DFHack;
using df::global::world;
bool Vegetation::isValid()
{
return (world != NULL);
}
uint32_t Vegetation::getCount()
{
return world->plants.all.size();
}
df::plant * Vegetation::getPlant(const int32_t index)
{
if (uint32_t(index) >= getCount())
return NULL;
return world->plants.all[index];
}
bool Vegetation::copyPlant(const int32_t index, t_plant &out)
{
if (uint32_t(index) >= getCount())
return false;
out.origin = world->plants.all[index];
out.name = out.origin->name;
out.flags = out.origin->flags;
out.material = out.origin->material;
out.pos = out.origin->pos;
out.grow_counter = out.origin->grow_counter;
out.temperature_1 = out.origin->temperature_1;
out.temperature_2 = out.origin->temperature_2;
out.is_burning = out.origin->is_burning;
out.hitpoints = out.origin->hitpoints;
out.update_order = out.origin->update_order;
//out.unk1 = out.origin->anon_1;
//out.unk2 = out.origin->anon_2;
//out.temperature_3 = out.origin->temperature_3;
//out.temperature_4 = out.origin->temperature_4;
//out.temperature_5 = out.origin->temperature_5;
return true;
}

@ -197,11 +197,15 @@ PersistentDataItem World::AddPersistentData(const std::string &key)
std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector(); std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector();
df::historical_figure *hfig = new df::historical_figure(); df::historical_figure *hfig = new df::historical_figure();
hfig->id = next_persistent_id--; hfig->id = next_persistent_id;
hfig->name.has_name = true; hfig->name.has_name = true;
hfig->name.first_name = key; hfig->name.first_name = key;
memset(hfig->name.words, 0xFF, sizeof(hfig->name.words)); memset(hfig->name.words, 0xFF, sizeof(hfig->name.words));
if (!hfvec.empty())
hfig->id = std::min(hfig->id, hfvec[0]->id-1);
next_persistent_id = hfig->id-1;
hfvec.insert(hfvec.begin(), hfig); hfvec.insert(hfvec.begin(), hfig);
persistent_index.insert(T_persistent_item(key, -hfig->id)); persistent_index.insert(T_persistent_item(key, -hfig->id));

@ -1 +1 @@
Subproject commit 4ab899319014d950214714a48cd3049a4beb5eb5 Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921

@ -0,0 +1,142 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
0x2ac6b
CC CC CC CC CC
66 39 E8 EB 53
.text:0042B86B loc_42B86B:
.text:0042B86B cmp ax, bp
.text:0042B86E jmp short loc_42B8C3
0x2ac7b
CC CC CC CC CC
E9 96 A2 00 00
.text:0042B87B loc_42B87B:
.text:0042B87B jmp loc_435B16
0x2acc3
CC CC CC CC CC CC CC CC CC CC CC CC CC
75 0A 66 FF 4C 24 16 79 03 58 EB AC C3
.text:0042B8C3 loc_42B8C3:
.text:0042B8C3 jnz short locret_42B8CF
.text:0042B8C5 dec word ptr [esp+16h] ; 4+8+8+2
.text:0042B8CA jns short locret_42B8CF
.text:0042B8CC pop eax
.text:0042B8CD jmp short loc_42B87B
.text:0042B8CF locret_42B8CF:
.text:0042B8CF retn
0x2b2a1
CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
66 C7 44 24 0E 01 00 8B 90 44 01 00 00 C3 CC
.text:0042BEA1 loc_42BEA1:
.text:0042BEA1 mov word ptr [esp+0Eh], 1 ; 4+8+2
.text:0042BEA8 mov edx, [eax+144h]
.text:0042BEAE retn
0x34d91
8B 90 44 01 00 00
E8 0B 65 FF FF 90
<<<<
.text:00435991 mov edx, [eax+144h]
====
.text:00435991 call loc_42BEA1
.text:00435996 nop
>>>>
0x34e53
0F 84 BD 00 00 00
E8 6B 5E FF FF 90
<<<<
.text:00435A53 jz loc_435B16
====
.text:00435A53 call loc_42B8C3
.text:00435A58 nop
>>>>
0x34ef3
66 3B C5 74 1E
E8 73 5D FF FF
<<<<
.text:00435AF3 cmp ax, bp
.text:00435AF6 jz short loc_435B16
====
.text:00435AF3 call loc_42B86B
>>>>
basically:
+ int allowed_count = 1; // to mean 2
...
- if (type(item) == new_type)
+ if (type(item) == new_type && --allowed_count < 0)
return false;
to allow up to two items of the same type at the same time
---8<---
This difference file is created by The Interactive Disassembler
Dwarf Fortress.exe
0002AC6B: CC 66
0002AC6C: CC 39
0002AC6D: CC E8
0002AC6E: CC EB
0002AC6F: CC 53
0002AC7B: CC E9
0002AC7C: CC 96
0002AC7D: CC A2
0002AC7E: CC 00
0002AC7F: CC 00
0002ACC3: CC 75
0002ACC4: CC 0A
0002ACC5: CC 66
0002ACC6: CC FF
0002ACC7: CC 4C
0002ACC8: CC 24
0002ACC9: CC 16
0002ACCA: CC 79
0002ACCB: CC 03
0002ACCC: CC 58
0002ACCD: CC EB
0002ACCE: CC AC
0002ACCF: CC C3
0002B2A1: CC 66
0002B2A2: CC C7
0002B2A3: CC 44
0002B2A4: CC 24
0002B2A5: CC 0E
0002B2A6: CC 01
0002B2A7: CC 00
0002B2A8: CC 8B
0002B2A9: CC 90
0002B2AA: CC 44
0002B2AB: CC 01
0002B2AC: CC 00
0002B2AD: CC 00
0002B2AE: CC C3
00034D91: 8B E8
00034D92: 90 0B
00034D93: 44 65
00034D94: 01 FF
00034D95: 00 FF
00034D96: 00 90
00034E53: 0F E8
00034E54: 84 6B
00034E55: BD 5E
00034E56: 00 FF
00034E57: 00 FF
00034E58: 00 90
00034EF3: 66 E8
00034EF4: 3B 73
00034EF5: C5 5D
00034EF6: 74 FF
00034EF7: 1E FF

@ -0,0 +1,91 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=808
Original code:
.text:00916BCE mov edi, ebp
.text:00916BD0 call eax
.text:00916BD2 test eax, eax
.text:00916BD4 jnz short loc_916C1C
.text:00916C0A mov edi, ebp
.text:00916C14 mov edi, ebp
Patch:
0x2ac34:
CC CC CC CC CC CC CC CC CC CC CC CC
8B 7C 24 78 8B 3C B7 FF D0 EB 25 CC
.text:0042B834 loc_42B834:
.text:0042B834 mov edi, [esp+78h]
.text:0042B838 mov edi, [edi+esi*4]
.text:0042B83B call eax
.text:0042B83D jmp short unk_42B864
0x2ac64
CC CC CC CC CC CC CC CC CC CC CC CC
85 C0 E9 69 B3 4E 00 CC CC CC CC CC
.text:0042B864 loc_42B864:
.text:0042B864 test eax, eax
.text:0042B866 jmp loc_916BD4
0x515fce
8B FD FF D0 85 C0
E9 61 4C B1 FF 90
.text:00916BCE jmp loc_42B834
.text:00916BD3 nop
.text:00916BD4 loc_916BD4:
0x51600a
8B FD
90 90
.text:00916C0A nop
.text:00916C0B nop
0x516014
8B FD
90 90
.text:00916C14 nop
.text:00916C15 nop
You can use this script to apply the generated patch below:
http://stalkr.net/files/ida/idadif.py
----8<----
This difference file is created by The Interactive Disassembler
Dwarf Fortress.exe
0002AC34: CC 8B
0002AC35: CC 7C
0002AC36: CC 24
0002AC37: CC 78
0002AC38: CC 8B
0002AC39: CC 3C
0002AC3A: CC B7
0002AC3B: CC FF
0002AC3C: CC D0
0002AC3D: CC EB
0002AC3E: CC 25
0002AC64: CC 85
0002AC65: CC C0
0002AC66: CC E9
0002AC67: CC 69
0002AC68: CC B3
0002AC69: CC 4E
0002AC6A: CC 00
00515FCE: 8B E9
00515FCF: FD 61
00515FD0: FF 4C
00515FD1: D0 B1
00515FD2: 85 FF
00515FD3: C0 90
0051600A: 8B 90
0051600B: FD 90
00516014: 8B 90
00516015: FD 90

@ -0,0 +1,61 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=5994
Original code:
.text:008629BD mov edi, [eax+38h]
.text:008629C0 mov eax, [eax+3Ch]
.text:008629C3 mov [esp+1Ch], eax
.text:008629C7 cmp edi, eax
.text:008629C9 jnb short loc_862A22
.text:008629CB jmp short loc_8629D0
.text:008629CD lea ecx, [ecx+0]
...
.text:00862A19 add edi, 4
.text:00862A1C cmp edi, [esp+1Ch]
.text:00862A20 jb short loc_8629D0
Patch:
0x461dbd
8B 78 38 8B 40 3C 89 44 24 1C 3B F8
8B 78 3C 8B 40 38 89 44 24 1C 39 F8
.text:008629BD mov edi, [eax+3Ch]
.text:008629C0 mov eax, [eax+38h]
.text:008629C3 mov [esp+1Ch], eax
.text:008629C7 cmp eax, edi
0x461dcb
EB 03 8D 49 00
83 EF 04 90 90
.text:008629CB sub edi, 4
.text:008629CE nop
.text:008629CF nop
0x461e19
83 C7 04 3B 7C 24 1C 72 AE
83 EF 04 3B 7C 24 1C 73 AE
.text:00862A19 sub edi, 4
.text:00862A1C cmp edi, [esp+1Ch]
.text:00862A20 jnb short loc_8629D0
You can use this script to apply the generated patch below:
http://stalkr.net/files/ida/idadif.py
----8<----
This difference file is created by The Interactive Disassembler
Dwarf_Fortress
00461DBF: 38 3C
00461DC2: 3C 38
00461DC7: 3B 39
00461DCB: EB 83
00461DCC: 03 EF
00461DCD: 8D 04
00461DCE: 49 90
00461DCF: 00 90
00461E1A: C7 EF
00461E20: 72 73

@ -0,0 +1,104 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=5994
0x461de2
F6 46 0C 01 74 31
E9 0A 8E BC FF 90
.text:008629E2 jmp near ptr loc_42B7F1 ; << CAVE
.text:008629E7 nop
.text:008629E8 loc_8629E8:
0x2abf1
CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
8B 4C 24 2C F6 46 0C 01 75 08 E9 19 72 43 00
.text:0042B7F1 loc_42B7F1:
.text:0042B7F1 mov ecx, [esp+2Ch] ; job
.text:0042B7F5 test byte ptr [esi+0Ch], 1
.text:0042B7F9 jnz short near ptr loc_42B803
.text:0042B7FB coord_test_jfail:
.text:0042B7FB jmp loc_862A19
0x2ac03
CC CC CC CC CC CC CC CC CC CC CC CC CC
8B 41 10 3B 46 04 75 F0 EB 06 CC CC CC
.text:0042B803 loc_42B803:
.text:0042B803 mov eax, [ecx+10h] ; job->pos.(x,y)
.text:0042B806 cmp eax, [esi+4] ; item->pos.(x,y)
.text:0042B809 jnz short coord_test_jfail
.text:0042B80B jmp short near ptr loc_42B813
0x2ac13
CC CC CC CC CC CC CC CC CC CC CC CC CC
66 8B 41 14 66 3B 46 08 75 DE EB 06 CC
text:0042B813 loc_42B813:
.text:0042B813 mov ax, [ecx+14h] ; job->pos.z
.text:0042B817 cmp ax, [esi+8] ; item->pos.z
.text:0042B81B jnz short coord_test_jfail
.text:0042B81D jmp short near ptr loc_42B825
0x2ac25
CC CC CC CC CC CC CC CC CC CC CC
E9 BE 71 43 00 CC CC CC CC CC CC
.text:0042B825 loc_42B825:
.text:0042B825 jmp loc_8629E8
You can use this script to apply the generated patch below:
http://stalkr.net/files/ida/idadif.py
----8<----
This difference file is created by The Interactive Disassembler
Dwarf Fortress.exe
0002ABF1: CC 8B
0002ABF2: CC 4C
0002ABF3: CC 24
0002ABF4: CC 2C
0002ABF5: CC F6
0002ABF6: CC 46
0002ABF7: CC 0C
0002ABF8: CC 01
0002ABF9: CC 75
0002ABFA: CC 08
0002ABFB: CC E9
0002ABFC: CC 19
0002ABFD: CC 72
0002ABFE: CC 43
0002ABFF: CC 00
0002AC03: CC 8B
0002AC04: CC 41
0002AC05: CC 10
0002AC06: CC 3B
0002AC07: CC 46
0002AC08: CC 04
0002AC09: CC 75
0002AC0A: CC F0
0002AC0B: CC EB
0002AC0C: CC 06
0002AC13: CC 66
0002AC14: CC 8B
0002AC15: CC 41
0002AC16: CC 14
0002AC17: CC 66
0002AC18: CC 3B
0002AC19: CC 46
0002AC1A: CC 08
0002AC1B: CC 75
0002AC1C: CC DE
0002AC1D: CC EB
0002AC1E: CC 06
0002AC25: CC E9
0002AC26: CC BE
0002AC27: CC 71
0002AC28: CC 43
0002AC29: CC 00
00461DE2: F6 E9
00461DE3: 46 0A
00461DE4: 0C 8E
00461DE5: 01 BC
00461DE6: 74 FF
00461DE7: 31 90

@ -0,0 +1,62 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=4406
1. Include store in hospital jobs when recomputing counters
0x68a63
0F 85 58 01 00 00
90 90 90 90 90 90
<<<<
.text:00469663 jnz loc_4697C1
====
.text:00469663 nop
.text:00469664 nop
.text:00469665 nop
.text:00469666 nop
.text:00469667 nop
.text:00469668 nop
>>>>
- if (job->getBuildingRef(BUILDING_DESTINATION) != this) continue;
+ // NOP
This reference points to the containers, not the hospital civzone.
Since fixing this properly is too hard for a patch, just remove the
check. Most people have only one hospital anyway, and it is better
to err on the side of caution here.
2. Make the stockpiling code increment the right stock counters
0x3dcbf9
8B 0C 90 8B 81 80 00 00 00
8B 3C 90 8B 87 80 00 00 00
<<<<
.text:007DD7F9 mov ecx, [eax+edx*4]
.text:007DD7FC mov eax, [ecx+80h]
====
.text:007DD7F9 mov edi, [eax+edx*4]
.text:007DD7FC mov eax, [edi+80h]
>>>>
- id = civzones[i]->children[child_idx[i]]->id
+ cur_civzone = civzones[i] // existing var from previous loop
+ id = cur_civzone->children[child_idx[i]]->id
The reason being, later code uses that var (at this point containing
useless data) to increment counters and amounts in the hospital.
---8<---
This difference file is created by The Interactive Disassembler
Dwarf Fortress.exe
00068A63: 0F 90
00068A64: 85 90
00068A65: 58 90
00068A66: 01 90
00068A67: 00 90
00068A68: 00 90
003DCBFA: 0C 3C
003DCBFD: 81 87

@ -0,0 +1,83 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=4530
0x2ac85
CC CC CC CC CC CC CC CC CC CC CC
29 44 24 24 8B 9D 28 01 00 00 C3
.text:0042B885 loc_42B885:
.text:0042B885 sub [esp+24h], eax
.text:0042B889 mov ebx, [ebp+128h]
.text:0042B88F retn
0x2ac94
CC CC CC CC CC CC CC CC CC CC CC CC
89 C1 8B 00 FF 90 34 02 00 00 EB E5
.text:0042B894 loc_42B894:
.text:0042B894 mov ecx, eax
.text:0042B896 mov eax, [eax]
.text:0042B898 call dword ptr [eax+234h]
.text:0042B89E jmp short loc_42B885
0x6e28ff
29 44 24 20
90 90 90 90
<<<<
.text:00AE34FF sub [esp+20h], eax
====
.text:00AE34FF nop
.text:00AE3500 nop
.text:00AE3501 nop
.text:00AE3502 nop
>>>>
0x6e2999
8B 9D 28 01 00 00
E8 F6 82 94 FF 90
<<<<
.text:00AE3599 mov ebx, [ebp+128h]
====
.text:00AE3599 call loc_42B894
.text:00AE359E nop
>>>>
---8<---
This difference file is created by The Interactive Disassembler
Dwarf Fortress.exe
0002AC85: CC 29
0002AC86: CC 44
0002AC87: CC 24
0002AC88: CC 24
0002AC89: CC 8B
0002AC8A: CC 9D
0002AC8B: CC 28
0002AC8C: CC 01
0002AC8D: CC 00
0002AC8E: CC 00
0002AC8F: CC C3
0002AC94: CC 89
0002AC95: CC C1
0002AC96: CC 8B
0002AC97: CC 00
0002AC98: CC FF
0002AC99: CC 90
0002AC9A: CC 34
0002AC9B: CC 02
0002AC9C: CC 00
0002AC9D: CC 00
0002AC9E: CC EB
0002AC9F: CC E5
006E28FF: 29 90
006E2900: 44 90
006E2901: 24 90
006E2902: 20 90
006E2999: 8B E8
006E299A: 9D F6
006E299B: 28 82
006E299C: 01 94
006E299D: 00 FF
006E299E: 00 90

@ -0,0 +1,61 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
0x4c05c4
8B 8C 24 80 00 00 00
89 C1 90 90 90 90 90
<<<<
.text:008C11C4 mov ecx, [esp+98h+var_18]
====
.text:008C11C4 mov ecx, eax
.text:008C11C6 nop
.text:008C11C7 nop
.text:008C11C8 nop
.text:008C11C9 nop
.text:008C11CA nop
>>>>
0x4c06a1
8B 8C 24 80 00 00 00
89 C1 90 90 90 90 90
<<<<
.text:008C12A1 mov ecx, [esp+98h+var_18]
====
.text:008C12A1 mov ecx, eax
.text:008C12A3 nop
.text:008C12A4 nop
.text:008C12A5 nop
.text:008C12A6 nop
.text:008C12A7 nop
>>>>
basically:
b_squad_id = building->getSpecificSquad();
- if (b_squad_id != squad->id || !building->canUse(some_squad_id, 4))
+ if (b_squad_id != squad->id || !building->canUse(b_squad_id, 4))
unassign(building);
the reason being, some_other_squad_id contains irrelevant garbage at this point
---8<---
This difference file is created by The Interactive Disassembler
Dwarf Fortress.exe
004C05C4: 8B 89
004C05C5: 8C C1
004C05C6: 24 90
004C05C7: 80 90
004C05C8: 00 90
004C05C9: 00 90
004C05CA: 00 90
004C06A1: 8B 89
004C06A2: 8C C1
004C06A3: 24 90
004C06A4: 80 90
004C06A5: 00 90
004C06A6: 00 90
004C06A7: 00 90

@ -0,0 +1,147 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
0x9461
90 90 90 90 90 90 90 90 90 90 90 90 90 90
C7 44 24 18 01 00 00 00 FF A0 44 01 00 00
.text:08051461 sub_8051461 proc near
.text:08051461 mov dword ptr [esp+18h], 1
.text:08051469 jmp dword ptr [eax+144h]
.text:08051469 sub_8051461 endp
0x9548
90 90 90 90 90 90 90 90
FF 4C 24 14 78 08 EB 0B
.text:08051548 loc_8051548:
.text:08051548 dec dword ptr [esp+14h]
.text:0805154C js short loc_8051556
.text:0805154E jmp short loc_805155B
0x9556
90 90 90 90 90 90 90 90 90 90
E9 F6 8C 05 00 E9 D0 8D 05 00
.text:08051556 loc_8051556:
.text:08051556 jmp loc_80AA251
.text:0805155B loc_805155B:
.text:0805155B jmp loc_80AA330
0x9568
90 90 90 90 90 90 90 90
FF 4C 24 14 78 E8 EB 06
.text:08051568 loc_8051568:
.text:08051568 dec [esp+14h]
.text:0805156C js short loc_8051556
.text:0805156E jmp short loc_8051576
0x9576
90 90 90 90 90
E9 4D 8E 05 00
.text:08051576 loc_8051576:
.text:08051576 jmp loc_80AA3C8
0x62243
FF 90 44 01 00 00
E8 19 72 FA FF 90
<<<<
.text:080AA243 call dword ptr [eax+144h]
====
.text:080AA243 call sub_8051461
.text:080AA248 nop
>>>>
0x62369
E9 E3 FE FF FF
E9 DA 71 FA FF
<<<<
.text:080AA369 jmp loc_80AA251
====
.text:080AA369 jmp loc_8051548
>>>>
0x623f6
E9 56 FE FF FF
E9 6D 71 FA FF
<<<<
.text:080AA3F6 jmp loc_80AA251
====
.text:080AA3F6 jmp loc_8051568
>>>>
basically:
+ int allowed_count = 1; // to mean 2
...
- if (type(item) == new_type)
+ if (type(item) == new_type && --allowed_count < 0)
return false;
to allow up to two items of the same type at the same time
---8<---
This difference file is created by The Interactive Disassembler
Dwarf_Fortress
00009461: 90 C7
00009462: 90 44
00009463: 90 24
00009464: 90 18
00009465: 90 01
00009466: 90 00
00009467: 90 00
00009468: 90 00
00009469: 90 FF
0000946A: 90 A0
0000946B: 90 44
0000946C: 90 01
0000946D: 90 00
0000946E: 90 00
00009548: 90 FF
00009549: 90 4C
0000954A: 90 24
0000954B: 90 14
0000954C: 90 78
0000954D: 90 08
0000954E: 90 EB
0000954F: 90 0B
00009556: 90 E9
00009557: 90 F6
00009558: 90 8C
00009559: 90 05
0000955A: 90 00
0000955B: 90 E9
0000955C: 90 D0
0000955D: 90 8D
0000955E: 90 05
0000955F: 90 00
00009568: 90 FF
00009569: 90 4C
0000956A: 90 24
0000956B: 90 14
0000956C: 90 78
0000956D: 90 E8
0000956E: 90 EB
0000956F: 90 06
00009576: 90 E9
00009577: 90 4D
00009578: 90 8E
00009579: 90 05
0000957A: 90 00
00062243: FF E8
00062244: 90 19
00062245: 44 72
00062246: 01 FA
00062247: 00 FF
00062248: 00 90
0006236A: E3 DA
0006236B: FE 71
0006236C: FF FA
000623F7: 56 6D
000623F8: FE 71
000623F9: FF FA

@ -0,0 +1,40 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=808
for (i = 0; i < num_items; i++)
{
ridx = reagent_idx[i];
sz = reagent_quantity[ridx]; // used quantity
if (sz <= 0) continue;
reag = reagent[ridx];
if (reag->flags.PRESERVE_REAGENT) continue;
rsz = items[i]->getTotalDimension();
<<<<<<<<
if (reag->flags3.ANY_RAW_MATERIAL)
rsz *= BASE_SIZE(items[i]->getType());
if (items[i]->subtractDimension(rsz))
========
/* Not in patch, but necessary for full correctness:
if (reag->flags3.ANY_RAW_MATERIAL)
rsz /= BASE_SIZE(items[i]->getType());
*/
if (reag->flags3.ANY_RAW_MATERIAL)
sz *= BASE_SIZE(items[i]->getType());
if (items[i]->subtractDimension(sz))
>>>>>>>>
destroy_item(items[i]);
reagent_quantity[ridx] -= rsz
if (reagent_quantity[ridx] < 0)
reagent_quantity[ridx] = 0;
}
You can use this script to apply the generated patch below:
http://stalkr.net/files/ida/idadif.py
----8<----
This difference file is created by The Interactive Disassembler
Dwarf_Fortress
0087F7EF: F8 D8
0087F86F: F8 D8
0087F9AD: C7 C3
0087F9ED: C7 C3

@ -0,0 +1,83 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=5994
Original code:
.text:087AC378 cmp edx, eax
.text:087AC37A mov [esp+4Ch], eax
.text:087AC37E jnb loc_87A7034
.text:087AC384 mov [esp+48h], edx
.text:087AC388 mov [esp+54h], ebx
...
.text:087AC440 add dword ptr [esp+48h], 4
.text:087AC445 mov ebp, [esp+48h]
.text:087AC449 cmp [esp+4Ch], ebp
.text:087AC44D ja loc_87AC38C
Patch:
0x76437a
89 44 24 4C
89 54 24 4C
.text:087AC37A mov [esp+4Ch], edx
0x764384
89 54 24 48 89 5C 24 54
E8 8A 51 8A FF 90 90 90
.text:087AC384 call sub_8051513
.text:087AC389 nop
.text:087AC38A nop
.text:087AC38B nop
0x764440
83 44 24 48 04 8B 6C 24 48 39 6C 24 4C 0F 87 39 FF FF FF
83 6C 24 48 04 8B 6C 24 48 39 6C 24 4C 0F 86 39 FF FF FF
.text:087AC440 sub dword ptr [esp+48h], 4
.text:087AC445 mov ebp, [esp+48h]
.text:087AC449 cmp [esp+4Ch], ebp
.text:087AC44D jbe loc_87AC38C
0x9513
90 90 90 90 90 90 90 90 90 90 90 90 90
83 E8 04 89 44 24 4C 89 5C 24 58 C3 90
.text:08051513 sub_8051513 proc near
.text:08051513 sub eax, 4
.text:08051516 mov [esp+4Ch], eax ; 48h
.text:0805151A mov [esp+58h], ebx ; 54h
.text:0805151E retn
.text:0805151E sub_8051513 endp
You can use this script to apply the generated patch below:
http://stalkr.net/files/ida/idadif.py
----8<----
This difference file is created by The Interactive Disassembler
Dwarf_Fortress
00009513: 90 83
00009514: 90 E8
00009515: 90 04
00009516: 90 89
00009517: 90 44
00009518: 90 24
00009519: 90 4C
0000951A: 90 89
0000951B: 90 5C
0000951C: 90 24
0000951D: 90 58
0000951E: 90 C3
0076437B: 44 54
00764384: 89 E8
00764385: 54 8A
00764386: 24 51
00764387: 48 8A
00764388: 89 FF
00764389: 5C 90
0076438A: 24 90
0076438B: 54 90
00764441: 44 6C
0076444E: 87 86

@ -0,0 +1,139 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=5994
0x7643f8
F6 46 0C 01 74 42
E9 B6 50 8A FF 90
.text:087AC3F8 jmp loc_80514B3 ; << CAVE
.text:087AC3FD nop
.text:087AC3FE loc_87AC3FE:
0x94b3
90 90 90 90 90 90 90 90 90 90 90 90 90
F6 46 0C 01 75 0A E9 82 AF 75 00 90 90
.text:080514B3 loc_80514B3:
.text:080514B3 test byte ptr [esi+0Ch], 1
.text:080514B7 jnz short loc_80514C3
.text:080514B9 coord_test_jfail:
.text:080514B9 jmp loc_87AC440
0x94c3
90 90 90 90 90 90 90 90 90 90 90 90 90
8D 9C 24 60 03 00 00 0F BF 03 EB 07 90
.text:080514C3 loc_80514C3:
.text:080514C3 lea ebx, [esp+360h]
.text:080514CA movsx eax, word ptr [ebx] ; job_z
.text:080514CD jmp short loc_80514D6
0x94d6
90 90 90 90 90 90 90 90 90 90
66 3B 46 08 75 DD EB 05 90 90
.text:080514D6 loc_80514D6:
.text:080514D6 cmp ax, [esi+8] ; item->pos.z
.text:080514DA jnz short coord_test_jfail
.text:080514DC jmp short loc_80514E3
0x94e3
90 90 90 90 90 90 90 90 90 90 90 90 90
0F BF 43 10 66 3B 46 04 75 CC EB 04 90
.text:080514E3 loc_80514E3:
.text:080514E3 movsx eax, word ptr [ebx+10h] ; job_x
.text:080514E7 cmp ax, [esi+4] ; item->pos.x
.text:080514EB jnz short coord_test_jfail
.text:080514ED jmp short loc_80514F3
0x94f3
90 90 90 90 90 90 90 90 90 90 90 90 90
0F BF 43 20 66 3B 46 06 75 BC EB 04 90
.text:080514F3 loc_80514F3:
.text:080514F3 movsx eax, word ptr [ebx+20h] ; job_y
.text:080514F7 cmp ax, [esi+6] ; item->pos.y
.text:080514FB jnz short coord_test_jfail
.text:080514FD jmp short loc_8051503
0x9503
90 90 90 90 90 90 90 90 90 90 90 90 90
E9 F6 AE 75 00 90 90 90 90 90 90 90 90
.text:08051503 loc_8051503:
.text:08051503 jmp loc_87AC3FE
You can use this script to apply the generated patch below:
http://stalkr.net/files/ida/idadif.py
----8<----
This difference file is created by The Interactive Disassembler
Dwarf_Fortress
000094B3: 90 F6
000094B4: 90 46
000094B5: 90 0C
000094B6: 90 01
000094B7: 90 75
000094B8: 90 0A
000094B9: 90 E9
000094BA: 90 82
000094BB: 90 AF
000094BC: 90 75
000094BD: 90 00
000094C3: 90 8D
000094C4: 90 9C
000094C5: 90 24
000094C6: 90 60
000094C7: 90 03
000094C8: 90 00
000094C9: 90 00
000094CA: 90 0F
000094CB: 90 BF
000094CC: 90 03
000094CD: 90 EB
000094CE: 90 07
000094D6: 90 66
000094D7: 90 3B
000094D8: 90 46
000094D9: 90 08
000094DA: 90 75
000094DB: 90 DD
000094DC: 90 EB
000094DD: 90 05
000094E3: 90 0F
000094E4: 90 BF
000094E5: 90 43
000094E6: 90 10
000094E7: 90 66
000094E8: 90 3B
000094E9: 90 46
000094EA: 90 04
000094EB: 90 75
000094EC: 90 CC
000094ED: 90 EB
000094EE: 90 04
000094F3: 90 0F
000094F4: 90 BF
000094F5: 90 43
000094F6: 90 20
000094F7: 90 66
000094F8: 90 3B
000094F9: 90 46
000094FA: 90 06
000094FB: 90 75
000094FC: 90 BC
000094FD: 90 EB
000094FE: 90 04
00009503: 90 E9
00009504: 90 F6
00009505: 90 AE
00009506: 90 75
00009507: 90 00
007643F8: F6 E9
007643F9: 46 B6
007643FA: 0C 50
007643FB: 01 8A
007643FC: 74 FF
007643FD: 42 90

@ -0,0 +1,60 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=4406
1. Include store in hospital jobs when recomputing counters
0x746d7
75 D7
90 90
<<<<
.text:080BC6D7 jnz short loc_80BC6B0
====
.text:080BC6D7 nop
.text:080BC6D8 nop
>>>>
- if (job->getBuildingRef(BUILDING_DESTINATION) != this) continue;
+ // NOP
This reference points to the containers, not the hospital civzone.
Since fixing this properly is too hard for a patch, just remove the
check. Most people have only one hospital anyway, and it is better
to err on the side of caution here.
2. Make the stockpiling code increment the right stock counters
0x67cb0e
0B 04 90
8B 1C 90
0x67cb18
8B 40 74
8B 43 74
<<<<
.text:086C4B0E mov eax, [eax+edx*4]
.text:086C4B11 mov edx, [esp+ecx*4+39Ch+var_2B4]
.text:086C4B18 mov eax, [eax+74h]
====
.text:086C4B0E mov ebx, [eax+edx*4]
.text:086C4B11 mov edx, [esp+ecx*4+39Ch+var_2B4]
.text:086C4B18 mov eax, [ebx+74h]
>>>>
- id = civzones[i]->children[child_idx[i]]->id
+ cur_civzone = civzones[i] // existing var from previous loop
+ id = cur_civzone->children[child_idx[i]]->id
The reason being, later code uses that var (at this point containing
useless data) to increment counters and amounts in the hospital.
---8<---
This difference file is created by The Interactive Disassembler
Dwarf_Fortress
000746D7: 75 90
000746D8: D7 90
0067CB0F: 04 1C
0067CB19: 40 43

@ -0,0 +1,85 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=4530
0x9508
90 90 90 90 90 90 90 90
E9 13 76 9C 00 90 90 90
.text:08051508 loc_8051508:
.text:08051508 jmp sub_8A18B20
0x9523
90 90 90 90 90 90 90 90 90 90 90 90 90
50 8B 03 53 FF 90 34 02 00 00 EB 09 90
.text:08051523 sub_8051523:
.text:08051523 push eax
.text:08051524 mov eax, [ebx]
.text:08051526 push ebx
.text:08051527 call dword ptr [eax+234h]
.text:0805152D jmp short loc_8051538
0x9538
90 90 90 90 90 90 90 90
29 44 24 64 5B 58 EB C8
.text:08051538 loc_8051538:
.text:08051538 sub [esp+64h], eax
.text:0805153C pop ebx
.text:0805153D pop eax
.text:0805153E jmp short loc_8051508
0xa5cdd0
29 44 24 58
90 90 90 90
.text:08AA4DD0 nop
.text:08AA4DD1 nop
.text:08AA4DD2 nop
.text:08AA4DD3 nop
0xa5e2c3
E8 58 28 F7 FF
E8 5B B2 5A FF
.text:08AA62C3 call sub_8051523
---8<---
This difference file is created by The Interactive Disassembler
Dwarf_Fortress
00009508: 90 E9
00009509: 90 13
0000950A: 90 76
0000950B: 90 9C
0000950C: 90 00
00009523: 90 50
00009524: 90 8B
00009525: 90 03
00009526: 90 53
00009527: 90 FF
00009529: 90 34
0000952A: 90 02
0000952B: 90 00
0000952C: 90 00
0000952D: 90 EB
0000952E: 90 09
00009538: 90 29
00009539: 90 44
0000953A: 90 24
0000953B: 90 64
0000953C: 90 5B
0000953D: 90 58
0000953E: 90 EB
0000953F: 90 C8
00A5CDD0: 29 90
00A5CDD1: 44 90
00A5CDD2: 24 90
00A5CDD3: 58 90
00A5E2C4: 58 5B
00A5E2C5: 28 B2
00A5E2C6: F7 5A

@ -0,0 +1,45 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
Fix use of uninitialized variables to stop auto-unassigning racks:
0x7ee948
8B 7C 24 3C
89 C7 90 90
.text:08836948 mov edi, eax
.text:0883694A nop
.text:0883694B nop
0x7eea2f
8B 7C 24 3C
89 C7 90 90
.text:08836A2F mov edi, eax
.text:08836A31 nop
.text:08836A32 nop
basically:
b_squad_id = building->getSpecificSquad();
- if (b_squad_id != squad->id || !building->canUse(some_squad_id, 4))
+ if (b_squad_id != squad->id || !building->canUse(b_squad_id, 4))
unassign(building);
the reason being, some_other_squad_id contains irrelevant garbage at this point
---8<---
This difference file is created by The Interactive Disassembler
Dwarf_Fortress
007EE948: 8B 89
007EE949: 7C C7
007EE94A: 24 90
007EE94B: 3C 90
007EEA2F: 8B 89
007EEA30: 7C C7
007EEA31: 24 90
007EEA32: 3C 90

@ -120,12 +120,14 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(forceequip forceequip.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp)
DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(manipulator manipulator.cpp)
DFHACK_PLUGIN(search search.cpp) DFHACK_PLUGIN(search search.cpp)
DFHACK_PLUGIN(automaterial automaterial.cpp)
# 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(steam-engine steam-engine.cpp)
DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(reactionhooks reactionhooks.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(add-spatter add-spatter.cpp) DFHACK_PLUGIN(add-spatter add-spatter.cpp)
DFHACK_PLUGIN(fix-armory fix-armory.cpp) DFHACK_PLUGIN(fix-armory fix-armory.cpp)
# not yet. busy with other crud again... # not yet. busy with other crud again...

@ -1,5 +1,5 @@
include_directories(include) include_directories(include)
include_directories("${dfhack_SOURCE_DIR}/library/depends/tthread")
FILE(GLOB DFUSION_CPPS src/*.c*) FILE(GLOB DFUSION_CPPS src/*.c*)
set( set(
DFUSION_CPPS_ALL DFUSION_CPPS_ALL
@ -9,6 +9,3 @@ set(
FILE(GLOB DFUSION_HS include/*) FILE(GLOB DFUSION_HS include/*)
SET_SOURCE_FILES_PROPERTIES( ${DFUSION_HS} PROPERTIES HEADER_FILE_ONLY TRUE ) SET_SOURCE_FILES_PROPERTIES( ${DFUSION_HS} PROPERTIES HEADER_FILE_ONLY TRUE )
DFHACK_PLUGIN(dfusion ${DFUSION_CPPS_ALL} ${DFUSION_HS} LINK_LIBRARIES lua dfhack-tinythread) DFHACK_PLUGIN(dfusion ${DFUSION_CPPS_ALL} ${DFUSION_HS} LINK_LIBRARIES lua dfhack-tinythread)
# installs into DF root
install(DIRECTORY luafiles/ DESTINATION dfusion)

@ -6,18 +6,11 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include "tinythread.h"
#include "luamain.h" #include "luamain.h"
#include "lua_Process.h" #include "lua_Process.h"
#include "lua_Hexsearch.h" #include "lua_Hexsearch.h"
#include "lua_Misc.h" #include "lua_Misc.h"
#include "lua_VersionInfo.h"
#include "functioncall.h"
#include "lua_FunctionCall.h"
#include "lua_Offsets.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "LuaTools.h" #include "LuaTools.h"
@ -25,177 +18,71 @@ using std::vector;
using std::string; using std::string;
using namespace DFHack; using namespace DFHack;
static tthread::mutex* mymutex=0;
static tthread::thread* thread_dfusion=0;
uint64_t timeLast=0;
DFHACK_PLUGIN("dfusion")
command_result dfusion (color_ostream &out, std::vector <std::string> &parameters);
command_result dfuse (color_ostream &out, std::vector <std::string> &parameters);
command_result lua_run (color_ostream &out, std::vector <std::string> &parameters);
command_result lua_run_file (color_ostream &out, std::vector <std::string> &parameters);
DFhackCExport const char * plugin_name ( void ) DFHACK_PLUGIN("dfusion")
{
return "dfusion";
}
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
lua::state st=lua::glua::Get();
//maybe remake it to run automaticaly
Lua::Open(out, st);
lua::RegisterProcess(st);
lua::RegisterHexsearch(st);
lua::RegisterMisc(st);
lua::RegisterVersionInfo(st);
lua::RegisterFunctionCall(st);
lua::RegisterEngine(st);
#ifdef LINUX_BUILD
st.push(1);
st.setglobal("LINUX");
#else
st.push(1);
st.setglobal("WINDOWS");
#endif
commands.push_back(PluginCommand("dfusion","Run dfusion system (interactive i.e. can input further commands).",dfusion,true));
commands.push_back(PluginCommand("dfuse","Init dfusion system (not interactive).",dfuse,false));
commands.push_back(PluginCommand("lua", "Run interactive interpreter. Use 'lua <filename>' to run <filename> instead.",lua_run,true));
commands.push_back(PluginCommand("runlua", "Run non-interactive interpreter. Use 'runlua <filename>' to run <filename>.",lua_run_file,false));
mymutex=new tthread::mutex;
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( Core * c ) static int loadObjectFile(lua_State* L)
{ {
std::string path;
path=luaL_checkstring(L,1);
OutFile::File f(path);
lua_newtable(L);
int table_pos=lua_gettop(L);
size_t size=f.GetTextSize();
Lua::Push(L,size);
lua_setfield(L,table_pos,"data_size");
char* buf=new char[size];
f.GetText(buf);
//Lua::PushDFObject(L,DFHack::,buf);
//Lua::Push(L,buf);
lua_pushlightuserdata(L,buf);
lua_setfield(L,table_pos,"data");
const OutFile::vSymbol &symbols=f.GetSymbols();
lua_newtable(L);
for(size_t i=0;i<symbols.size();i++)
{
Lua::Push(L,i);
lua_newtable(L);
Lua::Push(L,symbols[i].name);
lua_setfield(L,-2,"name");
Lua::Push(L,symbols[i].pos);
lua_setfield(L,-2,"pos");
// shutdown stuff
if(thread_dfusion)
delete thread_dfusion;
delete mymutex;
return CR_OK;
}
DFhackCExport command_result plugin_onupdate_DISABLED ( Core * c ) lua_settable(L,-3);
{
uint64_t time2 = GetTimeMs64();
uint64_t delta = time2-timeLast;
if(delta<100)
return CR_OK;
timeLast = time2;
mymutex->lock();
lua::state s=lua::glua::Get();
s.getglobal("OnTick");
if(s.is<lua::function>())
{
try{
s.pcall();
}
catch(lua::exception &e)
{
c->getConsole().printerr("Error OnTick:%s\n",e.what());
c->getConsole().printerr("%s\n",lua::DebugDump(lua::glua::Get()).c_str());
c->getConsole().msleep(1000);
}
} }
s.settop(0); lua_setfield(L,table_pos,"symbols");
mymutex->unlock(); return 1;
return CR_OK;
} }
command_result lua_run_file (color_ostream &out, std::vector <std::string> &parameters) static int markAsExecutable(lua_State* L)
{ {
if(parameters.size()==0) unsigned addr=luaL_checkunsigned(L,1);
std::vector<DFHack::t_memrange> ranges;
DFHack::Core::getInstance().p->getMemRanges(ranges);
for(size_t i=0;i<ranges.size();i++)
{ {
out.printerr("runlua without file to run!"); if(ranges[i].isInRange((void*)addr))
return CR_FAILURE;
}
return lua_run(out,parameters);
}
command_result lua_run (color_ostream &out, std::vector <std::string> &parameters)
{
if (!parameters.empty())
{
if (parameters[0] == "--core-context")
{
Lua::InterpreterLoop(out, Lua::Core::State, "core lua");
return CR_OK;
}
else if (parameters[0] == "--core-reload")
{
CoreSuspender suspend;
for (size_t i = 1; i < parameters.size(); i++)
{
lua_getglobal(Lua::Core::State, "reload");
lua_pushstring(Lua::Core::State, parameters[i].c_str());
Lua::SafeCall(out, Lua::Core::State, 1, 0);
}
return CR_OK;
}
}
mymutex->lock();
lua::state s=lua::glua::Get();
if(parameters.size()>0)
{
try{
s.loadfile(parameters[0]); //load file
for(size_t i=1;i<parameters.size();i++)
s.push(parameters[i]);
Lua::SafeCall(out, s, parameters.size()-1,0);// run it
}
catch(lua::exception &e)
{ {
out.printerr("Error:%s\n",e.what()); DFHack::t_memrange newperm=ranges[i];
out.printerr("%s\n",lua::DebugDump(lua::glua::Get()).c_str()); newperm.execute=true;
} DFHack::Core::getInstance().p->setPermisions(ranges[i],newperm);
} return 0;
else
{
Lua::InterpreterLoop(out, s);
}
s.settop(0);// clean up
mymutex->unlock();
return CR_OK;
}
void RunDfusion(color_ostream &out, std::vector <std::string> &parameters)
{
mymutex->lock();
lua::state s=lua::glua::Get();
try{
s.loadfile("dfusion/init.lua"); //load script
for(size_t i=0;i<parameters.size();i++)
s.push(parameters[i]);
Lua::SafeCall(out, s, parameters.size(),0);
} }
catch(lua::exception &e)
{
out.printerr("Error:%s\n",e.what());
out.printerr("%s\n",lua::DebugDump(lua::glua::Get()).c_str());
} }
s.settop(0);// clean up lua_pushlstring(L,"Memory range not found",23);
mymutex->unlock(); lua_error(L);
} return 0;
command_result dfuse(color_ostream &out, std::vector <std::string> &parameters)
{
lua::state s=lua::glua::Get();
s.push(1);
s.setglobal("INIT");
RunDfusion(out,parameters);
return CR_OK;
} }
command_result dfusion (color_ostream &out, std::vector <std::string> &parameters) DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(loadObjectFile),
DFHACK_LUA_COMMAND(markAsExecutable),
DFHACK_LUA_END
};
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{ {
lua::state s=lua::glua::Get();
s.push();
s.setglobal("INIT");
RunDfusion(out,parameters);
return CR_OK; return CR_OK;
} }

@ -105,7 +105,7 @@ public:
void GetText(char *ptr); void GetText(char *ptr);
size_t GetTextSize(); size_t GetTextSize();
void LoadSymbols(); void LoadSymbols();
vSymbol GetSymbols(){LoadSymbols();return symbols;}; const vSymbol& GetSymbols(){LoadSymbols();return symbols;};
void PrintSymbols(); void PrintSymbols();
void PrintRelocations(); void PrintRelocations();
protected: protected:

@ -1,26 +0,0 @@
#ifndef FUNCTIONCALL__H
#define FUNCTIONCALL__H
#include <vector>
using std::vector;
using std::size_t;
class FunctionCaller
{
public:
enum callconv
{
STD_CALL, //__stdcall - all in stack
FAST_CALL, //__fastcall - as much in registers as fits
THIS_CALL, //__thiscall - eax ptr to this, rest in stack
CDECL_CALL //__cdecl - same as stdcall but no stack realign
};
FunctionCaller(size_t base):base_(base){};
int CallFunction(size_t func_ptr,callconv conv,const vector<int> &arguments);
private:
int CallF(size_t count,callconv conv,void* f,const vector<int> &arguments);
size_t base_;
};
#endif //FUNCTIONCALL__H

@ -1,14 +0,0 @@
#ifndef LUA_FUNCTIONCALL__H
#define LUA_FUNCTIONCALL__H
#include "luamain.h"
#include "functioncall.h"
namespace lua
{
void RegisterFunctionCall(lua::state &st);
}
#endif

@ -7,7 +7,6 @@
#include <MemAccess.h> #include <MemAccess.h>
#include "luamain.h" #include "luamain.h"
#include "OutFile.h" #include "OutFile.h"
#include "functioncall.h"
#include "LuaTools.h" #include "LuaTools.h"
namespace lua namespace lua

@ -1,13 +0,0 @@
#ifndef LUA_OFFSETS_H
#define LUA_OFFSETS_H
#include "luamain.h"
namespace lua
{
void RegisterEngine(lua::state &st);
}
#endif

@ -1,12 +0,0 @@
#ifndef LUA_VERSIONINFO_H
#define LUA_VERSIONINFO_H
#include "Core.h"
#include <VersionInfo.h>
#include "luamain.h"
namespace lua
{
void RegisterVersionInfo(lua::state &st);
}
#endif

@ -1,133 +0,0 @@
adv_tools= {}
adv_tools.menu=MakeMenu()
--TODO make every tool generic (work for both modes)
function adv_tools.reincarnate(swap_soul) --only for adventurer i guess
if swap_soul==nil then
swap_soul=true
end
local adv=df.global.world.units.active[0]
if adv.flags1.dead==false then
error("You are not dead (yet)!")
end
local hist_fig=getNemesis(adv).figure
if hist_fig==nil then
error("No historical figure for adventurer...")
end
local events=df.global.world.history.events
local trg_hist_fig
for i=#events-1,0,-1 do -- reverse search because almost always it will be last entry
if df.history_event_hist_figure_diedst:is_instance(events[i]) then
--print("is instance:"..i)
if events[i].hfid==hist_fig.id then
--print("Is same id:"..i)
trg_hist_fig=events[i].slayer
if trg_hist_fig then
trg_hist_fig=df.historical_figure.find(trg_hist_fig)
end
break
end
end
end
if trg_hist_fig ==nil then
error("Slayer not found")
end
local trg_unit=trg_hist_fig.unit_id
if trg_unit==nil then
error("Unit id not found!")
end
local trg_unit_final=df.unit.find(trg_unit)
tools.change_adv(trg_unit_final)
if swap_soul then --actually add a soul...
t_soul=adv.status.current_soul
adv.status.current_soul=df.NULL
adv.status.souls:resize(0)
trg_unit_final.status.current_soul=t_soul
trg_unit_final.status.souls:insert(#trg_unit_final.status.souls,t_soul)
end
end
adv_tools.menu:add("Reincarnate",adv_tools.reincarnate)
function adv_tools.ressurect()
v2=engine.peek(vector:getval(indx),ptr_Creature.hurt1)
for i=0,v2:size()-1 do
v2:setval(i,0)
end
v2=engine.peek(vector:getval(indx),ptr_Creature.hurt2)
v2.type=DWORD
for i=0,v2:size()-1 do
v2:setval(i,0)
end
engine.poke(vector:getval(indx),ptr_Creature.bloodlvl,60000) --give blood
engine.poke(vector:getval(indx),ptr_Creature.bleedlvl,0) --stop some bleeding...
local flg=engine.peek(vector:getval(indx),ptr_Creature.flags)
flg:set(1,false) --ALIVE
flg:set(39,false) -- leave body yet again
flg:set(37,false) -- something todo with wounds- lets you walk again.
flg:set(58,true) -- makes them able to breathe
flg:set(61,true) -- gives them sight
engine.poke(vector:getval(indx),ptr_Creature.flags,flg)
end
function adv_tools.wagonmode() --by rumrusher
--first three lines same as before (because we will need an offset of creature at location x,y,z)
myoff=offsets.getEx("AdvCreatureVec")
vector=engine.peek(myoff,ptr_vector)
indx=GetCreatureAtPos(getxyz())
--indx=0
--print(string.format("%x",vector:getval(indx)))
flg=engine.peek(vector:getval(indx),ptr_Creature.flags) --get flags
flg:set(1,false)
flg:set(74,false)
engine.poke(vector:getval(indx),ptr_Creature.flags,flg)
print("To stay normal press y, else hit Enter turn Wagon mode on.")
r=io.stdin:read() -- repeat for it too work... also creature will be dead.
if r== "y" then
flg=engine.peek(vector:getval(indx),ptr_Creature.flags)
flg:set(1,false)
engine.poke(vector:getval(indx),ptr_Creature.flags,flg)
else
flg=engine.peek(vector:getval(indx),ptr_Creature.flags)
flg:set(1,false)
flg:flip(74)
engine.poke(vector:getval(indx),ptr_Creature.flags,flg)
end
end
function selectall()
local retvec={} --return vector (or a list)
myoff=offsets.getEx("AdvCreatureVec")
vector=engine.peek(myoff,ptr_vector) --standart start
for i=0,vector:size()-1 do --check all creatures
local off
off=vector:getval(i)
local flags=engine.peek(off,ptr_Creature.flags)
if flags:get(1)==true then --if dead ...
table.insert(retvec,off)--... add it to return vector
end
end
return retvec --return the "return vector" :)
end
function adv_tools.hostilate()
vector=engine.peek(offsets.getEx("AdvCreatureVec"),ptr_vector)
id=GetCreatureAtPos(getxyz())
print(string.format("Vec:%d cr:%d",vector:size(),id))
off=vector:getval(id)
crciv=engine.peek(vector:getval(id),ptr_Creature.civ)
curciv=engine.peek(vector:getval(0),ptr_Creature.civ)
if curciv==crciv then
print("Friendly-making enemy")
engine.poke(off,ptr_Creature.civ,-1)
flg=engine.peek(off,ptr_Creature.flags)
flg:set(17,true)
engine.poke(off,ptr_Creature.flags,flg)
else
print("Enemy- making friendly")
engine.poke(off,ptr_Creature.civ,curciv)
flg=engine.peek(off,ptr_Creature.flags)
flg:set(17,false)
flg:set(19,false)
engine.poke(off,ptr_Creature.flags,flg)
end
end

@ -1,3 +0,0 @@
if not(FILE) then
adv_tools.menu:display()
end

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