develop
Warmist 2012-06-15 13:08:46 +03:00
commit 93662034fe
117 changed files with 12737 additions and 941 deletions

@ -60,10 +60,10 @@ endif()
# set up versioning.
set(DF_VERSION_MAJOR "0")
set(DF_VERSION_MINOR "34")
set(DF_VERSION_PATCH "07")
set(DF_VERSION_PATCH "11")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
SET(DFHACK_RELEASE "r2" CACHE STRING "Current release revision.")
SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.")
set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")
@ -117,7 +117,9 @@ ENDIF()
ADD_DEFINITIONS(-DPROTOBUF_USE_DLLS)
ADD_DEFINITIONS(-DLUA_BUILD_AS_DLL)
if(UNIX)
if(APPLE)
add_definitions(-D_DARWIN)
elseif(UNIX)
add_definitions(-D_LINUX)
elseif(WIN32)
add_definitions(-DWIN32)
@ -137,6 +139,9 @@ include_directories(depends/tinyxml)
include_directories(depends/tthread)
include_directories(${ZLIB_INCLUDE_DIRS})
include_directories(depends/clsocket/src)
if(APPLE)
include_directories(${CMAKE_INSTALL_PREFIX}/libs/SDL.framework/Headers)
endif()
add_subdirectory(depends)
@ -155,7 +160,11 @@ endif()
# Packaging with CPack!
IF(UNIX)
SET(CPACK_GENERATOR "TGZ")
if(APPLE)
SET(CPACK_GENERATOR "ZIP")
else()
SET(CPACK_GENERATOR "TGZ")
endif()
ELSEIF(WIN32)
SET(CPACK_GENERATOR "ZIP")
ENDIF()

@ -3,7 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.8.1: http://docutils.sourceforge.net/" />
<meta name="generator" content="Docutils 0.9: http://docutils.sourceforge.net/" />
<title>Building DFHACK</title>
<style type="text/css">

@ -121,6 +121,12 @@ or as a result of calling the ``_field()`` method.
They behave as structs with one field ``value`` of the right type.
To make working with numeric buffers easier, they also allow
numeric indices. Note that other than excluding negative values
no bound checking is performed, since buffer length is not available.
Index 0 is equivalent to the ``value`` field.
Struct references
-----------------
@ -219,12 +225,21 @@ Bitfield references
-------------------
Bitfields behave like special fixed-size containers.
The ``_enum`` property points to the bitfield type.
Consider them to be something in between structs and
fixed-size vectors.
The ``_enum`` property points to the bitfield type.
Numerical indices correspond to the shift value,
and if a subfield occupies multiple bits, the
``ipairs`` order would have a gap.
Since currently there is no API to allocate a bitfield
object fully in GC-managed lua heap, consider using the
lua table assignment feature outlined below in order to
pass bitfield values to dfhack API functions that need
them, e.g. ``matinfo:matches{metal=true}``.
Named types
===========
@ -308,6 +323,24 @@ The ``df`` table itself contains the following functions and values:
Equivalent to the method, but also allows a reference as proxy for its type.
* ``df.new(ptype[,count])``
Allocate a new instance, or an array of built-in types.
The ``ptype`` argument is a string from the following list:
``string``, ``int8_t``, ``uint8_t``, ``int16_t``, ``uint16_t``,
``int32_t``, ``uint32_t``, ``int64_t``, ``uint64_t``, ``bool``,
``float``, ``double``. All of these except ``string`` can be
used with the count argument to allocate an array.
* ``df.reinterpret_cast(type,ptr)``
Converts ptr to a ref of specified type. The type may be anything
acceptable to ``df.is_instance``. Ptr may be *nil*, a ref,
a lightuserdata, or a number.
Returns *nil* if NULL, or a ref.
Recursive table assignment
==========================
@ -458,6 +491,13 @@ Currently it defines the following features:
Compares to coroutine.resume like dfhack.safecall vs pcall.
* ``dfhack.run_script(name[,args...])``
Run a lua script in hack/scripts/, as if it was started from dfhack command-line.
The ``name`` argument should be the name stem, as would be used on the command line.
Note that the script is re-read from the file every time it is called, and errors
are propagated to the caller.
* ``dfhack.with_suspend(f[,args...])``
Calls ``f`` with arguments after grabbing the DF core suspend lock.
@ -598,6 +638,30 @@ One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments
can be omitted.
* ``dfhack.getOSType()``
Returns the OS type string from ``symbols.xml``.
* ``dfhack.getDFVersion()``
Returns the DF version string from ``symbols.xml``.
* ``dfhack.getDFPath()``
Returns the DF directory path.
* ``dfhack.getHackPath()``
Returns the dfhack directory path, i.e. ``".../df/hack/"``.
* ``dfhack.isWorldLoaded()``
Checks if the world is loaded.
* ``dfhack.isMapLoaded()``
Checks if the world and map are loaded.
* ``dfhack.TranslateName(name[,in_english,only_last_name])``
Convert a language_name or only the last name part to string.
@ -605,6 +669,15 @@ can be omitted.
Gui module
----------
* ``dfhack.gui.getCurViewscreen()``
Returns the viewscreen that is current in the core.
* ``dfhack.gui.getFocusString(viewscreen)``
Returns a string representation of the current focus position
in the ui. The string has a "screen/foo/bar/baz..." format.
* ``dfhack.gui.getSelectedWorkshopJob([silent])``
When a job is selected in *'q'* mode, returns the job, else
@ -658,6 +731,14 @@ Job module
Returns the unit performing the job.
* ``dfhack.job.checkBuildingsNow()``
Instructs the game to check buildings for jobs next frame and assign workers.
* ``dfhack.job.checkDesignationsNow()``
Instructs the game to check designations for jobs next frame and assign workers.
* ``dfhack.job.is_equal(job1,job2)``
Compares important fields in the job and nested item structures.
@ -677,7 +758,7 @@ Units module
* ``dfhack.units.getPosition(unit)``
Returns true *x,y,z* of the unit; 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.getContainer(unit)``
@ -701,7 +782,7 @@ Units module
* ``dfhack.units.isDead(unit)``
The unit is completely dead and passive.
The unit is completely dead and passive, or a ghost.
* ``dfhack.units.isAlive(unit)``
@ -709,7 +790,16 @@ Units module
* ``dfhack.units.isSane(unit)``
The unit is capable of rational action, i.e. not dead, insane or zombie.
The unit is capable of rational action, i.e. not dead, insane, zombie, or active werewolf.
* ``dfhack.units.isDwarf(unit)``
The unit is of the correct race of the fortress.
* ``dfhack.units.isCitizen(unit)``
The unit is an alive sane citizen of the fortress; wraps the
same checks the game uses to decide game-over by extinction.
* ``dfhack.units.getAge(unit[,true_age])``
@ -736,7 +826,20 @@ Items module
* ``dfhack.items.getPosition(item)``
Returns true *x,y,z* of the item; may be not equal to item.pos if in inventory.
Returns true *x,y,z* of the item, or *nil* if invalid; may be not equal to item.pos if in inventory.
* ``dfhack.items.getDescription(item, type[, decorate])``
Returns the string description of the item, as produced by the getItemDescription
method. If decorate is true, also adds markings for quality and improvements.
* ``dfhack.items.getGeneralRef(item, type)``
Searches for a general_ref with the given type.
* ``dfhack.items.getSpecificRef(item, type)``
Searches for a specific_ref with the given type.
* ``dfhack.items.getOwner(item)``
@ -763,6 +866,14 @@ Items module
Move the item to the container. Returns *false* if impossible.
* ``dfhack.items.moveToBuilding(item,building,use_mode)``
Move the item to the building. Returns *false* if impossible.
* ``dfhack.items.moveToInventory(item,unit,use_mode,body_part)``
Move the item to the unit inventory. Returns *false* if impossible.
Maps module
-----------
@ -779,14 +890,18 @@ Maps module
Returns a map block object for given x,y,z in local block coordinates.
* ``dfhack.maps.getTileBlock(coords)``
* ``dfhack.maps.getTileBlock(coords)``, or ``getTileBlock(x,y,z)``
Returns a map block object for given df::coord in local tile coordinates.
Returns a map block object for given df::coord or x,y,z in local tile coordinates.
* ``dfhack.maps.getRegionBiome(region_coord2d)``
* ``dfhack.maps.getRegionBiome(region_coord2d)``, or ``getRegionBiome(x,y)``
Returns the biome info struct for the given global map region.
* ``dfhack.maps.enableBlockUpdates(block[,flow,temperature])``
Enables updates for liquid flow or temperature, unless already active.
* ``dfhack.maps.getGlobalInitFeature(index)``
Returns the global feature object with the given index.
@ -795,6 +910,10 @@ Maps module
Returns the local feature object with the given region coords and index.
* ``dfhack.maps.getTileBiomeRgn(coords)``, or ``getTileBiomeRgn(x,y,z)``
Returns *x, y* for use with ``getRegionBiome``.
* ``dfhack.maps.canWalkBetween(pos1, pos2)``
Checks if a dwarf may be able to walk between the two tiles,
@ -850,6 +969,214 @@ Burrows module
Adds or removes the tile from the burrow. Returns *false* if invalid coords.
Buildings module
----------------
* ``dfhack.buildings.getSize(building)``
Returns *width, height, centerx, centery*.
* ``dfhack.buildings.findAtTile(pos)``, or ``findAtTile(x,y,z)``
Scans the buildings for the one located at the given tile.
Does not work on civzones. Warning: linear scan if the map
tile indicates there are buildings at it.
* ``dfhack.buildings.findCivzonesAt(pos)``, or ``findCivzonesAt(x,y,z)``
Scans civzones, and returns a lua sequence of those that touch
the given tile, or *nil* if none.
* ``dfhack.buildings.getCorrectSize(width, height, type, subtype, custom, direction)``
Computes correct dimensions for the specified building type and orientation,
using width and height for flexible dimensions.
Returns *is_flexible, width, height, center_x, center_y*.
* ``dfhack.buildings.checkFreeTiles(pos,size[,extents,change_extents,allow_occupied])``
Checks if the rectangle defined by ``pos`` and ``size``, and possibly extents,
can be used for placing a building. If ``change_extents`` is true, bad tiles
are removed from extents. If ``allow_occupied``, the occupancy test is skipped.
* ``dfhack.buildings.countExtentTiles(extents,defval)``
Returns the number of tiles included by extents, or defval.
* ``dfhack.buildings.containsTile(building, x, y[, room])``
Checks if the building contains the specified tile, either directly, or as room.
* ``dfhack.buildings.hasSupport(pos,size)``
Checks if a bridge constructed at specified position would have
support from terrain, and thus won't collapse if retracted.
Low-level building creation functions;
* ``dfhack.buildings.allocInstance(pos, type, subtype, custom)``
Creates a new building instance of given type, subtype and custom type,
at specified position. Returns the object, or *nil* in case of an error.
* ``dfhack.buildings.setSize(building, width, height, direction)``
Configures an object returned by ``allocInstance``, using specified
parameters wherever appropriate. If the building has fixed size along
any dimension, the corresponding input parameter will be ignored.
Returns *false* if the building cannot be placed, or *true, width,
height, rect_area, true_area*. Returned width and height are the
final values used by the building; true_area is less than rect_area
if any tiles were removed from designation.
* ``dfhack.buildings.constructAbstract(building)``
Links a fully configured object created by ``allocInstance`` into the
world. The object must be an abstract building, i.e. a stockpile or civzone.
Returns *true*, or *false* if impossible.
* ``dfhack.buildings.constructWithItems(building, items)``
Links a fully configured object created by ``allocInstance`` into the
world for construction, using a list of specific items as material.
Returns *true*, or *false* if impossible.
* ``dfhack.buildings.constructWithFilters(building, job_items)``
Links a fully configured object created by ``allocInstance`` into the
world for construction, using a list of job_item filters as inputs.
Returns *true*, or *false* if impossible. Filter objects are claimed
and possibly destroyed in any case.
Use a negative ``quantity`` field value to auto-compute the amount
from the size of the building.
* ``dfhack.buildings.deconstruct(building)``
Destroys the building, or queues a deconstruction job.
Returns *true* if the building was destroyed and deallocated immediately.
More high-level functions are implemented in lua and can be loaded by
``require('dfhack.buildings')``. See ``hack/lua/dfhack/buildings.lua``.
Among them are:
* ``dfhack.buildings.getFiltersByType(argtable,type,subtype,custom)``
Returns a sequence of lua structures, describing input item filters
suitable for the specified building type, or *nil* if unknown or invalid.
The returned sequence is suitable for use as the ``job_items`` argument
of ``constructWithFilters``.
Uses tables defined in ``buildings.lua``.
Argtable members ``material`` (the default name), ``bucket``, ``barrel``,
``chain``, ``mechanism``, ``screw``, ``pipe``, ``anvil``, ``weapon`` are used to
augment the basic attributes with more detailed information if the
building has input items with the matching name (see the tables for naming details).
Note that it is impossible to *override* any properties this way, only supply those that
are not mentioned otherwise; one exception is that flags2.non_economic
is automatically cleared if an explicit material is specified.
* ``dfhack.buildings.constructBuilding{...}``
Creates a building in one call, using options contained
in the argument table. Returns the building, or *nil, error*.
**NOTE:** Despite the name, unless the building is abstract,
the function creates it in an 'unconstructed' stage, with
a queued in-game job that will actually construct it. I.e.
the function replicates programmatically what can be done
through the construct building menu in the game ui, except
that it does less environment constraint checking.
The following options can be used:
- ``pos = coordinates``, or ``x = ..., y = ..., z = ...``
Mandatory. Specifies the left upper corner of the building.
- ``type = df.building_type.FOO, subtype = ..., custom = ...``
Mandatory. Specifies the type of the building. Obviously, subtype
and custom are only expected if the type requires them.
- ``fields = { ... }``
Initializes fields of the building object after creation with ``df.assign``.
- ``width = ..., height = ..., direction = ...``
Sets size and orientation of the building. If it is
fixed-size, specified dimensions are ignored.
- ``full_rectangle = true``
For buildings like stockpiles or farm plots that can normally
accomodate individual tile exclusion, forces an error if any
tiles within the specified width*height are obstructed.
- ``items = { item, item ... }``, or ``filters = { {...}, {...}... }``
Specifies explicit items or item filters to use in construction.
It is the job of the user to ensure they are correct for the building type.
- ``abstract = true``
Specifies that the building is abstract and does not require construction.
Required for stockpiles and civzones; an error otherwise.
- ``material = {...}, mechanism = {...}, ...``
If none of ``items``, ``filter``, or ``abstract`` is used,
the function uses ``getFiltersByType`` to compute the input
item filters, and passes the argument table through. If no filters
can be determined this way, ``constructBuilding`` throws an error.
Constructions module
--------------------
* ``dfhack.constructions.designateNew(pos,type,item_type,mat_index)``
Designates a new construction at given position. If there already is
a planned but not completed construction there, changes its type.
Returns *true*, or *false* if obstructed.
Note that designated constructions are technically buildings.
* ``dfhack.constructions.designateRemove(pos)``, or ``designateRemove(x,y,z)``
If there is a construction or a planned construction at the specified
coordinates, designates it for removal, or instantly cancels the planned one.
Returns *true, was_only_planned* if removed; or *false* if none found.
Internal API
------------
These functions are intended for the use by dfhack developers,
and are only documented here for completeness:
* ``dfhack.internal.scripts``
The table used by ``dfhack.run_script()`` to give every script its own
global environment, persistent between calls to the script.
* ``dfhack.internal.getAddress(name)``
Returns the global address ``name``, or *nil*.
* ``dfhack.internal.setAddress(name, value)``
Sets the global address ``name``. Returns the value of ``getAddress`` before the change.
* ``dfhack.internal.getBase()``
Returns the base address of the process.
* ``dfhack.internal.getMemRanges()``
Returns a sequence of tables describing virtual memory ranges of the process.
Core interpreter context
========================
@ -863,6 +1190,25 @@ Core context specific functions:
Boolean value; *true* in the core context.
* ``dfhack.timeout(time,mode,callback)``
Arranges for the callback to be called once the specified
period of time passes. The ``mode`` argument specifies the
unit of time used, and may be one of ``'frames'`` (raw FPS),
``'ticks'`` (unpaused FPS), ``'days'``, ``'months'``,
``'years'`` (in-game time). All timers other than
``'frames'`` are cancelled when the world is unloaded,
and cannot be queued until it is loaded again.
Returns the timer id, or *nil* if unsuccessful due to
world being unloaded.
* ``dfhack.timeout_active(id[,new_callback])``
Returns the active callback with the given id, or *nil*
if inactive or nil id. If called with 2 arguments, replaces
the current callback with the given value, if still active.
Using ``timeout_active(id,nil)`` cancels the timer.
* ``dfhack.onStateChange.foo = function(code)``
Event. Receives the same codes as plugin_onstatechange in C++.
@ -920,7 +1266,7 @@ Events:
Emitted when a burrow might have been renamed either through
the game UI, or ``renameBurrow()``.
* ``onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype)``
* ``onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype,worker)``
Emitted when a tile might have been dug out. Only tracked if the
auto-growing burrows feature is enabled.

@ -3,7 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.8.1: http://docutils.sourceforge.net/" />
<meta name="generator" content="Docutils 0.9: http://docutils.sourceforge.net/" />
<title>DFHack Lua API</title>
<style type="text/css">
@ -343,17 +343,20 @@ ul.auto-toc {
<li><a class="reference internal" href="#items-module" id="id17">Items module</a></li>
<li><a class="reference internal" href="#maps-module" id="id18">Maps module</a></li>
<li><a class="reference internal" href="#burrows-module" id="id19">Burrows module</a></li>
<li><a class="reference internal" href="#buildings-module" id="id20">Buildings module</a></li>
<li><a class="reference internal" href="#constructions-module" id="id21">Constructions module</a></li>
<li><a class="reference internal" href="#internal-api" id="id22">Internal API</a></li>
</ul>
</li>
<li><a class="reference internal" href="#core-interpreter-context" id="id20">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id21">Event type</a></li>
<li><a class="reference internal" href="#core-interpreter-context" id="id23">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id24">Event type</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#plugins" id="id22">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id23">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id24">sort</a></li>
<li><a class="reference internal" href="#plugins" id="id25">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id26">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id27">sort</a></li>
</ul>
</li>
</ul>
@ -450,6 +453,10 @@ that don't fit any of the other reference types. Such
references can only appear as a value of a pointer field,
or as a result of calling the <tt class="docutils literal">_field()</tt> method.</p>
<p>They behave as structs with one field <tt class="docutils literal">value</tt> of the right type.</p>
<p>To make working with numeric buffers easier, they also allow
numeric indices. Note that other than excluding negative values
no bound checking is performed, since buffer length is not available.
Index 0 is equivalent to the <tt class="docutils literal">value</tt> field.</p>
</div>
<div class="section" id="struct-references">
<h3><a class="toc-backref" href="#id4">Struct references</a></h3>
@ -532,10 +539,17 @@ use <tt class="docutils literal">#ref</tt>, or just <tt class="docutils literal"
<div class="section" id="bitfield-references">
<h3><a class="toc-backref" href="#id6">Bitfield references</a></h3>
<p>Bitfields behave like special fixed-size containers.
The <tt class="docutils literal">_enum</tt> property points to the bitfield type.</p>
<p>Numerical indices correspond to the shift value,
Consider them to be something in between structs and
fixed-size vectors.</p>
<p>The <tt class="docutils literal">_enum</tt> property points to the bitfield type.
Numerical indices correspond to the shift value,
and if a subfield occupies multiple bits, the
<tt class="docutils literal">ipairs</tt> order would have a gap.</p>
<p>Since currently there is no API to allocate a bitfield
object fully in GC-managed lua heap, consider using the
lua table assignment feature outlined below in order to
pass bitfield values to dfhack API functions that need
them, e.g. <tt class="docutils literal">matinfo:matches{metal=true}</tt>.</p>
</div>
</div>
<div class="section" id="named-types">
@ -605,6 +619,20 @@ lightuserdata (step is mandatory then).</p>
<li><p class="first"><tt class="docutils literal">df.is_instance(type,obj)</tt></p>
<p>Equivalent to the method, but also allows a reference as proxy for its type.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">df.new(ptype[,count])</span></tt></p>
<p>Allocate a new instance, or an array of built-in types.
The <tt class="docutils literal">ptype</tt> argument is a string from the following list:
<tt class="docutils literal">string</tt>, <tt class="docutils literal">int8_t</tt>, <tt class="docutils literal">uint8_t</tt>, <tt class="docutils literal">int16_t</tt>, <tt class="docutils literal">uint16_t</tt>,
<tt class="docutils literal">int32_t</tt>, <tt class="docutils literal">uint32_t</tt>, <tt class="docutils literal">int64_t</tt>, <tt class="docutils literal">uint64_t</tt>, <tt class="docutils literal">bool</tt>,
<tt class="docutils literal">float</tt>, <tt class="docutils literal">double</tt>. All of these except <tt class="docutils literal">string</tt> can be
used with the count argument to allocate an array.</p>
</li>
<li><p class="first"><tt class="docutils literal">df.reinterpret_cast(type,ptr)</tt></p>
<p>Converts ptr to a ref of specified type. The type may be anything
acceptable to <tt class="docutils literal">df.is_instance</tt>. Ptr may be <em>nil</em>, a ref,
a lightuserdata, or a number.</p>
<p>Returns <em>nil</em> if NULL, or a ref.</p>
</li>
</ul>
</div>
<div class="section" id="recursive-table-assignment">
@ -738,6 +766,12 @@ returning. Intended as a convenience function.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.saferesume(coroutine[,args...])</span></tt></p>
<p>Compares to coroutine.resume like dfhack.safecall vs pcall.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.run_script(name[,args...])</span></tt></p>
<p>Run a lua script in hack/scripts/, as if it was started from dfhack command-line.
The <tt class="docutils literal">name</tt> argument should be the name stem, as would be used on the command line.
Note that the script is re-read from the file every time it is called, and errors
are propagated to the caller.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.with_suspend(f[,args...])</span></tt></p>
<p>Calls <tt class="docutils literal">f</tt> with arguments after grabbing the DF core suspend lock.
Suspending is necessary for accessing a consistent state of DF memory.</p>
@ -853,6 +887,24 @@ One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments
can be omitted.</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.getOSType()</tt></p>
<p>Returns the OS type string from <tt class="docutils literal">symbols.xml</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.getDFVersion()</tt></p>
<p>Returns the DF version string from <tt class="docutils literal">symbols.xml</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.getDFPath()</tt></p>
<p>Returns the DF directory path.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.getHackPath()</tt></p>
<p>Returns the dfhack directory path, i.e. <tt class="docutils literal"><span class="pre">&quot;.../df/hack/&quot;</span></tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.isWorldLoaded()</tt></p>
<p>Checks if the world is loaded.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.isMapLoaded()</tt></p>
<p>Checks if the world and map are loaded.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.TranslateName(name[,in_english,only_last_name])</span></tt></p>
<p>Convert a language_name or only the last name part to string.</p>
</li>
@ -860,6 +912,13 @@ can be omitted.</p>
<div class="section" id="gui-module">
<h3><a class="toc-backref" href="#id14">Gui module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.gui.getCurViewscreen()</tt></p>
<p>Returns the viewscreen that is current in the core.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.gui.getFocusString(viewscreen)</tt></p>
<p>Returns a string representation of the current focus position
in the ui. The string has a &quot;screen/foo/bar/baz...&quot; format.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.getSelectedWorkshopJob([silent])</span></tt></p>
<p>When a job is selected in <em>'q'</em> mode, returns the job, else
prints error unless silent and returns <em>nil</em>.</p>
@ -904,6 +963,12 @@ The is_bright boolean actually seems to invert the brightness.</p>
<li><p class="first"><tt class="docutils literal">dfhack.job.getWorker(job)</tt></p>
<p>Returns the unit performing the job.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.checkBuildingsNow()</tt></p>
<p>Instructs the game to check buildings for jobs next frame and assign workers.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.checkDesignationsNow()</tt></p>
<p>Instructs the game to check designations for jobs next frame and assign workers.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.is_equal(job1,job2)</tt></p>
<p>Compares important fields in the job and nested item structures.</p>
</li>
@ -921,7 +986,7 @@ a lua list containing them.</p>
<h3><a class="toc-backref" href="#id16">Units module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.units.getPosition(unit)</tt></p>
<p>Returns true <em>x,y,z</em> of the unit; 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><p class="first"><tt class="docutils literal">dfhack.units.getContainer(unit)</tt></p>
<p>Returns the container (cage) item or <em>nil</em>.</p>
@ -939,13 +1004,20 @@ a lua list containing them.</p>
<p>Returns the nemesis record of the unit if it has one, or <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isDead(unit)</tt></p>
<p>The unit is completely dead and passive.</p>
<p>The unit is completely dead and passive, or a ghost.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isAlive(unit)</tt></p>
<p>The unit isn't dead or undead.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isSane(unit)</tt></p>
<p>The unit is capable of rational action, i.e. not dead, insane or zombie.</p>
<p>The unit is capable of rational action, i.e. not dead, insane, zombie, or active werewolf.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isDwarf(unit)</tt></p>
<p>The unit is of the correct race of the fortress.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isCitizen(unit)</tt></p>
<p>The unit is an alive sane citizen of the fortress; wraps the
same checks the game uses to decide game-over by extinction.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.units.getAge(unit[,true_age])</span></tt></p>
<p>Returns the age of the unit in years as a floating-point value.
@ -968,7 +1040,17 @@ or raws. The <tt class="docutils literal">ignore_noble</tt> boolean disables the
<h3><a class="toc-backref" href="#id17">Items module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.items.getPosition(item)</tt></p>
<p>Returns true <em>x,y,z</em> of the item; may be not equal to item.pos if in inventory.</p>
<p>Returns true <em>x,y,z</em> of the item, or <em>nil</em> if invalid; may be not equal to item.pos if in inventory.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getDescription(item, type[, decorate])</tt></p>
<p>Returns the string description of the item, as produced by the getItemDescription
method. If decorate is true, also adds markings for quality and improvements.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getGeneralRef(item, type)</tt></p>
<p>Searches for a general_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getSpecificRef(item, type)</tt></p>
<p>Searches for a specific_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getOwner(item)</tt></p>
<p>Returns the owner unit or <em>nil</em>.</p>
@ -989,6 +1071,12 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.items.moveToContainer(item,container)</tt></p>
<p>Move the item to the container. Returns <em>false</em> if impossible.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.moveToBuilding(item,building,use_mode)</tt></p>
<p>Move the item to the building. Returns <em>false</em> if impossible.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.moveToInventory(item,unit,use_mode,body_part)</tt></p>
<p>Move the item to the unit inventory. Returns <em>false</em> if impossible.</p>
</li>
</ul>
</div>
<div class="section" id="maps-module">
@ -1003,18 +1091,24 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getBlock(x,y,z)</tt></p>
<p>Returns a map block object for given x,y,z in local block coordinates.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBlock(coords)</tt></p>
<p>Returns a map block object for given df::coord in local tile coordinates.</p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBlock(coords)</tt>, or <tt class="docutils literal">getTileBlock(x,y,z)</tt></p>
<p>Returns a map block object for given df::coord or x,y,z in local tile coordinates.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getRegionBiome(region_coord2d)</tt></p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getRegionBiome(region_coord2d)</tt>, or <tt class="docutils literal">getRegionBiome(x,y)</tt></p>
<p>Returns the biome info struct for the given global map region.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.maps.enableBlockUpdates(block[,flow,temperature])</span></tt></p>
<p>Enables updates for liquid flow or temperature, unless already active.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getGlobalInitFeature(index)</tt></p>
<p>Returns the global feature object with the given index.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getLocalInitFeature(region_coord2d,index)</tt></p>
<p>Returns the local feature object with the given region coords and index.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBiomeRgn(coords)</tt>, or <tt class="docutils literal">getTileBiomeRgn(x,y,z)</tt></p>
<p>Returns <em>x, y</em> for use with <tt class="docutils literal">getRegionBiome</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.canWalkBetween(pos1, pos2)</tt></p>
<p>Checks if a dwarf may be able to walk between the two tiles,
using a pathfinding cache maintained by the game. Note that
@ -1061,9 +1155,188 @@ burrows, or the presence of invaders.</p>
</li>
</ul>
</div>
<div class="section" id="buildings-module">
<h3><a class="toc-backref" href="#id20">Buildings module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getSize(building)</tt></p>
<p>Returns <em>width, height, centerx, centery</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.findAtTile(pos)</tt>, or <tt class="docutils literal">findAtTile(x,y,z)</tt></p>
<p>Scans the buildings for the one located at the given tile.
Does not work on civzones. Warning: linear scan if the map
tile indicates there are buildings at it.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.findCivzonesAt(pos)</tt>, or <tt class="docutils literal">findCivzonesAt(x,y,z)</tt></p>
<p>Scans civzones, and returns a lua sequence of those that touch
the given tile, or <em>nil</em> if none.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getCorrectSize(width, height, type, subtype, custom, direction)</tt></p>
<p>Computes correct dimensions for the specified building type and orientation,
using width and height for flexible dimensions.
Returns <em>is_flexible, width, height, center_x, center_y</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.buildings.checkFreeTiles(pos,size[,extents,change_extents,allow_occupied])</span></tt></p>
<p>Checks if the rectangle defined by <tt class="docutils literal">pos</tt> and <tt class="docutils literal">size</tt>, and possibly extents,
can be used for placing a building. If <tt class="docutils literal">change_extents</tt> is true, bad tiles
are removed from extents. If <tt class="docutils literal">allow_occupied</tt>, the occupancy test is skipped.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.countExtentTiles(extents,defval)</tt></p>
<p>Returns the number of tiles included by extents, or defval.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.containsTile(building, x, y[, room])</tt></p>
<p>Checks if the building contains the specified tile, either directly, or as room.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.hasSupport(pos,size)</tt></p>
<p>Checks if a bridge constructed at specified position would have
support from terrain, and thus won't collapse if retracted.</p>
</li>
</ul>
<p>Low-level building creation functions;</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.allocInstance(pos, type, subtype, custom)</tt></p>
<p>Creates a new building instance of given type, subtype and custom type,
at specified position. Returns the object, or <em>nil</em> in case of an error.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.setSize(building, width, height, direction)</tt></p>
<p>Configures an object returned by <tt class="docutils literal">allocInstance</tt>, using specified
parameters wherever appropriate. If the building has fixed size along
any dimension, the corresponding input parameter will be ignored.
Returns <em>false</em> if the building cannot be placed, or <em>true, width,
height, rect_area, true_area</em>. Returned width and height are the
final values used by the building; true_area is less than rect_area
if any tiles were removed from designation.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.constructAbstract(building)</tt></p>
<p>Links a fully configured object created by <tt class="docutils literal">allocInstance</tt> into the
world. The object must be an abstract building, i.e. a stockpile or civzone.
Returns <em>true</em>, or <em>false</em> if impossible.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.constructWithItems(building, items)</tt></p>
<p>Links a fully configured object created by <tt class="docutils literal">allocInstance</tt> into the
world for construction, using a list of specific items as material.
Returns <em>true</em>, or <em>false</em> if impossible.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.constructWithFilters(building, job_items)</tt></p>
<p>Links a fully configured object created by <tt class="docutils literal">allocInstance</tt> into the
world for construction, using a list of job_item filters as inputs.
Returns <em>true</em>, or <em>false</em> if impossible. Filter objects are claimed
and possibly destroyed in any case.
Use a negative <tt class="docutils literal">quantity</tt> field value to auto-compute the amount
from the size of the building.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.deconstruct(building)</tt></p>
<p>Destroys the building, or queues a deconstruction job.
Returns <em>true</em> if the building was destroyed and deallocated immediately.</p>
</li>
</ul>
<p>More high-level functions are implemented in lua and can be loaded by
<tt class="docutils literal"><span class="pre">require('dfhack.buildings')</span></tt>. See <tt class="docutils literal">hack/lua/dfhack/buildings.lua</tt>.</p>
<p>Among them are:</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getFiltersByType(argtable,type,subtype,custom)</tt></p>
<p>Returns a sequence of lua structures, describing input item filters
suitable for the specified building type, or <em>nil</em> if unknown or invalid.
The returned sequence is suitable for use as the <tt class="docutils literal">job_items</tt> argument
of <tt class="docutils literal">constructWithFilters</tt>.
Uses tables defined in <tt class="docutils literal">buildings.lua</tt>.</p>
<p>Argtable members <tt class="docutils literal">material</tt> (the default name), <tt class="docutils literal">bucket</tt>, <tt class="docutils literal">barrel</tt>,
<tt class="docutils literal">chain</tt>, <tt class="docutils literal">mechanism</tt>, <tt class="docutils literal">screw</tt>, <tt class="docutils literal">pipe</tt>, <tt class="docutils literal">anvil</tt>, <tt class="docutils literal">weapon</tt> are used to
augment the basic attributes with more detailed information if the
building has input items with the matching name (see the tables for naming details).
Note that it is impossible to <em>override</em> any properties this way, only supply those that
are not mentioned otherwise; one exception is that flags2.non_economic
is automatically cleared if an explicit material is specified.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.buildings.constructBuilding{...}</span></tt></p>
<p>Creates a building in one call, using options contained
in the argument table. Returns the building, or <em>nil, error</em>.</p>
<p><strong>NOTE:</strong> Despite the name, unless the building is abstract,
the function creates it in an 'unconstructed' stage, with
a queued in-game job that will actually construct it. I.e.
the function replicates programmatically what can be done
through the construct building menu in the game ui, except
that it does less environment constraint checking.</p>
<p>The following options can be used:</p>
<ul>
<li><p class="first"><tt class="docutils literal">pos = coordinates</tt>, or <tt class="docutils literal">x = <span class="pre">...,</span> y = <span class="pre">...,</span> z = ...</tt></p>
<p>Mandatory. Specifies the left upper corner of the building.</p>
</li>
<li><p class="first"><tt class="docutils literal">type = df.building_type.FOO, subtype = <span class="pre">...,</span> custom = ...</tt></p>
<p>Mandatory. Specifies the type of the building. Obviously, subtype
and custom are only expected if the type requires them.</p>
</li>
<li><p class="first"><tt class="docutils literal">fields = { ... }</tt></p>
<p>Initializes fields of the building object after creation with <tt class="docutils literal">df.assign</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">width = <span class="pre">...,</span> height = <span class="pre">...,</span> direction = ...</tt></p>
<p>Sets size and orientation of the building. If it is
fixed-size, specified dimensions are ignored.</p>
</li>
<li><p class="first"><tt class="docutils literal">full_rectangle = true</tt></p>
<p>For buildings like stockpiles or farm plots that can normally
accomodate individual tile exclusion, forces an error if any
tiles within the specified width*height are obstructed.</p>
</li>
<li><p class="first"><tt class="docutils literal">items = { item, item ... }</tt>, or <tt class="docutils literal">filters = { <span class="pre">{...},</span> <span class="pre">{...}...</span> }</tt></p>
<p>Specifies explicit items or item filters to use in construction.
It is the job of the user to ensure they are correct for the building type.</p>
</li>
<li><p class="first"><tt class="docutils literal">abstract = true</tt></p>
<p>Specifies that the building is abstract and does not require construction.
Required for stockpiles and civzones; an error otherwise.</p>
</li>
<li><p class="first"><tt class="docutils literal">material = <span class="pre">{...},</span> mechanism = <span class="pre">{...},</span> ...</tt></p>
<p>If none of <tt class="docutils literal">items</tt>, <tt class="docutils literal">filter</tt>, or <tt class="docutils literal">abstract</tt> is used,
the function uses <tt class="docutils literal">getFiltersByType</tt> to compute the input
item filters, and passes the argument table through. If no filters
can be determined this way, <tt class="docutils literal">constructBuilding</tt> throws an error.</p>
</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="constructions-module">
<h3><a class="toc-backref" href="#id21">Constructions module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.constructions.designateNew(pos,type,item_type,mat_index)</tt></p>
<p>Designates a new construction at given position. If there already is
a planned but not completed construction there, changes its type.
Returns <em>true</em>, or <em>false</em> if obstructed.
Note that designated constructions are technically buildings.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.constructions.designateRemove(pos)</tt>, or <tt class="docutils literal">designateRemove(x,y,z)</tt></p>
<p>If there is a construction or a planned construction at the specified
coordinates, designates it for removal, or instantly cancels the planned one.
Returns <em>true, was_only_planned</em> if removed; or <em>false</em> if none found.</p>
</li>
</ul>
</div>
<div class="section" id="internal-api">
<h3><a class="toc-backref" href="#id22">Internal API</a></h3>
<p>These functions are intended for the use by dfhack developers,
and are only documented here for completeness:</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.internal.scripts</tt></p>
<p>The table used by <tt class="docutils literal">dfhack.run_script()</tt> to give every script its own
global environment, persistent between calls to the script.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getAddress(name)</tt></p>
<p>Returns the global address <tt class="docutils literal">name</tt>, or <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.setAddress(name, value)</tt></p>
<p>Sets the global address <tt class="docutils literal">name</tt>. Returns the value of <tt class="docutils literal">getAddress</tt> before the change.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getBase()</tt></p>
<p>Returns the base address of the process.</p>
</li>
<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>
</li>
</ul>
</div>
</div>
<div class="section" id="core-interpreter-context">
<h2><a class="toc-backref" href="#id20">Core interpreter context</a></h2>
<h2><a class="toc-backref" href="#id23">Core interpreter context</a></h2>
<p>While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.</p>
@ -1072,12 +1345,29 @@ only context that can receive events from DF and plugins.</p>
<li><p class="first"><tt class="docutils literal">dfhack.is_core_context</tt></p>
<p>Boolean value; <em>true</em> in the core context.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.timeout(time,mode,callback)</tt></p>
<p>Arranges for the callback to be called once the specified
period of time passes. The <tt class="docutils literal">mode</tt> argument specifies the
unit of time used, and may be one of <tt class="docutils literal">'frames'</tt> (raw FPS),
<tt class="docutils literal">'ticks'</tt> (unpaused FPS), <tt class="docutils literal">'days'</tt>, <tt class="docutils literal">'months'</tt>,
<tt class="docutils literal">'years'</tt> (in-game time). All timers other than
<tt class="docutils literal">'frames'</tt> are cancelled when the world is unloaded,
and cannot be queued until it is loaded again.
Returns the timer id, or <em>nil</em> if unsuccessful due to
world being unloaded.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.timeout_active(id[,new_callback])</span></tt></p>
<p>Returns the active callback with the given id, or <em>nil</em>
if inactive or nil id. If called with 2 arguments, replaces
the current callback with the given value, if still active.
Using <tt class="docutils literal">timeout_active(id,nil)</tt> cancels the timer.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.onStateChange.foo = function(code)</tt></p>
<p>Event. Receives the same codes as plugin_onstatechange in C++.</p>
</li>
</ul>
<div class="section" id="event-type">
<h3><a class="toc-backref" href="#id21">Event type</a></h3>
<h3><a class="toc-backref" href="#id24">Event type</a></h3>
<p>An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
@ -1103,14 +1393,14 @@ order using <tt class="docutils literal">dfhack.safecall</tt>.</p>
</div>
</div>
<div class="section" id="plugins">
<h1><a class="toc-backref" href="#id22">Plugins</a></h1>
<h1><a class="toc-backref" href="#id25">Plugins</a></h1>
<p>DFHack plugins may export native functions and events
to lua contexts. They are automatically imported by
<tt class="docutils literal"><span class="pre">mkmodule('plugins.&lt;name&gt;')</span></tt>; this means that a lua
module file is still necessary for <tt class="docutils literal">require</tt> to read.</p>
<p>The following plugins have lua support.</p>
<div class="section" id="burrows">
<h2><a class="toc-backref" href="#id23">burrows</a></h2>
<h2><a class="toc-backref" href="#id26">burrows</a></h2>
<p>Implements extended burrow manipulations.</p>
<p>Events:</p>
<ul>
@ -1118,7 +1408,7 @@ module file is still necessary for <tt class="docutils literal">require</tt> to
<p>Emitted when a burrow might have been renamed either through
the game UI, or <tt class="docutils literal">renameBurrow()</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype)</tt></p>
<li><p class="first"><tt class="docutils literal">onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype,worker)</tt></p>
<p>Emitted when a tile might have been dug out. Only tracked if the
auto-growing burrows feature is enabled.</p>
</li>
@ -1148,7 +1438,7 @@ set is the same as used by the command line.</p>
<p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p>
</div>
<div class="section" id="sort">
<h2><a class="toc-backref" href="#id24">sort</a></h2>
<h2><a class="toc-backref" href="#id27">sort</a></h2>
<p>Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.</p>
</div>

@ -27,7 +27,7 @@ Compatibility
DFHack works on Windows XP, Vista, 7 or any modern Linux distribution.
OSX is not supported due to lack of developers with a Mac.
Currently, only versions 0.34.06 and 0.34.07 are supported. If you need DFHack
Currently, versions 0.34.08 - 0.34.11 are supported. If you need DFHack
for older versions, look for older releases.
On Windows, you have to use the SDL version of DF.
@ -635,6 +635,12 @@ produce undesirable results. There are a few good ones though.
You switch to the arena game mode, *assume control of a creature* and then
switch to adventure game mode(1).
You just lost a fortress and gained an adventurer.
You could also do this.
You are in fort game mode, managing your fortress and paused at the esc menu.
You switch to the adventure game mode, then use Dfusion to *assume control of a creature* and then
save or retire.
You just created a returnable mountain home and gained an adventurer.
I take no responsibility of anything that happens as a result of using this tool
@ -825,7 +831,7 @@ Or this:
This will hide previously revealed tiles (or show hidden with the 0 option).
Any paint or filter option can be disabled entirely by using the ANY keyword:
Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword:
::
@ -833,6 +839,7 @@ Any paint or filter option can be disabled entirely by using the ANY keyword:
paint shape ANY
filter material any
filter shape any
filter any
You can use several different brushes for painting tiles:
* Point. (point)

@ -3,7 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.8.1: http://docutils.sourceforge.net/" />
<meta name="generator" content="Docutils 0.9: http://docutils.sourceforge.net/" />
<title></title>
<style type="text/css">
@ -541,7 +541,7 @@ binaries at <a class="reference external" href="http://github.com/peterix/dfhac
<h1><a class="toc-backref" href="#id36">Compatibility</a></h1>
<p>DFHack works on Windows XP, Vista, 7 or any modern Linux distribution.
OSX is not supported due to lack of developers with a Mac.</p>
<p>Currently, only versions 0.34.06 and 0.34.07 are supported. If you need DFHack
<p>Currently, versions 0.34.08 - 0.34.11 are supported. If you need DFHack
for older versions, look for older releases.</p>
<p>On Windows, you have to use the SDL version of DF.</p>
<p>It is possible to use the Windows DFHack under wine/OSX.</p>
@ -1569,12 +1569,13 @@ paint hidden 1
paint hidden 0
</pre>
<p>This will hide previously revealed tiles (or show hidden with the 0 option).</p>
<p>Any paint or filter option can be disabled entirely by using the ANY keyword:</p>
<p>Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword:</p>
<pre class="literal-block">
paint hidden ANY
paint shape ANY
filter material any
filter shape any
filter any
</pre>
<dl class="docutils">
<dt>You can use several different brushes for painting tiles:</dt>

@ -1 +1 @@
Subproject commit 49fa800615a4e5c872164bcb4122030d2ebda9cf
Subproject commit c85e9fb35d3510c5dcc367056cda3237d77a7add

@ -200,7 +200,9 @@ google/protobuf/compiler/zip_writer.cc
LIST(APPEND LIBPROTOBUF_FULL_SRCS ${LIBPROTOBUF_LITE_SRCS})
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -Wno-sign-compare")
IF(CMAKE_COMPILER_IS_GNUCC)
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -Wno-sign-compare")
ENDIF()
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
SET(PROTOBUF_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR})

@ -13,6 +13,9 @@ keybinding add Ctrl-Shift-K autodump-destroy-here
# any item:
keybinding add Ctrl-K autodump-destroy-item
# quicksave, only in main dwarfmode screen and menu page
keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave
##############################
# Generic adv mode bindings #
##############################

@ -36,9 +36,6 @@ include/MiscUtils.h
include/Module.h
include/Pragma.h
include/MemAccess.h
include/SDL_events.h
include/SDL_keyboard.h
include/SDL_keysym.h
include/TileTypes.h
include/Types.h
include/VersionInfo.h
@ -64,6 +61,7 @@ DataStatics.cpp
DataStaticsCtor.cpp
DataStaticsFields.cpp
MiscUtils.cpp
Types.cpp
PluginManager.cpp
TileTypes.cpp
VersionInfoFactory.cpp
@ -91,6 +89,13 @@ PlugLoad-linux.cpp
Process-linux.cpp
)
SET(MAIN_SOURCES_DARWIN
Console-darwin.cpp
PlugLoad-darwin.cpp
Process-darwin.cpp
Hooks-darwin.cpp
)
SET(MAIN_SOURCES_LINUX_EGGY
Console-linux.cpp
Hooks-egg.cpp
@ -157,6 +162,8 @@ IF(UNIX)
OPTION(BUILD_EGGY "Make DFHack strangely egg-shaped." OFF)
IF(BUILD_EGGY)
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY})
ELSEIF(APPLE)
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN})
ELSE()
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX})
ENDIF()
@ -207,9 +214,10 @@ IF(UNIX)
SET_SOURCE_FILES_PROPERTIES(DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp
PROPERTIES COMPILE_FLAGS "-g0 -O1")
ELSE(WIN32)
SET_SOURCE_FILES_PROPERTIES(DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp
PROPERTIES COMPILE_FLAGS "/O1 /bigobj")
ENDIF()
# Compilation
ADD_DEFINITIONS(-DBUILD_DFHACK_LIB)
@ -222,6 +230,9 @@ ENDIF()
IF(UNIX)
SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
IF(APPLE)
SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ENDIF()
ELSE(WIN32)
#FIXME: do we really need psapi?
SET(PROJECT_LIBS psapi dfhack-tinyxml dfhack-tinythread)
@ -254,6 +265,14 @@ ENDIF()
#effectively disables debug builds...
SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" )
IF(APPLE)
SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework)
TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY})
# TARGET_LINK_LIBRARIES(dfhack /usr/lib/libc++.dylib)
SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0)
SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0)
ENDIF()
TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua ${PROJECT_LIBS})
SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "")
@ -261,11 +280,18 @@ TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket)
TARGET_LINK_LIBRARIES(dfhack-run dfhack-client)
IF(UNIX)
# On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack
if (APPLE)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack
DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run
DESTINATION .)
else()
# On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack
DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run
DESTINATION .)
endif()
ELSE()
if(NOT BUILD_EGGY)
# On windows, copy the renamed SDL so DF can still run.
@ -300,6 +326,9 @@ install(DIRECTORY lua/
DESTINATION ${DFHACK_LUA_DESTINATION}
FILES_MATCHING PATTERN "*.lua")
install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts
DESTINATION ${DFHACK_DATA_DESTINATION})
# Unused for so long that it's not even relevant now...
if(BUILD_DEVEL)
if(WIN32)

@ -0,0 +1,773 @@
/*
https://github.com/peterix/dfhack
A thread-safe logging console with a line editor.
Based on linenoise:
linenoise -- guerrilla line editing library against the idea that a
line editing lib needs to be 20,000 lines of C code.
You can find the latest source code at:
http://github.com/antirez/linenoise
Does a number of crazy assumptions that happen to be true in 99.9999% of
the 2010 UNIX computers around.
------------------------------------------------------------------------
Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
Copyright (c) 2011, Petr Mrázek <peterix@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string.h>
#include <string>
#include <stdarg.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <errno.h>
#include <deque>
// George Vulov for MacOSX
#ifndef __LINUX__
#define TEMP_FAILURE_RETRY(expr) \
({ long int _res; \
do _res = (long int) (expr); \
while (_res == -1L && errno == EINTR); \
_res; })
#endif
#include "Console.h"
#include "Hooks.h"
using namespace DFHack;
#include "tinythread.h"
using namespace tthread;
static int isUnsupportedTerm(void)
{
static const char *unsupported_term[] = {"dumb","cons25",NULL};
char *term = getenv("TERM");
int j;
if (term == NULL) return 0;
for (j = 0; unsupported_term[j]; j++)
if (!strcasecmp(term,unsupported_term[j])) return 1;
return 0;
}
const char * ANSI_CLS = "\033[2J";
const char * ANSI_BLACK = "\033[22;30m";
const char * ANSI_RED = "\033[22;31m";
const char * ANSI_GREEN = "\033[22;32m";
const char * ANSI_BROWN = "\033[22;33m";
const char * ANSI_BLUE = "\033[22;34m";
const char * ANSI_MAGENTA = "\033[22;35m";
const char * ANSI_CYAN = "\033[22;36m";
const char * ANSI_GREY = "\033[22;37m";
const char * ANSI_DARKGREY = "\033[01;30m";
const char * ANSI_LIGHTRED = "\033[01;31m";
const char * ANSI_LIGHTGREEN = "\033[01;32m";
const char * ANSI_YELLOW = "\033[01;33m";
const char * ANSI_LIGHTBLUE = "\033[01;34m";
const char * ANSI_LIGHTMAGENTA = "\033[01;35m";
const char * ANSI_LIGHTCYAN = "\033[01;36m";
const char * ANSI_WHITE = "\033[01;37m";
const char * RESETCOLOR = "\033[0m";
const char * getANSIColor(const int c)
{
switch (c)
{
case -1: return RESETCOLOR; // HACK! :P
case 0 : return ANSI_BLACK;
case 1 : return ANSI_BLUE; // non-ANSI
case 2 : return ANSI_GREEN;
case 3 : return ANSI_CYAN; // non-ANSI
case 4 : return ANSI_RED; // non-ANSI
case 5 : return ANSI_MAGENTA;
case 6 : return ANSI_BROWN;
case 7 : return ANSI_GREY;
case 8 : return ANSI_DARKGREY;
case 9 : return ANSI_LIGHTBLUE; // non-ANSI
case 10: return ANSI_LIGHTGREEN;
case 11: return ANSI_LIGHTCYAN; // non-ANSI;
case 12: return ANSI_LIGHTRED; // non-ANSI;
case 13: return ANSI_LIGHTMAGENTA;
case 14: return ANSI_YELLOW; // non-ANSI
case 15: return ANSI_WHITE;
default: return "";
}
}
namespace DFHack
{
class Private
{
public:
Private()
{
dfout_C = NULL;
rawmode = false;
in_batch = false;
supported_terminal = false;
state = con_unclaimed;
};
virtual ~Private()
{
//sync();
}
private:
bool read_char(unsigned char & out)
{
FD_ZERO(&descriptor_set);
FD_SET(STDIN_FILENO, &descriptor_set);
FD_SET(exit_pipe[0], &descriptor_set);
int ret = TEMP_FAILURE_RETRY(
select (FD_SETSIZE,&descriptor_set, NULL, NULL, NULL)
);
if(ret == -1)
return false;
if (FD_ISSET(exit_pipe[0], &descriptor_set))
return false;
if (FD_ISSET(STDIN_FILENO, &descriptor_set))
{
// read byte from stdin
ret = TEMP_FAILURE_RETRY(
read(STDIN_FILENO, &out, 1)
);
if(ret == -1)
return false;
return true;
}
return false;
}
public:
void print(const char *data)
{
fputs(data, dfout_C);
}
void print_text(color_ostream::color_value clr, const std::string &chunk)
{
if(!in_batch && state == con_lineedit)
{
disable_raw();
fprintf(dfout_C,"\x1b[1G");
fprintf(dfout_C,"\x1b[0K");
color(clr);
print(chunk.c_str());
reset_color();
enable_raw();
prompt_refresh();
}
else
{
color(clr);
print(chunk.c_str());
}
}
void begin_batch()
{
assert(!in_batch);
in_batch = true;
if (state == con_lineedit)
{
disable_raw();
fprintf(dfout_C,"\x1b[1G");
fprintf(dfout_C,"\x1b[0K");
}
}
void end_batch()
{
assert(in_batch);
flush();
in_batch = false;
if (state == con_lineedit)
{
reset_color();
enable_raw();
prompt_refresh();
}
}
void flush()
{
if (!rawmode)
fflush(dfout_C);
}
/// Clear the console, along with its scrollback
void clear()
{
if(rawmode)
{
const char * clr = "\033c\033[3J\033[H";
::write(STDIN_FILENO,clr,strlen(clr));
}
else
{
print("\033c\033[3J\033[H");
fflush(dfout_C);
}
}
/// Position cursor at x,y. 1,1 = top left corner
void gotoxy(int x, int y)
{
char tmp[64];
sprintf(tmp,"\033[%d;%dH", y,x);
print(tmp);
}
/// Set color (ANSI color number)
void color(Console::color_value index)
{
if(!rawmode)
fprintf(dfout_C,getANSIColor(index));
else
{
const char * colstr = getANSIColor(index);
int lstr = strlen(colstr);
::write(STDIN_FILENO,colstr,lstr);
}
}
/// Reset color to default
void reset_color(void)
{
color(Console::COLOR_RESET);
if(!rawmode)
fflush(dfout_C);
}
/// Enable or disable the caret/cursor
void cursor(bool enable = true)
{
if(enable)
print("\033[?25h");
else
print("\033[?25l");
}
/// Waits given number of milliseconds before continuing.
void msleep(unsigned int msec);
/// get the current number of columns
int get_columns(void)
{
winsize ws;
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 80;
return ws.ws_col;
}
/// get the current number of rows
int get_rows(void)
{
winsize ws;
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 25;
return ws.ws_row;
}
/// beep. maybe?
//void beep (void);
/// A simple line edit (raw mode)
int lineedit(const std::string& prompt, std::string& output, recursive_mutex * lock, CommandHistory & ch)
{
output.clear();
reset_color();
this->prompt = prompt;
if (!supported_terminal)
{
print(prompt.c_str());
fflush(dfout_C);
// FIXME: what do we do here???
//SDL_recursive_mutexV(lock);
std::getline(std::cin, output);
//SDL_recursive_mutexP(lock);
return output.size();
}
else
{
int count;
if (enable_raw() == -1) return 0;
if(state == con_lineedit)
return -1;
state = con_lineedit;
count = prompt_loop(lock,ch);
state = con_unclaimed;
disable_raw();
print("\n");
if(count != -1)
{
output = raw_buffer;
}
return count;
}
}
int enable_raw()
{
struct termios raw;
if (!supported_terminal)
return -1;
if (tcgetattr(STDIN_FILENO,&orig_termios) == -1)
return -1;
raw = orig_termios; //modify the original mode
// input modes: no break, no CR to NL, no parity check, no strip char,
// no start/stop output control.
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
// output modes - disable post processing
raw.c_oflag &= ~(OPOST);
// control modes - set 8 bit chars
raw.c_cflag |= (CS8);
// local modes - choing off, canonical off, no extended functions,
// no signal chars (^Z,^C)
#ifdef CONSOLE_NO_CATCH
raw.c_lflag &= ~( ECHO | ICANON | IEXTEN );
#else
raw.c_lflag &= ~( ECHO | ICANON | IEXTEN | ISIG );
#endif
// control chars - set return condition: min number of bytes and timer.
// We want read to return every single byte, without timeout.
raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0;// 1 byte, no timer
// put terminal in raw mode after flushing
if (tcsetattr(STDIN_FILENO,TCSAFLUSH,&raw) < 0)
return -1;
rawmode = 1;
return 0;
}
void disable_raw()
{
/* Don't even check the return value as it's too late. */
if (rawmode && tcsetattr(STDIN_FILENO,TCSAFLUSH,&orig_termios) != -1)
rawmode = 0;
}
void prompt_refresh()
{
char seq[64];
int cols = get_columns();
int plen = prompt.size();
const char * buf = raw_buffer.c_str();
int len = raw_buffer.size();
int cooked_cursor = raw_cursor;
// Use math! This is silly.
while((plen+cooked_cursor) >= cols)
{
buf++;
len--;
cooked_cursor--;
}
while (plen+len > cols)
{
len--;
}
/* Cursor to left edge */
snprintf(seq,64,"\x1b[1G");
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
/* Write the prompt and the current buffer content */
if (::write(STDIN_FILENO,prompt.c_str(),plen) == -1) return;
if (::write(STDIN_FILENO,buf,len) == -1) return;
/* Erase to right */
snprintf(seq,64,"\x1b[0K");
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
/* Move cursor to original position. */
snprintf(seq,64,"\x1b[1G\x1b[%dC", (int)(cooked_cursor+plen));
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
}
int prompt_loop(recursive_mutex * lock, CommandHistory & history)
{
int fd = STDIN_FILENO;
size_t plen = prompt.size();
int history_index = 0;
raw_buffer.clear();
raw_cursor = 0;
/* The latest history entry is always our current buffer, that
* initially is just an empty string. */
const std::string empty;
history.add(empty);
if (::write(fd,prompt.c_str(),prompt.size()) == -1) return -1;
while(1)
{
unsigned char c;
int isok;
unsigned char seq[2], seq2;
lock->unlock();
if(!read_char(c))
{
lock->lock();
return -2;
}
lock->lock();
/* Only autocomplete when the callback is set. It returns < 0 when
* there was an error reading from fd. Otherwise it will return the
* character that should be handled next. */
if (c == 9)
{
/*
if( completionCallback != NULL) {
c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols);
// Return on errors
if (c < 0) return len;
// Read next character when 0
if (c == 0) continue;
}
else
{
// ignore tab
continue;
}
*/
// just ignore tabs
continue;
}
switch(c)
{
case 13: // enter
history.remove();
return raw_buffer.size();
case 3: // ctrl-c
errno = EAGAIN;
return -1;
case 127: // backspace
case 8: // ctrl-h
if (raw_cursor > 0 && raw_buffer.size() > 0)
{
raw_buffer.erase(raw_cursor-1,1);
raw_cursor--;
prompt_refresh();
}
break;
case 27: // escape sequence
lock->unlock();
if(!read_char(seq[0]) || !read_char(seq[1]))
{
lock->lock();
return -2;
}
lock->lock();
if(seq[0] == '[')
{
if (seq[1] == 'D')
{
left_arrow:
if (raw_cursor > 0)
{
raw_cursor--;
prompt_refresh();
}
}
else if ( seq[1] == 'C')
{
right_arrow:
/* right arrow */
if (size_t(raw_cursor) != raw_buffer.size())
{
raw_cursor++;
prompt_refresh();
}
}
else if (seq[1] == 'A' || seq[1] == 'B')
{
/* up and down arrow: history */
if (history.size() > 1)
{
/* Update the current history entry before to
* overwrite it with tne next one. */
history[history_index] = raw_buffer;
/* Show the new entry */
history_index += (seq[1] == 'A') ? 1 : -1;
if (history_index < 0)
{
history_index = 0;
break;
}
else if (size_t(history_index) >= history.size())
{
history_index = history.size()-1;
break;
}
raw_buffer = history[history_index];
raw_cursor = raw_buffer.size();
prompt_refresh();
}
}
else if(seq[1] == 'H')
{
// home
raw_cursor = 0;
prompt_refresh();
}
else if(seq[1] == 'F')
{
// end
raw_cursor = raw_buffer.size();
prompt_refresh();
}
else if (seq[1] > '0' && seq[1] < '7')
{
// extended escape
lock->unlock();
if(!read_char(seq2))
{
lock->lock();
return -2;
}
lock->lock();
if (seq[1] == '3' && seq2 == '~' )
{
// delete
if (raw_buffer.size() > 0 && size_t(raw_cursor) < raw_buffer.size())
{
raw_buffer.erase(raw_cursor,1);
prompt_refresh();
}
}
}
}
break;
default:
if (raw_buffer.size() == size_t(raw_cursor))
{
raw_buffer.append(1,c);
raw_cursor++;
if (plen+raw_buffer.size() < size_t(get_columns()))
{
/* Avoid a full update of the line in the
* trivial case. */
if (::write(fd,&c,1) == -1) return -1;
}
else
{
prompt_refresh();
}
}
else
{
raw_buffer.insert(raw_cursor,1,c);
raw_cursor++;
prompt_refresh();
}
break;
case 21: // Ctrl+u, delete the whole line.
raw_buffer.clear();
raw_cursor = 0;
prompt_refresh();
break;
case 11: // Ctrl+k, delete from current to end of line.
raw_buffer.erase(raw_cursor);
prompt_refresh();
break;
case 1: // Ctrl+a, go to the start of the line
raw_cursor = 0;
prompt_refresh();
break;
case 5: // ctrl+e, go to the end of the line
raw_cursor = raw_buffer.size();
prompt_refresh();
break;
case 12: // ctrl+l, clear screen
clear();
prompt_refresh();
}
}
return raw_buffer.size();
}
FILE * dfout_C;
bool supported_terminal;
// state variables
bool rawmode; // is raw mode active?
termios orig_termios; // saved/restored by raw mode
// current state
enum console_state
{
con_unclaimed,
con_lineedit
} state;
bool in_batch;
std::string prompt; // current prompt string
std::string raw_buffer; // current raw mode buffer
int raw_cursor; // cursor position in the buffer
// thread exit mechanism
int exit_pipe[2];
fd_set descriptor_set;
};
}
Console::Console()
{
d = 0;
inited = false;
// we can't create the mutex at this time. the SDL functions aren't hooked yet.
wlock = new recursive_mutex();
}
Console::~Console()
{
if(inited)
shutdown();
if(wlock)
delete wlock;
if(d)
delete d;
}
bool Console::init(bool sharing)
{
if(sharing)
{
inited = false;
return false;
}
freopen("stdout.log", "w", stdout);
d = new Private();
// make our own weird streams so our IO isn't redirected
d->dfout_C = fopen("/dev/tty", "w");
std::cin.tie(this);
clear();
d->supported_terminal = !isUnsupportedTerm() && isatty(STDIN_FILENO);
// init the exit mechanism
pipe(d->exit_pipe);
FD_ZERO(&d->descriptor_set);
FD_SET(STDIN_FILENO, &d->descriptor_set);
FD_SET(d->exit_pipe[0], &d->descriptor_set);
inited = true;
return true;
}
bool Console::shutdown(void)
{
if(!d)
return true;
lock_guard <recursive_mutex> g(*wlock);
if(d->rawmode)
d->disable_raw();
d->print("\n");
inited = false;
// kill the thing
close(d->exit_pipe[1]);
return true;
}
void Console::begin_batch()
{
//color_ostream::begin_batch();
wlock->lock();
if (inited)
d->begin_batch();
}
void Console::end_batch()
{
if (inited)
d->end_batch();
wlock->unlock();
}
void Console::flush_proxy()
{
lock_guard <recursive_mutex> g(*wlock);
if (inited)
d->flush();
}
void Console::add_text(color_value color, const std::string &text)
{
lock_guard <recursive_mutex> g(*wlock);
if (inited)
d->print_text(color, text);
}
int Console::get_columns(void)
{
lock_guard <recursive_mutex> g(*wlock);
int ret = -1;
if(inited)
ret = d->get_columns();
return ret;
}
int Console::get_rows(void)
{
lock_guard <recursive_mutex> g(*wlock);
int ret = -1;
if(inited)
ret = d->get_rows();
return ret;
}
void Console::clear()
{
lock_guard <recursive_mutex> g(*wlock);
if(inited)
d->clear();
}
void Console::gotoxy(int x, int y)
{
lock_guard <recursive_mutex> g(*wlock);
if(inited)
d->gotoxy(x,y);
}
void Console::cursor(bool enable)
{
lock_guard <recursive_mutex> g(*wlock);
if(inited)
d->cursor(enable);
}
int Console::lineedit(const std::string & prompt, std::string & output, CommandHistory & ch)
{
lock_guard <recursive_mutex> g(*wlock);
int ret = -2;
if(inited)
ret = d->lineedit(prompt,output,wlock,ch);
return ret;
}
void Console::msleep (unsigned int msec)
{
if (msec > 1000) sleep(msec/1000000);
usleep((msec % 1000000) * 1000);
}

@ -60,6 +60,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <errno.h>
#include <deque>
// George Vulov for MacOSX
#ifndef __LINUX__
#define TEMP_FAILURE_RETRY(expr) \
({ long int _res; \
do _res = (long int) (expr); \
while (_res == -1L && errno == EINTR); \
_res; })
#endif
#include "Console.h"
#include "Hooks.h"
using namespace DFHack;
@ -255,7 +264,7 @@ namespace DFHack
void color(Console::color_value index)
{
if(!rawmode)
fprintf(dfout_C,getANSIColor(index));
fprintf(dfout_C, "%s", getANSIColor(index));
else
{
const char * colstr = getANSIColor(index);
@ -707,6 +716,8 @@ void Console::add_text(color_value color, const std::string &text)
lock_guard <recursive_mutex> g(*wlock);
if (inited)
d->print_text(color, text);
else
fwrite(text.data(), 1, text.size(), stderr);
}
int Console::get_columns(void)

@ -51,6 +51,8 @@ using namespace std;
#include "RemoteServer.h"
#include "LuaTools.h"
#include "MiscUtils.h"
using namespace DFHack;
#include "df/ui.h"
@ -66,15 +68,16 @@ using namespace DFHack;
#include <fstream>
#include "tinythread.h"
#include "SDL_events.h"
using namespace tthread;
using namespace df::enums;
using df::global::init;
using df::global::world;
// FIXME: A lot of code in one file, all doing different things... there's something fishy about it.
static void loadScriptFile(Core *core, PluginManager *plug_mgr, string fname, bool silent);
static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clueless_counter, const string &command);
static bool parseKeySpec(std::string keyspec, int *psym, int *pmod);
static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string *pfocus = NULL);
struct Core::Cond
{
@ -178,21 +181,10 @@ void fHKthread(void * iodata)
{
color_ostream_proxy out(core->getConsole());
vector <string> args;
Core::cheap_tokenise(stuff, args);
if (args.empty()) {
out.printerr("Empty hotkey command.\n");
continue;
}
auto rv = core->runCommand(out, stuff);
string first = args[0];
args.erase(args.begin());
command_result cr = plug_mgr->InvokeCommand(out, first, args);
if(cr == CR_NEEDS_CONSOLE)
{
out.printerr("It isn't possible to run an interactive command outside the console.\n");
}
if (rv == CR_NOT_IMPLEMENTED)
out.printerr("Invalid hotkey command: '%s'\n", stuff.c_str());
}
}
}
@ -212,38 +204,126 @@ struct sortable
};
};
static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clueless_counter, const string &command)
static std::string getLuaHelp(std::string path)
{
Console & con = core->getConsole();
ifstream script(path.c_str());
if (script.good())
{
std::string help;
if (getline(script, help) &&
help.substr(0,3) == "-- ")
return help.substr(3);
}
return "Lua script.";
}
static std::map<string,string> listLuaScripts(std::string path)
{
std::vector<string> files;
getdir(path, files);
std::map<string,string> pset;
for (size_t i = 0; i < files.size(); i++)
{
if (hasEnding(files[i], ".lua"))
{
std::string help = getLuaHelp(path + files[i]);
pset[files[i].substr(0, files[i].size()-4)] = help;
}
}
return pset;
}
static bool fileExists(std::string path)
{
ifstream script(path.c_str());
return script.good();
}
namespace {
struct ScriptArgs {
const string *pcmd;
vector<string> *pargs;
};
}
static bool init_run_script(color_ostream &out, lua_State *state, void *info)
{
auto args = (ScriptArgs*)info;
if (!lua_checkstack(state, args->pargs->size()+10))
return false;
Lua::PushDFHack(state);
lua_getfield(state, -1, "run_script");
lua_remove(state, -2);
lua_pushstring(state, args->pcmd->c_str());
for (size_t i = 0; i < args->pargs->size(); i++)
lua_pushstring(state, (*args->pargs)[i].c_str());
return true;
}
static command_result runLuaScript(color_ostream &out, std::string name, vector<string> &args)
{
ScriptArgs data;
data.pcmd = &name;
data.pargs = &args;
bool ok = Lua::RunCoreQueryLoop(out, Lua::Core::State, init_run_script, &data);
return ok ? CR_OK : CR_FAILURE;
}
command_result Core::runCommand(color_ostream &out, const std::string &command)
{
fprintf(stderr,"Inside runCommand");
fprintf(stderr," with command %s\n",command.c_str());
if (!command.empty())
{
// cut the input into parts
fprintf(stderr,"Command is not empty, tokenizing\n");
vector <string> parts;
Core::cheap_tokenise(command,parts);
fprintf(stderr,"Tokenized, got %d parts\n",parts.size());
if(parts.size() == 0)
{
clueless_counter ++;
return;
}
return CR_NOT_IMPLEMENTED;
string first = parts[0];
fprintf(stderr,"Erasing beginning\n");
parts.erase(parts.begin());
if (first[0] == '#') return;
fprintf(stderr,"I think we're about there\n");
if (first[0] == '#')
return CR_OK;
cerr << "Invoking: " << command << endl;
fprintf(stderr,"Returning with the next recursion\n");
return runCommand(out, first, parts);
}
else
return CR_NOT_IMPLEMENTED;
}
command_result Core::runCommand(color_ostream &con, const std::string &first, vector<string> &parts)
{
if (!first.empty())
{
// let's see what we actually got
if(first=="help" || first == "?" || first == "man")
{
if(!parts.size())
{
con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n"
"Some basic editing capabilities are included (single-line text editing).\n"
"The console also has a command history - you can navigate it with Up and Down keys.\n"
"On Windows, you may have to resize your console window. The appropriate menu is accessible\n"
"by clicking on the program icon in the top bar of the window.\n\n"
"Basic commands:\n"
if (con.is_console())
{
con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n"
"Some basic editing capabilities are included (single-line text editing).\n"
"The console also has a command history - you can navigate it with Up and Down keys.\n"
"On Windows, you may have to resize your console window. The appropriate menu is accessible\n"
"by clicking on the program icon in the top bar of the window.\n\n");
}
con.print("Basic commands:\n"
" help|?|man - This text.\n"
" help COMMAND - Usage help for the given command.\n"
" ls|dir [PLUGIN] - List available commands. Optionally for single plugin.\n"
@ -274,9 +354,16 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
con.reset_color();
if (!pcmd.usage.empty())
con << "Usage:\n" << pcmd.usage << flush;
return;
return CR_OK;
}
}
auto filename = getHackPath() + "scripts/" + parts[0] + ".lua";
if (fileExists(filename))
{
string help = getLuaHelp(filename);
con.print("%s: %s\n", parts[0].c_str(), help.c_str());
return CR_OK;
}
con.printerr("Unknown command: %s\n", parts[0].c_str());
}
else
@ -421,6 +508,13 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
con.print(" %-22s- %s\n",(*iter).name.c_str(), (*iter).description.c_str());
con.reset_color();
}
auto scripts = listLuaScripts(getHackPath() + "scripts/");
if (!scripts.empty())
{
con.print("\nscripts:\n");
for (auto iter = scripts.begin(); iter != scripts.end(); ++iter)
con.print(" %-22s- %s\n", iter->first.c_str(), iter->second.c_str());
}
}
}
else if(first == "plug")
@ -439,10 +533,10 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{
std::string keystr = parts[1];
if (parts[0] == "set")
core->ClearKeyBindings(keystr);
ClearKeyBindings(keystr);
for (int i = parts.size()-1; i >= 2; i--)
{
if (!core->AddKeyBinding(keystr, parts[i])) {
if (!AddKeyBinding(keystr, parts[i])) {
con.printerr("Invalid key spec: %s\n", keystr.c_str());
break;
}
@ -452,7 +546,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{
for (size_t i = 1; i < parts.size(); i++)
{
if (!core->ClearKeyBindings(parts[i])) {
if (!ClearKeyBindings(parts[i])) {
con.printerr("Invalid key spec: %s\n", parts[i].c_str());
break;
}
@ -460,7 +554,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
}
else if (parts.size() == 2 && parts[0] == "list")
{
std::vector<std::string> list = core->ListKeyBindings(parts[1]);
std::vector<std::string> list = ListKeyBindings(parts[1]);
if (list.empty())
con << "No bindings." << endl;
for (size_t i = 0; i < list.size(); i++)
@ -470,21 +564,32 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{
con << "Usage:" << endl
<< " keybinding list <key>" << endl
<< " keybinding clear <key> <key>..." << endl
<< " keybinding set <key> \"cmdline\" \"cmdline\"..." << endl
<< " keybinding add <key> \"cmdline\" \"cmdline\"..." << endl
<< "Later adds, and earlier items within one command have priority." << endl;
<< " keybinding clear <key>[@context]..." << endl
<< " keybinding set <key>[@context] \"cmdline\" \"cmdline\"..." << endl
<< " keybinding add <key>[@context] \"cmdline\" \"cmdline\"..." << endl
<< "Later adds, and earlier items within one command have priority." << endl
<< "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, or F1-F9, or Enter)." << endl
<< "Context may be used to limit the scope of the binding, by" << endl
<< "requiring the current context to have a certain prefix." << endl
<< "Current UI context is: "
<< Gui::getFocusString(Core::getTopViewscreen()) << endl;
}
}
else if(first == "fpause")
{
World * w = core->getWorld();
World * w = getWorld();
w->SetPauseState(true);
con.print("The game was forced to pause!");
con.print("The game was forced to pause!\n");
}
else if(first == "cls")
{
con.clear();
if (con.is_console())
((Console&)con).clear();
else
{
con.printerr("No console to clear.\n");
return CR_NEEDS_CONSOLE;
}
}
else if(first == "die")
{
@ -494,12 +599,13 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{
if(parts.size() == 1)
{
loadScriptFile(core, plug_mgr, parts[0], false);
loadScriptFile(con, parts[0], false);
}
else
{
con << "Usage:" << endl
<< " script <filename>" << endl;
return CR_WRONG_USAGE;
}
}
else
@ -507,35 +613,44 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
command_result res = plug_mgr->InvokeCommand(con, first, parts);
if(res == CR_NOT_IMPLEMENTED)
{
con.printerr("%s is not a recognized command.\n", first.c_str());
clueless_counter ++;
auto filename = getHackPath() + "scripts/" + first + ".lua";
if (fileExists(filename))
res = runLuaScript(con, first, parts);
else
con.printerr("%s is not a recognized command.\n", first.c_str());
}
else if (res == CR_NEEDS_CONSOLE)
con.printerr("%s needs interactive console to work.\n", first.c_str());
return res;
}
return CR_OK;
}
return CR_NOT_IMPLEMENTED;
}
static void loadScriptFile(Core *core, PluginManager *plug_mgr, string fname, bool silent)
bool Core::loadScriptFile(color_ostream &out, string fname, bool silent)
{
if(!silent)
core->getConsole() << "Loading script at " << fname << std::endl;
ifstream script(fname);
out << "Loading script at " << fname << std::endl;
ifstream script(fname.c_str());
if (script.good())
{
int tmp = 0;
string command;
while (getline(script, command))
{
if (!command.empty())
runInteractiveCommand(core, plug_mgr, tmp, command);
runCommand(out, command);
}
return true;
}
else
{
if(!silent)
core->getConsole().printerr("Error loading script\n");
out.printerr("Error loading script\n");
return false;
}
script.close();
}
// A thread function... for the interactive console.
@ -555,7 +670,7 @@ void fIOthread(void * iodata)
return;
}
loadScriptFile(core, plug_mgr, "dfhack.init", true);
core->loadScriptFile(con, "dfhack.init", true);
con.print("DFHack is ready. Have a nice day!\n"
"Type in '?' or 'help' for general help, 'ls' to see all commands.\n");
@ -565,6 +680,7 @@ void fIOthread(void * iodata)
{
string command = "";
int ret = con.lineedit("[DFHack]# ",command, main_history);
fprintf(stderr,"Command: [%s]\n",command.c_str());
if(ret == -2)
{
cerr << "Console is shutting down properly." << endl;
@ -578,11 +694,18 @@ void fIOthread(void * iodata)
else if(ret)
{
// a proper, non-empty command was entered
fprintf(stderr,"Adding command to history\n");
main_history.add(command);
fprintf(stderr,"Saving history\n");
main_history.save("dfhack.history");
}
runInteractiveCommand(core, plug_mgr, clueless_counter, command);
fprintf(stderr,"Running command\n");
auto rv = core->runCommand(con, command);
if (rv == CR_NOT_IMPLEMENTED)
clueless_counter++;
if(clueless_counter == 3)
{
@ -636,6 +759,15 @@ void Core::fatal (std::string output, bool deactivate)
#endif
}
std::string Core::getHackPath()
{
#ifdef LINUX_BUILD
return p->getPath() + "/hack/";
#else
return p->getPath() + "\\hack\\";
#endif
}
bool Core::Init()
{
if(started)
@ -679,14 +811,19 @@ bool Core::Init()
}
cerr << "Version: " << vinfo->getVersion() << endl;
// Init global object pointers
df::global::InitGlobals();
cerr << "Initializing Console.\n";
// init the console.
bool is_text_mode = false;
if(init && init->display.flag.is_set(init_display_flags::TEXT))
{
is_text_mode = true;
con.init(true);
cerr << "Console is not available. Use dfhack-run to send commands.\n";
}
if(con.init(is_text_mode))
else if(con.init(false))
cerr << "Console is running.\n";
else
fatal ("Console has failed to initialize!\n", false);
@ -701,7 +838,6 @@ bool Core::Init()
*/
// initialize data defs
virtual_identity::Init(this);
df::global::InitGlobals();
// initialize common lua context
Lua::Core::Init(con);
@ -711,12 +847,15 @@ bool Core::Init()
cerr << "Initializing Plugins.\n";
// create plugin manager
plug_mgr = new PluginManager(this);
cerr << "Starting IO thread.\n";
// create IO thread
IODATA *temp = new IODATA;
temp->core = this;
temp->plug_mgr = plug_mgr;
thread * IO = new thread(fIOthread, (void *) temp);
if (!is_text_mode)
{
cerr << "Starting IO thread.\n";
// create IO thread
thread * IO = new thread(fIOthread, (void *) temp);
}
cerr << "Starting DF input capture thread.\n";
// set up hotkey capture
HotkeyMutex = new mutex();
@ -902,7 +1041,7 @@ int Core::Update()
Lua::Core::Reset(out, "DF code execution");
if (first_update)
plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED);
onStateChange(out, SC_CORE_INITIALIZED);
// detect if the game was loaded or unloaded in the meantime
void *new_wdata = NULL;
@ -928,11 +1067,11 @@ int Core::Update()
// and if the world is going away, we report the map change first
if(had_map)
plug_mgr->OnStateChange(out, SC_MAP_UNLOADED);
onStateChange(out, SC_MAP_UNLOADED);
// and if the world is appearing, we report map change after that
plug_mgr->OnStateChange(out, new_wdata ? SC_WORLD_LOADED : SC_WORLD_UNLOADED);
onStateChange(out, new_wdata ? SC_WORLD_LOADED : SC_WORLD_UNLOADED);
if(isMapLoaded())
plug_mgr->OnStateChange(out, SC_MAP_LOADED);
onStateChange(out, SC_MAP_LOADED);
}
// otherwise just check for map change...
else if (new_mapdata != last_local_map_ptr)
@ -943,7 +1082,7 @@ int Core::Update()
if (isMapLoaded() != had_map)
{
getWorld()->ClearPersistentCache();
plug_mgr->OnStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED);
onStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED);
}
}
@ -956,12 +1095,12 @@ int Core::Update()
if (screen != top_viewscreen)
{
top_viewscreen = screen;
plug_mgr->OnStateChange(out, SC_VIEWSCREEN_CHANGED);
onStateChange(out, SC_VIEWSCREEN_CHANGED);
}
}
// notify all the plugins that a game tick is finished
plug_mgr->OnUpdate(out);
// Execute per-frame handlers
onUpdate(out);
// Release the fake suspend lock
{
@ -998,6 +1137,34 @@ int Core::Update()
return 0;
};
extern bool buildings_do_onupdate;
void buildings_onStateChange(color_ostream &out, state_change_event event);
void buildings_onUpdate(color_ostream &out);
static int buildings_timer = 0;
void Core::onUpdate(color_ostream &out)
{
// convert building reagents
if (buildings_do_onupdate && (++buildings_timer & 1))
buildings_onUpdate(out);
// notify all the plugins that a game tick is finished
plug_mgr->OnUpdate(out);
// process timers in lua
Lua::Core::onUpdate(out);
}
void Core::onStateChange(color_ostream &out, state_change_event event)
{
buildings_onStateChange(out, event);
plug_mgr->OnStateChange(out, event);
Lua::Core::onStateChange(out, event);
}
// FIXME: needs to terminate the IO threads and properly dismantle all the machinery involved.
int Core::Shutdown ( void )
{
@ -1056,12 +1223,25 @@ bool Core::ncurses_wgetch(int in, int & out)
return true;
}
int Core::UnicodeAwareSym(const SDL::KeyboardEvent& ke)
int UnicodeAwareSym(const SDL::KeyboardEvent& ke)
{
// Assume keyboard layouts don't change the order of numbers:
if( '0' <= ke.ksym.sym && ke.ksym.sym <= '9') return ke.ksym.sym;
if(SDL::K_F1 <= ke.ksym.sym && ke.ksym.sym <= SDL::K_F12) return ke.ksym.sym;
// These keys are mapped to the same control codes as Ctrl-?
switch (ke.ksym.sym)
{
case SDL::K_RETURN:
case SDL::K_KP_ENTER:
case SDL::K_TAB:
case SDL::K_ESCAPE:
case SDL::K_DELETE:
return ke.ksym.sym;
default:
break;
}
int unicode = ke.ksym.unicode;
// convert Ctrl characters to their 0x40-0x5F counterparts:
@ -1091,16 +1271,17 @@ int Core::UnicodeAwareSym(const SDL::KeyboardEvent& ke)
return unicode;
}
//MEMO: return false if event is consumed
int Core::SDL_Event(SDL::Event* ev)
int Core::DFH_SDL_Event(SDL::Event* ev)
{
// do NOT process events before we are ready.
if(!started) return true;
if(!ev)
return true;
if(ev && ev->type == SDL::ET_KEYDOWN || ev->type == SDL::ET_KEYUP)
if(ev && (ev->type == SDL::ET_KEYDOWN || ev->type == SDL::ET_KEYUP))
{
SDL::KeyboardEvent * ke = (SDL::KeyboardEvent *)ev;
auto ke = (SDL::KeyboardEvent *)ev;
if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym])
{
@ -1143,6 +1324,9 @@ bool Core::SelectHotkey(int sym, int modifiers)
while (screen->child)
screen = screen->child;
if (sym == SDL::K_KP_ENTER)
sym = SDL::K_RETURN;
std::string cmd;
{
@ -1153,6 +1337,9 @@ bool Core::SelectHotkey(int sym, int modifiers)
for (int i = bindings.size()-1; i >= 0; --i) {
if (bindings[i].modifiers != modifiers)
continue;
if (!bindings[i].focus.empty() &&
!prefix_matches(bindings[i].focus, Gui::getFocusString(screen)))
continue;
if (!plug_mgr->CanInvokeHotkey(bindings[i].command[0], screen))
continue;
cmd = bindings[i].cmdline;
@ -1184,10 +1371,22 @@ bool Core::SelectHotkey(int sym, int modifiers)
return false;
}
static bool parseKeySpec(std::string keyspec, int *psym, int *pmod)
static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string *pfocus)
{
*pmod = 0;
if (pfocus)
{
*pfocus = "";
size_t idx = keyspec.find('@');
if (idx != std::string::npos)
{
*pfocus = keyspec.substr(idx+1);
keyspec = keyspec.substr(0, idx);
}
}
// ugh, ugly
for (;;) {
if (keyspec.size() > 6 && keyspec.substr(0, 6) == "Shift-") {
@ -1209,6 +1408,9 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod)
} else if (keyspec.size() == 2 && keyspec[0] == 'F' && keyspec[1] >= '1' && keyspec[1] <= '9') {
*psym = SDL::K_F1 + (keyspec[1]-'1');
return true;
} else if (keyspec == "Enter") {
*psym = SDL::K_RETURN;
return true;
} else
return false;
}
@ -1216,14 +1418,15 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod)
bool Core::ClearKeyBindings(std::string keyspec)
{
int sym, mod;
if (!parseKeySpec(keyspec, &sym, &mod))
std::string focus;
if (!parseKeySpec(keyspec, &sym, &mod, &focus))
return false;
tthread::lock_guard<tthread::mutex> lock(*HotkeyMutex);
std::vector<KeyBinding> &bindings = key_bindings[sym];
for (int i = bindings.size()-1; i >= 0; --i) {
if (bindings[i].modifiers == mod)
if (bindings[i].modifiers == mod && prefix_matches(focus, bindings[i].focus))
bindings.erase(bindings.begin()+i);
}
@ -1234,7 +1437,7 @@ bool Core::AddKeyBinding(std::string keyspec, std::string cmdline)
{
int sym;
KeyBinding binding;
if (!parseKeySpec(keyspec, &sym, &binding.modifiers))
if (!parseKeySpec(keyspec, &sym, &binding.modifiers, &binding.focus))
return false;
cheap_tokenise(cmdline, binding.command);
@ -1247,7 +1450,8 @@ bool Core::AddKeyBinding(std::string keyspec, std::string cmdline)
std::vector<KeyBinding> &bindings = key_bindings[sym];
for (int i = bindings.size()-1; i >= 0; --i) {
if (bindings[i].modifiers == binding.modifiers &&
bindings[i].cmdline == cmdline)
bindings[i].cmdline == cmdline &&
bindings[i].focus == binding.focus)
return true;
}
@ -1268,7 +1472,12 @@ std::vector<std::string> Core::ListKeyBindings(std::string keyspec)
std::vector<KeyBinding> &bindings = key_bindings[sym];
for (int i = bindings.size()-1; i >= 0; --i) {
if (bindings[i].modifiers == mod)
rv.push_back(bindings[i].cmdline);
{
std::string cmd = bindings[i].cmdline;
if (!bindings[i].focus.empty())
cmd = "@" + bindings[i].focus + ": " + cmd;
rv.push_back(cmd);
}
}
return rv;

@ -232,15 +232,26 @@ void virtual_identity::doInit(Core *core)
known[vtable_ptr] = this;
}
virtual_identity *virtual_identity::find(const std::string &name)
{
auto name_it = name_lookup.find(name);
return (name_it != name_lookup.end()) ? name_it->second : NULL;
}
virtual_identity *virtual_identity::get(virtual_ptr instance_ptr)
{
if (!instance_ptr) return NULL;
return find(get_vtable(instance_ptr));
}
virtual_identity *virtual_identity::find(void *vtable)
{
// Actually, a reader/writer lock would be sufficient,
// since the table is only written once per class.
tthread::lock_guard<tthread::mutex> lock(*known_mutex);
void *vtable = get_vtable(instance_ptr);
std::map<void*, virtual_identity*>::iterator it = known.find(vtable);
if (it != known.end())

@ -0,0 +1,163 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2011 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 <stdio.h>
#include <dlfcn.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <map>
/*typedef struct interpose_s
{
void *new_func;
void *orig_func;
} interpose_t;*/
#include "DFHack.h"
#include "Core.h"
#include "Hooks.h"
#include <iostream>
/*static const interpose_t interposers[] __attribute__ ((section("__DATA, __interpose"))) =
{
{ (void *)DFH_SDL_Init, (void *)SDL_Init },
{ (void *)DFH_SDL_PollEvent, (void *)SDL_PollEvent },
{ (void *)DFH_SDL_Quit, (void *)SDL_Quit },
{ (void *)DFH_SDL_NumJoysticks, (void *)SDL_NumJoysticks },
};*/
/*******************************************************************************
* SDL part starts here *
*******************************************************************************/
// hook - called for each game tick (or more often)
DFhackCExport int SDL_NumJoysticks(void)
{
DFHack::Core & c = DFHack::Core::getInstance();
return c.Update();
}
// hook - called at program exit
static void (*_SDL_Quit)(void) = 0;
DFhackCExport void SDL_Quit(void)
{
DFHack::Core & c = DFHack::Core::getInstance();
c.Shutdown();
/*if(_SDL_Quit)
{
_SDL_Quit();
}*/
_SDL_Quit();
}
// called by DF to check input events
static int (*_SDL_PollEvent)(SDL_Event* event) = 0;
DFhackCExport int SDL_PollEvent(SDL_Event* event)
{
pollevent_again:
// if SDL returns 0 here, it means there are no more events. return 0
int orig_return = _SDL_PollEvent(event);
if(!orig_return)
return 0;
// otherwise we have an event to filter
else if( event != 0 )
{
DFHack::Core & c = DFHack::Core::getInstance();
// if we consume the event, ask SDL for more.
if(!c.DFH_SDL_Event(event))
goto pollevent_again;
}
return orig_return;
}
struct WINDOW;
DFhackCExport int wgetch(WINDOW *win)
{
static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch");
if(!_wgetch)
{
exit(EXIT_FAILURE);
}
DFHack::Core & c = DFHack::Core::getInstance();
wgetch_again:
int in = _wgetch(win);
int out;
if(c.ncurses_wgetch(in, out))
{
// not consumed, give to DF
return out;
}
else
{
// consumed, repeat
goto wgetch_again;
}
}
// hook - called at program start, initialize some stuffs we'll use later
static int (*_SDL_Init)(uint32_t flags) = 0;
DFhackCExport int SDL_Init(uint32_t flags)
{
// reroute stderr
fprintf(stderr,"dfhack: attempting to hook in\n");
freopen("stderr.log", "w", stderr);
// we don't reroute stdout until we figure out if this should be done at all
// See: Console-linux.cpp
// find real functions
fprintf(stderr,"dfhack: saving real SDL functions\n");
_SDL_Init = (int (*)( uint32_t )) dlsym(RTLD_NEXT, "SDL_Init");
_SDL_Quit = (void (*)( void )) dlsym(RTLD_NEXT, "SDL_Quit");
_SDL_PollEvent = (int (*)(SDL_Event*))dlsym(RTLD_NEXT,"SDL_PollEvent");
fprintf(stderr,"dfhack: saved real SDL functions\n");
// check if we got them
if(_SDL_Init && _SDL_Quit && _SDL_PollEvent)
{
fprintf(stderr,"dfhack: hooking successful\n");
}
else
{
// bail, this would be a disaster otherwise
fprintf(stderr,"dfhack: something went horribly wrong\n");
exit(1);
}
DFHack::Core & c = DFHack::Core::getInstance();
//c.Init();
int ret = _SDL_Init(flags);
return ret;
}

@ -65,13 +65,13 @@ DFhackCExport int egg_prerender(void)
}
// hook - called for each SDL event, returns 0 when the event has been consumed. 1 otherwise
DFhackCExport int egg_sdl_event(SDL::Event* event)
DFhackCExport int egg_sdl_event(SDL_Event* event)
{
// if the event is valid, intercept
if( event != 0 )
{
DFHack::Core & c = DFHack::Core::getInstance();
return c.SDL_Event(event);
return c.DFH_SDL_Event(event);
}
return true;
}

@ -79,7 +79,7 @@ DFhackCExport int SDL_PollEvent(SDL::Event* event)
{
DFHack::Core & c = DFHack::Core::getInstance();
// if we consume the event, ask SDL for more.
if(!c.SDL_Event(event))
if(!c.DFH_SDL_Event(event))
goto pollevent_again;
}
return orig_return;

@ -431,7 +431,7 @@ DFhackCExport int SDL_PollEvent(SDL::Event* event)
{
DFHack::Core & c = DFHack::Core::getInstance();
// if we consume the event, ask SDL for more.
if(!c.SDL_Event(event))
if(!c.DFH_SDL_Event(event))
goto pollevent_again;
}
return orig_return;

@ -47,6 +47,8 @@ distribution.
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "modules/Burrows.h"
#include "modules/Buildings.h"
#include "modules/Constructions.h"
#include "LuaWrapper.h"
#include "LuaTools.h"
@ -59,6 +61,7 @@ distribution.
#include "df/unit.h"
#include "df/item.h"
#include "df/material.h"
#include "df/viewscreen.h"
#include "df/assumed_identity.h"
#include "df/nemesis_record.h"
#include "df/historical_figure.h"
@ -72,6 +75,7 @@ distribution.
#include "df/dfhack_material_category.h"
#include "df/job_material_category.h"
#include "df/burrow.h"
#include "df/building_civzonest.h"
#include <lua.h>
#include <lauxlib.h>
@ -80,6 +84,8 @@ distribution.
using namespace DFHack;
using namespace DFHack::LuaWrapper;
void dfhack_printerr(lua_State *S, const std::string &str);
void Lua::Push(lua_State *state, const Units::NoblePosition &pos)
{
lua_createtable(state, 0, 3);
@ -91,6 +97,26 @@ void Lua::Push(lua_State *state, const Units::NoblePosition &pos)
lua_setfield(state, -2, "position");
}
void Lua::Push(lua_State *state, df::coord pos)
{
lua_createtable(state, 0, 3);
lua_pushinteger(state, pos.x);
lua_setfield(state, -2, "x");
lua_pushinteger(state, pos.y);
lua_setfield(state, -2, "y");
lua_pushinteger(state, pos.z);
lua_setfield(state, -2, "z");
}
void Lua::Push(lua_State *state, df::coord2d pos)
{
lua_createtable(state, 0, 2);
lua_pushinteger(state, pos.x);
lua_setfield(state, -2, "x");
lua_pushinteger(state, pos.y);
lua_setfield(state, -2, "y");
}
int Lua::PushPosXYZ(lua_State *state, df::coord pos)
{
if (!pos.isValid())
@ -107,6 +133,52 @@ int Lua::PushPosXYZ(lua_State *state, df::coord pos)
}
}
int Lua::PushPosXY(lua_State *state, df::coord2d pos)
{
if (!pos.isValid())
{
lua_pushnil(state);
return 1;
}
else
{
lua_pushinteger(state, pos.x);
lua_pushinteger(state, pos.y);
return 2;
}
}
static df::coord2d CheckCoordXY(lua_State *state, int base, bool vararg = false)
{
df::coord2d p;
if (vararg && lua_gettop(state) <= base)
Lua::CheckDFAssign(state, &p, base);
else
{
p = df::coord2d(
luaL_checkint(state, base),
luaL_checkint(state, base+1)
);
}
return p;
}
static df::coord CheckCoordXYZ(lua_State *state, int base, bool vararg = false)
{
df::coord p;
if (vararg && lua_gettop(state) <= base)
Lua::CheckDFAssign(state, &p, base);
else
{
p = df::coord(
luaL_checkint(state, base),
luaL_checkint(state, base+1),
luaL_checkint(state, base+2)
);
}
return p;
}
/**************************************************
* Per-world persistent configuration storage API *
**************************************************/
@ -518,8 +590,7 @@ static int dfhack_matinfo_matches(lua_State *state)
else if (lua_istable(state, 2))
{
df::dfhack_material_category tmp;
if (!Lua::AssignDFObject(*Lua::GetOutput(state), state, &tmp, 2, false))
lua_error(state);
Lua::CheckDFAssign(state, &tmp, 2, false);
lua_pushboolean(state, info.matches(tmp));
}
else
@ -569,9 +640,41 @@ static void OpenModule(lua_State *state, const char *mname,
#define WRAP(function) { #function, df::wrap_function(function,true) }
#define WRAPN(name, function) { #name, df::wrap_function(function,true) }
/***** Translation module *****/
/***** DFHack module *****/
static std::string getOSType()
{
switch (Core::getInstance().vinfo->getOS())
{
case OS_WINDOWS:
return "windows";
case OS_LINUX:
return "linux";
case OS_APPLE:
return "darwin";
default:
return "unknown";
}
}
static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); }
static std::string getDFPath() { return Core::getInstance().p->getPath(); }
static std::string getHackPath() { return Core::getInstance().getHackPath(); }
static bool isWorldLoaded() { return Core::getInstance().isWorldLoaded(); }
static bool isMapLoaded() { return Core::getInstance().isMapLoaded(); }
static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(getOSType),
WRAP(getDFVersion),
WRAP(getDFPath),
WRAP(getHackPath),
WRAP(isWorldLoaded),
WRAP(isMapLoaded),
WRAPM(Translation, TranslateName),
{ NULL, NULL }
};
@ -579,6 +682,8 @@ static const LuaWrapper::FunctionReg dfhack_module[] = {
/***** Gui module *****/
static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getCurViewscreen),
WRAPM(Gui, getFocusString),
WRAPM(Gui, getSelectedWorkshopJob),
WRAPM(Gui, getSelectedJob),
WRAPM(Gui, getSelectedUnit),
@ -599,6 +704,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,printJobDetails),
WRAPM(Job,getHolder),
WRAPM(Job,getWorker),
WRAPM(Job,checkBuildingsNow),
WRAPM(Job,checkDesignationsNow),
WRAPN(is_equal, jobEqual),
WRAPN(is_item_equal, jobItemEqual),
{ NULL, NULL }
@ -637,6 +744,8 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, isDead),
WRAPM(Units, isAlive),
WRAPM(Units, isSane),
WRAPM(Units, isDwarf),
WRAPM(Units, isCitizen),
WRAPM(Units, getAge),
WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName),
@ -680,12 +789,30 @@ static bool items_moveToContainer(df::item *item, df::item *container)
return Items::moveToContainer(mc, item, container);
}
static bool items_moveToBuilding(df::item *item, df::building_actual *building, int use_mode)
{
MapExtras::MapCache mc;
return Items::moveToBuilding(mc, item, building,use_mode);
}
static bool items_moveToInventory
(df::item *item, df::unit *unit, df::unit_inventory_item::T_mode mode, int body_part)
{
MapExtras::MapCache mc;
return Items::moveToInventory(mc, item, unit, mode, body_part);
}
static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getGeneralRef),
WRAPM(Items, getSpecificRef),
WRAPM(Items, getOwner),
WRAPM(Items, setOwner),
WRAPM(Items, getContainer),
WRAPM(Items, getDescription),
WRAPN(moveToGround, items_moveToGround),
WRAPN(moveToContainer, items_moveToContainer),
WRAPN(moveToBuilding, items_moveToBuilding),
WRAPN(moveToInventory, items_moveToInventory),
{ NULL, NULL }
};
@ -712,15 +839,38 @@ static const luaL_Reg dfhack_items_funcs[] = {
static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock),
WRAPN(getTileBlock, (df::map_block* (*)(df::coord))Maps::getTileBlock),
WRAPM(Maps, getRegionBiome),
WRAPM(Maps, enableBlockUpdates),
WRAPM(Maps, getGlobalInitFeature),
WRAPM(Maps, getLocalInitFeature),
WRAPM(Maps, canWalkBetween),
{ NULL, NULL }
};
static int maps_getTileBlock(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Maps::getTileBlock(pos));
return 1;
}
static int maps_getRegionBiome(lua_State *L)
{
auto pos = CheckCoordXY(L, 1, true);
Lua::PushDFObject(L, Maps::getRegionBiome(pos));
return 1;
}
static int maps_getTileBiomeRgn(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos));
return 1;
}
static const luaL_Reg dfhack_maps_funcs[] = {
{ "getTileBlock", maps_getTileBlock },
{ "getRegionBiome", maps_getRegionBiome },
{ "getTileBiomeRgn", maps_getTileBiomeRgn },
{ NULL, NULL }
};
@ -762,6 +912,198 @@ static const luaL_Reg dfhack_burrows_funcs[] = {
{ NULL, NULL }
};
/***** Buildings module *****/
static bool buildings_containsTile(df::building *bld, int x, int y, bool room) {
return Buildings::containsTile(bld, df::coord2d(x,y), room);
}
static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
WRAPM(Buildings, allocInstance),
WRAPM(Buildings, checkFreeTiles),
WRAPM(Buildings, countExtentTiles),
WRAPN(containsTile, buildings_containsTile),
WRAPM(Buildings, hasSupport),
WRAPM(Buildings, constructAbstract),
WRAPM(Buildings, constructWithItems),
WRAPM(Buildings, constructWithFilters),
WRAPM(Buildings, deconstruct),
{ NULL, NULL }
};
static int buildings_findAtTile(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushDFObject(L, Buildings::findAtTile(pos));
return 1;
}
static int buildings_findCivzonesAt(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
std::vector<df::building_civzonest*> pvec;
if (Buildings::findCivzonesAt(&pvec, pos))
Lua::PushVector(L, pvec);
else
lua_pushnil(L);
return 1;
}
static int buildings_getCorrectSize(lua_State *state)
{
df::coord2d size(luaL_optint(state, 1, 1), luaL_optint(state, 2, 1));
auto t = (df::building_type)luaL_optint(state, 3, -1);
int st = luaL_optint(state, 4, -1);
int cu = luaL_optint(state, 5, -1);
int d = luaL_optint(state, 6, 0);
df::coord2d center;
bool flexible = Buildings::getCorrectSize(size, center, t, st, cu, d);
lua_pushboolean(state, flexible);
lua_pushinteger(state, size.x);
lua_pushinteger(state, size.y);
lua_pushinteger(state, center.x);
lua_pushinteger(state, center.y);
return 5;
}
static int buildings_setSize(lua_State *state)
{
auto bld = Lua::CheckDFObject<df::building>(state, 1);
df::coord2d size(luaL_optint(state, 2, 1), luaL_optint(state, 3, 1));
int dir = luaL_optint(state, 4, 0);
bool ok = Buildings::setSize(bld, size, dir);
lua_pushboolean(state, ok);
if (ok)
{
auto size = Buildings::getSize(bld).second;
int area = size.x*size.y;
lua_pushinteger(state, size.x);
lua_pushinteger(state, size.y);
lua_pushinteger(state, area);
lua_pushinteger(state, Buildings::countExtentTiles(&bld->room, area));
return 5;
}
else
return 1;
}
static const luaL_Reg dfhack_buildings_funcs[] = {
{ "findAtTile", buildings_findAtTile },
{ "findCivzonesAt", buildings_findCivzonesAt },
{ "getCorrectSize", buildings_getCorrectSize },
{ "setSize", buildings_setSize },
{ NULL, NULL }
};
/***** Constructions module *****/
static const LuaWrapper::FunctionReg dfhack_constructions_module[] = {
WRAPM(Constructions, designateNew),
{ NULL, NULL }
};
static int constructions_designateRemove(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
bool imm = false;
lua_pushboolean(L, Constructions::designateRemove(pos, &imm));
lua_pushboolean(L, imm);
return 2;
}
static const luaL_Reg dfhack_constructions_funcs[] = {
{ "designateRemove", constructions_designateRemove },
{ NULL, NULL }
};
/***** Internal module *****/
static uint32_t getBase() { return Core::getInstance().p->getBase(); }
static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
WRAP(getBase),
{ NULL, NULL }
};
static int internal_getAddress(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
uint32_t addr = Core::getInstance().vinfo->getAddress(name);
if (addr)
lua_pushnumber(L, addr);
else
lua_pushnil(L);
return 1;
}
static int internal_setAddress(lua_State *L)
{
std::string name = luaL_checkstring(L, 1);
uint32_t addr = luaL_checkint(L, 2);
internal_getAddress(L);
// Set the address
Core::getInstance().vinfo->setAddress(name, addr);
auto fields = df::global::_identity.getFields();
for (int i = 0; fields && fields[i].mode != struct_field_info::END; ++i)
{
if (fields[i].name != name)
continue;
*(void**)fields[i].offset = (void*)addr;
}
// Print via printerr, so that it is definitely logged to stderr.log.
std::string msg = stl_sprintf("<global-address name='%s' value='0x%x'/>", name.c_str(), addr);
dfhack_printerr(L, msg);
return 1;
}
static int internal_getMemRanges(lua_State *L)
{
std::vector<DFHack::t_memrange> ranges;
Core::getInstance().p->getMemRanges(ranges);
lua_newtable(L);
for(size_t i = 0; i < ranges.size(); i++)
{
lua_newtable(L);
lua_pushnumber(L, (uint32_t)ranges[i].start);
lua_setfield(L, -2, "start_addr");
lua_pushnumber(L, (uint32_t)ranges[i].end);
lua_setfield(L, -2, "end_addr");
lua_pushstring(L, ranges[i].name);
lua_setfield(L, -2, "name");
lua_pushboolean(L, ranges[i].read);
lua_setfield(L, -2, "read");
lua_pushboolean(L, ranges[i].write);
lua_setfield(L, -2, "write");
lua_pushboolean(L, ranges[i].execute);
lua_setfield(L, -2, "execute");
lua_pushboolean(L, ranges[i].shared);
lua_setfield(L, -2, "shared");
lua_pushboolean(L, ranges[i].valid);
lua_setfield(L, -2, "valid");
lua_rawseti(L, -2, i+1);
}
return 1;
}
static const luaL_Reg dfhack_internal_funcs[] = {
{ "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress },
{ "getMemRanges", internal_getMemRanges },
{ NULL, NULL }
};
/************************
* Main Open function *
@ -779,4 +1121,7 @@ void OpenDFHackApi(lua_State *state)
OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs);
OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs);
OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs);
OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs);
OpenModule(state, "constructions", dfhack_constructions_module);
OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs);
}

@ -53,6 +53,7 @@ distribution.
#include "df/building.h"
#include "df/unit.h"
#include "df/item.h"
#include "df/world.h"
#include <lua.h>
#include <lauxlib.h>
@ -65,6 +66,8 @@ using namespace DFHack::LuaWrapper;
lua_State *DFHack::Lua::Core::State = NULL;
void dfhack_printerr(lua_State *S, const std::string &str);
inline void AssertCoreSuspend(lua_State *state)
{
assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended());
@ -84,7 +87,7 @@ void *DFHack::Lua::GetDFObject(lua_State *state, type_identity *type, int val_in
return get_object_internal(state, type, val_index, exact_type, false);
}
void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type)
static void check_valid_ptr_index(lua_State *state, int val_index)
{
if (lua_type(state, val_index) == LUA_TNONE)
{
@ -93,23 +96,45 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_
else
luaL_error(state, "at index %d: pointer expected", val_index);
}
}
if (lua_isnil(state, val_index))
return NULL;
void *rv = get_object_internal(state, type, val_index, exact_type, false);
static void signal_typeid_error(color_ostream *out, lua_State *state,
type_identity *type, const char *msg,
int val_index, bool perr, bool signal)
{
std::string error = stl_sprintf(msg, type->getFullName().c_str());
if (!rv)
if (signal)
{
std::string error = "invalid pointer type";
if (type)
error += "; expected: " + type->getFullName();
if (val_index > 0)
luaL_argerror(state, val_index, error.c_str());
else
luaL_error(state, "at index %d: %s", val_index, error.c_str());
}
else if (perr)
{
if (out)
out->printerr("%s", error.c_str());
else
dfhack_printerr(state, error);
}
else
lua_pushstring(state, error.c_str());
}
void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type)
{
check_valid_ptr_index(state, val_index);
if (lua_isnil(state, val_index))
return NULL;
void *rv = get_object_internal(state, type, val_index, exact_type, false);
if (!rv)
signal_typeid_error(NULL, state, type, "invalid pointer type; expected: %s",
val_index, false, true);
return rv;
}
@ -208,7 +233,7 @@ static int lua_dfhack_println(lua_State *S)
return 0;
}
static void dfhack_printerr(lua_State *S, const std::string &str)
void dfhack_printerr(lua_State *S, const std::string &str)
{
if (color_ostream *out = Lua::GetOutput(S))
out->printerr("%s\n", str.c_str());
@ -343,7 +368,7 @@ static void error_tostring(lua_State *L, bool keep_old = false)
}
}
static void report_error(lua_State *L, color_ostream *out = NULL)
static void report_error(lua_State *L, color_ostream *out = NULL, bool pop = false)
{
error_tostring(L, true);
@ -355,7 +380,7 @@ static void report_error(lua_State *L, color_ostream *out = NULL)
else
dfhack_printerr(L, msg);
lua_pop(L, 1);
lua_pop(L, pop?2:1);
}
static bool convert_to_exception(lua_State *L, int slevel, lua_State *thread = NULL)
@ -544,10 +569,7 @@ bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres
bool ok = lua_pcall(L, nargs, nres, base) == LUA_OK;
if (!ok && perr)
{
report_error(L, &out);
lua_pop(L, 1);
}
report_error(L, &out, true);
lua_remove(L, base);
set_dfhack_output(L, cur_out);
@ -658,10 +680,7 @@ int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, lua_State *thre
int rv = resume_helper(from, thread, nargs, nres);
if (!Lua::IsSuccess(rv) && perr)
{
report_error(from, &out);
lua_pop(from, 1);
}
report_error(from, &out, true);
set_dfhack_output(from, cur_out);
@ -688,6 +707,16 @@ static int DFHACK_DFHACK_TOKEN = 0;
static int DFHACK_BASE_G_TOKEN = 0;
static int DFHACK_REQUIRE_TOKEN = 0;
void Lua::PushDFHack(lua_State *state)
{
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN);
}
void Lua::PushBaseGlobals(lua_State *state)
{
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN);
}
bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *module)
{
AssertCoreSuspend(state);
@ -746,14 +775,64 @@ bool DFHack::Lua::Require(color_ostream &out, lua_State *state,
return true;
}
static bool doAssignDFObject(color_ostream *out, lua_State *state,
type_identity *type, void *target, int val_index,
bool exact, bool perr, bool signal)
{
if (signal)
check_valid_ptr_index(state, val_index);
if (lua_istable(state, val_index))
{
val_index = lua_absindex(state, val_index);
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME);
Lua::PushDFObject(state, type, target);
lua_pushvalue(state, val_index);
if (signal)
{
lua_call(state, 2, 0);
return true;
}
else
return Lua::SafeCall(*out, state, 2, 0, perr);
}
else if (!lua_isuserdata(state, val_index))
{
signal_typeid_error(out, state, type, "pointer to %s expected",
val_index, perr, signal);
return false;
}
else
{
void *in_ptr = Lua::GetDFObject(state, type, val_index, exact);
if (!in_ptr)
{
signal_typeid_error(out, state, type, "incompatible pointer type: %s expected",
val_index, perr, signal);
return false;
}
if (!type->copy(target, in_ptr))
{
signal_typeid_error(out, state, type, "no copy support for %s",
val_index, perr, signal);
return false;
}
return true;
}
}
bool DFHack::Lua::AssignDFObject(color_ostream &out, lua_State *state,
type_identity *type, void *target, int val_index, bool perr)
type_identity *type, void *target, int val_index,
bool exact_type, bool perr)
{
return doAssignDFObject(&out, state, type, target, val_index, exact_type, perr, false);
}
void DFHack::Lua::CheckDFAssign(lua_State *state, type_identity *type,
void *target, int val_index, bool exact_type)
{
val_index = lua_absindex(state, val_index);
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME);
PushDFObject(state, type, target);
lua_pushvalue(state, val_index);
return Lua::SafeCall(out, state, 2, 0, perr);
doAssignDFObject(NULL, state, type, target, val_index, exact_type, false, true);
}
bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std::string &code,
@ -773,10 +852,7 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std
if (luaL_loadbuffer(state, code.data(), code.size(), debug_tag) != LUA_OK)
{
if (perr)
{
report_error(state, &out);
lua_pop(state, 1);
}
report_error(state, &out, true);
return false;
}
@ -819,13 +895,9 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state,
bool (*init)(color_ostream&, lua_State*, void*),
void *arg)
{
if (!out.is_console())
return false;
if (!lua_checkstack(state, 20))
return false;
Console &con = static_cast<Console&>(out);
lua_State *thread;
int rv;
std::string prompt;
@ -845,6 +917,10 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state,
return false;
}
// If not interactive, run without coroutine and bail out
if (!out.is_console())
return SafeCall(out, state, lua_gettop(state)-base-1, 0);
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN);
lua_pushvalue(state, base+1);
lua_remove(state, base+1);
@ -855,6 +931,8 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state,
rv = resume_query_loop(out, thread, state, lua_gettop(state)-base, prompt, histfile);
}
Console &con = static_cast<Console&>(out);
while (rv == LUA_YIELD)
{
if (histfile != histname)
@ -1113,10 +1191,7 @@ static void do_invoke_event(lua_State *L, int argbase, int num_args, int errorfu
lua_pushvalue(L, argbase+i);
if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK)
{
report_error(L);
lua_pop(L, 1);
}
report_error(L, NULL, true);
}
static void dfhack_event_invoke(lua_State *L, int base, bool from_c)
@ -1338,13 +1413,162 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
return state;
}
static int next_timeout_id = 0;
static int frame_idx = 0;
static std::multimap<int,int> frame_timers;
static std::multimap<int,int> tick_timers;
int DFHACK_TIMEOUTS_TOKEN = 0;
static const char *const timeout_modes[] = {
"frames", "ticks", "days", "months", "years", NULL
};
int dfhack_timeout(lua_State *L)
{
using df::global::world;
using df::global::enabler;
// Parse arguments
lua_Number time = luaL_checknumber(L, 1);
int mode = luaL_checkoption(L, 2, NULL, timeout_modes);
luaL_checktype(L, 3, LUA_TFUNCTION);
lua_settop(L, 3);
if (mode > 0 && !Core::getInstance().isWorldLoaded())
{
lua_pushnil(L);
return 1;
}
// Compute timeout value
switch (mode)
{
case 2:
time *= 1200;
break;
case 3:
time *= 33600;
break;
case 4:
time *= 403200;
break;
default:;
}
int delta = time;
if (delta <= 0)
luaL_error(L, "Invalid timeout: %d", delta);
// Queue the timeout
int id = next_timeout_id++;
if (mode)
tick_timers.insert(std::pair<int,int>(world->frame_counter+delta, id));
else
frame_timers.insert(std::pair<int,int>(frame_idx+delta, id));
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
lua_swap(L);
lua_rawseti(L, -2, id);
lua_pushinteger(L, id);
return 1;
}
int dfhack_timeout_active(lua_State *L)
{
int id = luaL_optint(L, 1, -1);
bool set_cb = (lua_gettop(L) >= 2);
lua_settop(L, 2);
if (!lua_isnil(L, 2))
luaL_checktype(L, 2, LUA_TFUNCTION);
if (id < 0)
{
lua_pushnil(L);
return 1;
}
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
lua_rawgeti(L, 3, id);
if (set_cb && !lua_isnil(L, -1))
{
lua_pushvalue(L, 2);
lua_rawseti(L, 3, id);
}
return 1;
}
static void cancel_timers(std::multimap<int,int> &timers)
{
using Lua::Core::State;
Lua::StackUnwinder frame(State);
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
for (auto it = timers.begin(); it != timers.end(); ++it)
{
lua_pushnil(State);
lua_rawseti(State, frame[1], it->second);
}
timers.clear();
}
void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) {
if (!State) return;
switch (code)
{
case SC_MAP_UNLOADED:
case SC_WORLD_UNLOADED:
cancel_timers(tick_timers);
break;
default:;
}
Lua::Push(State, code);
Lua::InvokeEvent(out, State, (void*)onStateChange, 1);
}
static void run_timers(color_ostream &out, lua_State *L,
std::multimap<int,int> &timers, int table, int bound)
{
while (!timers.empty() && timers.begin()->first <= bound)
{
int id = timers.begin()->second;
timers.erase(timers.begin());
lua_rawgeti(L, table, id);
if (lua_isnil(L, -1))
lua_pop(L, 1);
else
{
lua_pushnil(L);
lua_rawseti(L, table, id);
Lua::SafeCall(out, L, 0, 0);
}
}
}
void DFHack::Lua::Core::onUpdate(color_ostream &out)
{
using df::global::world;
if (frame_timers.empty() && tick_timers.empty())
return;
Lua::StackUnwinder frame(State);
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
run_timers(out, State, frame_timers, frame[1], ++frame_idx);
run_timers(out, State, tick_timers, frame[1], world->frame_counter);
}
void DFHack::Lua::Core::Init(color_ostream &out)
{
if (State)
@ -1354,12 +1578,20 @@ void DFHack::Lua::Core::Init(color_ostream &out)
Lua::Open(out, State);
lua_newtable(State);
lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
// Register events
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN);
MakeEvent(State, (void*)onStateChange);
lua_setfield(State, -2, "onStateChange");
lua_pushcfunction(State, dfhack_timeout);
lua_setfield(State, -2, "timeout");
lua_pushcfunction(State, dfhack_timeout_active);
lua_setfield(State, -2, "timeout_active");
lua_pop(State, 1);
}

@ -635,6 +635,28 @@ static int meta_struct_next(lua_State *state)
return 2;
}
/**
* Field lookup for primitive refs: behave as a quasi-array with numeric indices.
*/
static type_identity *find_primitive_field(lua_State *state, int field, const char *mode, uint8_t **ptr)
{
if (lua_type(state, field) == LUA_TNUMBER)
{
int idx = lua_tointeger(state, field);
if (idx < 0)
field_error(state, 2, "negative index", mode);
lua_rawgetp(state, UPVAL_METATABLE, &DFHACK_IDENTITY_FIELD_TOKEN);
auto id = (type_identity *)lua_touserdata(state, -1);
lua_pop(state, 1);
*ptr += int(id->byte_size()) * idx;
return id;
}
return (type_identity*)find_field(state, field, mode);
}
/**
* Metamethod: __index for primitives, i.e. simple object references.
* Fields point to identity, or NULL for metafields.
@ -642,7 +664,7 @@ static int meta_struct_next(lua_State *state)
static int meta_primitive_index(lua_State *state)
{
uint8_t *ptr = get_object_addr(state, 1, 2, "read");
auto type = (type_identity*)find_field(state, 2, "read");
auto type = find_primitive_field(state, 2, "read", &ptr);
if (!type)
return 1;
type->lua_read(state, 2, ptr);
@ -655,7 +677,7 @@ static int meta_primitive_index(lua_State *state)
static int meta_primitive_newindex(lua_State *state)
{
uint8_t *ptr = get_object_addr(state, 1, 2, "write");
auto type = (type_identity*)find_field(state, 2, "write");
auto type = find_primitive_field(state, 2, "write", &ptr);
if (!type)
field_error(state, 2, "builtin property or method", "write");
type->lua_write(state, 2, ptr, 3);
@ -1029,6 +1051,11 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id
std::string tmp = stl_sprintf("NULL pointer: %s", vn ? vn : "?");
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
}
catch (Error::InvalidArgument &e) {
const char *vn = e.expr();
std::string tmp = stl_sprintf("Invalid argument; expected: %s", vn ? vn : "?");
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
}
catch (std::exception &e) {
std::string tmp = stl_sprintf("C++ exception: %s", e.what());
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
@ -1098,26 +1125,39 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bo
name = pstruct->getName() + ("." + name);
lua_pop(state, 1);
bool add_to_enum = true;
// Handle the field
switch (fields[i].mode)
{
case struct_field_info::OBJ_METHOD:
AddMethodWrapper(state, base+1, base+2, name.c_str(),
(function_identity_base*)fields[i].type);
break;
continue;
case struct_field_info::CLASS_METHOD:
continue;
case struct_field_info::POINTER:
// Skip class-typed pointers within unions
if ((fields[i].count & 2) != 0 && fields[i].type &&
fields[i].type->type() == IDTYPE_CLASS)
add_to_enum = false;
break;
default:
// Do not add invalid globals to the enumeration order
if (!globals || *(void**)fields[i].offset)
AssociateId(state, base+3, ++cnt, name.c_str());
lua_pushlightuserdata(state, (void*)&fields[i]);
lua_setfield(state, base+2, name.c_str());
break;
}
// Do not add invalid globals to the enumeration order
if (globals && !*(void**)fields[i].offset)
add_to_enum = false;
if (add_to_enum)
AssociateId(state, base+3, ++cnt, name.c_str());
lua_pushlightuserdata(state, (void*)&fields[i]);
lua_setfield(state, base+2, name.c_str());
}
}

@ -446,6 +446,24 @@ Lua::ObjectClass Lua::IsDFObject(lua_State *state, int val_index)
return ok ? cls : Lua::OBJ_INVALID;
}
static const char *const primitive_types[] = {
"string",
"int8_t", "uint8_t", "int16_t", "uint16_t",
"int32_t", "uint32_t", "int64_t", "uint64_t",
"bool", "float", "double",
NULL
};
static type_identity *const primitive_identities[] = {
df::identity_traits<std::string>::get(),
df::identity_traits<int8_t>::get(), df::identity_traits<uint8_t>::get(),
df::identity_traits<int16_t>::get(), df::identity_traits<uint16_t>::get(),
df::identity_traits<int32_t>::get(), df::identity_traits<uint32_t>::get(),
df::identity_traits<int64_t>::get(), df::identity_traits<uint64_t>::get(),
df::identity_traits<bool>::get(),
df::identity_traits<float>::get(), df::identity_traits<double>::get(),
NULL
};
/**
* Given a DF object reference or type, safely retrieve its identity pointer.
*/
@ -453,6 +471,12 @@ type_identity *LuaWrapper::get_object_identity(lua_State *state, int objidx,
const char *ctx, bool allow_type,
bool keep_metatable)
{
if (allow_type && !keep_metatable && lua_isstring(state, objidx))
{
int idx = luaL_checkoption(state, objidx, NULL, primitive_types);
return primitive_identities[idx];
}
if (!lua_getmetatable(state, objidx))
luaL_error(state, "Invalid object in %s", ctx);
@ -631,12 +655,32 @@ static int meta_new(lua_State *state)
{
int argc = lua_gettop(state);
if (argc != 1)
luaL_error(state, "Usage: object:new() or df.new(object)");
if (argc != 1 && argc != 2)
luaL_error(state, "Usage: object:new() or df.new(object) or df.new(ptype,count)");
type_identity *id = get_object_identity(state, 1, "df.new()", true);
void *ptr = id->allocate();
void *ptr;
// Support arrays of primitive types
if (argc == 2)
{
int cnt = luaL_checkint(state, 2);
if (cnt <= 0)
luaL_error(state, "Invalid array size in df.new()");
if (id->type() != IDTYPE_PRIMITIVE)
luaL_error(state, "Cannot allocate arrays of non-primitive types.");
size_t sz = id->byte_size() * cnt;
ptr = malloc(sz);
if (ptr)
memset(ptr, 0, sz);
}
else
{
ptr = id->allocate();
}
if (!ptr)
luaL_error(state, "Cannot allocate %s", id->getFullName().c_str());
@ -653,6 +697,48 @@ static int meta_new(lua_State *state)
return 1;
}
/**
* Method: type casting of pointers.
*/
static int meta_reinterpret_cast(lua_State *state)
{
int argc = lua_gettop(state);
if (argc != 2)
luaL_error(state, "Usage: df.reinterpret_cast(type,ptr)");
type_identity *id = get_object_identity(state, 1, "df.reinterpret_cast()", true);
// Find the raw pointer value
void *ptr;
if (lua_isnil(state, 2))
ptr = NULL;
else if (lua_isnumber(state, 2))
ptr = (void*)lua_tointeger(state, 2);
else
{
ptr = get_object_internal(state, NULL, 2, false, true);
if (!ptr)
luaL_error(state, "Invalid pointer argument in df.reinterpret_cast.\n");
}
// Convert it to the appropriate representation
if (ptr == NULL)
{
lua_pushnil(state);
}
else if (lua_isuserdata(state, 1))
{
lua_getmetatable(state, 1);
push_object_ref(state, ptr);
}
else
push_object_internal(state, id, ptr);
return 1;
}
static void invoke_resize(lua_State *state, int table, lua_Integer size)
{
lua_getfield(state, table, "resize");
@ -1419,6 +1505,10 @@ static int DoAttach(lua_State *state)
lua_pushcclosure(state, meta_new, 1);
lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_NEW_NAME);
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN);
lua_pushcclosure(state, meta_reinterpret_cast, 1);
lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_CAST_NAME);
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN);
lua_pushcclosure(state, meta_assign, 1);
lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME);
@ -1450,6 +1540,8 @@ static int DoAttach(lua_State *state)
lua_setfield(state, -2, "assign");
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_IS_INSTANCE_NAME);
lua_setfield(state, -2, "is_instance");
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_CAST_NAME);
lua_setfield(state, -2, "reinterpret_cast");
lua_pushlightuserdata(state, NULL);
lua_setfield(state, -2, "NULL");

@ -0,0 +1,12 @@
/*
* MacPool.h
* Handles creation and destruction of autorelease pool for DFHack on the Mac
*/
#ifndef MACPOOL_H
#define MACPOOL_H
int create_pool();
int destroy_pool();
#endif

@ -0,0 +1,22 @@
/*
* MacPool.m
*
*/
#import <Foundation/Foundation.h>
#import "MacPool.h"
NSAutoreleasePool *thePool;
int create_pool() {
fprintf(stderr,"Creating autorelease pool\n");
thePool = [[NSAutoreleasePool alloc] init];
return 1;
}
int destroy_pool() {
fprintf(stderr,"Draining and releasing autorelease pool\n");
[thePool drain];
[thePool release];
return 0;
}

@ -36,12 +36,17 @@ distribution.
#include <ctype.h>
#include <stdarg.h>
#include <string.h>
#include <sstream>
#include <map>
const char *DFHack::Error::NullPointer::what() const throw() {
return "NULL pointer access";
return "DFHack::Error::NullPointer";
}
const char *DFHack::Error::InvalidArgument::what() const throw() {
return "DFHack::Error::InvalidArgument";
}
std::string stl_sprintf(const char *fmt, ...) {
@ -120,6 +125,29 @@ std::string toLower(const std::string &str)
return rv;
}
bool prefix_matches(const std::string &prefix, const std::string &key, std::string *tail)
{
size_t ksize = key.size();
size_t psize = prefix.size();
if (ksize < psize || memcmp(prefix.data(), key.data(), psize) != 0)
return false;
if (tail)
tail->clear();
if (ksize == psize)
return true;
if (psize == 0 || prefix[psize-1] == '/')
{
if (tail) *tail = key.substr(psize);
return true;
}
if (key[psize] == '/')
{
if (tail) *tail = key.substr(psize+1);
return true;
}
return false;
}
#ifdef LINUX_BUILD // Linux
uint64_t GetTimeMs64()
{

@ -0,0 +1,44 @@
#include <stdio.h>
#include <dlfcn.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <map>
#include "DFHack.h"
#include "PluginManager.h"
#include "Hooks.h"
#include <iostream>
/*
* Plugin loading functions
*/
namespace DFHack
{
DFLibrary * OpenPlugin (const char * filename)
{
dlerror();
DFLibrary * ret = (DFLibrary *) dlopen(filename, RTLD_NOW);
if(!ret)
{
std::cerr << dlerror() << std::endl;
}
return ret;
}
void * LookupPlugin (DFLibrary * plugin ,const char * function)
{
return (DFLibrary *) dlsym((void *)plugin, function);
}
void ClosePlugin (DFLibrary * plugin)
{
dlclose((void *) plugin);
}
}

@ -28,6 +28,7 @@ distribution.
#include <stdint.h>
#include <vector>
#include <string>
#include "Core.h"
#include "PluginManager.h"
#include "Hooks.h"
#include <stdio.h>

@ -28,6 +28,7 @@ distribution.
#include "PluginManager.h"
#include "RemoteServer.h"
#include "Console.h"
#include "Types.h"
#include "DataDefs.h"
#include "MiscUtils.h"
@ -45,41 +46,8 @@ using namespace std;
#include "tinythread.h"
using namespace tthread;
#ifdef LINUX_BUILD
#include <dirent.h>
#include <errno.h>
#else
#include "wdirent.h"
#endif
#include <assert.h>
static int getdir (string dir, vector<string> &files)
{
DIR *dp;
struct dirent *dirp;
if((dp = opendir(dir.c_str())) == NULL)
{
return errno;
}
while ((dirp = readdir(dp)) != NULL) {
files.push_back(string(dirp->d_name));
}
closedir(dp);
return 0;
}
bool hasEnding (std::string const &fullString, std::string const &ending)
{
if (fullString.length() > ending.length())
{
return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
}
else
{
return false;
}
}
struct Plugin::RefLock
{
RefLock()
@ -563,10 +531,10 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn)
PluginManager::PluginManager(Core * core)
{
#ifdef LINUX_BUILD
string path = core->p->getPath() + "/hack/plugins/";
string path = core->getHackPath() + "plugins/";
const string searchstr = ".plug.so";
#else
string path = core->p->getPath() + "\\hack\\plugins\\";
string path = core->getHackPath() + "plugins\\";
const string searchstr = ".plug.dll";
#endif
cmdlist_mutex = new mutex();
@ -624,7 +592,7 @@ command_result PluginManager::InvokeCommand(color_ostream &out, const std::strin
bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *top)
{
Plugin *plugin = getPluginByCommand(command);
return plugin ? plugin->can_invoke_hotkey(command, top) : false;
return plugin ? plugin->can_invoke_hotkey(command, top) : true;
}
void PluginManager::OnUpdate(color_ostream &out)
@ -641,8 +609,6 @@ void PluginManager::OnStateChange(color_ostream &out, state_change_event event)
{
all_plugins[i]->on_state_change(out, event);
}
Lua::Core::onStateChange(out, event);
}
// FIXME: doesn't check name collisions!

@ -0,0 +1,229 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2011 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 <dirent.h>
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
#include <mach-o/dyld.h>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <cstdio>
#include <cstring>
using namespace std;
#include <md5wrapper.h>
#include "MemAccess.h"
#include "VersionInfoFactory.h"
#include "VersionInfo.h"
#include "Error.h"
#include <string.h>
using namespace DFHack;
Process::Process(VersionInfoFactory * known_versions)
{
int target_result;
char path[1024];
char *real_path;
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL);
}
identified = false;
my_descriptor = 0;
md5wrapper md5;
uint32_t length;
uint8_t first_kb [1024];
memset(first_kb, 0, sizeof(first_kb));
// get hash of the running DF process
string hash = md5.getHashFromFile(real_path, length, (char *) first_kb);
// create linux process, add it to the vector
VersionInfo * vinfo = known_versions->getVersionInfoByMD5(hash);
if(vinfo)
{
my_descriptor = new VersionInfo(*vinfo);
identified = true;
}
else
{
char * wd = getcwd(NULL, 0);
cerr << "Unable to retrieve version information.\n";
cerr << "File: " << real_path << endl;
cerr << "MD5: " << hash << endl;
cerr << "working dir: " << wd << endl;
cerr << "length:" << length << endl;
cerr << "1KB hexdump follows:" << endl;
for(int i = 0; i < 64; i++)
{
fprintf(stderr, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
first_kb[i*16],
first_kb[i*16+1],
first_kb[i*16+2],
first_kb[i*16+3],
first_kb[i*16+4],
first_kb[i*16+5],
first_kb[i*16+6],
first_kb[i*16+7],
first_kb[i*16+8],
first_kb[i*16+9],
first_kb[i*16+10],
first_kb[i*16+11],
first_kb[i*16+12],
first_kb[i*16+13],
first_kb[i*16+14],
first_kb[i*16+15]
);
}
free(wd);
}
}
Process::~Process()
{
// destroy our copy of the memory descriptor
delete my_descriptor;
}
string Process::doReadClassName (void * vptr)
{
//FIXME: BAD!!!!!
char * typeinfo = Process::readPtr(((char *)vptr - 0x4));
char * typestring = Process::readPtr(typeinfo + 0x4);
string raw = readCString(typestring);
size_t start = raw.find_first_of("abcdefghijklmnopqrstuvwxyz");// trim numbers
size_t end = raw.length();
return raw.substr(start,end-start);
}
//FIXME: cross-reference with ELF segment entries?
void Process::getMemRanges( vector<t_memrange> & ranges )
{
char buffer[1024];
char permissions[5]; // r/-, w/-, x/-, p/s, 0
FILE *mapFile = ::fopen("/proc/self/maps", "r");
size_t start, end, offset, device1, device2, node;
while (fgets(buffer, 1024, mapFile))
{
t_memrange temp;
temp.name[0] = 0;
sscanf(buffer, "%zx-%zx %s %zx %2zx:%2zx %zu %[^\n]",
&start,
&end,
(char*)&permissions,
&offset, &device1, &device2, &node,
(char*)temp.name);
temp.start = (void *) start;
temp.end = (void *) end;
temp.read = permissions[0] == 'r';
temp.write = permissions[1] == 'w';
temp.execute = permissions[2] == 'x';
temp.shared = permissions[3] == 's';
temp.valid = true;
ranges.push_back(temp);
}
}
uint32_t Process::getBase()
{
return 0;
}
static int getdir (string dir, vector<string> &files)
{
DIR *dp;
struct dirent *dirp;
if((dp = opendir(dir.c_str())) == NULL)
{
cout << "Error(" << errno << ") opening " << dir << endl;
return errno;
}
while ((dirp = readdir(dp)) != NULL) {
files.push_back(string(dirp->d_name));
}
closedir(dp);
return 0;
}
bool Process::getThreadIDs(vector<uint32_t> & threads )
{
stringstream ss;
vector<string> subdirs;
if(getdir("/proc/self/task/",subdirs) != 0)
{
//FIXME: needs exceptions. this is a fatal error
cerr << "unable to enumerate threads. This is BAD!" << endl;
return false;
}
threads.clear();
for(size_t i = 0; i < subdirs.size();i++)
{
uint32_t tid;
if(sscanf(subdirs[i].c_str(),"%d", &tid))
{
threads.push_back(tid);
}
}
return true;
}
string Process::getPath()
{
char path[1024];
char *real_path;
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL);
}
std::string path_string(real_path);
int last_slash = path_string.find_last_of("/");
std::string directory = path_string.substr(0,last_slash);
return directory;
}
int Process::getPID()
{
return getpid();
}
bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
{
int result;
int protect=0;
if(trgrange.read)protect|=PROT_READ;
if(trgrange.write)protect|=PROT_WRITE;
if(trgrange.execute)protect|=PROT_EXEC;
result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect);
return result==0;
}

@ -329,17 +329,19 @@ bool sendRemoteMessage(CSimpleSocket *socket, int16_t id, const MessageLite *msg
int size = size_ready ? msg->GetCachedSize() : msg->ByteSize();
int fullsz = size + sizeof(RPCMessageHeader);
std::auto_ptr<uint8_t> data(new uint8_t[fullsz]);
RPCMessageHeader *hdr = (RPCMessageHeader*)data.get();
uint8_t *data = new uint8_t[fullsz];
RPCMessageHeader *hdr = (RPCMessageHeader*)data;
hdr->id = id;
hdr->size = size;
uint8_t *pstart = data.get() + sizeof(RPCMessageHeader);
uint8_t *pstart = data + sizeof(RPCMessageHeader);
uint8_t *pend = msg->SerializeWithCachedSizesToArray(pstart);
assert((pend - pstart) == size);
return (socket->Send(data.get(), fullsz) == fullsz);
int got = socket->Send(data, fullsz);
delete[] data;
return (got == fullsz);
}
command_result RemoteFunctionBase::execute(color_ostream &out,
@ -402,9 +404,9 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
return CR_LINK_FAILURE;
}
std::auto_ptr<uint8_t> buf(new uint8_t[header.size]);
uint8_t *buf = new uint8_t[header.size];
if (!readFullBuffer(p_client->socket, buf.get(), header.size))
if (!readFullBuffer(p_client->socket, buf, header.size))
{
out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n",
this->proto.c_str(), this->name.c_str(), header.size);
@ -413,18 +415,20 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
switch (header.id) {
case RPC_REPLY_RESULT:
if (!output->ParseFromArray(buf.get(), header.size))
if (!output->ParseFromArray(buf, header.size))
{
out.printerr("In call to %s::%s: error parsing received result.\n",
this->proto.c_str(), this->name.c_str());
delete[] buf;
return CR_LINK_FAILURE;
}
delete[] buf;
return CR_OK;
case RPC_REPLY_TEXT:
text_data.Clear();
if (text_data.ParseFromArray(buf.get(), header.size))
if (text_data.ParseFromArray(buf, header.size))
text_decoder.decode(&text_data);
else
out.printerr("In call to %s::%s: received invalid text data.\n",
@ -434,5 +438,6 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
default:
break;
}
delete[] buf;
}
}

@ -220,7 +220,7 @@ void ServerConnection::threadFn()
}
if (memcmp(header.magic, RPCHandshakeHeader::REQUEST_MAGIC, sizeof(header.magic)) ||
header.version != 1)
header.version < 1 || header.version > 255)
{
out << "In RPC server: invalid handshake header." << endl;
return;

@ -711,7 +711,7 @@ command_result CoreService::RunCommand(color_ostream &stream,
for (int i = 0; i < in->arguments_size(); i++)
args.push_back(in->arguments(i));
return Core::getInstance().plug_mgr->InvokeCommand(stream, cmd, args);
return Core::getInstance().runCommand(stream, cmd, args);
}
command_result CoreService::CoreSuspend(color_ostream &stream, const EmptyMessage*, IntMessage *cnt)

@ -0,0 +1,129 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2011 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 "Export.h"
#include "MiscUtils.h"
#include "Error.h"
#include "Types.h"
#ifndef LINUX_BUILD
#include <Windows.h>
#include "wdirent.h"
#else
#include <sys/time.h>
#include <ctime>
#include <dirent.h>
#include <errno.h>
#endif
#include <ctype.h>
#include <stdarg.h>
#include <sstream>
#include <map>
int DFHack::getdir(std::string dir, std::vector<std::string> &files)
{
DIR *dp;
struct dirent *dirp;
if((dp = opendir(dir.c_str())) == NULL)
{
return errno;
}
while ((dirp = readdir(dp)) != NULL) {
files.push_back(std::string(dirp->d_name));
}
closedir(dp);
return 0;
}
bool DFHack::hasEnding (std::string const &fullString, std::string const &ending)
{
if (fullString.length() > ending.length())
{
return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
}
else
{
return false;
}
}
df::general_ref *DFHack::findRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{
for (int i = vec.size()-1; i >= 0; i--)
{
df::general_ref *ref = vec[i];
if (ref->getType() == type)
return ref;
}
return NULL;
}
bool DFHack::removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type type, int id)
{
for (int i = vec.size()-1; i >= 0; i--)
{
df::general_ref *ref = vec[i];
if (ref->getType() != type || ref->getID() != id)
continue;
vector_erase_at(vec, i);
delete ref;
return true;
}
return false;
}
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--)
{
df::specific_ref *ref = vec[i];
if (ref->type == type)
return ref;
}
return NULL;
}
bool DFHack::removeRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type, void *ptr)
{
for (int i = vec.size()-1; i >= 0; i--)
{
df::specific_ref *ref = vec[i];
if (ref->type != type || ref->object != ptr)
continue;
vector_erase_at(vec, i);
delete ref;
return true;
}
return false;
}

@ -107,7 +107,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
}
else
{
throw Error::SymbolsXmlBadAttribute("os-type");
return; // ignore it if it's invalid
}
// process additional entries

@ -65,13 +65,20 @@ namespace DFHack
bool save (const char * filename)
{
std::ofstream outfile (filename);
fprintf(stderr,"Save: Initialized stream\n");
if(outfile.bad())
return false;
fprintf(stderr,"Save: Iterating...\n");
for(auto iter = history.begin();iter < history.end(); iter++)
{
fprintf(stderr,"Save: Dumping %s\n",(*iter).c_str());
outfile << *iter << std::endl;
fprintf(stderr,"Save: Flushing\n");
outfile.flush();
}
fprintf(stderr,"Save: Closing\n");
outfile.close();
fprintf(stderr,"Save: Done\n");
return true;
}
/// add a command to the history

@ -33,7 +33,8 @@ distribution.
#include <stdint.h>
#include "Console.h"
#include "modules/Graphic.h"
#include "SDL_events.h"
#include "RemoteClient.h"
struct WINDOW;
@ -66,6 +67,17 @@ namespace DFHack
class df_window;
}
enum state_change_event
{
SC_WORLD_LOADED = 0,
SC_WORLD_UNLOADED = 1,
SC_MAP_LOADED = 2,
SC_MAP_UNLOADED = 3,
SC_VIEWSCREEN_CHANGED = 4,
SC_CORE_INITIALIZED = 5,
SC_BEGIN_UNLOAD = 6
};
// Core is a singleton. Why? Because it is closely tied to SDL calls. It tracks the global state of DF.
// There should never be more than one instance
// Better than tracking some weird variables all over the place.
@ -117,10 +129,16 @@ namespace DFHack
/// returns a named pointer.
void *GetData(std::string key);
command_result runCommand(color_ostream &out, const std::string &command, std::vector <std::string> &parameters);
command_result runCommand(color_ostream &out, const std::string &command);
bool loadScriptFile(color_ostream &out, std::string fname, bool silent = false);
bool ClearKeyBindings(std::string keyspec);
bool AddKeyBinding(std::string keyspec, std::string cmdline);
std::vector<std::string> ListKeyBindings(std::string keyspec);
std::string getHackPath();
bool isWorldLoaded() { return (last_world_data_ptr != NULL); }
bool isMapLoaded() { return (last_local_map_ptr != NULL && last_world_data_ptr != NULL); }
@ -151,9 +169,12 @@ namespace DFHack
int Update (void);
int TileUpdate (void);
int Shutdown (void);
int SDL_Event(SDL::Event* event);
int DFH_SDL_Event(SDL::Event* event);
bool ncurses_wgetch(int in, int & out);
void onUpdate(color_ostream &out);
void onStateChange(color_ostream &out, state_change_event event);
Core(Core const&); // Don't Implement
void operator=(Core const&); // Don't implement
@ -183,6 +204,7 @@ namespace DFHack
int modifiers;
std::vector<std::string> command;
std::string cmdline;
std::string focus;
};
std::map<int, std::vector<KeyBinding> > key_bindings;
@ -192,7 +214,6 @@ namespace DFHack
tthread::mutex * HotkeyMutex;
tthread::condition_variable * HotkeyCond;
int UnicodeAwareSym(const SDL::KeyboardEvent& ke);
bool SelectHotkey(int key, int modifiers);
// for state change tracking

@ -318,6 +318,9 @@ namespace DFHack
public:
static virtual_identity *get(virtual_ptr instance_ptr);
static virtual_identity *find(void *vtable);
static virtual_identity *find(const std::string &name);
bool is_instance(virtual_ptr instance_ptr) {
if (!instance_ptr) return false;
if (vtable_ptr) {
@ -449,6 +452,9 @@ namespace df
}
};
template<class ET, class IT>
struct enum_traits<enum_field<ET, IT> > : public enum_traits<ET> {};
template<class EnumType, class IntType1, class IntType2>
inline bool operator== (enum_field<EnumType,IntType1> a, enum_field<EnumType,IntType2> b)
{

@ -143,6 +143,34 @@ INSTANTIATE_WRAPPERS(4, (OSTREAM_ARG,A1,A2,A3,A4), (out,vA1,vA2,vA3,vA4),
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5))
INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (vA1,vA2,vA3,vA4,vA5),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); LOAD_ARG(A5);)
INSTANTIATE_WRAPPERS(5, (OSTREAM_ARG,A1,A2,A3,A4,A5), (out,vA1,vA2,vA3,vA4,vA5),
LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2);
LOAD_ARG(A3); LOAD_ARG(A4); LOAD_ARG(A5);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6))
INSTANTIATE_WRAPPERS(6, (A1,A2,A3,A4,A5,A6), (vA1,vA2,vA3,vA4,vA5,vA6),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);
LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6);)
INSTANTIATE_WRAPPERS(6, (OSTREAM_ARG,A1,A2,A3,A4,A5,A6), (out,vA1,vA2,vA3,vA4,vA5,vA6),
LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);
LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7))
INSTANTIATE_WRAPPERS(7, (A1,A2,A3,A4,A5,A6,A7), (vA1,vA2,vA3,vA4,vA5,vA6,vA7),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);
LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6);
LOAD_ARG(A7);)
INSTANTIATE_WRAPPERS(7, (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7), (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7),
LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);
LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8))
#undef FW_TARGS
#undef FW_TARGSC

@ -51,6 +51,18 @@ namespace DFHack
#define CHECK_NULL_POINTER(var) \
{ if (var == NULL) throw DFHack::Error::NullPointer(#var); }
class DFHACK_EXPORT InvalidArgument : public All {
const char *expr_;
public:
InvalidArgument(const char *expr_ = NULL) : expr_(expr_) {}
const char *expr() const { return expr_; }
virtual const char *what() const throw();
};
#define CHECK_INVALID_ARGUMENT(expr) \
{ if (!(expr)) throw DFHack::Error::InvalidArgument(#expr); }
class DFHACK_EXPORT AllSymbols : public All{};
// Syntax errors and whatnot, the xml can't be read
class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols

@ -48,6 +48,9 @@ namespace DFHack {namespace Lua {
*/
DFHACK_EXPORT lua_State *Open(color_ostream &out, lua_State *state = NULL);
DFHACK_EXPORT void PushDFHack(lua_State *state);
DFHACK_EXPORT void PushBaseGlobals(lua_State *state);
/**
* Load a module using require(). Leaves the stack as is.
*/
@ -109,7 +112,15 @@ namespace DFHack {namespace Lua {
* Return behavior is of SafeCall below.
*/
DFHACK_EXPORT bool AssignDFObject(color_ostream &out, lua_State *state,
type_identity *type, void *target, int val_index, bool perr = true);
type_identity *type, void *target, int val_index,
bool exact_type = false, bool perr = true);
/**
* Assign the value at val_index to the target of given identity using df.assign().
* Otherwise throws an error.
*/
DFHACK_EXPORT void CheckDFAssign(lua_State *state, type_identity *type,
void *target, int val_index, bool exact_type = false);
/**
* Push the pointer onto the stack as a wrapped DF object of a specific type.
@ -139,8 +150,19 @@ namespace DFHack {namespace Lua {
* Assign the value at val_index to the target using df.assign().
*/
template<class T>
bool AssignDFObject(color_ostream &out, lua_State *state, T *target, int val_index, bool perr = true) {
return AssignDFObject(out, state, df::identity_traits<T>::get(), target, val_index, perr);
bool AssignDFObject(color_ostream &out, lua_State *state, T *target,
int val_index, bool exact_type = false, bool perr = true) {
return AssignDFObject(out, state, df::identity_traits<T>::get(),
target, val_index, exact_type, perr);
}
/**
* Assign the value at val_index to the target using df.assign().
* Throws in case of an error.
*/
template<class T>
void CheckDFAssign(lua_State *state, T *target, int val_index, bool exact_type = false) {
CheckDFAssign(state, df::identity_traits<T>::get(), target, val_index, exact_type);
}
/**
@ -242,11 +264,14 @@ namespace DFHack {namespace Lua {
inline void Push(lua_State *state, bool value) {
lua_pushboolean(state, value);
}
inline void Push(lua_State *state, const char *str) {
lua_pushstring(state, str);
}
inline void Push(lua_State *state, const std::string &str) {
lua_pushlstring(state, str.data(), str.size());
}
inline void Push(lua_State *state, df::coord &obj) { PushDFObject(state, &obj); }
inline void Push(lua_State *state, df::coord2d &obj) { PushDFObject(state, &obj); }
DFHACK_EXPORT void Push(lua_State *state, df::coord obj);
DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj);
void Push(lua_State *state, const Units::NoblePosition &pos);
template<class T> inline void Push(lua_State *state, T *ptr) {
PushDFObject(state, ptr);
@ -271,6 +296,7 @@ namespace DFHack {namespace Lua {
}
DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos);
DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos);
DFHACK_EXPORT bool IsCoreContext(lua_State *state);
@ -311,6 +337,8 @@ namespace DFHack {namespace Lua {
// Events signalled by the core
void onStateChange(color_ostream &out, int code);
// Signals timers
void onUpdate(color_ostream &out);
template<class T> inline void Push(T &arg) { Lua::Push(State, arg); }
template<class T> inline void Push(const T &arg) { Lua::Push(State, arg); }
@ -400,3 +428,17 @@ namespace DFHack {namespace Lua {
name##_event.invoke(out, 4); \
} \
}
#define DEFINE_LUA_EVENT_5(name, handler, arg_type1, arg_type2, arg_type3, arg_type4, arg_type5) \
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) { \
handler(out, arg1, arg2, arg3, arg4, arg5); \
if (auto state = name##_event.get_state()) { \
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); \
name##_event.invoke(out, 5); \
} \
}

@ -77,6 +77,7 @@ namespace DFHack { namespace LuaWrapper {
#define DFHACK_ASSIGN_NAME "DFHack::Assign"
#define DFHACK_IS_INSTANCE_NAME "DFHack::IsInstance"
#define DFHACK_DELETE_NAME "DFHack::Delete"
#define DFHACK_CAST_NAME "DFHack::Cast"
extern LuaToken DFHACK_EMPTY_TABLE_TOKEN;

@ -262,6 +262,51 @@ Link *linked_list_insert_after(Link *pos, Link *link)
return link;
}
template<typename T>
inline typename T::mapped_type map_find(
const T &map, const typename T::key_type &key,
const typename T::mapped_type &defval = typename T::mapped_type()
) {
auto it = map.find(key);
return (it == map.end()) ? defval : it->second;
}
DFHACK_EXPORT bool prefix_matches(const std::string &prefix, const std::string &key, std::string *tail = NULL);
template<typename T>
typename T::mapped_type findPrefixInMap(
const T &table, const std::string &key,
const typename T::mapped_type& defval = typename T::mapped_type()
) {
auto it = table.lower_bound(key);
if (it != table.end() && it->first == key)
return it->second;
if (it != table.begin()) {
--it;
if (prefix_matches(it->first, key))
return it->second;
}
return defval;
}
#ifdef __GNUC__
#define VARIABLE_IS_NOT_USED __attribute__ ((unused))
#else
#define VARIABLE_IS_NOT_USED
#endif
template<class CT>
inline bool static_add_to_map(CT *pmap, typename CT::key_type key, typename CT::mapped_type value) {
(*pmap)[key] = value;
return true;
}
#define CONCAT_TOKENS2(a,b) a##b
#define CONCAT_TOKENS(a,b) CONCAT_TOKENS2(a,b)
#define DFHACK_STATIC_ADD_TO_MAP(pmap,key,value) \
static bool VARIABLE_IS_NOT_USED CONCAT_TOKENS(static_add_to_map_,__LINE__)\
= static_add_to_map(pmap,key,value)
/*
* MISC
*/

@ -31,6 +31,8 @@ distribution.
#include <string>
#include <vector>
#include "Core.h"
#include "RemoteClient.h"
typedef struct lua_State lua_State;
@ -59,22 +61,12 @@ namespace DFHack
struct DFLibrary;
// Open a plugin library
DFLibrary * OpenPlugin (const char * filename);
DFHACK_EXPORT DFLibrary * OpenPlugin (const char * filename);
// find a symbol inside plugin
void * LookupPlugin (DFLibrary * plugin ,const char * function);
DFHACK_EXPORT void * LookupPlugin (DFLibrary * plugin ,const char * function);
// Close a plugin library
void ClosePlugin (DFLibrary * plugin);
DFHACK_EXPORT void ClosePlugin (DFLibrary * plugin);
enum state_change_event
{
SC_WORLD_LOADED = 0,
SC_WORLD_UNLOADED = 1,
SC_MAP_LOADED = 2,
SC_MAP_UNLOADED = 3,
SC_VIEWSCREEN_CHANGED = 4,
SC_CORE_INITIALIZED = 5,
SC_BEGIN_UNLOAD = 6
};
struct DFHACK_EXPORT CommandReg {
const char *name;
int (*command)(lua_State*);

@ -28,6 +28,10 @@ distribution.
#include "Pragma.h"
#include "Export.h"
#include "DataDefs.h"
#include "df/general_ref.h"
#include "df/specific_ref.h"
namespace DFHack
{
struct t_matglossPair
@ -69,4 +73,13 @@ namespace DFHack
std::string name;
uint32_t xpNxtLvl;
};
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 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 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);
}// namespace DFHack

@ -34,6 +34,14 @@ distribution.
#include "df/siegeengine_type.h"
#include "df/trap_type.h"
namespace df
{
struct job_item;
struct item;
struct building_extents;
struct building_civzonest;
}
namespace DFHack
{
namespace Buildings
@ -84,5 +92,86 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building);
*/
DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map <uint32_t, std::string> & btypes);
/**
* Find the building located at the specified tile.
* Does not work on civzones.
*/
DFHACK_EXPORT df::building *findAtTile(df::coord pos);
/**
* Find civzones located at the specified tile.
*/
DFHACK_EXPORT bool findCivzonesAt(std::vector<df::building_civzonest*> *pvec, df::coord pos);
/**
* Allocates a building object using this type and position.
*/
DFHACK_EXPORT df::building *allocInstance(df::coord pos, df::building_type type, int subtype = -1, int custom = -1);
/**
* Sets size and center to the correct dimensions of the building.
* If part of the original size value was used, returns true.
*/
DFHACK_EXPORT bool getCorrectSize(df::coord2d &size, df::coord2d &center,
df::building_type type, int subtype = -1, int custom = -1,
int direction = 0);
/**
* Checks if the tiles are free to be built upon.
*/
DFHACK_EXPORT bool checkFreeTiles(df::coord pos, df::coord2d size,
df::building_extents *ext = NULL,
bool create_ext = false, bool allow_occupied = false);
/**
* Returns the number of tiles included by the extent, or defval.
*/
DFHACK_EXPORT int countExtentTiles(df::building_extents *ext, int defval = -1);
/**
* Checks if the building contains the specified tile.
*/
DFHACK_EXPORT bool containsTile(df::building *bld, df::coord2d tile, bool room = false);
/**
* Checks if the area has support from the terrain.
*/
DFHACK_EXPORT bool hasSupport(df::coord pos, df::coord2d size);
/**
* Sets the size of the building, using size and direction as guidance.
* Returns true if the building can be created at its position, using that size.
*/
DFHACK_EXPORT bool setSize(df::building *bld, df::coord2d size, int direction = 0);
/**
* Decodes the size of the building into pos and size.
*/
DFHACK_EXPORT std::pair<df::coord,df::coord2d> getSize(df::building *bld);
/**
* Constructs an abstract building, i.e. stockpile or civzone.
*/
DFHACK_EXPORT bool constructAbstract(df::building *bld);
/**
* Initiates construction of the building, using specified items as inputs.
* Returns true if success.
*/
DFHACK_EXPORT bool constructWithItems(df::building *bld, std::vector<df::item*> items);
/**
* Initiates construction of the building, using specified item filters.
* Assumes immediate ownership of the item objects, and deletes them in case of error.
* Returns true if success.
*/
DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector<df::job_item*> items);
/**
* Deconstructs or queues deconstruction of a building.
* Returns true if the building has been destroyed instantly.
*/
DFHACK_EXPORT bool deconstruct(df::building *bld);
}
}

@ -31,6 +31,8 @@ distribution.
#include "Export.h"
#include "DataDefs.h"
#include "df/construction.h"
#include "df/construction_type.h"
#include "df/item_type.h"
/**
* \defgroup grp_constructions Construction module parts
@ -57,6 +59,12 @@ DFHACK_EXPORT bool isValid();
DFHACK_EXPORT uint32_t getCount();
DFHACK_EXPORT bool copyConstruction (const int32_t index, t_construction &out);
DFHACK_EXPORT df::construction * getConstruction (const int32_t index);
DFHACK_EXPORT bool designateNew(df::coord pos, df::construction_type type,
df::item_type item = df::item_type::NONE, int mat_index = -1);
DFHACK_EXPORT bool designateRemove(df::coord pos, bool *immediate = NULL);
}
}
#endif

@ -55,6 +55,10 @@ namespace DFHack
*/
namespace Gui
{
inline df::viewscreen *getCurViewscreen() { return Core::getTopViewscreen(); }
DFHACK_EXPORT std::string getFocusString(df::viewscreen *top);
// Full-screen item details view
DFHACK_EXPORT bool item_details_hotkey(df::viewscreen *top);
// 'u'nits or 'j'obs full-screen view

@ -36,6 +36,10 @@ distribution.
#include "df/item.h"
#include "df/item_type.h"
#include "df/general_ref.h"
#include "df/specific_ref.h"
#include "df/building_actual.h"
#include "df/body_part_raw.h"
#include "df/unit_inventory_item.h"
namespace df
{
@ -126,6 +130,10 @@ DFHACK_EXPORT bool copyItem(df::item * source, dfh_item & target);
/// write copied item back to its origin
DFHACK_EXPORT bool writeItem(const dfh_item & item);
/// Retrieve refs
DFHACK_EXPORT df::general_ref *getGeneralRef(df::item *item, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *getSpecificRef(df::item *item, df::specific_ref_type type);
/// Retrieve the owner of the item.
DFHACK_EXPORT df::unit *getOwner(df::item *item);
/// Set the owner of the item. Pass NULL as unit to remove the owner.
@ -139,8 +147,13 @@ DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector<df::
/// Returns the true position of the item.
DFHACK_EXPORT df::coord getPosition(df::item *item);
/// Returns the description string of the item.
DFHACK_EXPORT std::string getDescription(df::item *item, int type = 0, bool decorate = false);
DFHACK_EXPORT bool moveToGround(MapExtras::MapCache &mc, df::item *item, df::coord pos);
DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container);
DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode);
DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit,
df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1);
}
}

@ -30,6 +30,9 @@ distribution.
#include "Module.h"
#include <ostream>
#include "DataDefs.h"
#include "df/job_item_ref.h"
namespace df
{
struct job;
@ -54,10 +57,18 @@ namespace DFHack
DFHACK_EXPORT df::building *getHolder(df::job *job);
DFHACK_EXPORT df::unit *getWorker(df::job *job);
// Instruct the game to check and assign workers
DFHACK_EXPORT void checkBuildingsNow();
DFHACK_EXPORT void checkDesignationsNow();
DFHACK_EXPORT bool linkIntoWorld(df::job *job, bool new_id = true);
// lists jobs with ids >= *id_var, and sets *id_var = *job_next_id;
DFHACK_EXPORT bool listNewlyCreated(std::vector<df::job*> *pvec, int *id_var);
DFHACK_EXPORT bool attachJobItem(df::job *job, df::item *item,
df::job_item_ref::T_role role,
int filter_idx = -1, int insert_idx = -1);
}
DFHACK_EXPORT bool operator== (const df::job_item &a, const df::job_item &b);

@ -93,6 +93,12 @@ public:
Block(MapCache *parent, DFCoord _bcoord);
~Block();
DFCoord getCoord() { return bcoord; }
void enableBlockUpdates(bool flow = false, bool temp = false) {
Maps::enableBlockUpdates(block, flow, temp);
}
/*
* All coordinates are taken mod 16.
*/
@ -208,11 +214,8 @@ public:
dirty_designations = true;
//printf("setting block %d/%d/%d , %d %d\n",x,y,z, p.x, p.y);
index_tile<df::tile_designation&>(designation,p) = des;
if(des.bits.dig)
{
dirty_blockflags = true;
blockflags.bits.designated = true;
}
if(des.bits.dig && block)
block->flags.bits.designated = true;
return true;
}
@ -236,15 +239,7 @@ public:
t_blockflags BlockFlags()
{
return blockflags;
}
bool setBlockFlags(t_blockflags des)
{
if(!valid) return false;
dirty_blockflags = true;
//printf("setting block %d/%d/%d , %d %d\n",x,y,z, p.x, p.y);
blockflags = des;
return true;
return block ? block->flags : t_blockflags();
}
bool Write();
@ -273,7 +268,6 @@ private:
bool dirty_designations:1;
bool dirty_tiles:1;
bool dirty_temperatures:1;
bool dirty_blockflags:1;
bool dirty_occupancies:1;
DFCoord bcoord;
@ -328,7 +322,6 @@ private:
designations40d designation;
occupancies40d occupancy;
t_blockflags blockflags;
t_temperatures temp1;
t_temperatures temp2;

@ -241,8 +241,37 @@ extern DFHACK_EXPORT df::map_block * getTileBlock (int32_t x, int32_t y, int32_t
inline df::map_block * getBlock (df::coord pos) { return getBlock(pos.x, pos.y, pos.z); }
inline df::map_block * getTileBlock (df::coord pos) { return getTileBlock(pos.x, pos.y, pos.z); }
extern DFHACK_EXPORT df::tiletype *getTileType(int32_t x, int32_t y, int32_t z);
extern DFHACK_EXPORT df::tile_designation *getTileDesignation(int32_t x, int32_t y, int32_t z);
extern DFHACK_EXPORT df::tile_occupancy *getTileOccupancy(int32_t x, int32_t y, int32_t z);
inline df::tiletype *getTileType(df::coord pos) {
return getTileType(pos.x, pos.y, pos.z);
}
inline df::tile_designation *getTileDesignation(df::coord pos) {
return getTileDesignation(pos.x, pos.y, pos.z);
}
inline df::tile_occupancy *getTileOccupancy(df::coord pos) {
return getTileOccupancy(pos.x, pos.y, pos.z);
}
/**
* Returns biome info about the specified world region.
*/
DFHACK_EXPORT df::world_data::T_region_map *getRegionBiome(df::coord2d rgn_pos);
/**
* Returns biome world region coordinates for the given tile within given block.
*/
DFHACK_EXPORT df::coord2d getBlockTileBiomeRgn(df::map_block *block, df::coord2d pos);
inline df::coord2d getTileBiomeRgn(df::coord pos) {
return getBlockTileBiomeRgn(getTileBlock(pos), pos);
}
// Enables per-frame updates for liquid flow and/or temperature.
DFHACK_EXPORT void enableBlockUpdates(df::map_block *blk, bool flow = false, bool temperature = false);
/// sorts the block event vector into multiple vectors by type
/// mineral veins, what's under ice, blood smears and mud
extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block,

@ -37,16 +37,6 @@ distribution.
namespace DFHack
{
/**
* \ingroup grp_world
*/
enum WeatherType
{
CLEAR,
RAINING,
SNOWING
};
typedef unsigned char weather_map [5][5];
/**
* \ingroup grp_world
*/
@ -113,7 +103,6 @@ namespace DFHack
class DFHACK_EXPORT World : public Module
{
public:
weather_map * wmap;
World();
~World();
bool Start();

@ -111,9 +111,11 @@ function copyall(table)
end
function pos2xyz(pos)
local x = pos.x
if x and x ~= -30000 then
return x, pos.y, pos.z
if pos then
local x = pos.x
if x and x ~= -30000 then
return x, pos.y, pos.z
end
end
end
@ -173,6 +175,11 @@ function dfhack.maps.getTileSize()
return map.x_count, map.y_count, map.z_count
end
function dfhack.buildings.getSize(bld)
local x, y = bld.x1, bld.y1
return bld.x2+1-x, bld.y2+1-y, bld.centerx-x, bld.centery-y
end
-- Interactive
local print_banner = true
@ -199,6 +206,25 @@ function dfhack.interpreter(prompt,hfile,env)
local t_prompt = nil
local vcnt = 1
local pfix_handlers = {
['!'] = function(data)
print(table.unpack(data,2,data.n))
end,
['~'] = function(data)
print(table.unpack(data,2,data.n))
printall(data[2])
end,
['='] = function(data)
for i=2,data.n do
local varname = '_'..vcnt
prompt_env[varname] = data[i]
dfhack.print(varname..' = ')
safecall(print, data[i])
vcnt = vcnt + 1
end
end
}
setmetatable(prompt_env, { __index = env or _G })
while true do
@ -209,7 +235,7 @@ function dfhack.interpreter(prompt,hfile,env)
elseif cmdline ~= '' then
local pfix = string.sub(cmdline,1,1)
if not t_prompt and (pfix == '!' or pfix == '=') then
if not t_prompt and pfix_handlers[pfix] then
cmdline = 'return '..string.sub(cmdline,2)
else
pfix = nil
@ -236,18 +262,7 @@ function dfhack.interpreter(prompt,hfile,env)
if data[1] and data.n > 1 then
prompt_env._ = data[2]
if pfix == '!' then
safecall(print, table.unpack(data,2,data.n))
else
for i=2,data.n do
local varname = '_'..vcnt
prompt_env[varname] = data[i]
dfhack.print(varname..' = ')
safecall(print, data[i])
vcnt = vcnt + 1
end
end
safecall(pfix_handlers[pfix], data)
end
end
end
@ -256,5 +271,28 @@ function dfhack.interpreter(prompt,hfile,env)
return true
end
-- Command scripts
dfhack.internal.scripts = dfhack.internal.scripts or {}
local scripts = dfhack.internal.scripts
local hack_path = dfhack.getHackPath()
function dfhack.run_script(name,...)
local key = string.lower(name)
local file = hack_path..'scripts/'..name..'.lua'
local env = scripts[key]
if env == nil then
env = {}
setmetatable(env, { __index = base_env })
end
local f,perr = loadfile(file, 't', env)
if f == nil then
error(perr)
end
scripts[key] = env
return f(...)
end
-- Feed the table back to the require() mechanism.
return dfhack

@ -0,0 +1,500 @@
local dfhack = dfhack
local _ENV = dfhack.BASE_G
local buildings = dfhack.buildings
local utils = require 'utils'
-- Uninteresting values for filter attributes when reading them from DF memory.
-- Differs from the actual defaults of the job_item constructor in allow_artifact.
buildings.input_filter_defaults = {
item_type = -1,
item_subtype = -1,
mat_type = -1,
mat_index = -1,
flags1 = {},
-- Instead of noting those that allow artifacts, mark those that forbid them.
-- Leaves actually enabling artifacts to the discretion of the API user,
-- which is the right thing because unlike the game UI these filters are
-- used in a way that does not give the user a chance to choose manually.
flags2 = { allow_artifact = true },
flags3 = {},
flags4 = 0,
flags5 = 0,
reaction_class = '',
has_material_reaction_product = '',
metal_ore = -1,
min_dimension = -1,
has_tool_use = -1,
quantity = 1
}
--[[ Building input material table. ]]
local building_inputs = {
[df.building_type.Chair] = { { item_type=df.item_type.CHAIR, vector_id=df.job_item_vector_id.CHAIR } },
[df.building_type.Bed] = { { item_type=df.item_type.BED, vector_id=df.job_item_vector_id.BED } },
[df.building_type.Table] = { { item_type=df.item_type.TABLE, vector_id=df.job_item_vector_id.TABLE } },
[df.building_type.Coffin] = { { item_type=df.item_type.COFFIN, vector_id=df.job_item_vector_id.COFFIN } },
[df.building_type.FarmPlot] = { },
[df.building_type.TradeDepot] = { { flags2={ building_material=true, non_economic=true }, quantity=3 } },
[df.building_type.Door] = { { item_type=df.item_type.DOOR, vector_id=df.job_item_vector_id.DOOR } },
[df.building_type.Floodgate] = {
{
item_type=df.item_type.FLOODGATE,
vector_id=df.job_item_vector_id.FLOODGATE
}
},
[df.building_type.Box] = {
{
flags1={ empty=true },
item_type=df.item_type.BOX,
vector_id=df.job_item_vector_id.BOX
}
},
[df.building_type.Weaponrack] = {
{
item_type=df.item_type.WEAPONRACK,
vector_id=df.job_item_vector_id.WEAPONRACK
}
},
[df.building_type.Armorstand] = {
{
item_type=df.item_type.ARMORSTAND,
vector_id=df.job_item_vector_id.ARMORSTAND
}
},
[df.building_type.Cabinet] = {
{ item_type=df.item_type.CABINET, vector_id=df.job_item_vector_id.CABINET }
},
[df.building_type.Statue] = { { item_type=df.item_type.STATUE, vector_id=df.job_item_vector_id.STATUE } },
[df.building_type.WindowGlass] = { { item_type=df.item_type.WINDOW, vector_id=df.job_item_vector_id.WINDOW } },
[df.building_type.WindowGem] = {
{
item_type=df.item_type.SMALLGEM,
quantity=3,
vector_id=df.job_item_vector_id.ANY_GENERIC35
}
},
[df.building_type.Well] = {
{
item_type=df.item_type.BLOCKS,
vector_id=df.job_item_vector_id.ANY_GENERIC35
},
{
name='bucket',
flags2={ lye_milk_free=true },
item_type=df.item_type.BUCKET,
vector_id=df.job_item_vector_id.BUCKET
},
{
name='chain',
item_type=df.item_type.CHAIN,
vector_id=df.job_item_vector_id.CHAIN
},
{
name='mechanism',
item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.TRAPPARTS
}
},
[df.building_type.Bridge] = { { flags2={ building_material=true, non_economic=true }, quantity=-1 } },
[df.building_type.RoadDirt] = { },
[df.building_type.RoadPaved] = { { flags2={ building_material=true, non_economic=true }, quantity=-1 } },
[df.building_type.AnimalTrap] = {
{
flags1={ empty=true },
item_type=df.item_type.ANIMALTRAP,
vector_id=df.job_item_vector_id.ANIMALTRAP
}
},
[df.building_type.Support] = { { flags2={ building_material=true, non_economic=true } } },
[df.building_type.ArcheryTarget] = { { flags2={ building_material=true, non_economic=true } } },
[df.building_type.Chain] = { { item_type=df.item_type.CHAIN, vector_id=df.job_item_vector_id.CHAIN } },
[df.building_type.Cage] = { { item_type=df.item_type.CAGE, vector_id=df.job_item_vector_id.CAGE } },
[df.building_type.Weapon] = { { name='weapon', vector_id=df.job_item_vector_id.ANY_SPIKE } },
[df.building_type.ScrewPump] = {
{
item_type=df.item_type.BLOCKS,
vector_id=df.job_item_vector_id.ANY_GENERIC35
},
{
name='screw',
flags2={ screw=true },
item_type=df.item_type.TRAPCOMP,
vector_id=df.job_item_vector_id.ANY_WEAPON
},
{
name='pipe',
item_type=df.item_type.PIPE_SECTION,
vector_id=df.job_item_vector_id.PIPE_SECTION
}
},
[df.building_type.Construction] = { { flags2={ building_material=true, non_economic=true } } },
[df.building_type.Hatch] = {
{
item_type=df.item_type.HATCH_COVER,
vector_id=df.job_item_vector_id.HATCH_COVER
}
},
[df.building_type.GrateWall] = { { item_type=df.item_type.GRATE, vector_id=df.job_item_vector_id.GRATE } },
[df.building_type.GrateFloor] = { { item_type=df.item_type.GRATE, vector_id=df.job_item_vector_id.GRATE } },
[df.building_type.BarsVertical] = {
{ item_type=df.item_type.BAR, vector_id=df.job_item_vector_id.ANY_GENERIC35 }
},
[df.building_type.BarsFloor] = {
{ item_type=df.item_type.BAR, vector_id=df.job_item_vector_id.ANY_GENERIC35 }
},
[df.building_type.GearAssembly] = {
{
name='mechanism',
item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.TRAPPARTS
}
},
[df.building_type.AxleHorizontal] = {
{ item_type=df.item_type.WOOD, vector_id=df.job_item_vector_id.WOOD, quantity=-1 }
},
[df.building_type.AxleVertical] = { { item_type=df.item_type.WOOD, vector_id=df.job_item_vector_id.WOOD } },
[df.building_type.WaterWheel] = {
{
item_type=df.item_type.WOOD,
quantity=3,
vector_id=df.job_item_vector_id.WOOD
}
},
[df.building_type.Windmill] = {
{
item_type=df.item_type.WOOD,
quantity=4,
vector_id=df.job_item_vector_id.WOOD
}
},
[df.building_type.TractionBench] = {
{
item_type=df.item_type.TRACTION_BENCH,
vector_id=df.job_item_vector_id.TRACTION_BENCH
}
},
[df.building_type.Slab] = { { item_type=df.item_type.SLAB } },
[df.building_type.NestBox] = { { has_tool_use=df.tool_uses.NEST_BOX, item_type=df.item_type.TOOL } },
[df.building_type.Hive] = { { has_tool_use=df.tool_uses.HIVE, item_type=df.item_type.TOOL } },
[df.building_type.Rollers] = {
{
name='mechanism',
item_type=df.item_type.TRAPPARTS,
quantity=-1,
vector_id=df.job_item_vector_id.TRAPPARTS
},
{
name='chain',
item_type=df.item_type.CHAIN,
vector_id=df.job_item_vector_id.CHAIN
}
}
}
--[[ Furnace building input material table. ]]
local furnace_inputs = {
[df.furnace_type.WoodFurnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
[df.furnace_type.Smelter] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
[df.furnace_type.GlassFurnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
[df.furnace_type.Kiln] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
[df.furnace_type.MagmaSmelter] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } },
[df.furnace_type.MagmaGlassFurnace] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } },
[df.furnace_type.MagmaKiln] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } }
}
--[[ Workshop building input material table. ]]
local workshop_inputs = {
[df.workshop_type.Carpenters] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Farmers] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Masons] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Craftsdwarfs] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Jewelers] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.MetalsmithsForge] = {
{
name='anvil',
flags2={ fire_safe=true },
item_type=df.item_type.ANVIL,
vector_id=df.job_item_vector_id.ANVIL
},
{ flags2={ building_material=true, fire_safe=true, non_economic=true } }
},
[df.workshop_type.MagmaForge] = {
{
name='anvil',
flags2={ magma_safe=true },
item_type=df.item_type.ANVIL,
vector_id=df.job_item_vector_id.ANVIL
},
{ flags2={ building_material=true, magma_safe=true, non_economic=true } }
},
[df.workshop_type.Bowyers] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Mechanics] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Siege] = { { flags2={ building_material=true, non_economic=true }, quantity=3 } },
[df.workshop_type.Butchers] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Leatherworks] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Tanners] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Clothiers] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Fishery] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Still] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Loom] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Quern] = { { item_type=df.item_type.QUERN, vector_id=df.job_item_vector_id.QUERN } },
[df.workshop_type.Kennels] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Kitchen] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Ashery] = {
{
item_type=df.item_type.BLOCKS,
vector_id=df.job_item_vector_id.ANY_GENERIC35
},
{
name='barrel',
flags1={ empty=true },
item_type=df.item_type.BARREL,
vector_id=df.job_item_vector_id.BARREL
},
{
name='bucket',
flags2={ lye_milk_free=true },
item_type=df.item_type.BUCKET,
vector_id=df.job_item_vector_id.BUCKET
}
},
[df.workshop_type.Dyers] = {
{
name='barrel',
flags1={ empty=true },
item_type=df.item_type.BARREL,
vector_id=df.job_item_vector_id.BARREL
},
{
name='bucket',
flags2={ lye_milk_free=true },
item_type=df.item_type.BUCKET,
vector_id=df.job_item_vector_id.BUCKET
}
},
[df.workshop_type.Millstone] = {
{
item_type=df.item_type.MILLSTONE,
vector_id=df.job_item_vector_id.MILLSTONE
},
{
name='mechanism',
item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.TRAPPARTS
}
}
}
--[[ Trap building input material table. ]]
local trap_inputs = {
[df.trap_type.StoneFallTrap] = {
{
name='mechanism',
item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.TRAPPARTS
}
},
[df.trap_type.WeaponTrap] = {
{
name='mechanism',
item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.TRAPPARTS
},
{
name='weapon',
vector_id=df.job_item_vector_id.ANY_WEAPON
}
},
[df.trap_type.Lever] = {
{
name='mechanism',
item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.TRAPPARTS
}
},
[df.trap_type.PressurePlate] = {
{
name='mechanism',
item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.TRAPPARTS
}
},
[df.trap_type.CageTrap] = {
{
name='mechanism',
item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.TRAPPARTS
}
},
[df.trap_type.TrackStop] = { { flags2={ building_material=true, non_economic=true } } }
}
--[[ Functions for lookup in tables. ]]
local function get_custom_inputs(custom)
local defn = df.building_def.find(custom)
if defn ~= nil then
return utils.clone_with_default(defn.build_items, buildings.input_filter_defaults)
end
end
local function get_inputs_by_type(type,subtype,custom)
if type == df.building_type.Workshop then
if subtype == df.workshop_type.Custom then
return get_custom_inputs(custom)
else
return workshop_inputs[subtype]
end
elseif type == df.building_type.Furnace then
if subtype == df.furnace_type.Custom then
return get_custom_inputs(custom)
else
return furnace_inputs[subtype]
end
elseif type == df.building_type.Trap then
return trap_inputs[subtype]
else
return building_inputs[type]
end
end
local function augment_input(input, argtable)
local rv = {}
local arg = argtable[input.name or 'material']
if arg then
utils.assign(rv, arg)
end
utils.assign(rv, input)
if rv.mat_index and safe_index(rv, 'flags2', 'non_economic') then
rv.flags2.non_economic = false
end
rv.new = true
rv.name = nil
return rv
end
function buildings.getFiltersByType(argtable,type,subtype,custom)
local inputs = get_inputs_by_type(type,subtype,custom)
if not inputs then
return nil
end
local rv = {}
for i,v in ipairs(inputs) do
rv[i] = augment_input(v, argtable)
end
return rv
end
--[[
Wraps all steps necessary to create a building with
a construct job into one function.
dfhack.buildings.constructBuilding{
-- Position:
pos = { x = ..., y = ..., z = ... },
-- OR
x = ..., y = ..., z = ...,
-- Type:
type = df.building_type.FOO, subtype = ..., custom = ...,
-- Field initialization:
fields = { ... },
-- Size and orientation:
width = ..., height = ..., direction = ...,
-- Abort if not all tiles in the rectangle are available:
full_rectangle = true,
-- Materials:
items = { item, item ... },
-- OR
filters = { { ... }, { ... }... }
-- OR
abstract = true
-- OR
material = { filter_properties... }
mechanism = { filter_properties... }
barrel, bucket, chain, anvil, screw, pipe
}
Returns: the created building, or 'nil, error'
--]]
function buildings.constructBuilding(info)
local btype = info.type
local subtype = info.subtype or -1
local custom = info.custom or -1
local filters = info.filters
if not (info.pos or info.x) then
error('position is required')
end
if not (info.abstract or info.items or filters) then
filters = buildings.getFiltersByType(info,btype,subtype,custom)
if not filters then
error('one of items, filters or abstract is required')
end
elseif filters then
for _,v in ipairs(filters) do
v.new = true
end
end
if type(btype) ~= 'number' or not df.building_type[btype] then
error('Invalid building type: '..tostring(btype))
end
local pos = info.pos or xyz2pos(info.x, info.y, info.z)
local instance = buildings.allocInstance(pos, btype, subtype, custom)
if not instance then
error('Could not create building of type '..df.building_type[btype])
end
local to_delete = instance
return dfhack.with_finalize(
function()
df.delete(to_delete)
end,
function()
if info.fields then
instance:assign(info.fields)
end
local ok,w,h,area,r_area = buildings.setSize(
instance,info.width,info.height,info.direction
)
if not ok then
return nil, "cannot place at this position"
end
if info.full_rectangle and area ~= r_area then
return nil, "not all tiles can be used"
end
if info.abstract then
ok = buildings.constructAbstract(instance)
elseif info.items then
ok = buildings.constructWithItems(instance, info.items)
else
ok = buildings.constructWithFilters(instance, filters)
end
if not ok then
return nil, "could not construct the building"
end
-- Success
to_delete = nil
return instance
end
)
end
return buildings

@ -0,0 +1,234 @@
--[[ DataDumper.lua
Copyright (c) 2007 Olivetti-Engineering SA
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
]]
local _ENV = mkmodule('dumper')
local dumplua_closure = [[
local closures = {}
local function closure(t)
closures[#closures+1] = t
t[1] = assert(loadstring(t[1]))
return t[1]
end
for _,t in pairs(closures) do
for i = 2,#t do
debug.setupvalue(t[1], i-1, t[i])
end
end
]]
local lua_reserved_keywords = {
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while' }
local function keys(t)
local res = {}
local oktypes = { stringstring = true, numbernumber = true }
local function cmpfct(a,b)
if oktypes[type(a)..type(b)] then
return a < b
else
return type(a) < type(b)
end
end
for k in pairs(t) do
res[#res+1] = k
end
table.sort(res, cmpfct)
return res
end
local c_functions = {}
for _,lib in pairs{'_G', 'string', 'table', 'math',
'io', 'os', 'coroutine', 'package', 'debug'} do
local t = _G[lib] or {}
lib = lib .. "."
if lib == "_G." then lib = "" end
for k,v in pairs(t) do
if type(v) == 'function' and not pcall(string.dump, v) then
c_functions[v] = lib..k
end
end
end
function DataDumper(value, varname, fastmode, ident, indent_step)
indent_step = indent_step or 2
local defined, dumplua = {}
-- Local variables for speed optimization
local string_format, type, string_dump, string_rep =
string.format, type, string.dump, string.rep
local tostring, pairs, table_concat =
tostring, pairs, table.concat
local keycache, strvalcache, out, closure_cnt = {}, {}, {}, 0
setmetatable(strvalcache, {__index = function(t,value)
local res = string_format('%q', value)
t[value] = res
return res
end})
local fcts = {
string = function(value) return strvalcache[value] end,
number = function(value) return value end,
boolean = function(value) return tostring(value) end,
['nil'] = function(value) return 'nil' end,
['function'] = function(value)
return string_format("loadstring(%q)", string_dump(value))
end,
userdata = function() error("Cannot dump userdata") end,
thread = function() error("Cannot dump threads") end,
}
local function test_defined(value, path)
if defined[value] then
if path:match("^getmetatable.*%)$") then
out[#out+1] = string_format("s%s, %s)\n", path:sub(2,-2), defined[value])
else
out[#out+1] = path .. " = " .. defined[value] .. "\n"
end
return true
end
defined[value] = path
end
local function make_key(t, key)
local s
if type(key) == 'string' and key:match('^[_%a][_%w]*$') then
s = key .. "="
else
s = "[" .. dumplua(key, 0) .. "]="
end
t[key] = s
return s
end
for _,k in ipairs(lua_reserved_keywords) do
keycache[k] = '["'..k..'"] = '
end
if fastmode then
fcts.table = function (value)
-- Table value
local numidx = 1
out[#out+1] = "{"
for key,val in pairs(value) do
if key == numidx then
numidx = numidx + 1
else
out[#out+1] = keycache[key]
end
local str = dumplua(val)
out[#out+1] = str..","
end
if string.sub(out[#out], -1) == "," then
out[#out] = string.sub(out[#out], 1, -2);
end
out[#out+1] = "}"
return ""
end
else
fcts.table = function (value, ident, path)
if test_defined(value, path) then return "nil" end
-- Table value
local sep, str, numidx, totallen = " ", {}, 1, 0
local meta, metastr = (debug or getfenv()).getmetatable(value)
if meta then
ident = ident + 1
metastr = dumplua(meta, ident, "getmetatable("..path..")")
totallen = totallen + #metastr + 16
end
for _,key in pairs(keys(value)) do
local val = value[key]
local s = ""
local subpath = path
if key == numidx then
subpath = subpath .. "[" .. numidx .. "]"
numidx = numidx + 1
else
s = keycache[key]
if not s:match "^%[" then subpath = subpath .. "." end
subpath = subpath .. s:gsub("%s*=%s*$","")
end
s = s .. dumplua(val, ident+1, subpath)
str[#str+1] = s
totallen = totallen + #s + 2
end
if totallen > 80 then
sep = "\n" .. string_rep(' ', indent_step*(ident+1))
end
str = "{"..sep..table_concat(str, ","..sep).." "..sep:sub(1,-1-indent_step).."}"
if meta then
sep = sep:sub(1,-3)
return "setmetatable("..sep..str..","..sep..metastr..sep:sub(1,-3)..")"
end
return str
end
fcts['function'] = function (value, ident, path)
if test_defined(value, path) then return "nil" end
if c_functions[value] then
return c_functions[value]
elseif debug == nil or debug.getupvalue(value, 1) == nil then
return string_format("loadstring(%q)", string_dump(value))
end
closure_cnt = closure_cnt + 1
local res = {string.dump(value)}
for i = 1,math.huge do
local name, v = debug.getupvalue(value,i)
if name == nil then break end
res[i+1] = v
end
return "closure " .. dumplua(res, ident, "closures["..closure_cnt.."]")
end
end
function dumplua(value, ident, path)
return fcts[type(value)](value, ident, path)
end
if varname == nil then
varname = "return "
elseif varname:match("^[%a_][%w_]*$") then
varname = varname .. " = "
end
if fastmode then
setmetatable(keycache, {__index = make_key })
out[1] = varname
table.insert(out,dumplua(value, 0))
return table.concat(out)
else
setmetatable(keycache, {__index = make_key })
local items = {}
for i=1,10 do items[i] = '' end
items[3] = dumplua(value, ident or 0, "t")
if closure_cnt > 0 then
items[1], items[6] = dumplua_closure:match("(.*\n)\n(.*)")
out[#out+1] = ""
end
if #out > 0 then
items[2], items[4] = "local t = ", "\n"
items[5] = table.concat(out)
items[7] = varname .. "t"
else
items[2] = varname
end
return table.concat(items)
end
end
return _ENV

@ -1,5 +1,7 @@
local _ENV = mkmodule('utils')
local df = df
-- Comparator function
function compare(a,b)
if a < b then
@ -26,6 +28,43 @@ function compare_name(a,b)
end
end
-- Make a field comparator
function compare_field(field,cmp)
cmp = cmp or compare
if field then
return function (a,b)
return cmp(a[field],b[field])
end
else
return cmp
end
end
-- Make a comparator of field vs key
function compare_field_key(field,cmp)
cmp = cmp or compare
if field then
return function (a,b)
return cmp(a[field],b)
end
else
return cmp
end
end
function is_container(obj)
return df.isvalid(obj) == 'ref' and obj._kind == 'container'
end
-- Make a sequence of numbers in 1..size
function make_index_sequence(size)
local index = {}
for i=1,size do
index[i] = i
end
return index
end
--[[
Sort items in data according to ordering.
@ -75,10 +114,7 @@ function make_sort_order(data,ordering)
end
-- Make an order table
local index = {}
for i=1,size do
index[i] = i
end
local index = make_index_sequence(size)
-- Sort the ordering table
table.sort(index, function(ia,ib)
@ -111,4 +147,218 @@ function make_sort_order(data,ordering)
return index
end
--[[
Recursively assign data into a table.
--]]
function assign(tgt,src)
if df.isvalid(tgt) == 'ref' then
df.assign(tgt, src)
elseif type(tgt) == 'table' then
for k,v in pairs(src) do
if type(v) == 'table' then
local cv = tgt[k]
if cv == nil then
cv = {}
tgt[k] = cv
end
assign(cv, v)
else
tgt[k] = v
end
end
else
error('Invalid assign target type: '..tostring(tgt))
end
return tgt
end
local function copy_field(obj,k,v,deep)
if v == nil then
return NULL
end
if deep then
local field = obj:_field(k)
if field == v then
return clone(v,deep)
end
end
return v
end
-- Copy the object as lua data structures.
function clone(obj,deep)
if type(obj) == 'table' then
if deep then
return assign({},obj)
else
return copyall(obj)
end
elseif df.isvalid(obj) == 'ref' then
local kind = obj._kind
if kind == 'primitive' then
return obj.value
elseif kind == 'bitfield' then
local rv = {}
for k,v in pairs(obj) do
rv[k] = v
end
return rv
elseif kind == 'container' then
local rv = {}
for k,v in ipairs(obj) do
rv[k+1] = copy_field(obj,k,v,deep)
end
return rv
else -- struct
local rv = {}
for k,v in pairs(obj) do
rv[k] = copy_field(obj,k,v,deep)
end
return rv
end
else
return obj
end
end
local function get_default(default,key,base)
if type(default) == 'table' then
local dv = default[key]
if dv == nil then
dv = default._default
end
if dv == nil then
dv = base
end
return dv
else
return default
end
end
-- Copy the object as lua data structures, skipping values matching defaults.
function clone_with_default(obj,default,force)
local rv = nil
local function setrv(k,v)
if v ~= nil then
if rv == nil then
rv = {}
end
rv[k] = v
end
end
if default == nil then
return nil
elseif type(obj) == 'table' then
for k,v in pairs(obj) do
setrv(k, clone_with_default(v, get_default(default,k)))
end
elseif df.isvalid(obj) == 'ref' then
local kind = obj._kind
if kind == 'primitive' then
return clone_with_default(obj.value,default,force)
elseif kind == 'bitfield' then
for k,v in pairs(obj) do
setrv(k, clone_with_default(v, get_default(default,k,false)))
end
elseif kind == 'container' then
for k,v in ipairs(obj) do
setrv(k+1, clone_with_default(v, default, true))
end
else -- struct
for k,v in pairs(obj) do
setrv(k, clone_with_default(v, get_default(default,k)))
end
end
elseif obj == default and not force then
return nil
elseif obj == nil then
return NULL
else
return obj
end
if force and rv == nil then
rv = {}
end
return rv
end
-- Sort a vector or lua table
function sort_vector(vector,field,cmp)
local fcmp = compare_field(field,cmp)
local scmp = function(a,b)
return fcmp(a,b) < 0
end
if df.isvalid(vector) then
if vector._kind ~= 'container' then
error('Container expected: '..tostring(vector))
end
local items = clone(vector, true)
table.sort(items, scmp)
vector:assign(items)
else
table.sort(vector, scmp)
end
return vector
end
-- Binary search in a vector or lua table
function binsearch(vector,key,field,cmp,min,max)
if not(min and max) then
if df.isvalid(vector) then
min = -1
max = #vector
else
min = 0
max = #vector+1
end
end
local mf = math.floor
local fcmp = compare_field_key(field,cmp)
while true do
local mid = mf((min+max)/2)
if mid <= min then
return nil, false, max
end
local item = vector[mid]
local cv = fcmp(item, key)
if cv == 0 then
return item, true, mid
elseif cv < 0 then
min = mid
else
max = mid
end
end
end
-- Binary search and insert
function insert_sorted(vector,item,field,cmp)
local key = item
if field and item then
key = item[field]
end
local cur,found,pos = binsearch(vector,key,field,cmp)
if found then
return false,cur,pos
else
if df.isvalid(vector) then
vector:insert(pos, item)
else
table.insert(vector, pos, item)
end
return true,vector[pos],pos
end
end
-- Binary search, then insert or overwrite
function insert_or_update(vector,item,field,cmp)
local added,cur,pos = insert_sorted(vector,item,field,cmp)
if not added then
vector[pos] = item
cur = vector[pos]
end
return added,cur,pos
end
return _ENV

@ -35,19 +35,110 @@ using namespace std;
#include "Types.h"
#include "Error.h"
#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/Job.h"
#include "ModuleFactory.h"
#include "Core.h"
#include "TileTypes.h"
#include "MiscUtils.h"
using namespace DFHack;
#include "DataDefs.h"
#include "df/world.h"
#include "df/ui.h"
#include "df/ui_look_list.h"
#include "df/d_init.h"
#include "df/item.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/general_ref_building_holderst.h"
#include "df/buildings_other_id.h"
#include "df/building_design.h"
#include "df/building_def.h"
#include "df/building_axle_horizontalst.h"
#include "df/building_trapst.h"
#include "df/building_bridgest.h"
#include "df/building_coffinst.h"
#include "df/building_civzonest.h"
#include "df/building_stockpilest.h"
#include "df/building_furnacest.h"
#include "df/building_workshopst.h"
#include "df/building_screw_pumpst.h"
#include "df/building_water_wheelst.h"
#include "df/building_wellst.h"
#include "df/building_rollersst.h"
using namespace df::enums;
using df::global::ui;
using df::global::world;
using df::global::d_init;
using df::global::building_next_id;
using df::global::process_jobs;
using df::building_def;
static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile)
{
if (!extent.extents)
return NULL;
int dx = tile.x - extent.x;
int dy = tile.y - extent.y;
if (dx < 0 || dy < 0 || dx >= extent.width || dy >= extent.height)
return NULL;
return &extent.extents[dx + dy*extent.width];
}
/*
* A monitor to work around this bug, in its application to buildings:
*
* http://www.bay12games.com/dwarves/mantisbt/view.php?id=1416
*/
bool buildings_do_onupdate = false;
void buildings_onStateChange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
buildings_do_onupdate = true;
break;
case SC_MAP_UNLOADED:
buildings_do_onupdate = false;
break;
default:
break;
}
}
void buildings_onUpdate(color_ostream &out)
{
buildings_do_onupdate = false;
df::job_list_link *link = world->job_list.next;
for (; link; link = link->next) {
df::job *job = link->item;
if (job->job_type != job_type::ConstructBuilding)
continue;
if (job->job_items.empty())
continue;
buildings_do_onupdate = true;
for (size_t i = 0; i < job->items.size(); i++)
{
df::job_item_ref *iref = job->items[i];
if (iref->role != df::job_item_ref::Reagent)
continue;
df::job_item *item = vector_get(job->job_items, iref->job_item_idx);
if (!item)
continue;
// Convert Reagent to Hauled, while decrementing quantity
item->quantity = std::max(0, item->quantity-1);
iref->role = df::job_item_ref::Hauled;
iref->job_item_idx = -1;
}
}
}
uint32_t Buildings::getNumBuildings()
{
return world->buildings.all.size();
@ -86,3 +177,850 @@ bool Buildings::ReadCustomWorkshopTypes(map <uint32_t, string> & btypes)
return true;
}
df::building *Buildings::findAtTile(df::coord pos)
{
auto occ = Maps::getTileOccupancy(pos);
if (!occ || !occ->bits.building)
return NULL;
auto &vec = df::building::get_vector();
for (size_t i = 0; i < vec.size(); i++)
{
auto bld = vec[i];
if (pos.z != bld->z ||
pos.x < bld->x1 || pos.x > bld->x2 ||
pos.y < bld->y1 || pos.y > bld->y2)
continue;
if (!bld->isSettingOccupancy())
continue;
if (bld->room.extents && bld->isExtentShaped())
{
auto etile = getExtentTile(bld->room, pos);
if (!etile || !*etile)
continue;
}
return bld;
}
return NULL;
}
bool Buildings::findCivzonesAt(std::vector<df::building_civzonest*> *pvec, df::coord pos)
{
pvec->clear();
auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE];
for (size_t i = 0; i < vec.size(); i++)
{
auto bld = strict_virtual_cast<df::building_civzonest>(vec[i]);
if (!bld || bld->z != pos.z || !containsTile(bld, pos))
continue;
pvec->push_back(bld);
}
return !pvec->empty();
}
df::building *Buildings::allocInstance(df::coord pos, df::building_type type, int subtype, int custom)
{
if (!building_next_id)
return NULL;
// Allocate object
const char *classname = ENUM_ATTR(building_type, classname, type);
if (!classname)
return NULL;
auto id = virtual_identity::find(classname);
if (!id)
return NULL;
df::building *bld = (df::building*)id->allocate();
if (!bld)
return NULL;
// Init base fields
bld->x1 = bld->x2 = bld->centerx = pos.x;
bld->y1 = bld->y2 = bld->centery = pos.y;
bld->z = pos.z;
bld->race = ui->race_id;
if (subtype != -1)
bld->setSubtype(subtype);
if (custom != -1)
bld->setCustomType(custom);
bld->setMaterialAmount(1);
// Type specific init
switch (type)
{
case building_type::Well:
{
auto obj = (df::building_wellst*)bld;
obj->bucket_z = bld->z;
break;
}
case building_type::Furnace:
{
auto obj = (df::building_furnacest*)bld;
obj->melt_remainder.resize(df::inorganic_raw::get_vector().size(), 0);
break;
}
case building_type::Coffin:
{
auto obj = (df::building_coffinst*)bld;
obj->initBurialFlags(); // DF has this copy&pasted
break;
}
case building_type::Trap:
{
auto obj = (df::building_trapst*)bld;
if (obj->trap_type == trap_type::PressurePlate)
obj->unk_cc = 500;
break;
}
default:
break;
}
return bld;
}
static void makeOneDim(df::coord2d &size, df::coord2d &center, bool vertical)
{
if (vertical)
size.x = 1;
else
size.y = 1;
center = size/2;
}
bool Buildings::getCorrectSize(df::coord2d &size, df::coord2d &center,
df::building_type type, int subtype, int custom, int direction)
{
using namespace df::enums::building_type;
if (size.x <= 0)
size.x = 1;
if (size.y <= 0)
size.y = 1;
switch (type)
{
case FarmPlot:
case Bridge:
case RoadDirt:
case RoadPaved:
case Stockpile:
case Civzone:
center = size/2;
return true;
case TradeDepot:
case Shop:
size = df::coord2d(5,5);
center = df::coord2d(2,2);
return false;
case SiegeEngine:
case Windmill:
case Wagon:
size = df::coord2d(3,3);
center = df::coord2d(1,1);
return false;
case AxleHorizontal:
makeOneDim(size, center, direction);
return true;
case Rollers:
makeOneDim(size, center, (direction&1) == 0);
return true;
case WaterWheel:
size = df::coord2d(3,3);
makeOneDim(size, center, direction);
return false;
case Workshop:
{
using namespace df::enums::workshop_type;
switch ((df::workshop_type)subtype)
{
case Quern:
case Millstone:
case Tool:
size = df::coord2d(1,1);
center = df::coord2d(0,0);
break;
case Siege:
case Kennels:
size = df::coord2d(5,5);
center = df::coord2d(2,2);
break;
case Custom:
if (auto def = df::building_def::find(custom))
{
size = df::coord2d(def->dim_x, def->dim_y);
center = df::coord2d(def->workloc_x, def->workloc_y);
break;
}
default:
size = df::coord2d(3,3);
center = df::coord2d(1,1);
}
return false;
}
case Furnace:
{
using namespace df::enums::furnace_type;
switch ((df::furnace_type)subtype)
{
case Custom:
if (auto def = df::building_def::find(custom))
{
size = df::coord2d(def->dim_x, def->dim_y);
center = df::coord2d(def->workloc_x, def->workloc_y);
break;
}
default:
size = df::coord2d(3,3);
center = df::coord2d(1,1);
}
return false;
}
case ScrewPump:
{
using namespace df::enums::screw_pump_direction;
switch ((df::screw_pump_direction)direction)
{
case FromEast:
size = df::coord2d(2,1);
center = df::coord2d(1,0);
break;
case FromSouth:
size = df::coord2d(1,2);
center = df::coord2d(0,1);
break;
case FromWest:
size = df::coord2d(2,1);
center = df::coord2d(0,0);
break;
default:
size = df::coord2d(1,2);
center = df::coord2d(0,0);
}
return false;
}
default:
size = df::coord2d(1,1);
center = df::coord2d(0,0);
return false;
}
}
bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size,
df::building_extents *ext,
bool create_ext, bool allow_occupied)
{
bool found_any = false;
for (int dx = 0; dx < size.x; dx++)
{
for (int dy = 0; dy < size.y; dy++)
{
df::coord tile = pos + df::coord(dx,dy,0);
uint8_t *etile = NULL;
// Exclude using extents
if (ext && ext->extents)
{
etile = getExtentTile(*ext, tile);
if (!etile || !*etile)
continue;
}
// Look up map block
df::map_block *block = Maps::getTileBlock(tile);
if (!block)
return false;
df::coord2d btile = df::coord2d(tile) & 15;
bool allowed = true;
// Check occupancy and tile type
if (!allow_occupied &&
block->occupancy[btile.x][btile.y].bits.building)
allowed = false;
else
{
auto tile = block->tiletype[btile.x][btile.y];
if (!HighPassable(tile))
allowed = false;
}
// Create extents if requested
if (allowed)
found_any = true;
else
{
if (!ext || !create_ext)
return false;
if (!ext->extents)
{
ext->extents = new uint8_t[size.x*size.y];
ext->x = pos.x;
ext->y = pos.y;
ext->width = size.x;
ext->height = size.y;
memset(ext->extents, 1, size.x*size.y);
etile = getExtentTile(*ext, tile);
}
if (!etile)
return false;
*etile = 0;
}
}
}
return found_any;
}
std::pair<df::coord,df::coord2d> Buildings::getSize(df::building *bld)
{
CHECK_NULL_POINTER(bld);
df::coord pos(bld->x1,bld->y1,bld->z);
return std::pair<df::coord,df::coord2d>(pos, df::coord2d(bld->x2+1,bld->y2+1) - pos);
}
static bool checkBuildingTiles(df::building *bld, bool can_change)
{
auto psize = Buildings::getSize(bld);
return Buildings::checkFreeTiles(psize.first, psize.second, &bld->room,
can_change && bld->isExtentShaped(),
!bld->isSettingOccupancy());
}
int Buildings::countExtentTiles(df::building_extents *ext, int defval)
{
if (!ext || !ext->extents)
return defval;
int cnt = 0;
for (int i = 0; i < ext->width * ext->height; i++)
if (ext->extents[i])
cnt++;
return cnt;
}
bool Buildings::containsTile(df::building *bld, df::coord2d tile, bool room)
{
CHECK_NULL_POINTER(bld);
if (room)
{
if (!bld->is_room || !bld->room.extents)
return false;
}
else
{
if (tile.x < bld->x1 || tile.x > bld->x2 || tile.y < bld->y1 || tile.y >= bld->y2)
return false;
}
if (bld->room.extents && (room || bld->isExtentShaped()))
{
uint8_t *etile = getExtentTile(bld->room, tile);
if (!etile || !*etile)
return false;
}
return true;
}
bool Buildings::hasSupport(df::coord pos, df::coord2d size)
{
for (int dx = -1; dx <= size.x; dx++)
{
for (int dy = -1; dy <= size.y; dy++)
{
// skip corners
if ((dx < 0 || dx == size.x) && (dy < 0 || dy == size.y))
continue;
df::coord tile = pos + df::coord(dx,dy,0);
df::map_block *block = Maps::getTileBlock(tile);
if (!block)
continue;
df::coord2d btile = df::coord2d(tile) & 15;
if (!isOpenTerrain(block->tiletype[btile.x][btile.y]))
return true;
}
}
return false;
}
static int computeMaterialAmount(df::building *bld)
{
auto size = Buildings::getSize(bld).second;
int cnt = size.x * size.y;
if (bld->room.extents && bld->isExtentShaped())
cnt = Buildings::countExtentTiles(&bld->room, cnt);
return cnt/4 + 1;
}
bool Buildings::setSize(df::building *bld, df::coord2d size, int direction)
{
CHECK_NULL_POINTER(bld);
CHECK_INVALID_ARGUMENT(bld->id == -1);
// Delete old extents
if (bld->room.extents)
{
delete[] bld->room.extents;
bld->room.extents = NULL;
}
// Compute correct size and apply it
df::coord2d center;
getCorrectSize(size, center, bld->getType(), bld->getSubtype(),
bld->getCustomType(), direction);
bld->x2 = bld->x1 + size.x - 1;
bld->y2 = bld->y1 + size.y - 1;
bld->centerx = bld->x1 + center.x;
bld->centery = bld->y1 + center.y;
auto type = bld->getType();
using namespace df::enums::building_type;
switch (type)
{
case WaterWheel:
{
auto obj = (df::building_water_wheelst*)bld;
obj->is_vertical = !!direction;
break;
}
case AxleHorizontal:
{
auto obj = (df::building_axle_horizontalst*)bld;
obj->is_vertical = !!direction;
break;
}
case ScrewPump:
{
auto obj = (df::building_screw_pumpst*)bld;
obj->direction = (df::screw_pump_direction)direction;
break;
}
case Rollers:
{
auto obj = (df::building_rollersst*)bld;
obj->direction = (df::screw_pump_direction)direction;
break;
}
case Bridge:
{
auto obj = (df::building_bridgest*)bld;
auto psize = getSize(bld);
obj->gate_flags.bits.has_support = hasSupport(psize.first, psize.second);
obj->direction = (df::building_bridgest::T_direction)direction;
break;
}
default:
break;
}
bool ok = checkBuildingTiles(bld, true);
if (type != Construction)
bld->setMaterialAmount(computeMaterialAmount(bld));
return ok;
}
static void markBuildingTiles(df::building *bld, bool remove)
{
bool use_extents = bld->room.extents && bld->isExtentShaped();
bool stockpile = (bld->getType() == building_type::Stockpile);
bool complete = (bld->getBuildStage() >= bld->getMaxBuildStage());
if (remove)
stockpile = complete = false;
for (int tx = bld->x1; tx <= bld->x2; tx++)
{
for (int ty = bld->y1; ty <= bld->y2; ty++)
{
df::coord tile(tx,ty,bld->z);
if (use_extents)
{
uint8_t *etile = getExtentTile(bld->room, tile);
if (!etile || !*etile)
continue;
}
df::map_block *block = Maps::getTileBlock(tile);
df::coord2d btile = df::coord2d(tile) & 15;
auto &des = block->designation[btile.x][btile.y];
des.bits.pile = stockpile;
if (!remove)
des.bits.dig = tile_dig_designation::No;
if (complete)
bld->updateOccupancy(tx, ty);
else
{
auto &occ = block->occupancy[btile.x][btile.y];
if (remove)
occ.bits.building = tile_building_occ::None;
else
occ.bits.building = tile_building_occ::Planned;
}
}
}
}
static void linkRooms(df::building *bld)
{
auto &vec = world->buildings.other[buildings_other_id::ANY_FREE];
bool changed = false;
for (size_t i = 0; i < vec.size(); i++)
{
auto room = vec[i];
if (!room->is_room || room->z != bld->z)
continue;
uint8_t *pext = getExtentTile(room->room, df::coord2d(bld->x1, bld->y1));
if (!pext || !*pext)
continue;
changed = true;
room->children.push_back(bld);
bld->parents.push_back(room);
// TODO: the game updates room rent here if economy is enabled
}
if (changed)
df::global::ui->equipment.update.bits.buildings = true;
}
static void unlinkRooms(df::building *bld)
{
for (size_t i = 0; i < bld->parents.size(); i++)
{
auto parent = bld->parents[i];
int idx = linear_index(parent->children, bld);
vector_erase_at(parent->children, idx);
}
bld->parents.clear();
}
static void linkBuilding(df::building *bld)
{
bld->id = (*building_next_id)++;
world->buildings.all.push_back(bld);
bld->categorize(true);
if (bld->isSettingOccupancy())
markBuildingTiles(bld, false);
linkRooms(bld);
Job::checkBuildingsNow();
}
static void createDesign(df::building *bld, bool rough)
{
auto job = bld->jobs[0];
job->mat_type = bld->mat_type;
job->mat_index = bld->mat_index;
if (bld->needsDesign())
{
auto act = (df::building_actual*)bld;
act->design = new df::building_design();
act->design->flags.bits.rough = rough;
}
}
static int getMaxStockpileId()
{
auto &vec = world->buildings.other[buildings_other_id::STOCKPILE];
int max_id = 0;
for (size_t i = 0; i < vec.size(); i++)
{
auto bld = strict_virtual_cast<df::building_stockpilest>(vec[i]);
if (bld)
max_id = std::max(max_id, bld->stockpile_number);
}
return max_id;
}
bool Buildings::constructAbstract(df::building *bld)
{
CHECK_NULL_POINTER(bld);
CHECK_INVALID_ARGUMENT(bld->id == -1);
CHECK_INVALID_ARGUMENT(!bld->isActual());
if (!checkBuildingTiles(bld, false))
return false;
switch (bld->getType())
{
case building_type::Stockpile:
if (auto stock = strict_virtual_cast<df::building_stockpilest>(bld))
stock->stockpile_number = getMaxStockpileId() + 1;
break;
default:
break;
}
linkBuilding(bld);
if (!bld->flags.bits.exists)
{
bld->flags.bits.exists = true;
bld->initFarmSeasons();
}
return true;
}
static bool linkForConstruct(df::job* &job, df::building *bld)
{
if (!checkBuildingTiles(bld, false))
return false;
auto ref = df::allocate<df::general_ref_building_holderst>();
if (!ref)
{
Core::printerr("Could not allocate general_ref_building_holderst\n");
return false;
}
linkBuilding(bld);
ref->building_id = bld->id;
job = new df::job();
job->job_type = df::job_type::ConstructBuilding;
job->pos = df::coord(bld->centerx, bld->centery, bld->z);
job->references.push_back(ref);
bld->jobs.push_back(job);
Job::linkIntoWorld(job);
return true;
}
static bool needsItems(df::building *bld)
{
if (!bld->isActual())
return false;
switch (bld->getType())
{
case building_type::FarmPlot:
case building_type::RoadDirt:
return false;
default:
return true;
}
}
bool Buildings::constructWithItems(df::building *bld, std::vector<df::item*> items)
{
CHECK_NULL_POINTER(bld);
CHECK_INVALID_ARGUMENT(bld->id == -1);
CHECK_INVALID_ARGUMENT(bld->isActual());
CHECK_INVALID_ARGUMENT(!items.empty() == needsItems(bld));
for (size_t i = 0; i < items.size(); i++)
{
CHECK_NULL_POINTER(items[i]);
if (items[i]->flags.bits.in_job)
return false;
}
df::job *job = NULL;
if (!linkForConstruct(job, bld))
return false;
bool rough = false;
for (size_t i = 0; i < items.size(); i++)
{
Job::attachJobItem(job, items[i], df::job_item_ref::Hauled);
if (items[i]->getType() == item_type::BOULDER)
rough = true;
if (bld->mat_type == -1)
bld->mat_type = items[i]->getMaterial();
if (bld->mat_index == -1)
bld->mat_index = items[i]->getMaterialIndex();
}
createDesign(bld, rough);
return true;
}
bool Buildings::constructWithFilters(df::building *bld, std::vector<df::job_item*> items)
{
CHECK_NULL_POINTER(bld);
CHECK_INVALID_ARGUMENT(bld->id == -1);
CHECK_INVALID_ARGUMENT(bld->isActual());
CHECK_INVALID_ARGUMENT(!items.empty() == needsItems(bld));
for (size_t i = 0; i < items.size(); i++)
CHECK_NULL_POINTER(items[i]);
df::job *job = NULL;
if (!linkForConstruct(job, bld))
{
for (size_t i = 0; i < items.size(); i++)
delete items[i];
return false;
}
bool rough = false;
for (size_t i = 0; i < items.size(); i++)
{
if (items[i]->quantity < 0)
items[i]->quantity = computeMaterialAmount(bld);
/* The game picks up explicitly listed items in reverse
* order, but processes filters straight. This reverses
* the order of filters so as to produce the same final
* contained_items ordering as the normal ui way. */
vector_insert_at(job->job_items, 0, items[i]);
if (items[i]->item_type == item_type::BOULDER)
rough = true;
if (bld->mat_type == -1)
bld->mat_type = items[i]->mat_type;
if (bld->mat_index == -1)
bld->mat_index = items[i]->mat_index;
}
buildings_do_onupdate = true;
createDesign(bld, rough);
return true;
}
bool Buildings::deconstruct(df::building *bld)
{
using df::global::ui;
using df::global::world;
using df::global::ui_look_list;
CHECK_NULL_POINTER(bld);
if (bld->isActual() && bld->getBuildStage() > 0)
{
bld->queueDestroy();
return false;
}
/* Immediate destruction code path.
Should only happen for abstract and unconstructed buildings.*/
if (bld->isSettingOccupancy())
{
markBuildingTiles(bld, true);
bld->cleanupMap();
}
bld->removeUses(false, false);
// Assume: no parties.
unlinkRooms(bld);
// Assume: not unit destroy target
vector_erase_at(ui->unk8.unk10, linear_index(ui->unk8.unk10, bld));
// Assume: not used in punishment
// Assume: not used in non-own jobs
// Assume: does not affect pathfinding
bld->deconstructItems(false, false);
// Don't clear arrows.
bld->uncategorize();
delete bld;
if (world->selected_building == bld)
{
world->selected_building = NULL;
world->update_selected_building = true;
}
for (int i = ui_look_list->items.size()-1; i >= 0; i--)
{
auto item = ui_look_list->items[i];
if (item->type == df::ui_look_list::T_items::Building &&
item->building == bld)
{
vector_erase_at(ui_look_list->items, i);
delete item;
}
}
Job::checkBuildingsNow();
Job::checkDesignationsNow();
return true;
}

@ -35,9 +35,20 @@ using namespace std;
#include "MemAccess.h"
#include "Types.h"
#include "Core.h"
#include "modules/Constructions.h"
#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "TileTypes.h"
#include "df/world.h"
#include "df/job_item.h"
#include "df/building_type.h"
#include "df/building_constructionst.h"
using namespace DFHack;
using namespace df::enums;
using df::global::world;
bool Constructions::isValid()
@ -73,3 +84,88 @@ bool Constructions::copyConstruction(const int32_t index, t_construction &out)
out.original_tile = out.origin->original_tile;
return true;
}
bool Constructions::designateNew(df::coord pos, df::construction_type type,
df::item_type item, int mat_index)
{
auto ttype = Maps::getTileType(pos);
if (!ttype || tileMaterial(*ttype) == tiletype_material::CONSTRUCTION)
return false;
auto current = Buildings::findAtTile(pos);
if (current)
{
auto cons = strict_virtual_cast<df::building_constructionst>(current);
if (!cons)
return false;
cons->type = type;
return true;
}
auto newinst = Buildings::allocInstance(pos, building_type::Construction);
if (!newinst)
return false;
auto newcons = strict_virtual_cast<df::building_constructionst>(newinst);
newcons->type = type;
df::job_item *filter = new df::job_item();
filter->item_type = item;
filter->mat_index = mat_index;
filter->flags2.bits.building_material = true;
if (mat_index < 0)
filter->flags2.bits.non_economic = true;
std::vector<df::job_item*> filters;
filters.push_back(filter);
if (!Buildings::constructWithFilters(newinst, filters))
{
delete newinst;
return false;
}
return true;
}
bool Constructions::designateRemove(df::coord pos, bool *immediate)
{
using df::global::process_dig;
if (immediate)
*immediate = false;
if (auto current = Buildings::findAtTile(pos))
{
auto cons = strict_virtual_cast<df::building_constructionst>(current);
if (!cons)
return false;
if (Buildings::deconstruct(cons))
{
if (immediate)
*immediate = true;
}
return true;
}
auto block = Maps::getTileBlock(pos);
if (!block)
return false;
auto ttype = block->tiletype[pos.x&15][pos.y&15];
if (tileMaterial(ttype) == tiletype_material::CONSTRUCTION)
{
auto &dsgn = block->designation[pos.x&15][pos.y&15];
dsgn.bits.dig = tile_dig_designation::Default;
block->flags.bits.designated = true;
if (process_dig)
*process_dig = true;
return true;
}
return false;
}

@ -41,6 +41,8 @@ using namespace std;
#include "MiscUtils.h"
using namespace DFHack;
#include "modules/Job.h"
#include "DataDefs.h"
#include "df/world.h"
#include "df/global_objects.h"
@ -54,14 +56,22 @@ using namespace DFHack;
#include "df/viewscreen_layer_workshop_profilest.h"
#include "df/viewscreen_layer_noblelistst.h"
#include "df/viewscreen_layer_overall_healthst.h"
#include "df/viewscreen_layer_assigntradest.h"
#include "df/viewscreen_layer_militaryst.h"
#include "df/viewscreen_layer_stockpilest.h"
#include "df/viewscreen_petst.h"
#include "df/viewscreen_tradegoodsst.h"
#include "df/viewscreen_storesst.h"
#include "df/ui_unit_view_mode.h"
#include "df/ui_sidebar_menus.h"
#include "df/ui_look_list.h"
#include "df/ui_advmode.h"
#include "df/job.h"
#include "df/ui_build_selector.h"
#include "df/building_workshopst.h"
#include "df/building_furnacest.h"
#include "df/building_trapst.h"
#include "df/building_civzonest.h"
#include "df/general_ref.h"
#include "df/unit_inventory_item.h"
#include "df/report.h"
@ -69,17 +79,401 @@ using namespace DFHack;
#include "df/interfacest.h"
#include "df/graphic.h"
#include "df/layer_object_listst.h"
#include "df/assign_trade_status.h"
using namespace df::enums;
using df::global::gview;
using df::global::init;
using df::global::gps;
using df::global::ui;
using df::global::world;
static df::layer_object_listst *getLayerList(df::viewscreen_layerst *layer, int idx)
{
return virtual_cast<df::layer_object_listst>(vector_get(layer->layer_objects,idx));
}
static std::string getNameChunk(virtual_identity *id, int start, int end)
{
if (!id)
return "UNKNOWN";
const char *name = id->getName();
int len = strlen(name);
if (len > start + end)
return std::string(name+start, len-start-end);
else
return name;
}
/*
* Classifying focus context by means of a string path.
*/
typedef void (*getFocusStringHandler)(std::string &str, df::viewscreen *screen);
static std::map<virtual_identity*, getFocusStringHandler> getFocusStringHandlers;
#define VIEWSCREEN(name) df::viewscreen_##name##st
#define DEFINE_GET_FOCUS_STRING_HANDLER(screen_type) \
static void getFocusString_##screen_type(std::string &focus, VIEWSCREEN(screen_type) *screen);\
DFHACK_STATIC_ADD_TO_MAP(\
&getFocusStringHandlers, &VIEWSCREEN(screen_type)::_identity, \
(getFocusStringHandler)getFocusString_##screen_type \
); \
static void getFocusString_##screen_type(std::string &focus, VIEWSCREEN(screen_type) *screen)
DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
{
using namespace df::enums::ui_sidebar_mode;
using df::global::ui_workshop_in_add;
using df::global::ui_build_selector;
using df::global::ui_selected_unit;
using df::global::ui_look_list;
using df::global::ui_look_cursor;
using df::global::ui_building_item_cursor;
using df::global::ui_building_assign_type;
using df::global::ui_building_assign_is_marked;
using df::global::ui_building_assign_units;
using df::global::ui_building_assign_items;
using df::global::ui_building_in_assign;
focus += "/" + enum_item_key(ui->main.mode);
switch (ui->main.mode)
{
case QueryBuilding:
if (df::building *selected = world->selected_building)
{
if (!selected->jobs.empty() &&
selected->jobs[0]->job_type == job_type::DestroyBuilding)
{
focus += "/Destroying";
break;
}
focus += "/Some";
virtual_identity *id = virtual_identity::get(selected);
bool jobs = false;
if (id == &df::building_workshopst::_identity ||
id == &df::building_furnacest::_identity)
{
focus += "/Workshop";
jobs = true;
}
else if (id == &df::building_trapst::_identity)
{
auto trap = (df::building_trapst*)selected;
if (trap->trap_type == trap_type::Lever) {
focus += "/Lever";
jobs = true;
}
}
else if (ui_building_in_assign && *ui_building_in_assign &&
ui_building_assign_type && ui_building_assign_units &&
ui_building_assign_type->size() == ui_building_assign_units->size())
{
focus += "/Assign";
if (ui_building_item_cursor)
{
auto unit = vector_get(*ui_building_assign_units, *ui_building_item_cursor);
focus += unit ? "/Unit" : "/None";
}
}
if (jobs)
{
if (ui_workshop_in_add && *ui_workshop_in_add)
focus += "/AddJob";
else if (!selected->jobs.empty())
focus += "/Job";
else
focus += "/Empty";
}
}
else
focus += "/None";
break;
case Build:
if (ui_build_selector)
{
// Not selecting, or no choices?
if (ui_build_selector->building_type < 0)
focus += "/Type";
else if (ui_build_selector->stage != 2)
focus += "/Position";
else
{
focus += "/Material";
if (ui_build_selector->is_grouped)
focus += "/Groups";
else
focus += "/Items";
}
}
break;
case ViewUnits:
if (ui_selected_unit)
{
if (auto unit = vector_get(world->units.active, *ui_selected_unit))
{
focus += "/Some";
using df::global::ui_unit_view_mode;
if (ui_unit_view_mode)
focus += "/" + enum_item_key(ui_unit_view_mode->value);
}
else
focus += "/None";
}
break;
case LookAround:
if (ui_look_list && ui_look_cursor)
{
auto item = vector_get(ui_look_list->items, *ui_look_cursor);
if (item)
focus += "/" + enum_item_key(item->type);
else
focus += "/None";
}
break;
case BuildingItems:
if (VIRTUAL_CAST_VAR(selected, df::building_actual, world->selected_building))
{
if (selected->contained_items.empty())
focus += "/Some/Empty";
else
focus += "/Some/Item";
}
else
focus += "/None";
break;
case ZonesPenInfo:
if (ui_building_assign_type && ui_building_assign_units &&
ui_building_assign_is_marked && ui_building_assign_items &&
ui_building_assign_type->size() == ui_building_assign_units->size())
{
focus += "/Assign";
if (ui_building_item_cursor)
{
if (vector_get(*ui_building_assign_units, *ui_building_item_cursor))
focus += "/Unit";
else if (vector_get(*ui_building_assign_items, *ui_building_item_cursor))
focus += "/Vermin";
else
focus += "/None";
}
}
break;
case Burrows:
if (ui->burrows.in_confirm_delete)
focus += "/ConfirmDelete";
else if (ui->burrows.in_add_units_mode)
focus += "/AddUnits";
else if (ui->burrows.in_edit_name_mode)
focus += "/EditName";
else if (ui->burrows.in_define_mode)
focus += "/Define";
else
focus += "/List";
break;
default:
break;
}
}
DEFINE_GET_FOCUS_STRING_HANDLER(dungeonmode)
{
using df::global::ui_advmode;
if (!ui_advmode)
return;
focus += "/" + enum_item_key(ui_advmode->menu);
}
DEFINE_GET_FOCUS_STRING_HANDLER(unitlist)
{
focus += "/" + enum_item_key(screen->page);
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_military)
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
auto list3 = getLayerList(screen, 2);
if (!list1 || !list2 || !list3) return;
focus += "/" + enum_item_key(screen->page);
int cur_list;
if (list1->bright) cur_list = 0;
else if (list2->bright) cur_list = 1;
else if (list3->bright) cur_list = 2;
else return;
switch (screen->page)
{
case df::viewscreen_layer_militaryst::Positions:
{
static const char *lists[] = { "/Squads", "/Positions", "/Candidates" };
focus += lists[cur_list];
break;
}
default:
break;
}
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_workshop_profile)
{
auto list1 = getLayerList(screen, 0);
if (!list1) return;
if (vector_get(screen->workers, list1->cursor))
focus += "/Unit";
else
focus += "/None";
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_noblelist)
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
if (!list1 || !list2) return;
focus += "/" + enum_item_key(screen->mode);
}
DEFINE_GET_FOCUS_STRING_HANDLER(pet)
{
focus += "/" + enum_item_key(screen->mode);
switch (screen->mode)
{
case df::viewscreen_petst::List:
focus += vector_get(screen->is_vermin, screen->cursor) ? "/Vermin" : "/Unit";
break;
case df::viewscreen_petst::SelectTrainer:
if (vector_get(screen->trainer_unit, screen->trainer_cursor))
focus += "/Unit";
break;
default:
break;
}
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_overall_health)
{
auto list1 = getLayerList(screen, 0);
if (!list1) return;
focus += "/Units";
}
DEFINE_GET_FOCUS_STRING_HANDLER(tradegoods)
{
if (!screen->has_traders || screen->is_unloading)
focus += "/NoTraders";
else if (screen->in_edit_count)
focus += "/EditCount";
else
focus += (screen->in_right_pane ? "/Items/Broker" : "/Items/Trader");
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_assigntrade)
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
if (!list1 || !list2) return;
int list_idx = vector_get(screen->visible_lists, list1->cursor, (int16_t)-1);
unsigned num_lists = sizeof(screen->lists)/sizeof(screen->lists[0]);
if (unsigned(list_idx) >= num_lists)
return;
if (list1->bright)
focus += "/Groups";
else
focus += "/Items";
}
DEFINE_GET_FOCUS_STRING_HANDLER(stores)
{
if (!screen->in_right_list)
focus += "/Categories";
else if (screen->in_group_mode)
focus += "/Groups";
else
focus += "/Items";
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_stockpile)
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
auto list3 = getLayerList(screen, 2);
if (!list1 || !list2 || !list3 || !screen->settings) return;
auto group = screen->cur_group;
if (group != vector_get(screen->group_ids, list1->cursor))
return;
focus += "/" + enum_item_key(group);
auto bits = vector_get(screen->group_bits, list1->cursor);
if (bits.whole && !(bits.whole & screen->settings->flags.whole))
{
focus += "/Off";
return;
}
focus += "/On";
if (list2->bright || list3->bright || screen->list_ids.empty()) {
focus += "/" + enum_item_key(screen->cur_list);
if (list3->bright)
focus += (screen->item_names.empty() ? "/None" : "/Item");
}
}
std::string Gui::getFocusString(df::viewscreen *top)
{
if (!top)
return "";
if (virtual_identity *id = virtual_identity::get(top))
{
std::string name = getNameChunk(id, 11, 2);
auto handler = map_find(getFocusStringHandlers, id);
if (handler)
handler(name, top);
return name;
}
else
{
Core &core = Core::getInstance();
std::string name = core.p->readClassName(*(void**)top);
return name.substr(11, name.size()-11-2);
}
}
// Predefined common guard functions
bool Gui::default_hotkey(df::viewscreen *top)
@ -291,7 +685,13 @@ static df::unit *getAnyUnit(df::viewscreen *top)
using df::global::ui_selected_unit;
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_joblistst, top))
return vector_get(screen->units, screen->cursor_pos);
{
if (auto unit = vector_get(screen->units, screen->cursor_pos))
return unit;
if (auto job = vector_get(screen->jobs, screen->cursor_pos))
return Job::getWorker(job);
return NULL;
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitlistst, top))
return vector_get(screen->units[screen->page], screen->cursor_pos[screen->page]);
@ -420,6 +820,41 @@ static df::item *getAnyItem(df::viewscreen *top)
return ref ? ref->getItem() : NULL;
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_assigntradest, top))
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
if (!list1 || !list2 || !list2->bright)
return NULL;
int list_idx = vector_get(screen->visible_lists, list1->cursor, (int16_t)-1);
unsigned num_lists = sizeof(screen->lists)/sizeof(std::vector<int32_t>);
if (unsigned(list_idx) >= num_lists)
return NULL;
int idx = vector_get(screen->lists[list_idx], list2->cursor, -1);
if (auto info = vector_get(screen->info, idx))
return info->item;
return NULL;
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_tradegoodsst, top))
{
if (screen->in_right_pane)
return vector_get(screen->broker_items, screen->broker_cursor);
else
return vector_get(screen->trader_items, screen->trader_cursor);
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_storesst, top))
{
if (screen->in_right_list && !screen->in_group_mode)
return vector_get(screen->items, screen->item_cursor);
return NULL;
}
if (!Gui::dwarfmode_hotkey(top))
return NULL;
@ -515,7 +950,7 @@ void Gui::showAnnouncement(std::string message, int color, bool bright)
new_rep->flags.bits.continuation = continued;
new_rep->flags.bits.announcement = true;
int size = std::min(message.size(), 73U);
int size = std::min(message.size(), (size_t)73);
new_rep->text = message.substr(0, size);
message = message.substr(size);
@ -606,16 +1041,29 @@ bool Gui::setDesignationCoords (const int32_t x, const int32_t y, const int32_t
bool Gui::getMousePos (int32_t & x, int32_t & y)
{
x = gps->mouse_x;
y = gps->mouse_y;
if (gps) {
x = gps->mouse_x;
y = gps->mouse_y;
}
else {
x = -1;
y = -1;
}
return (x == -1) ? false : true;
}
bool Gui::getWindowSize (int32_t &width, int32_t &height)
{
width = gps->dimx;
height = gps->dimy;
return true;
if (gps) {
width = gps->dimx;
height = gps->dimy;
return true;
}
else {
width = 80;
height = 25;
return false;
}
}
bool Gui::getMenuWidth(uint8_t &menu_width, uint8_t &area_map_width)

@ -45,9 +45,11 @@ using namespace std;
#include "Error.h"
#include "MiscUtils.h"
#include "df/ui.h"
#include "df/world.h"
#include "df/item.h"
#include "df/building.h"
#include "df/building_actual.h"
#include "df/tool_uses.h"
#include "df/itemdef_weaponst.h"
#include "df/itemdef_trapcompst.h"
@ -69,10 +71,23 @@ using namespace std;
#include "df/general_ref_unit_itemownerst.h"
#include "df/general_ref_contains_itemst.h"
#include "df/general_ref_contained_in_itemst.h"
#include "df/general_ref_building_holderst.h"
#include "df/viewscreen_itemst.h"
#include "df/vermin.h"
#include "df/unit_inventory_item.h"
#include "df/body_part_raw.h"
#include "df/unit.h"
#include "df/creature_raw.h"
#include "df/caste_raw.h"
#include "df/body_part_template_flags.h"
#include "df/general_ref_unit_holderst.h"
using namespace DFHack;
using namespace df::enums;
using df::global::world;
using df::global::ui;
using df::global::ui_selected_unit;
#define ITEMDEF_VECTORS \
ITEM(WEAPON, weapons, itemdef_weaponst) \
@ -421,18 +436,25 @@ bool Items::copyItem(df::item * itembase, DFHack::dfh_item &item)
return true;
}
df::unit *Items::getOwner(df::item * item)
df::general_ref *Items::getGeneralRef(df::item *item, df::general_ref_type type)
{
CHECK_NULL_POINTER(item);
for (size_t i = 0; i < item->itemrefs.size(); i++)
{
df::general_ref *ref = item->itemrefs[i];
if (strict_virtual_cast<df::general_ref_unit_itemownerst>(ref))
return ref->getUnit();
}
return findRef(item->itemrefs, type);
}
return NULL;
df::specific_ref *Items::getSpecificRef(df::item *item, df::specific_ref_type type)
{
CHECK_NULL_POINTER(item);
return findRef(item->specific_refs, type);
}
df::unit *Items::getOwner(df::item * item)
{
auto ref = getGeneralRef(item, general_ref_type::UNIT_ITEMOWNER);
return ref ? ref->getUnit() : NULL;
}
bool Items::setOwner(df::item *item, df::unit *unit)
@ -478,16 +500,9 @@ bool Items::setOwner(df::item *item, df::unit *unit)
df::item *Items::getContainer(df::item * item)
{
CHECK_NULL_POINTER(item);
auto ref = getGeneralRef(item, general_ref_type::CONTAINED_IN_ITEM);
for (size_t i = 0; i < item->itemrefs.size(); i++)
{
df::general_ref *ref = item->itemrefs[i];
if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM)
return ref->getItem();
}
return NULL;
return ref ? ref->getItem() : NULL;
}
void Items::getContainedItems(df::item *item, std::vector<df::item*> *items)
@ -512,9 +527,12 @@ df::coord Items::getPosition(df::item *item)
{
CHECK_NULL_POINTER(item);
if (item->flags.bits.in_inventory ||
item->flags.bits.in_chest ||
item->flags.bits.in_building)
/* Function reverse-engineered from DF code. */
if (item->flags.bits.removed)
return df::coord();
if (item->flags.bits.in_inventory)
{
for (size_t i = 0; i < item->itemrefs.size(); i++)
{
@ -532,35 +550,108 @@ df::coord Items::getPosition(df::item *item)
return Units::getPosition(unit);
break;
case general_ref_type::BUILDING_HOLDER:
/*case general_ref_type::BUILDING_HOLDER:
if (auto bld = ref->getBuilding())
return df::coord(bld->centerx, bld->centery, bld->z);
break;*/
default:
break;
}
}
for (size_t i = 0; i < item->specific_refs.size(); i++)
{
df::specific_ref *ref = item->specific_refs[i];
switch (ref->type)
{
case specific_ref_type::VERMIN_ESCAPED_PET:
return ref->vermin->pos;
default:
break;
}
}
return df::coord();
}
return item->pos;
}
static void removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type type, int id)
static char quality_table[] = { 0, '-', '+', '*', '=', '@' };
static void addQuality(std::string &tmp, int quality)
{
for (int i = vec.size()-1; i >= 0; i--)
{
df::general_ref *ref = vec[i];
if (ref->getType() != type || ref->getID() != id)
continue;
if (quality > 0 && quality <= 5) {
char c = quality_table[quality];
tmp = c + tmp + c;
}
}
vector_erase_at(vec, i);
delete ref;
std::string Items::getDescription(df::item *item, int type, bool decorate)
{
CHECK_NULL_POINTER(item);
std::string tmp;
item->getItemDescription(&tmp, type);
if (decorate) {
if (item->flags.bits.foreign)
tmp = "(" + tmp + ")";
addQuality(tmp, item->getQuality());
if (item->isImproved()) {
tmp = "<" + tmp + ">";
addQuality(tmp, item->getImprovementQuality());
}
}
return tmp;
}
static void resetUnitInvFlags(df::unit *unit, df::unit_inventory_item *inv_item)
{
if (inv_item->mode == df::unit_inventory_item::Worn ||
inv_item->mode == df::unit_inventory_item::WrappedAround)
{
unit->flags2.bits.calculated_inventory = false;
unit->flags2.bits.calculated_insulation = false;
}
else if (inv_item->mode == df::unit_inventory_item::StuckIn)
{
unit->flags3.bits.unk2 = false;
}
}
static bool detachItem(MapExtras::MapCache &mc, df::item *item)
{
if (!item->specific_refs.empty())
return false;
if (item->world_data_id != -1)
return false;
for (size_t i = 0; i < item->itemrefs.size(); i++)
{
df::general_ref *ref = item->itemrefs[i];
switch (ref->getType())
{
case general_ref_type::PROJECTILE:
case general_ref_type::BUILDING_HOLDER:
case general_ref_type::BUILDING_CAGED:
case general_ref_type::BUILDING_TRIGGER:
case general_ref_type::BUILDING_TRIGGERTARGET:
case general_ref_type::BUILDING_CIVZONE_ASSIGNED:
return false;
default:
continue;
}
}
if (item->flags.bits.on_ground)
{
if (!mc.removeItemOnGround(item))
@ -583,6 +674,14 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
case general_ref_type::CONTAINED_IN_ITEM:
if (auto item2 = ref->getItem())
{
// Viewscreens hold general_ref_contains_itemst pointers
for (auto screen = Core::getTopViewscreen(); screen; screen = screen->parent)
{
auto vsitem = strict_virtual_cast<df::viewscreen_itemst>(screen);
if (vsitem && vsitem->item == item2)
return false;
}
item2->flags.bits.weight_computed = false;
removeRef(item2->itemrefs, general_ref_type::CONTAINS_ITEM, item->id);
@ -590,8 +689,27 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
break;
case general_ref_type::UNIT_HOLDER:
case general_ref_type::BUILDING_HOLDER:
return false;
if (auto unit = ref->getUnit())
{
// Unit view sidebar holds inventory item pointers
if (ui->main.mode == ui_sidebar_mode::ViewUnits &&
(!ui_selected_unit ||
vector_get(world->units.active, *ui_selected_unit) == unit))
return false;
for (int i = unit->inventory.size()-1; i >= 0; i--)
{
df::unit_inventory_item *inv_item = unit->inventory[i];
if (inv_item->item != item)
continue;
resetUnitInvFlags(unit, inv_item);
vector_erase_at(unit->inventory, i);
delete inv_item;
}
}
break;
default:
continue;
@ -638,7 +756,8 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
CHECK_NULL_POINTER(item);
CHECK_NULL_POINTER(container);
if (!detachItem(mc, item))
auto cpos = getPosition(container);
if (!cpos.isValid())
return false;
auto ref1 = df::allocate<df::general_ref_contains_itemst>();
@ -648,20 +767,102 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
{
delete ref1; delete ref2;
Core::printerr("Could not allocate container refs.\n");
putOnGround(mc, item, getPosition(container));
return false;
}
if (!detachItem(mc, item))
{
delete ref1; delete ref2;
return false;
}
item->pos = container->pos;
item->flags.bits.in_inventory = true;
container->flags.bits.container = true;
container->flags.bits.container = true;
container->flags.bits.weight_computed = false;
ref1->item_id = item->id;
container->itemrefs.push_back(ref1);
ref2->item_id = container->id;
item->itemrefs.push_back(ref2);
return true;
}
bool DFHack::Items::moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode)
{
CHECK_NULL_POINTER(item);
CHECK_NULL_POINTER(building);
CHECK_INVALID_ARGUMENT(use_mode == 0 || use_mode == 2);
auto ref = df::allocate<df::general_ref_building_holderst>();
if(!ref)
{
delete ref;
Core::printerr("Could not allocate building holder refs.\n");
return false;
}
if (!detachItem(mc, item))
{
delete ref;
return false;
}
item->pos.x=building->centerx;
item->pos.y=building->centery;
item->pos.z=building->z;
item->flags.bits.in_building=true;
ref->building_id=building->id;
item->itemrefs.push_back(ref);
auto con=new df::building_actual::T_contained_items;
con->item=item;
con->use_mode=use_mode;
building->contained_items.push_back(con);
return true;
}
bool DFHack::Items::moveToInventory(
MapExtras::MapCache &mc, df::item *item, df::unit *unit,
df::unit_inventory_item::T_mode mode, int body_part
) {
CHECK_NULL_POINTER(item);
CHECK_NULL_POINTER(unit);
CHECK_NULL_POINTER(unit->body.body_plan);
CHECK_INVALID_ARGUMENT(is_valid_enum_item(mode));
int body_plan_size = unit->body.body_plan->body_parts.size();
CHECK_INVALID_ARGUMENT(body_part < 0 || body_part <= body_plan_size);
auto holderReference = df::allocate<df::general_ref_unit_holderst>();
if (!holderReference)
{
Core::printerr("Could not allocate UNIT_HOLDER reference.\n");
return false;
}
if (!detachItem(mc, item))
{
delete holderReference;
return false;
}
item->flags.bits.in_inventory = true;
auto newInventoryItem = new df::unit_inventory_item();
newInventoryItem->item = item;
newInventoryItem->mode = mode;
newInventoryItem->body_part_id = body_part;
unit->inventory.push_back(newInventoryItem);
holderReference->unit_id = unit->id;
item->itemrefs.push_back(holderReference);
resetUnitInvFlags(unit, newInventoryItem);
return true;
}

@ -46,6 +46,7 @@ using namespace std;
#include "df/job.h"
#include "df/job_item.h"
#include "df/job_list_link.h"
#include "df/specific_ref.h"
#include "df/general_ref.h"
#include "df/general_ref_unit_workerst.h"
#include "df/general_ref_building_holderst.h"
@ -67,7 +68,7 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job)
pnew->list_link = NULL;
pnew->completion_timer = -1;
pnew->items.clear();
pnew->misc_links.clear();
pnew->specific_refs.clear();
// Clone refs
for (int i = pnew->references.size()-1; i >= 0; i--)
@ -93,7 +94,7 @@ void DFHack::Job::deleteJobStruct(df::job *job)
return;
// Only allow free-floating job structs
assert(!job->list_link && job->items.empty() && job->misc_links.empty());
assert(!job->list_link && job->items.empty() && job->specific_refs.empty());
for (int i = job->references.size()-1; i >= 0; i--)
delete job->references[i];
@ -255,6 +256,18 @@ df::unit *DFHack::Job::getWorker(df::job *job)
return NULL;
}
void DFHack::Job::checkBuildingsNow()
{
if (df::global::process_jobs)
*df::global::process_jobs = true;
}
void DFHack::Job::checkDesignationsNow()
{
if (df::global::process_dig)
*df::global::process_dig = true;
}
bool DFHack::Job::linkIntoWorld(df::job *job, bool new_id)
{
using df::global::world;
@ -311,3 +324,40 @@ bool DFHack::Job::listNewlyCreated(std::vector<df::job*> *pvec, int *id_var)
return true;
}
bool DFHack::Job::attachJobItem(df::job *job, df::item *item,
df::job_item_ref::T_role role,
int filter_idx, int insert_idx)
{
CHECK_NULL_POINTER(job);
CHECK_NULL_POINTER(item);
/*
* Functionality 100% reverse-engineered from DF code.
*/
if (role != df::job_item_ref::TargetContainer)
{
if (item->flags.bits.in_job)
return false;
item->flags.bits.in_job = true;
}
auto item_link = new df::specific_ref();
item_link->type = specific_ref_type::JOB;
item_link->job = job;
item->specific_refs.push_back(item_link);
auto job_link = new df::job_item_ref();
job_link->item = item;
job_link->role = role;
job_link->job_item_idx = filter_idx;
if (size_t(insert_idx) < job->items.size())
vector_insert_at(job->items, insert_idx, job_link);
else
job->items.push_back(job_link);
return true;
}

@ -41,6 +41,8 @@ using namespace std;
#include "Core.h"
#include "MiscUtils.h"
#include "modules/Buildings.h"
#include "DataDefs.h"
#include "df/world_data.h"
#include "df/world_underground_region.h"
@ -54,6 +56,7 @@ using namespace std;
#include "df/world_region_details.h"
#include "df/builtin_mats.h"
#include "df/block_square_event_grassst.h"
#include "df/z_level_flags.h"
using namespace DFHack;
using namespace df::enums;
@ -145,6 +148,24 @@ df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z)
return world->map.block_index[x >> 4][y >> 4][z];
}
df::tiletype *Maps::getTileType(int32_t x, int32_t y, int32_t z)
{
df::map_block *block = getTileBlock(x,y,z);
return block ? &block->tiletype[x&15][y&15] : NULL;
}
df::tile_designation *Maps::getTileDesignation(int32_t x, int32_t y, int32_t z)
{
df::map_block *block = getTileBlock(x,y,z);
return block ? &block->designation[x&15][y&15] : NULL;
}
df::tile_occupancy *Maps::getTileOccupancy(int32_t x, int32_t y, int32_t z)
{
df::map_block *block = getTileBlock(x,y,z);
return block ? &block->occupancy[x&15][y&15] : NULL;
}
df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos)
{
auto data = world->world_data;
@ -158,6 +179,30 @@ df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos)
return &data->region_map[rgn_pos.x][rgn_pos.y];
}
void Maps::enableBlockUpdates(df::map_block *blk, bool flow, bool temperature)
{
if (!blk || !(flow || temperature)) return;
if (temperature)
blk->flags.bits.update_temperature = true;
if (flow)
{
blk->flags.bits.update_liquid = true;
blk->flags.bits.update_liquid_twice = true;
}
auto z_flags = world->map.z_level_flags;
int z_level = blk->map_pos.z;
if (z_flags && z_level >= 0 && z_level < world->map.z_count_block)
{
z_flags += z_level;
z_flags->bits.update = true;
z_flags->bits.update_twice = true;
}
}
df::feature_init *Maps::getGlobalInitFeature(int32_t index)
{
auto data = world->world_data;
@ -326,6 +371,39 @@ bool Maps::RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square
return false;
}
static df::coord2d biome_offsets[9] = {
df::coord2d(-1,-1), df::coord2d(0,-1), df::coord2d(1,-1),
df::coord2d(-1,0), df::coord2d(0,0), df::coord2d(1,0),
df::coord2d(-1,1), df::coord2d(0,1), df::coord2d(1,1)
};
inline df::coord2d getBiomeRgnPos(df::coord2d base, int idx)
{
auto r = base + biome_offsets[idx];
int world_width = world->world_data->world_width;
int world_height = world->world_data->world_height;
return df::coord2d(clip_range(r.x,0,world_width-1),clip_range(r.y,0,world_height-1));
}
df::coord2d Maps::getBlockTileBiomeRgn(df::map_block *block, df::coord2d pos)
{
if (!block || !world->world_data)
return df::coord2d();
auto des = MapExtras::index_tile<df::tile_designation>(block->designation,pos);
unsigned idx = des.bits.biome;
if (idx < 9)
{
idx = block->region_offset[idx];
if (idx < 9)
return getBiomeRgnPos(block->region_pos, idx);
}
return df::coord2d();
}
/*
* Layer geology
*/
@ -343,20 +421,14 @@ bool Maps::ReadGeology(vector<vector<int16_t> > *layer_mats, vector<df::coord2d>
(*geoidx)[i] = df::coord2d(-30000,-30000);
}
int world_width = world->world_data->world_width;
int world_height = world->world_data->world_height;
// regionX is in embark squares
// regionX/16 is in 16x16 embark square regions
df::coord2d map_region(world->map.region_x / 16, world->map.region_y / 16);
// iterate over 8 surrounding regions + local region
for (int i = eNorthWest; i < eBiomeCount; i++)
{
// check against worldmap boundaries, fix if needed
// regionX is in embark squares
// regionX/16 is in 16x16 embark square regions
// i provides -1 .. +1 offset from the current region
int bioRX = world->map.region_x / 16 + ((i % 3) - 1);
int bioRY = world->map.region_y / 16 + ((i / 3) - 1);
df::coord2d rgn_pos(clip_range(bioRX,0,world_width-1),clip_range(bioRY,0,world_height-1));
df::coord2d rgn_pos = getBiomeRgnPos(map_region, i);
(*geoidx)[i] = rgn_pos;
@ -408,7 +480,6 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
dirty_designations = false;
dirty_tiles = false;
dirty_temperatures = false;
dirty_blockflags = false;
dirty_occupancies = false;
valid = false;
bcoord = _bcoord;
@ -422,7 +493,6 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
{
COPY(designation, block->designation);
COPY(occupancy, block->occupancy);
blockflags = block->flags;
COPY(temp1, block->temperature_1);
COPY(temp2, block->temperature_2);
@ -431,7 +501,6 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
}
else
{
blockflags.whole = 0;
memset(designation,0,sizeof(designation));
memset(occupancy,0,sizeof(occupancy));
memset(temp1,0,sizeof(temp1));
@ -616,11 +685,6 @@ bool MapExtras::Block::Write ()
{
if(!valid) return false;
if(dirty_blockflags)
{
block->flags = blockflags;
dirty_blockflags = false;
}
if(dirty_designations)
{
COPY(block->designation, designation);
@ -993,7 +1057,21 @@ bool MapExtras::Block::removeItemOnGround(df::item *item)
if (--count == 0)
{
index_tile<df::tile_occupancy&>(occupancy,item->pos).bits.item = false;
index_tile<df::tile_occupancy&>(block->occupancy,item->pos).bits.item = false;
auto &occ = index_tile<df::tile_occupancy&>(block->occupancy,item->pos);
occ.bits.item = false;
// Clear the 'site blocked' flag in the building, if any.
// Otherwise the job would be re-suspended without actually checking items.
if (occ.bits.building == tile_building_occ::Planned)
{
if (auto bld = Buildings::findAtTile(item->pos))
{
// TODO: maybe recheck other tiles like the game does.
bld->flags.bits.site_blocked = false;
}
}
}
return true;

@ -761,8 +761,8 @@ bool Materials::ReadCreatureTypesEx (void)
{
df::body_part_raw *bp = ca->body_info.body_parts[k];
t_bodypart part;
part.id = bp->part_code;
part.category = bp->part_name;
part.id = bp->token;
part.category = bp->category;
caste.bodypart.push_back(part);
}
using namespace df::enums::mental_attribute_type;

@ -49,6 +49,7 @@ using namespace std;
#include "df/world.h"
#include "df/ui.h"
#include "df/job.h"
#include "df/unit_inventory_item.h"
#include "df/unit_soul.h"
#include "df/nemesis_record.h"
@ -151,7 +152,7 @@ void Units::CopyCreature(df::unit * source, t_unit & furball)
// mood stuff
furball.mood = source->mood;
furball.mood_skill = source->job.unk_2f8; // FIXME: really? More like currently used skill anyway.
furball.mood_skill = source->job.mood_skill; // FIXME: really? More like currently used skill anyway.
Translation::readName(furball.artifact_name, &source->status.artifact_name);
// labors
@ -215,24 +216,18 @@ void Units::CopyCreature(df::unit * source, t_unit & furball)
}
}
*/
/*
furball.current_job.occupationPtr = p->readDWord (addr_cr + offs.current_job_offset);
if(furball.current_job.occupationPtr)
{
furball.current_job.active = true;
furball.current_job.jobType = p->readByte (furball.current_job.occupationPtr + offs.job_type_offset );
furball.current_job.jobId = p->readWord (furball.current_job.occupationPtr + offs.job_id_offset);
}
else
if(source->job.current_job == NULL)
{
furball.current_job.active = false;
}
*/
// no jobs for now...
else
{
furball.current_job.active = false;
furball.current_job.active = true;
furball.current_job.jobType = source->job.current_job->job_type;
furball.current_job.jobId = source->job.current_job->id;
}
}
int32_t Units::FindIndexById(int32_t creature_id)
{
return df::unit::binsearch_index(world->units.all, creature_id);
@ -618,12 +613,45 @@ df::nemesis_record *Units::getNemesis(df::unit *unit)
return NULL;
}
static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag)
{
auto creature = df::creature_raw::find(race);
if (!creature)
return false;
auto craw = vector_get(creature->caste, caste);
if (!craw)
return false;
return craw->flags.is_set(flag);
}
static bool isCrazed(df::unit *unit)
{
if (unit->flags3.bits.scuttle)
return false;
if (unit->curse.rem_tags1.bits.CRAZED)
return false;
if (unit->curse.add_tags1.bits.CRAZED)
return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED);
}
static bool isOpposedToLife(df::unit *unit)
{
if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE)
return false;
if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE)
return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CANNOT_UNDEAD);
}
bool DFHack::Units::isDead(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
return unit->flags1.bits.dead;
return unit->flags1.bits.dead ||
unit->flags3.bits.ghostly;
}
bool DFHack::Units::isAlive(df::unit *unit)
@ -641,8 +669,11 @@ bool DFHack::Units::isSane(df::unit *unit)
if (unit->flags1.bits.dead ||
unit->flags3.bits.ghostly ||
unit->curse.add_tags1.bits.OPPOSED_TO_LIFE ||
unit->curse.add_tags1.bits.CRAZED)
isOpposedToLife(unit) ||
unit->unknown8.unk2)
return false;
if (unit->unknown8.normal_race == unit->unknown8.were_race && isCrazed(unit))
return false;
switch (unit->mood)
@ -662,19 +693,38 @@ bool DFHack::Units::isCitizen(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
// Copied from the conditions used to decide game over,
// except that the game appears to let melancholy/raving
// dwarves count as citizens.
if (!isDwarf(unit) || !isSane(unit))
return false;
if (unit->flags1.bits.marauder ||
unit->flags1.bits.invader_origin ||
unit->flags1.bits.active_invader ||
unit->flags1.bits.forest ||
unit->flags1.bits.merchant ||
unit->flags1.bits.diplomat)
return false;
if (unit->flags1.bits.tame)
return true;
return unit->civ_id == ui->civ_id &&
!unit->flags1.bits.merchant &&
!unit->flags1.bits.diplomat &&
unit->civ_id != -1 &&
!unit->flags2.bits.underworld &&
!unit->flags2.bits.resident &&
!unit->flags1.bits.dead &&
!unit->flags3.bits.ghostly;
!unit->flags2.bits.visitor_uninvited &&
!unit->flags2.bits.visitor;
}
bool DFHack::Units::isDwarf(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
return unit->race == ui->race_id;
return unit->race == ui->race_id ||
unit->unknown8.normal_race == ui->race_id;
}
double DFHack::Units::getAge(df::unit *unit, bool true_age)

@ -34,10 +34,12 @@ distribution.
#include "modules/Windows.h"
using namespace DFHack;
using df::global::gps;
Windows::df_screentile *Windows::getScreenBuffer()
{
return (df_screentile *) df::global::gps->screen;
if (!gps) return NULL;
return (df_screentile *) gps->screen;
}
Windows::df_window::df_window(int x, int y, unsigned int width, unsigned int height)
@ -79,7 +81,7 @@ bool Windows::df_window::unlock (painter * painter)
return false;
}
Windows::top_level_window::top_level_window(): df_window(0,0,df::global::gps->dimx,df::global::gps->dimy)
Windows::top_level_window::top_level_window() : df_window(0,0,gps ? gps->dimx : 80,gps ? gps->dimy : 25)
{
buffer = 0;
}

@ -63,15 +63,8 @@ struct World::Private
bool Inited;
bool PauseInited;
void * pause_state_offset;
bool StartedWeather;
char * weather_offset;
bool StartedMode;
void * gamemode_offset;
void * controlmode_offset;
void * controlmodecopy_offset;
int next_persistent_id;
std::multimap<std::string, int> persistent_index;
@ -86,21 +79,14 @@ World::World()
Core & c = Core::getInstance();
d = new Private;
d->owner = c.p;
wmap = 0;
d->pause_state_offset = (void *) c.vinfo->getAddress ("pause_state");
if(d->pause_state_offset)
if(df::global::pause_state)
d->PauseInited = true;
d->weather_offset = (char *) c.vinfo->getAddress( "current_weather" );
if(d->weather_offset)
{
wmap = (weather_map *) d->weather_offset;
if(df::global::current_weather)
d->StartedWeather = true;
}
d->gamemode_offset = (void *) c.vinfo->getAddress( "game_mode" );
d->controlmode_offset = (void *) c.vinfo->getAddress( "control_mode" );
d->StartedMode = true;
if (df::global::game_mode && df::global::control_mode)
d->StartedMode = true;
d->Inited = true;
}
@ -123,14 +109,13 @@ bool World::Finish()
bool World::ReadPauseState()
{
if(!d->PauseInited) return false;
uint8_t pauseState = d->owner->readByte (d->pause_state_offset);
return pauseState & 1;
return *df::global::pause_state;
}
void World::SetPauseState(bool paused)
{
if(!d->PauseInited) return;
d->owner->writeByte (d->pause_state_offset, paused);
if (d->PauseInited)
*df::global::pause_state = paused;
}
uint32_t World::ReadCurrentYear()
@ -147,8 +132,8 @@ bool World::ReadGameMode(t_gamemodes& rd)
{
if(d->Inited && d->StartedMode)
{
rd.g_mode = (GameMode) d->owner->readDWord( d->controlmode_offset);
rd.g_type = (GameType) d->owner->readDWord(d->gamemode_offset);
rd.g_mode = (DFHack::GameMode)*df::global::control_mode;
rd.g_type = (DFHack::GameType)*df::global::game_mode;
return true;
}
return false;
@ -157,8 +142,8 @@ bool World::WriteGameMode(const t_gamemodes & wr)
{
if(d->Inited && d->StartedMode)
{
d->owner->writeDWord(d->gamemode_offset,wr.g_type);
d->owner->writeDWord(d->controlmode_offset,wr.g_mode);
*df::global::control_mode = wr.g_mode;
*df::global::game_mode = wr.g_type;
return true;
}
return false;
@ -199,18 +184,14 @@ uint32_t World::ReadCurrentDay()
uint8_t World::ReadCurrentWeather()
{
if (d->Inited && d->StartedWeather)
return(d->owner->readByte(d->weather_offset + 12));
return (*df::global::current_weather)[2][2];
return 0;
}
void World::SetCurrentWeather(uint8_t weather)
{
if (d->Inited && d->StartedWeather)
{
uint8_t buf[25];
memset(&buf,weather, sizeof(buf));
d->owner->write(d->weather_offset,sizeof(buf),buf);
}
memset(df::global::current_weather, weather, 25);
}
string World::ReadWorldFolder()

@ -1 +1 @@
Subproject commit f649d31001e6023a9df5fe83c7971c17afe0d87d
Subproject commit c381884664c71adefbec44258a734def2c88dacc

@ -42,27 +42,27 @@ case "$1" in
shift
echo "set environment LD_PRELOAD=./hack/libdfhack.so" > gdbcmd.tmp
echo "set environment MALLOC_PERTURB_=45" >> gdbcmd.tmp
gdb $DF_GDB_OPTS -x gdbcmd.tmp ./libs/Dwarf_Fortress $*
gdb $DF_GDB_OPTS -x gdbcmd.tmp ./libs/Dwarf_Fortress "$@"
rm gdbcmd.tmp
ret=$?
;;
-h | --helgrind)
shift
LD_PRELOAD=./hack/libdfhack.so setarch i386 -R valgrind $DF_HELGRIND_OPTS --tool=helgrind --log-file=helgrind.log ./libs/Dwarf_Fortress $*
LD_PRELOAD=./hack/libdfhack.so setarch i386 -R valgrind $DF_HELGRIND_OPTS --tool=helgrind --log-file=helgrind.log ./libs/Dwarf_Fortress "$@"
ret=$?
;;
-v | --valgrind)
shift
LD_PRELOAD=./hack/libdfhack.so setarch i386 -R valgrind $DF_VALGRIND_OPTS --log-file=valgrind.log ./libs/Dwarf_Fortress $*
LD_PRELOAD=./hack/libdfhack.so setarch i386 -R valgrind $DF_VALGRIND_OPTS --log-file=valgrind.log ./libs/Dwarf_Fortress "$@"
ret=$?
;;
-c | --callgrind)
shift
LD_PRELOAD=./hack/libdfhack.so setarch i386 -R valgrind $DF_CALLGRIND_OPTS --tool=callgrind --separate-threads=yes --dump-instr=yes --instr-atstart=no --log-file=callgrind.log ./libs/Dwarf_Fortress $*
LD_PRELOAD=./hack/libdfhack.so setarch i386 -R valgrind $DF_CALLGRIND_OPTS --tool=callgrind --separate-threads=yes --dump-instr=yes --instr-atstart=no --log-file=callgrind.log ./libs/Dwarf_Fortress "$@"
ret=$?
;;
*)
LD_PRELOAD=./hack/libdfhack.so setarch i386 -R ./libs/Dwarf_Fortress $*
setarch i386 -R env LD_PRELOAD=./hack/libdfhack.so ./libs/Dwarf_Fortress "$@"
ret=$?
;;
esac

@ -1,4 +1,9 @@
#pragma once
#include <llimits.h>
#include <sstream>
#include <string>
#include <stack>
#include <set>
typedef vector <df::coord> coord_vec;
class Brush
@ -60,10 +65,15 @@ public:
};
~RectangleBrush(){};
std::string str() const {
if (x_ == 1 && y_ == 1 && z_ == 1) {
if (x_ == 1 && y_ == 1 && z_ == 1)
{
return "point";
} else {
return "rectangle";
}
else
{
std::ostringstream ss;
ss << "rect: " << x_ << "/" << y_ << "/" << z_ << std::endl;
return ss.str();
}
}
private:
@ -196,12 +206,85 @@ private:
Core *c_;
};
inline std::ostream &operator<<(std::ostream &stream, const Brush& brush) {
command_result parseRectangle(color_ostream & out,
vector<string> & input, int start, int end,
int & width, int & height, int & zLevels,
bool hasConsole = true)
{
int newWidth = 0, newHeight = 0, newZLevels = 0;
if (end > start + 1)
{
newWidth = atoi(input[start++].c_str());
newHeight = atoi(input[start++].c_str());
if (end > start) {
newZLevels = atoi(input[start++].c_str());
} else {
newZLevels = 1; // So 'range w h' won't ask for it.
}
}
string command = "";
std::stringstream str;
CommandHistory hist;
if (newWidth < 1) {
if (hasConsole) {
Console &con = static_cast<Console&>(out);
str.str("");
str << "Set range width <" << width << "> ";
con.lineedit(str.str(), command, hist);
hist.add(command);
newWidth = command.empty() ? width : atoi(command.c_str());
} else {
return CR_WRONG_USAGE;
}
}
if (newHeight < 1) {
if (hasConsole) {
Console &con = static_cast<Console&>(out);
str.str("");
str << "Set range height <" << height << "> ";
con.lineedit(str.str(), command, hist);
hist.add(command);
newHeight = command.empty() ? height : atoi(command.c_str());
} else {
return CR_WRONG_USAGE;
}
}
if (newZLevels < 1) {
if (hasConsole) {
Console &con = static_cast<Console&>(out);
str.str("");
str << "Set range z-levels <" << zLevels << "> ";
con.lineedit(str.str(), command, hist);
hist.add(command);
newZLevels = command.empty() ? zLevels : atoi(command.c_str());
} else {
return CR_WRONG_USAGE;
}
}
width = newWidth < 1? 1 : newWidth;
height = newHeight < 1? 1 : newHeight;
zLevels = newZLevels < 1? 1 : newZLevels;
return CR_OK;
}
inline std::ostream &operator<<(std::ostream &stream, const Brush& brush)
{
stream << brush.str();
return stream;
}
inline std::ostream &operator<<(std::ostream &stream, const Brush* brush) {
inline std::ostream &operator<<(std::ostream &stream, const Brush* brush)
{
stream << brush->str();
return stream;
}

@ -36,6 +36,11 @@ if (BUILD_DWARFEXPORT)
add_subdirectory (dwarfexport)
endif()
OPTION(BUILD_RUBY "Build ruby binding." OFF)
if (BUILD_RUBY)
add_subdirectory (ruby)
endif()
install(DIRECTORY lua/
DESTINATION ${DFHACK_LUA_DESTINATION}/plugins
FILES_MATCHING PATTERN "*.lua")
@ -104,6 +109,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(zone zone.cpp)
DFHACK_PLUGIN(catsplosion catsplosion.cpp)
DFHACK_PLUGIN(regrass regrass.cpp)
DFHACK_PLUGIN(forceequip forceequip.cpp)
# this one exports functions to lua
DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua)

@ -1,6 +1,7 @@
#include "lua_Console.h"
#include "LuaTools.h"
#include "lua_Console.h"
#include <sstream>
//TODO error management. Using lua error? or something other?

@ -156,11 +156,11 @@ static command_result autodump_main(color_ostream &out, vector <string> & parame
// only dump the stuff marked for dumping and laying on the ground
if ( !itm->flags.bits.dump
|| !itm->flags.bits.on_ground
// || !itm->flags.bits.on_ground
|| itm->flags.bits.construction
|| itm->flags.bits.in_building
|| itm->flags.bits.in_chest
|| itm->flags.bits.in_inventory
// || itm->flags.bits.in_inventory
|| itm->flags.bits.artifact1
)
continue;
@ -182,7 +182,11 @@ static command_result autodump_main(color_ostream &out, vector <string> & parame
// Don't move items if they're already at the cursor
if (pos_cursor != pos_item)
Items::moveToGround(MC, itm, pos_cursor);
{
if (!Items::moveToGround(MC, itm, pos_cursor))
out.print("Could not move item: %s\n",
Items::getDescription(itm, 0, true).c_str());
}
}
else // destroy
{

@ -23,6 +23,16 @@
#include <df/building.h>
#include <df/workshop_type.h>
#include <df/unit_misc_trait.h>
#include <df/entity_position_responsibility.h>
#include <df/historical_figure.h>
#include <df/historical_entity.h>
#include <df/histfig_entity_link.h>
#include <df/histfig_entity_link_positionst.h>
#include <df/entity_position_assignment.h>
#include <df/entity_position.h>
#include <df/building_tradedepotst.h>
#include <MiscUtils.h>
using std::string;
using std::endl;
@ -338,7 +348,12 @@ static const dwarf_state dwarf_states[] = {
OTHER /* CauseTrouble */,
OTHER /* DrinkBlood */,
OTHER /* ReportCrime */,
OTHER /* ExecuteCriminal */
OTHER /* ExecuteCriminal */,
BUSY /* TrainAnimal */,
BUSY /* CarveTrack */,
BUSY /* PushTrackVehicle */,
BUSY /* PlaceTrackVehicle */,
BUSY /* StoreItemInVehicle */
};
struct labor_info
@ -442,25 +457,50 @@ static const struct labor_default default_labor_infos[] = {
/* POTTERY */ {AUTOMATIC, false, 1, 200, 0},
/* GLAZING */ {AUTOMATIC, false, 1, 200, 0},
/* PRESSING */ {AUTOMATIC, false, 1, 200, 0},
/* BEEKEEPING */ {AUTOMATIC, false, 1, 200, 0},
/* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0},
/* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981)
/* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0},
/* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0}
};
static const df::job_skill noble_skills[] = {
df::enums::job_skill::APPRAISAL,
df::enums::job_skill::ORGANIZATION,
df::enums::job_skill::RECORD_KEEPING,
static const int responsibility_penalties[] = {
0, /* LAW_MAKING */
0, /* LAW_ENFORCEMENT */
3000, /* RECEIVE_DIPLOMATS */
0, /* MEET_WORKERS */
1000, /* MANAGE_PRODUCTION */
3000, /* TRADE */
1000, /* ACCOUNTING */
0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */
0, /* MAKE_INTRODUCTIONS */
0, /* MAKE_PEACE_AGREEMENTS */
0, /* MAKE_TOPIC_AGREEMENTS */
0, /* COLLECT_TAXES */
0, /* ESCORT_TAX_COLLECTOR */
0, /* EXECUTIONS */
0, /* TAME_EXOTICS */
0, /* RELIGION */
0, /* ATTACK_ENEMIES */
0, /* PATROL_TERRITORY */
0, /* MILITARY_GOALS */
0, /* MILITARY_STRATEGY */
0, /* UPGRADE_SQUAD_EQUIPMENT */
0, /* EQUIPMENT_MANIFESTS */
0, /* SORT_AMMUNITION */
0, /* BUILD_MORALE */
5000 /* HEALTH_MANAGEMENT */
};
struct dwarf_info_t
{
int highest_skill;
int total_skill;
bool is_best_noble;
int mastery_penalty;
int assigned_jobs;
dwarf_state state;
bool has_exclusive_labor;
int noble_penalty; // penalty for assignment due to noble status
bool medical; // this dwarf has medical responsibility
bool trader; // this dwarf has trade responsibility
};
static bool isOptionEnabled(unsigned flag)
@ -666,7 +706,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
static int step_count = 0;
// check run conditions
if(!world->map.block_index || !enable_autolabor)
if(!world || !world->map.block_index || !enable_autolabor)
{
// give up if we shouldn't be running'
return CR_OK;
@ -683,6 +723,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
bool has_butchers = false;
bool has_fishery = false;
bool trader_requested = false;
for (int i = 0; i < world->buildings.all.size(); ++i)
{
@ -695,7 +736,14 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
has_butchers = true;
if (df::enums::workshop_type::Fishery == subType)
has_fishery = true;
}
}
else if (df::enums::building_type::TradeDepot == type)
{
df::building_tradedepotst* depot = (df::building_tradedepotst*) build;
trader_requested = depot->flags.bits.trader_requested;
if (print_debug)
out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n");
}
}
for (int i = 0; i < world->units.all.size(); ++i)
@ -714,15 +762,49 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
std::vector<dwarf_info_t> dwarf_info(n_dwarfs);
std::vector<int> best_noble(ARRAY_COUNT(noble_skills));
std::vector<int> highest_noble_skill(ARRAY_COUNT(noble_skills));
std::vector<int> highest_noble_experience(ARRAY_COUNT(noble_skills));
// Find total skill and highest skill for each dwarf. More skilled dwarves shouldn't be used for minor tasks.
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{
assert(dwarfs[dwarf]->status.souls.size() > 0);
// assert(dwarfs[dwarf]->status.souls.size() > 0);
// assert fails can cause DF to crash, so don't do that
if (dwarfs[dwarf]->status.souls.size() <= 0)
continue;
// compute noble penalty
int noble_penalty = 0;
df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id);
for (int i = 0; i < hf->entity_links.size(); i++) {
df::histfig_entity_link* hfelink = hf->entity_links.at(i);
if (hfelink->getType() == df::histfig_entity_link_type::POSITION) {
df::histfig_entity_link_positionst *epos =
(df::histfig_entity_link_positionst*) hfelink;
df::historical_entity* entity = df::historical_entity::find(epos->entity_id);
if (!entity)
continue;
df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id);
if (!assignment)
continue;
df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id);
if (!position)
continue;
for (int n = 0; n < 25; n++)
if (position->responsibilities[n])
noble_penalty += responsibility_penalties[n];
if (position->responsibilities[df::entity_position_responsibility::HEALTH_MANAGEMENT])
dwarf_info[dwarf].medical = true;
if (position->responsibilities[df::entity_position_responsibility::TRADE])
dwarf_info[dwarf].trader = true;
}
dwarf_info[dwarf].noble_penalty = noble_penalty;
}
for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s != dwarfs[dwarf]->status.souls[0]->skills.end(); s++)
{
@ -733,30 +815,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
int skill_level = (*s)->rating;
int skill_experience = (*s)->experience;
// Track the dwarfs with the best Appraisal, Organization, and Record Keeping skills.
// They are likely to have appointed noble positions, so should be kept free where possible.
int noble_skill_id = -1;
for (int i = 0; i < ARRAY_COUNT(noble_skills); i++)
{
if (skill == noble_skills[i])
noble_skill_id = i;
}
if (noble_skill_id >= 0)
{
assert(noble_skill_id < ARRAY_COUNT(noble_skills));
if (highest_noble_skill[noble_skill_id] < skill_level ||
(highest_noble_skill[noble_skill_id] == skill_level &&
highest_noble_experience[noble_skill_id] < skill_experience))
{
highest_noble_skill[noble_skill_id] = skill_level;
highest_noble_experience[noble_skill_id] = skill_experience;
best_noble[noble_skill_id] = dwarf;
}
}
// Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.)
if (skill_class != df::enums::job_skill_class::Normal && skill_class != df::enums::job_skill_class::Medical)
@ -768,24 +826,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
}
}
// Mark the best nobles, so we try to keep them non-busy. (It would be better to find the actual assigned nobles.)
for (int i = 0; i < ARRAY_COUNT(noble_skills); i++)
{
assert(best_noble[i] >= 0);
assert(best_noble[i] < n_dwarfs);
dwarf_info[best_noble[i]].is_best_noble = true;
}
// Calculate a base penalty for using each dwarf for a task he isn't good at.
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{
dwarf_info[dwarf].mastery_penalty -= 40 * dwarf_info[dwarf].highest_skill;
dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill;
if (dwarf_info[dwarf].is_best_noble)
dwarf_info[dwarf].mastery_penalty -= 250;
dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty;
for (int labor = ENUM_FIRST_ITEM(unit_labor); labor <= ENUM_LAST_ITEM(unit_labor); labor++)
{
@ -832,7 +879,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
if (is_on_break)
dwarf_info[dwarf].state = OTHER;
else if (dwarfs[dwarf]->meetings.size() > 0)
else if (dwarfs[dwarf]->specific_refs.size() > 0)
dwarf_info[dwarf].state = OTHER;
else
dwarf_info[dwarf].state = IDLE;
@ -845,8 +892,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
assert(job >= 0);
assert(job < ARRAY_COUNT(dwarf_states));
*/
dwarf_info[dwarf].state = dwarf_states[job];
if (job >= 0 && job < ARRAY_COUNT(dwarf_states))
dwarf_info[dwarf].state = dwarf_states[job];
else
{
out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job);
dwarf_info[dwarf].state = OTHER;
}
}
state_count[dwarf_info[dwarf].state]++;
@ -1032,6 +1084,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
* Note that only idle and busy dwarfs count towards the number of dwarfs. "Other" dwarfs
* (sleeping, eating, on break, etc.) will have labors assigned, but will not be counted.
* Military and children/nobles will not have labors assigned.
* Dwarfs with the "health management" responsibility are always assigned DIAGNOSIS.
*/
for (int i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++)
{
@ -1047,6 +1100,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
preferred_dwarf = true;
if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive)
preferred_dwarf = true;
if (dwarf_info[dwarf].medical && labor == df::unit_labor::DIAGNOSE)
preferred_dwarf = true;
if (dwarf_info[dwarf].trader && trader_requested)
continue;
if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf)
continue;
@ -1084,6 +1141,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
std::vector<int> hauler_ids;
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{
if (dwarf_info[dwarf].trader && trader_requested)
continue;
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY)
hauler_ids.push_back(dwarf);
}
@ -1213,7 +1272,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
if (labor == df::enums::unit_labor::NONE)
{
out.printerr("Could not find labor %s.", parameters[0].c_str());
out.printerr("Could not find labor %s.\n", parameters[0].c_str());
return CR_WRONG_USAGE;
}

@ -153,10 +153,10 @@ static int next_job_id_save = 0;
static std::map<int,DigJob> diggers;
static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos,
df::tiletype old_tile, df::tiletype new_tile);
df::tiletype old_tile, df::tiletype new_tile, df::unit *worker);
DEFINE_LUA_EVENT_4(onDigComplete, handle_dig_complete,
df::job_type, df::coord, df::tiletype, df::tiletype);
DEFINE_LUA_EVENT_5(onDigComplete, handle_dig_complete,
df::job_type, df::coord, df::tiletype, df::tiletype, df::unit*);
static void detect_digging(color_ostream &out)
{
@ -179,10 +179,7 @@ static void detect_digging(color_ostream &out)
if (new_tile != it->second.old_tile)
{
onDigComplete(out, it->second.job, pos, it->second.old_tile, new_tile);
//if (worker && !worker->job.current_job)
// worker->counters.think_counter = worker->counters.job_counter = 0;
onDigComplete(out, it->second.job, pos, it->second.old_tile, new_tile, worker);
}
}
@ -370,7 +367,7 @@ static void add_walls_to_burrows(color_ostream &out, std::vector<df::burrow*> &b
}
static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos,
df::tiletype old_tile, df::tiletype new_tile)
df::tiletype old_tile, df::tiletype new_tile, df::unit *worker)
{
if (!isWalkable(new_tile))
return;
@ -390,9 +387,11 @@ static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord
return;
MapExtras::MapCache mc;
bool changed = false;
if (!isWalkable(old_tile))
{
changed = true;
add_walls_to_burrows(out, to_grow, mc, pos+df::coord(-1,-1,0), pos+df::coord(1,1,0));
if (isWalkableUp(new_tile))
@ -407,6 +406,7 @@ static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord
if (LowPassable(new_tile) && !LowPassable(old_tile))
{
changed = true;
add_to_burrows(to_grow, pos-df::coord(0,0,1));
if (tileShape(new_tile) == tiletype_shape::RAMP_TOP)
@ -415,6 +415,9 @@ static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord
pos+df::coord(-1,-1,-1), pos+df::coord(1,1,-1));
}
}
if (changed && worker && !worker->job.current_job)
Job::checkDesignationsNow();
}
static void renameBurrow(color_ostream &out, df::burrow *burrow, std::string name)

@ -13,4 +13,7 @@ DFHACK_PLUGIN(frozen frozen.cpp)
DFHACK_PLUGIN(dumpmats dumpmats.cpp)
#DFHACK_PLUGIN(tiles tiles.cpp)
DFHACK_PLUGIN(counters counters.cpp)
DFHACK_PLUGIN(stockcheck stockcheck.cpp)
DFHACK_PLUGIN(stripcaged stripcaged.cpp)
DFHACK_PLUGIN(rprobe rprobe.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp)

@ -25,7 +25,7 @@ command_result df_counters (color_ostream &out, vector <string> & parameters)
for (size_t i = 0; i < counters.size(); i++)
{
auto counter = counters[i];
out.print("%i (%s): %i\n", counter->id, ENUM_KEY_STR(unit_misc_trait::T_id, counter->id).c_str(), counter->value);
out.print("%i (%s): %i\n", counter->id, ENUM_KEY_STR(misc_trait_type, counter->id).c_str(), counter->value);
}
return CR_OK;

@ -0,0 +1,116 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "DataDefs.h"
#include "df/world.h"
#include "df/ui.h"
#include "df/building_nest_boxst.h"
#include "df/building_type.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/unit.h"
#include "df/building.h"
#include "df/items_other_id.h"
#include "df/creature_raw.h"
#include "modules/MapCache.h"
#include "modules/Items.h"
using std::vector;
using std::string;
using std::endl;
using namespace DFHack;
using namespace df::enums;
using df::global::world;
using df::global::ui;
static command_result nestboxes(color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("nestboxes");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
if (world && ui) {
commands.push_back(
PluginCommand("nestboxes", "Derp.",
nestboxes, false,
"Derp.\n"
)
);
}
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
static command_result nestboxes(color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
bool clean = false;
int dump_count = 0;
int good_egg = 0;
if (parameters.size() == 1 && parameters[0] == "clean")
{
clean = true;
}
for (int i = 0; i < world->buildings.all.size(); ++i)
{
df::building *build = world->buildings.all[i];
auto type = build->getType();
if (df::enums::building_type::NestBox == type)
{
bool needs_clean = false;
df::building_nest_boxst *nb = virtual_cast<df::building_nest_boxst>(build);
out << "Nestbox at (" << nb->x1 << "," << nb->y1 << ","<< nb->z << "): claimed-by " << nb->claimed_by << ", contained item count " << nb->contained_items.size() << " (" << nb->anon_1 << ")" << endl;
if (nb->contained_items.size() > 1)
needs_clean = true;
if (nb->claimed_by != -1)
{
df::unit* u = df::unit::find(nb->claimed_by);
if (u)
{
out << " Claimed by ";
if (u->name.has_name)
out << u->name.first_name << ", ";
df::creature_raw *raw = df::global::world->raws.creatures.all[u->race];
out << raw->creature_id
<< ", pregnancy timer " << u->relations.pregnancy_timer << endl;
if (u->relations.pregnancy_timer > 0)
needs_clean = false;
}
}
for (int j = 1; j < nb->contained_items.size(); j++)
{
df::item* item = nb->contained_items[j]->item;
if (needs_clean) {
if (clean && !item->flags.bits.dump)
{
item->flags.bits.dump = 1;
dump_count += item->getStackSize();
}
} else {
good_egg += item->getStackSize();
}
}
}
}
if (clean)
{
out << dump_count << " eggs dumped." << endl;
}
out << good_egg << " fertile eggs found." << endl;
return CR_OK;
}

@ -0,0 +1,163 @@
// Produces a list of materials available on the map.
// Options:
// -a : show unrevealed tiles
// -p : don't show plants
// -s : don't show slade
// -t : don't show demon temple
//#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <map>
#include <algorithm>
#include <vector>
using namespace std;
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/MapCache.h"
#include "MiscUtils.h"
#include "DataDefs.h"
#include "df/world.h"
#include "df/world_data.h"
#include "df/world_region_details.h"
#include "df/world_geo_biome.h"
#include "df/world_geo_layer.h"
#include "df/inclusion_type.h"
#include "df/viewscreen_choose_start_sitest.h"
using namespace DFHack;
using namespace df::enums;
using df::global::world;
using df::coord2d;
command_result rprobe (color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("rprobe");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"rprobe", "Display assorted region information from embark screen",
rprobe, false,
"Display assorted region information from embark screen\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
command_result rprobe (color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
bool set = false;
int to_set, set_field, set_val;
// Embark screen active: estimate using world geology data
VIRTUAL_CAST_VAR(screen, df::viewscreen_choose_start_sitest, Core::getTopViewscreen());
if (!screen)
return CR_WRONG_USAGE;
if (!world || !world->world_data)
{
out.printerr("World data is not available.\n");
return CR_FAILURE;
}
if (parameters.size() == 2)
{
if (parameters[0] == "wet")
set_field = 0;
else if (parameters[0] == "veg")
set_field = 1;
else if (parameters[0] == "tem")
set_field = 2;
else if (parameters[0] == "evi")
set_field = 3;
else if (parameters[0] == "hil")
set_field = 4;
else if (parameters[0] == "sav")
set_field = 5;
else if (parameters[0] == "sal")
set_field = 6;
else
return CR_WRONG_USAGE;
if (screen->biome_highlighted)
to_set = screen->biome_idx;
else
to_set = 0;
set = true;
set_val = atoi(parameters[1].c_str());
}
df::world_data *data = world->world_data;
coord2d cur_region = screen->region_pos;
// Compute biomes
for (int i = 0; i < screen->biome_rgn.size(); i++)
{
coord2d rg = screen->biome_rgn[i];
df::world_data::T_region_map* rd = &data->region_map[rg.x][rg.y];
if (set && i == to_set) {
if (set_field == 0)
rd->wetness = set_val;
else if (set_field == 1)
rd->vegetation = set_val;
else if (set_field == 2)
rd->temperature = set_val;
else if (set_field == 3)
rd->evilness = set_val;
else if (set_field == 4)
rd->hilliness = set_val;
else if (set_field == 5)
rd->savagery = set_val;
else if (set_field == 6)
rd->saltiness = set_val;
}
out << i << ": x = " << rg.x << ", y = " << rg.y;
out <<
" region_id: " << rd->region_id <<
" geo_index: " << rd->geo_index <<
" landmass_id: " << rd->landmass_id <<
" flags: " << hex << rd->flags.as_int() << dec << endl;
out <<
"wet: " << rd->wetness << " " <<
"veg: " << rd->vegetation << " " <<
"tem: " << rd->temperature << " " <<
"evi: " << rd->evilness << " " <<
"hil: " << rd->hilliness << " " <<
"sav: " << rd->savagery << " " <<
"sal: " << rd->saltiness;
int32_t *p = (int32_t *)rd;
int c = sizeof(*rd) / sizeof(int32_t);
for (int j = 0; j < c; j++) {
if (j % 8 == 0)
out << endl << setfill('0') << setw(8) << hex << (int)(rd+j) << ": ";
out << " " << setfill('0') << setw(8) << hex << p[j];
}
out << setfill(' ') << setw(0) << dec << endl;
}
return CR_OK;
}

@ -0,0 +1,282 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "DataDefs.h"
#include "df/world.h"
#include "df/ui.h"
#include "df/building_stockpilest.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/unit.h"
#include "df/building.h"
#include "df/items_other_id.h"
#include "df/item_stockpile_ref.h"
#include "modules/MapCache.h"
#include "modules/Items.h"
using std::vector;
using std::string;
using std::endl;
using namespace DFHack;
using namespace df::enums;
using df::global::world;
using df::global::ui;
using df::global::selection_rect;
using df::building_stockpilest;
static command_result copystock(color_ostream &out, vector <string> & parameters);
static command_result stockcheck(color_ostream &out, vector <string> & parameters);
static bool copystock_guard(df::viewscreen *top);
DFHACK_PLUGIN("stockcheck");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
if (world && ui) {
commands.push_back(
PluginCommand("stockcheck", "Check for unprotected rottable items.",
stockcheck, false,
"Scan world for items that are susceptible to rot. Currently just lists the items.\n"
)
);
}
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
struct StockpileInfo {
building_stockpilest* sp;
int size;
int free;
int x1, x2, y1, y2, z;
public:
StockpileInfo(building_stockpilest *sp_) : sp(sp_)
{
MapExtras::MapCache mc;
z = sp_->z;
x1 = sp_->room.x;
x2 = sp_->room.x + sp_->room.width;
y1 = sp_->room.y;
y2 = sp_->room.y + sp_->room.height;
int e = 0;
size = 0;
free = 0;
for (int y = y1; y < y2; y++)
for (int x = x1; x < x2; x++)
if (sp_->room.extents[e++] == 1)
{
size++;
DFCoord cursor (x,y,z);
uint32_t blockX = x / 16;
uint32_t tileX = x % 16;
uint32_t blockY = y / 16;
uint32_t tileY = y % 16;
MapExtras::Block * b = mc.BlockAt(cursor/16);
if(b && b->is_valid())
{
auto &block = *b->getRaw();
df::tile_occupancy &occ = block.occupancy[tileX][tileY];
if (!occ.bits.item)
free++;
}
}
}
bool isFull() { return free == 0; }
bool canHold(df::item *i)
{
return false;
}
bool inStockpile(df::item *i)
{
df::item *container = Items::getContainer(i);
if (container)
return inStockpile(container);
if (i->pos.z != z) return false;
if (i->pos.x < x1 || i->pos.x >= x2 ||
i->pos.y < y1 || i->pos.y >= y2) return false;
int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width;
return sp->room.extents[e] == 1;
}
int getId() { return sp->id; }
};
static command_result stockcheck(color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
std::vector<StockpileInfo*> stockpiles;
for (int i = 0; i < world->buildings.all.size(); ++i)
{
df::building *build = world->buildings.all[i];
auto type = build->getType();
if (df::enums::building_type::Stockpile == type)
{
building_stockpilest *sp = virtual_cast<building_stockpilest>(build);
StockpileInfo *spi = new StockpileInfo(sp);
stockpiles.push_back(spi);
}
}
std::vector<df::item*> &items = world->items.other[items_other_id::ANY_FREE];
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;
bad_flags.whole = 0;
#define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact1);
F(spider_web); F(owned); F(in_job);
#undef F
for (size_t i = 0; i < items.size(); i++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
// we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG
df::item_type typ = item->getType();
if (typ != df::enums::item_type::MEAT &&
typ != df::enums::item_type::FISH &&
typ != df::enums::item_type::FISH_RAW &&
typ != df::enums::item_type::PLANT &&
typ != df::enums::item_type::CHEESE &&
typ != df::enums::item_type::FOOD &&
typ != df::enums::item_type::EGG)
continue;
df::item *container = 0;
df::unit *holder = 0;
df::building *building = 0;
for (size_t i = 0; i < item->itemrefs.size(); i++)
{
df::general_ref *ref = item->itemrefs[i];
switch (ref->getType())
{
case general_ref_type::CONTAINED_IN_ITEM:
container = ref->getItem();
break;
case general_ref_type::UNIT_HOLDER:
holder = ref->getUnit();
break;
case general_ref_type::BUILDING_HOLDER:
building = ref->getBuilding();
break;
default:
break;
}
}
df::item *nextcontainer = container;
df::item *lastcontainer = 0;
while(nextcontainer) {
df::item *thiscontainer = nextcontainer;
nextcontainer = 0;
for (size_t i = 0; i < thiscontainer->itemrefs.size(); i++)
{
df::general_ref *ref = thiscontainer->itemrefs[i];
switch (ref->getType())
{
case general_ref_type::CONTAINED_IN_ITEM:
lastcontainer = nextcontainer = ref->getItem();
break;
case general_ref_type::UNIT_HOLDER:
holder = ref->getUnit();
break;
case general_ref_type::BUILDING_HOLDER:
building = ref->getBuilding();
break;
default:
break;
}
}
}
if (holder)
continue; // carried items do not rot as far as i know
if (building) {
df::building_type btype = building->getType();
if (btype == df::enums::building_type::TradeDepot ||
btype == df::enums::building_type::Wagon)
continue; // items in trade depot or the embark wagon do not rot
if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox)
continue; // eggs in nest box do not rot
}
int canHoldCount = 0;
StockpileInfo *current = 0;
for (int idx = 0; idx < stockpiles.size(); idx++)
{
StockpileInfo *spi = stockpiles[idx];
if (spi->canHold(item)) canHoldCount++;
if (spi->inStockpile(item)) current=spi;
}
if (current)
continue;
std::string description;
item->getItemDescription(&description, 0);
out << " * " << description;
if (container) {
std::string containerDescription;
container->getItemDescription(&containerDescription, 0);
out << ", in container " << containerDescription;
if (lastcontainer) {
std::string lastcontainerDescription;
lastcontainer->getItemDescription(&lastcontainerDescription, 0);
out << ", in container " << lastcontainerDescription;
}
}
if (holder) {
out << ", carried";
}
if (building) {
out << ", in building " << building->id << " (type=" << building->getType() << ")";
}
out << ", flags=" << std::hex << item->flags.whole << std::dec;
out << endl;
}
return CR_OK;
}

@ -0,0 +1,103 @@
#include <iostream>
#include <iomanip>
#include <climits>
#include <vector>
#include <algorithm>
#include <string>
#include <sstream>
#include <ctime>
#include <cstdio>
using namespace std;
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Units.h"
#include "modules/Maps.h"
#include "modules/Gui.h"
#include "modules/World.h"
#include "MiscUtils.h"
#include <df/ui.h>
#include "df/world.h"
#include "df/world_raws.h"
#include "df/building_def.h"
#include "df/unit_inventory_item.h"
#include <df/creature_raw.h>
#include <df/caste_raw.h>
using std::vector;
using std::string;
using namespace DFHack;
using namespace df::enums;
using df::global::world;
using df::global::cursor;
using df::global::ui;
using namespace DFHack::Gui;
command_result df_stripcaged(color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("stripcaged");
// check if contained in item (e.g. animals in cages)
bool isContainedInItem(df::unit* unit)
{
bool contained = false;
for (size_t r=0; r < unit->refs.size(); r++)
{
df::general_ref * ref = unit->refs[r];
auto rtype = ref->getType();
if(rtype == df::general_ref_type::CONTAINED_IN_ITEM)
{
contained = true;
break;
}
}
return contained;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"stripcaged", "strip caged units of all items",
df_stripcaged, false,
"Clears forbid and sets dump for the inventories of all caged units."
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
command_result df_stripcaged(color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
size_t count = 0;
for (size_t i=0; i < world->units.all.size(); i++)
{
df::unit* unit = world->units.all[i];
if (isContainedInItem(unit))
{
for (size_t j=0; j < unit->inventory.size(); j++)
{
df::unit_inventory_item* uii = unit->inventory[j];
if (uii->item)
{
uii->item->flags.bits.forbid = 0;
uii->item->flags.bits.dump = 1;
count++;
}
}
}
}
out << count << " items marked for dumping" << endl;
return CR_OK;
}

@ -89,13 +89,13 @@ static void element(const char* name, const uint32_t content, ostream& out, cons
static void printAttributes(color_ostream &con, df::unit* cre, ostream& out) {
out << " <Attributes>" << endl;
for (int i = 0; i < NUM_CREATURE_PHYSICAL_ATTRIBUTES; i++) {
element(physicals[i], cre->body.physical_attrs[i].unk1, out, " ");
element(physicals[i], cre->body.physical_attrs[i].value, out, " ");
}
df::unit_soul * s = cre->status.current_soul;
if (s) {
for (int i = 0; i < NUM_CREATURE_MENTAL_ATTRIBUTES; i++) {
element(mentals[i], s->mental_attrs[i].unk1, out, " ");
element(mentals[i], s->mental_attrs[i].value, out, " ");
}
}
out << " </Attributes>" << endl;
@ -213,7 +213,7 @@ command_result export_dwarves (color_ostream &con, std::vector <std::string> & p
return CR_OK;
}
ofstream outf(filename);
ofstream outf(filename.c_str());
if (!outf) {
con.printerr("Failed to open file %s\n", filename.c_str());
return CR_FAILURE;

@ -28,7 +28,7 @@ static int enable_fastdwarf = false;
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
// check run conditions
if(!world->map.block_index || !enable_fastdwarf)
if(!world || !world->map.block_index || !enable_fastdwarf)
{
// give up if we shouldn't be running'
return CR_OK;

@ -0,0 +1,641 @@
// forceequip plugin
// Moves local items from the ground into a unit's inventory
#include <iostream>
#include <iomanip>
#include <sstream>
#include <climits>
#include <vector>
#include <string>
#include <algorithm>
#include <set>
using namespace std;
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Maps.h"
#include "modules/Gui.h"
#include "modules/Items.h"
#include "modules/Materials.h"
#include "modules/MapCache.h"
#include "DataDefs.h"
#include "df/item.h"
#include "df/itemdef.h"
#include "df/world.h"
#include "df/general_ref.h"
#include "df/unit.h"
#include "df/body_part_raw.h"
#include "MiscUtils.h"
#include "df/unit_inventory_item.h"
#include "df/body_part_raw_flags.h"
#include "df/creature_raw.h"
#include "df/caste_raw.h"
#include "df/body_detail_plan.h"
#include "df/body_template.h"
#include "df/body_part_template.h"
#include "df/unit_soul.h"
#include "df/unit_skill.h"
#include "df/general_ref.h"
#include "df/caste_raw.h"
#include "DFHack.h"
using namespace DFHack;
using namespace df::enums;
using MapExtras::Block;
using MapExtras::MapCache;
using df::global::world;
const int const_GloveRightHandedness = 1;
const int const_GloveLeftHandedness = 2;
DFHACK_PLUGIN("forceequip");
command_result df_forceequip(color_ostream &out, vector <string> & parameters);
const string forceequip_help =
"ForceEquip moves local items into a unit's inventory. It is typically\n"
"used to equip specific clothing/armor items onto a dwarf, but can also\n"
"be used to put armor onto a war animal or to add unusual items (such\n"
"as crowns) to any unit.\n"
"This plugin can process multiple items in a single call, but will only\n"
"work with a single unit (the first one it finds under the cursor).\n"
"In order to minimize confusion, it is recommended that you use\n"
"forceequip only when you have a unit standing alone atop a pile of\n"
"gear that you would like it to wear. Items which are stored in bins\n"
"or other containers (e.g. chests, armor racks) may also work, but\n"
"piling items on the floor (via a garbage dump activity zone, of the\n"
"DFHack autodump command) is the most reliable way to do it.\n"
"The plugin will ignore any items that are forbidden. Hence, you\n"
"can setup a large pile of surplus gear, walk a unit onto it (or\n"
"pasture an animal on it), unforbid a few items and run forceequip.\n"
"The (forbidden) majority of your gear will remain in-place, ready\n"
"for the next passerby."
"\n"
"As mentioned above, this plugin can be used to equip items onto\n"
"units (such as animals) which cannot normally equip gear. There's\n"
"an important caveat here - such creatures will automatically drop\n"
"inappropriate gear almost immediately (within 10 game ticks).\n"
"If you want them to retain their equipment, you must forbid it\n"
"AFTER using forceequip to get it into their inventory.\n"
"This technique can also be used to clothe dwarven infants, but\n"
"only if you're able to separate them from their mothers.\n"
"\n"
"By default, the forceequip plugin will attempt to avoid\n"
"conflicts and outright cheating. For instance, it will skip\n"
"any item which is flagged for use in a job, and will not\n"
"equip more than one piece of clothing/armor onto any given\n"
"body part. These restrictions can be overridden via command\n"
"switches (see examples below) but doing so puts you at greater\n"
"risk of unexpected consequences. For instance, a dwarf who\n"
"is wearing three breastplates will not be able to move very\n"
"quickly.\n"
"\n"
"Items equipped by this plugin DO NOT become owned by the\n"
"recipient. Adult dwarves are free to adjust their own\n"
"wardrobe, and may promptly decide to doff your gear in\n"
"favour of their owned items. Animals, as described above,\n"
"will tend to discard ALL clothing immediately unless it is\n"
"manually forbidden. Armor items seem to be an exception;\n"
"an animal will tend to retain an equipped suit of mail\n"
"even if you neglect to Forbid it.\n"
"\n"
"Please note that armored animals are quite vulnerable to ranged\n"
"attacks. Unlike dwarves, animals cannot block, dodge, or deflect\n"
"arrows, and they are slowed by the weight of their armor.\n"
"\n"
"This plugin currently does not support weapons.\n"
"\n"
"Options:\n"
" here, h - process the unit and item(s) under the cursor.\n"
" - This option is enabled by default since the plugin\n"
" - does not currently support remote equpping.\n"
" ignore, i - bypasses the usual item eligibility checks (such as\n"
" - \"Never equip gear belonging to another dwarf\" and\n"
" - \"Nobody is allowed to equip a Hive\".)\n"
" multi, m - bypasses the 1-item-per-bodypart limit, allowing\n"
" - the unit to receive an unlimited amount of gear.\n"
" - Can be used legitimately (e.g. mitten + gauntlet)\n"
" - or for cheating (e.g. twelve breastplates).\n"
" m2, m3, m4 - alters the 1-item-per-bodypart limit, allowing\n"
" - each part to receive 2, 3, or 4 pieces of gear.\n"
" selected, s - rather than processing all items piled at a unit's\n"
" - feet, process only the one item currently selected.\n"
" bodypart, bp - must be followed by a bodypart code (e.g. LH).\n"
" - Instructs the plugin to equip all available items\n"
" - onto this body part only. Typically used in\n"
" - conjunction with the f switch (to over-armor\n"
" - a particular bodypart) or the i switch (to equip\n"
" - an unusual item onto a specific slot).\n"
" verbose, v - provides detailed narration and error messages.\n"
" - Can be helpful in resolving failures; not needed\n"
" - for casual use.\n"
"\n"
"Examples:\n"
" forceequip\n"
" attempts to equip all of the items under the cursor onto the unit\n"
" under the cursor. Uses only clothing/armor items; ignores all\n"
" other types. Equips a maximum of 1 item onto each bodypart,\n"
" and equips only \"appropriate\" items in each slot (e.g. glove\n"
" --> hand). Bypasses any item which might cause a conflict,\n"
" such as a Boot belonging to a different dwarf.\n"
" forceequip bp LH\n"
" attempts to equip all local items onto the left hand of the local\n"
" unit. If the hand is already equipped then nothing will happen,\n"
" and if it is not equipped then only one appropriate item (e.g. \n"
" a single mitten or gauntlet) will be equipped. This command can\n"
" be useful if you don't want to selectively forbid individual items\n"
" and simply want the unit to equip, say, a Helmet while leaving\n"
" the rest of the pile alone.\n"
" forceequip m bp LH\n"
" as above, but will equip ALL appropriate items onto the unit's\n"
" left hand. After running this command, it might end up wearing\n"
" a dozen left-handed mittens. Use with caution, and remember\n"
" that dwarves will tend to drop supernumary items ASAP.\n"
" forceequip m\n"
" as above, but will equip ALL appropriate items onto any\n"
" appropriate bodypart. Tends to put several boots onto the right\n"
" foot while leaving the left foot bare.\n"
" forceequip m2\n"
" as above, but will equip up to two appropriate items onto each\n"
" bodypart. Helps to balance footwear, but doesn't ensure proper\n"
" placement (e.g. left foot gets two socks, right foot gets two\n"
" shoes). For best results, use \"selected bp LH\" and\n"
" \"selected bp RH\" instead.\n"
" forceequip i\n"
" performs the standard \"equip appropriate items onto appropriate\n"
" bodyparts\" logic, but also includes items that would normally\n"
" be considered ineligible (such as a sock which is owned by\n"
" a different dwarf).\n"
" forceequip bp NECK\n"
" attempts to equip any appropriate gear onto the Neck of the\n"
" local unit. Since the plugin believes that no items are actually\n"
" appropriate for the Neck slot, this command does nothing.\n"
" forceequip i bp NECK\n"
" attempts to equip items from the local pile onto the Neck\n"
" of the local unit. Ignores appropriateness restrictions.\n"
" If there's a millstone or an albatross carcass sitting on\n"
" the same square as the targeted unit, then there's a good\n"
" chance that it will end up around his neck. For precise\n"
" control, remember that you can selectively forbid some of\n"
" the items that are piled on the ground.\n"
" forceequip i m bp NECK\n"
" as above, but equips an unlimited number of items onto the\n"
" targeted bodypart. Effectively, all unforbidden items\n"
" (including helms, millstones, boulders, etc) will be\n"
" moved from the local pile and placed in the dwarf's\n"
" inventory (specifically, on his neck). When used with\n"
" a large pile of goods, this will leave the dwarf heavily\n"
" encumbered and very slow to move.\n"
" forceequip s\n"
" requires that a single item be selected using the k menu.\n"
" This item must occupy the same square as the target unit,\n"
" and must be unforbidden. Attempts to equip this single\n"
" item onto an appropriate slot in the unit's inventory.\n"
" Can serve as a quicker alternative to the selective-\n"
" unforbidding approach described above.\n"
" forceequip s m i bp HD\n"
" equips the selected item onto the unit's head. Ignores\n"
" all possible restrictions and conflicts. If you know\n"
" exactly what you want to equip, and exactly where you\n"
" want it to go, then this is the most straightforward\n"
" and reliable option.\n"
" forceequip v bp QQQ\n"
" guaranteed to fail (and accomplish nothing) because\n"
" there are no bodyparts called QQQ. However, since the\n"
" verbose switch is used, the resulting error messages\n"
" will list every bodypart that the unit DOES possess.\n"
" May be useful if you're unfamiliar with the BP codes\n"
" used by Dwarf Fortress, or if you're experimenting\n"
" with an exotic creature.\n"
"\n"
;
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"forceequip", "Moves local items from the ground into a unit's inventory",
df_forceequip, false,
forceequip_help.c_str()
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, df::body_part_raw * targetBodyPart, bool ignoreRestrictions, int multiEquipLimit, bool verbose)
{
// Step 1: Check for anti-requisite conditions
df::unit * itemOwner = Items::getOwner(item);
if (ignoreRestrictions)
{
// If the ignoreRestrictions cmdline switch was specified, then skip all of the normal preventative rules
if (verbose) { Core::print("Skipping integrity checks...\n"); }
}
else if(!item->isClothing() && !item->isArmorNotClothing())
{
if (verbose) { Core::printerr("Item %d is not clothing or armor; it cannot be equipped. Please choose a different item (or use the Ignore option if you really want to equip an inappropriate item).\n", item->id); }
return false;
}
else if (item->getType() != df::enums::item_type::GLOVES &&
item->getType() != df::enums::item_type::HELM &&
item->getType() != df::enums::item_type::ARMOR &&
item->getType() != df::enums::item_type::PANTS &&
item->getType() != df::enums::item_type::SHOES &&
!targetBodyPart)
{
if (verbose) { Core::printerr("Item %d is of an unrecognized type; it cannot be equipped (because the module wouldn't know where to put it).\n", item->id); }
return false;
}
else if (itemOwner && itemOwner->id != unit->id)
{
if (verbose) { Core::printerr("Item %d is owned by someone else. Equipping it on this unit is not recommended. Please use DFHack's Confiscate plugin, choose a different item, or use the Ignore option to proceed in spite of this warning.\n", item->id); }
return false;
}
else if (item->flags.bits.in_inventory)
{
if (verbose) { Core::printerr("Item %d is already in a unit's inventory. Direct inventory transfers are not recommended; please move the item to the ground first (or use the Ignore option).\n", item->id); }
return false;
}
else if (item->flags.bits.in_job)
{
if (verbose) { Core::printerr("Item %d is reserved for use in a queued job. Equipping it is not recommended, as this might interfere with the completion of vital jobs. Use the Ignore option to ignore this warning.\n", item->id); }
return false;
}
// ASSERT: anti-requisite conditions have been satisfied (or disregarded)
// Step 2: Try to find a bodypart which is eligible to receive equipment AND which is appropriate for the specified item
df::body_part_raw * confirmedBodyPart = NULL;
int bpIndex;
for(bpIndex = 0; bpIndex < unit->body.body_plan->body_parts.size(); bpIndex++)
{
df::body_part_raw * currPart = unit->body.body_plan->body_parts[bpIndex];
// Short-circuit the search process if a BP was specified in the function call
// Note: this causes a bit of inefficient busy-looping, but the search space is tiny (<100) and we NEED to get the correct bpIndex value in order to perform inventory manipulations
if (!targetBodyPart)
{
// The function call did not specify any particular body part; proceed with normal iteration and evaluation of BP eligibility
}
else if (currPart == targetBodyPart)
{
// A specific body part was included in the function call, and we've found it; proceed with the normal BP evaluation (suitability, emptiness, etc)
}
else if (bpIndex < unit->body.body_plan->body_parts.size())
{
// The current body part is not the one that was specified in the function call, but we can keep searching
if (verbose) { Core::printerr("Found bodypart %s; not a match; continuing search.\n", currPart->token.c_str()); }
continue;
}
else
{
// The specified body part has not been found, and we've reached the end of the list. Report failure.
if (verbose) { Core::printerr("The specified body part (%s) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n"); }
return false;
}
if (verbose) { Core::print("Inspecting bodypart %s.\n", currPart->token.c_str()); }
// Inspect the current bodypart
if (item->getType() == df::enums::item_type::GLOVES && currPart->flags.is_set(df::body_part_raw_flags::GRASP) &&
((item->getGloveHandedness() == const_GloveLeftHandedness && currPart->flags.is_set(df::body_part_raw_flags::LEFT)) ||
(item->getGloveHandedness() == const_GloveRightHandedness && currPart->flags.is_set(df::body_part_raw_flags::RIGHT))))
{
if (verbose) { Core::print("Hand found (%s)...", currPart->token.c_str()); }
}
else if (item->getType() == df::enums::item_type::HELM && currPart->flags.is_set(df::body_part_raw_flags::HEAD))
{
if (verbose) { Core::print("Head found (%s)...", currPart->token.c_str()); }
}
else if (item->getType() == df::enums::item_type::ARMOR && currPart->flags.is_set(df::body_part_raw_flags::UPPERBODY))
{
if (verbose) { Core::print("Upper body found (%s)...", currPart->token.c_str()); }
}
else if (item->getType() == df::enums::item_type::PANTS && currPart->flags.is_set(df::body_part_raw_flags::LOWERBODY))
{
if (verbose) { Core::print("Lower body found (%s)...", currPart->token.c_str()); }
}
else if (item->getType() == df::enums::item_type::SHOES && currPart->flags.is_set(df::body_part_raw_flags::STANCE))
{
if (verbose) { Core::print("Foot found (%s)...", currPart->token.c_str()); }
}
else if (targetBodyPart && ignoreRestrictions)
{
// The BP in question would normally be considered ineligible for equipment. But since it was deliberately specified by the user, we'll proceed anyways.
if (verbose) { Core::print("Non-standard bodypart found (%s)...", targetBodyPart->token.c_str()); }
}
else if (targetBodyPart)
{
// The BP in question is not eligible for equipment and the ignore flag was not specified. Report failure.
if (verbose) { Core::printerr("Non-standard bodypart found, but it is ineligible for standard equipment. Use the Ignore flag to override this warning.\n"); }
return false;
}
else
{
if (verbose) { Core::print("Skipping ineligible bodypart.\n"); }
// This body part is not eligible for the equipment in question; skip it
continue;
}
// ASSERT: The current body part is able to support the specified equipment (or the test has been overridden). Check whether it is currently empty/available.
if (multiEquipLimit == INT_MAX)
{
// Note: this loop/check is skipped if the MultiEquip option is specified; we'll simply add the item to the bodyPart even if it's already holding a dozen gloves, shoes, and millstones (or whatever)
if (verbose) { Core::print(" inventory checking skipped..."); }
confirmedBodyPart = currPart;
break;
}
else
{
confirmedBodyPart = currPart; // Assume that the bodypart is valid; we'll invalidate it if we detect too many collisions while looping
int collisions = 0;
for (int inventoryID=0; inventoryID < unit->inventory.size(); inventoryID++)
{
df::unit_inventory_item * currInvItem = unit->inventory[inventoryID];
if (currInvItem->body_part_id == bpIndex)
{
// Collision detected; have we reached the limit?
if (++collisions >= multiEquipLimit)
{
if (verbose) { Core::printerr(" but it already carries %d piece(s) of equipment. Either remove the existing equipment or use the Multi option.\n", multiEquipLimit); }
confirmedBodyPart = NULL;
break;
}
}
}
if (confirmedBodyPart)
{
// Match found; no need to examine any other BPs
if (verbose) { Core::print(" eligibility confirmed..."); }
break;
}
else if (!targetBodyPart)
{
// This body part is not eligible to receive the specified equipment; return to the loop and check the next BP
continue;
}
else
{
// A specific body part was designated in the function call, but it was found to be ineligible.
// Don't return to the BP loop; just fall-through to the failure-reporting code a few lines below.
break;
}
}
}
if (!confirmedBodyPart) {
// No matching body parts found; report failure
if (verbose) { Core::printerr("\nThe item could not be equipped because the relevant body part(s) of the unit are missing or already occupied. Try again with the Multi option if you're like to over-equip a body part, or choose a different unit-item combination (e.g. stop trying to put shoes on a trout).\n" ); }
return false;
}
if (!Items::moveToInventory(mc, item, unit, df::unit_inventory_item::Worn, bpIndex))
{
if (verbose) { Core::printerr("\nEquipping failed - failed to retrieve item from its current location/container/inventory. Please move it to the ground and try again.\n"); }
return false;
}
if (verbose) { Core::print(" Success!\n"); }
return true;
}
command_result df_forceequip(color_ostream &out, vector <string> & parameters)
{
// The "here" option is hardcoded to true, because the plugin currently doesn't support
// equip-at-a-distance (e.g. grab items within 10 squares of the targeted unit)
bool here = true;
// For balance (anti-cheating) reasons, the plugin applies a limit on the number of
// item that can be equipped on any bodypart. This limit defaults to 1 but can be
// overridden with cmdline switches.
int multiEquipLimit = 1;
// The plugin applies several pre-checks in order to reduce the risk of conflict
// and unintended side-effects. Most of these checks can be disabled via cmdline
bool ignore = false;
// By default, the plugin uses all gear piled on the selected square. Optionally,
// it can target only a single item (selected on the k menu) instead
bool selected = false;
// Most of the plugin's text output is suppressed by default. It can be enabled
// to provide insight into errors, and/or for debugging purposes.
bool verbose = false;
// By default, the plugin will mate each item to an appropriate bodypart. This
// behaviour can be skipped if the user specifies a particular BP in the cmdline input.
std::string targetBodyPartCode;
// Parse the input
for (size_t i = 0; i < parameters.size(); i++)
{
string & p = parameters[i];
if (p == "help" || p == "?" || p == "h" || p == "/?" || p == "info" || p == "man")
{
out << forceequip_help << endl;
return CR_OK;
}
else if (p == "here" || p == "h")
{
here = true;
}
else if (p == "ignore" || p == "i")
{
ignore = true;
}
else if (p == "multi" || p == "m")
{
multiEquipLimit = INT_MAX;
}
else if (p == "m2")
{
multiEquipLimit = 2;
}
else if (p == "m3")
{
multiEquipLimit = 3;
}
else if (p == "m4")
{
multiEquipLimit = 4;
}
else if (p == "selected" || p == "s")
{
selected = true;
}
else if (p == "verbose" || p == "v")
{
verbose = true;
}
else if (p == "bodypart" || p == "bp" )
{
// must be followed by bodypart code (e.g. NECK)
if(i == parameters.size()-1 || parameters[i+1].size() == 0)
{
out.printerr("The bp switch must be followed by a bodypart code!\n");
return CR_FAILURE;
}
targetBodyPartCode = parameters[i+1];
i++;
}
else
{
out << p << ": Unknown command! Type \"forceequip help\" for assistance." << endl;
return CR_FAILURE;
}
}
// Ensure that the map information is available (e.g. a game is actually in-progress)
if (!Maps::IsValid())
{
out.printerr("Map is not available!\n");
return CR_FAILURE;
}
// Lookup the cursor position
int cx, cy, cz;
DFCoord pos_cursor;
// needs a cursor
if (!Gui::getCursorCoords(cx,cy,cz))
{
out.printerr("Cursor position not found. Please enable the cursor.\n");
return CR_FAILURE;
}
pos_cursor = DFCoord(cx,cy,cz);
// Iterate over all units, process the first one whose pos == pos_cursor
df::unit * targetUnit;
size_t numUnits = world->units.all.size();
for(size_t i=0; i< numUnits; i++)
{
targetUnit = world->units.all[i]; // tentatively assume that we have a match; then verify
DFCoord pos_unit(targetUnit->pos.x, targetUnit->pos.y, targetUnit->pos.z);
if (pos_unit == pos_cursor)
break;
if (i + 1 == numUnits)
{
out.printerr("No unit found at cursor!\n");
return CR_FAILURE;
}
}
// Assert: unit found.
// If a specific bodypart was included in the command arguments, then search for it now
df::body_part_raw * targetBodyPart = NULL;
if (targetBodyPartCode.size() > 0) {
for (int bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++)
{
// Tentatively assume that the part is a match
targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex);
if (targetBodyPart->token.compare(targetBodyPartCode) == 0)
{
// It is indeed a match; exit the loop (while leaving the variable populated)
if (verbose) { out.print("Matching bodypart (%s) found.\n", targetBodyPart->token.c_str()); }
break;
}
else
{
// Not a match; nullify the variable (it will get re-populated on the next pass through the loop)
if (verbose) { out.printerr("Bodypart \"%s\" does not match \"%s\".\n", targetBodyPart->token.c_str(), targetBodyPartCode.c_str()); }
targetBodyPart = NULL;
}
}
if (!targetBodyPart)
{
// Loop iteration is complete but no match was found.
out.printerr("The unit does not possess a bodypart of type \"%s\". Please check the spelling or choose a different unit.\n", targetBodyPartCode.c_str());
return CR_FAILURE;
}
}
// Search for item(s)
MapCache mc;
// iterate over all items, process those where pos == pos_cursor
int itemsEquipped = 0;
int itemsFound = 0;
int numItems = world->items.all.size(); // Normally, we iterate through EVERY ITEM in the world. This is expensive, but currently necessary.
if (selected) { numItems = 1; } // If the user wants to process only the selected item, then the loop is trivialized (only one pass is needed).
for(int i=0; i< numItems; i++)
{
df::item * currentItem;
// Search behaviour depends on whether the operation is driven by cursor location or UI selection
if (selected)
{
// The "search" is trivial - the selection must always cover either one or zero items
currentItem = Gui::getSelectedItem(out);
if (!currentItem) { return CR_FAILURE; }
}
else
{
// Lookup the current item in the world-space
currentItem = world->items.all[i];
// Test the item's position
DFCoord pos_item(currentItem->pos.x, currentItem->pos.y, currentItem->pos.z);
if (pos_item != pos_cursor)
{
// The item is in the wrong place; skip it
// Note: we do not emit any notification, even with the "verbose" switch, because the search space is enormous and we'd invariably flood the UI with useless text
continue;
}
// Bypass any forbidden items
else if (currentItem->flags.bits.forbid == 1)
{
// The item is forbidden; skip it
if (verbose) { out.printerr("Forbidden item encountered; skipping to next item.\n"); }
}
}
// Test the item; check whether we have any grounds to disqualify/reject it
if (currentItem->flags.bits.in_inventory == 1)
{
// The item is in a unit's inventory; skip it
if (verbose) { out.printerr("Inventory item encountered; skipping to next item.\n"); }
}
else
{
itemsFound ++; // Track the number of items found under the cursor (for feedback purposes)
if (moveToInventory(mc, currentItem, targetUnit, targetBodyPart, ignore, multiEquipLimit, verbose))
{
// // TODO TEMP EXPERIMENTAL - try to alter the item size in order to conform to its wearer
// currentItem->getRace();
// out.print("Critter size: %d| %d | Armor size: %d", world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_1, world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_2, currentItem->getTotalDimension());
itemsEquipped++; // Track the number of items successfully processed (for feedback purposes)
}
}
}
if (itemsFound == 0) {
out.printerr("No usable items found at the cursor position. Please choose a different location and try again.\n");
return CR_OK;
}
if (itemsEquipped == 0 && !verbose) { out.printerr("Some items were found but no equipment changes could be made. Use the /verbose switch to display the reasons for failure.\n"); }
if (itemsEquipped > 0) { out.print("%d items equipped.\n", itemsEquipped); }
// At this point, some changes may have been made (such as detaching items from their original position), regardless of whether any equipment changes succeeded.
// Therefore, we must update the map.
mc.WriteAll();
// Note: we might expect to recalculate the unit's weight at this point, in order to account for the
// added items. In fact, this recalculation occurs automatically during each dwarf's "turn".
// The slight delay in recalculation is probably not worth worrying about.
// Work complete; report success
return CR_OK;
}

@ -272,7 +272,7 @@ static command_result job_duplicate(color_ostream &out, vector <string> & parame
if (!job)
return CR_FAILURE;
if (!job->misc_links.empty() ||
if (!job->specific_refs.empty() ||
(job->job_items.empty() &&
job->job_type != job_type::CollectSand &&
job->job_type != job_type::CollectClay))

@ -78,7 +78,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
static string brushname = "point";
static string mode="magma";
static string flowmode="f+";
static string setmode ="s.";
static string _setmode ="s.";
static unsigned int amount = 7;
static int width = 1, height = 1, z_levels = 1;
@ -107,22 +107,27 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
return CR_FAILURE;
}
std::vector<std::string> commands;
bool end = false;
out << "Welcome to the liquid spawner.\nType 'help' or '?' for a list of available commands, 'q' to quit.\nPress return after a command to confirm." << std::endl;
while(!end)
{
string command = "";
string input = "";
std::stringstream str;
str <<"[" << mode << ":" << brushname;
if (brushname == "range")
str << "(w" << width << ":h" << height << ":z" << z_levels << ")";
str << ":" << amount << ":" << flowmode << ":" << setmode << "]#";
if(out.lineedit(str.str(),command,liquids_hist) == -1)
str << ":" << amount << ":" << flowmode << ":" << _setmode << "]#";
if(out.lineedit(str.str(),input,liquids_hist) == -1)
return CR_FAILURE;
liquids_hist.add(command);
liquids_hist.add(input);
commands.clear();
Core::cheap_tokenise(input, commands);
string command = commands.empty() ? "" : commands[0];
if(command=="help" || command == "?")
{
@ -195,28 +200,14 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
}
else if(command == "range" || command == "r")
{
std::stringstream str;
CommandHistory range_hist;
str << " :set range width<" << width << "># ";
out.lineedit(str.str(),command,range_hist);
range_hist.add(command);
width = command == "" ? width : atoi (command.c_str());
if(width < 1) width = 1;
str.str("");
str << " :set range height<" << height << "># ";
out.lineedit(str.str(),command,range_hist);
range_hist.add(command);
height = command == "" ? height : atoi (command.c_str());
if(height < 1) height = 1;
command_result res = parseRectangle(out, commands, 1, commands.size(),
width, height, z_levels);
if (res != CR_OK)
{
return res;
}
str.str("");
str << " :set range z-levels<" << z_levels << "># ";
out.lineedit(str.str(),command,range_hist);
range_hist.add(command);
z_levels = command == "" ? z_levels : atoi (command.c_str());
if(z_levels < 1) z_levels = 1;
if(width == 1 && height == 1 && z_levels == 1)
if (width == 1 && height == 1 && z_levels == 1)
{
brushname = "point";
}
@ -255,15 +246,15 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
}
else if(command == "s+")
{
setmode = "s+";
_setmode = "s+";
}
else if(command == "s-")
{
setmode = "s-";
_setmode = "s-";
}
else if(command == "s.")
{
setmode = "s.";
_setmode = "s.";
}
// blah blah, bad code, bite me.
else if(command == "0")
@ -310,7 +301,7 @@ command_result df_liquids_here (color_ostream &out, vector <string> & parameters
out << "[" << mode << ":" << brushname;
if (brushname == "range")
out << "(w" << width << ":h" << height << ":z" << z_levels << ")";
out << ":" << amount << ":" << flowmode << ":" << setmode << "]\n";
out << ":" << amount << ":" << flowmode << ":" << _setmode << "]\n";
return df_liquids_execute(out);
}
@ -418,10 +409,7 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setDesignationAt(*iter,a);
Block * b = mcache.BlockAt((*iter)/16);
DFHack::t_blockflags bf = b->BlockFlags();
bf.bits.update_liquid = true;
bf.bits.update_liquid_twice = true;
b->setBlockFlags(bf);
b->enableBlockUpdates(true);
iter++;
}
@ -448,7 +436,8 @@ command_result df_liquids_execute(color_ostream &out)
DFHack::DFCoord current = *iter; // current tile coord
DFHack::DFCoord curblock = current /16; // current block coord
// check if the block is actually there
if(!mcache.BlockAt(curblock))
auto block = mcache.BlockAt(curblock);
if(!block)
{
iter ++;
continue;
@ -463,64 +452,77 @@ command_result df_liquids_execute(color_ostream &out)
}
if(mode != "flowbits")
{
if(setmode == "s.")
{
des.bits.flow_size = amount;
}
else if(setmode == "s+")
{
if(des.bits.flow_size < amount)
des.bits.flow_size = amount;
}
else if(setmode == "s-")
unsigned old_amount = des.bits.flow_size;
unsigned new_amount = old_amount;
df::tile_liquid old_liquid = des.bits.liquid_type;
df::tile_liquid new_liquid = old_liquid;
// Compute new liquid type and amount
if(_setmode == "s.")
{
if (des.bits.flow_size > amount)
des.bits.flow_size = amount;
new_amount = amount;
}
if(amount != 0 && mode == "magma")
else if(_setmode == "s+")
{
des.bits.liquid_type = tile_liquid::Magma;
mcache.setTemp1At(current,12000);
mcache.setTemp2At(current,12000);
if(old_amount < amount)
new_amount = amount;
}
else if(amount != 0 && mode == "water")
else if(_setmode == "s-")
{
des.bits.liquid_type = tile_liquid::Water;
mcache.setTemp1At(current,10015);
mcache.setTemp2At(current,10015);
if (old_amount > amount)
new_amount = amount;
}
else if(amount == 0 && (mode == "water" || mode == "magma"))
if (mode == "magma")
new_liquid = tile_liquid::Magma;
else if (mode == "water")
new_liquid = tile_liquid::Water;
// Store new amount and type
des.bits.flow_size = new_amount;
des.bits.liquid_type = new_liquid;
// Compute temperature
if (!old_amount)
old_liquid = tile_liquid::Water;
if (!new_amount)
new_liquid = tile_liquid::Water;
if (old_liquid != new_liquid)
{
// reset temperature to sane default
mcache.setTemp1At(current,10015);
mcache.setTemp2At(current,10015);
if (new_liquid == tile_liquid::Water)
{
mcache.setTemp1At(current,10015);
mcache.setTemp2At(current,10015);
}
else
{
mcache.setTemp1At(current,12000);
mcache.setTemp2At(current,12000);
}
}
// mark the tile passable or impassable like the game does
des.bits.flow_forbid = des.bits.flow_size &&
(des.bits.liquid_type == tile_liquid::Magma || des.bits.flow_size > 3);
des.bits.flow_forbid = (new_liquid == tile_liquid::Magma || new_amount > 3);
mcache.setDesignationAt(current,des);
// request flow engine updates
block->enableBlockUpdates(new_amount != old_amount, new_liquid != old_liquid);
}
seen_blocks.insert(mcache.BlockAt(current / 16));
seen_blocks.insert(block);
iter++;
}
set <Block *>::iterator biter = seen_blocks.begin();
while (biter != seen_blocks.end())
{
DFHack::t_blockflags bflags = (*biter)->BlockFlags();
if(flowmode == "f+")
{
bflags.bits.update_liquid = true;
bflags.bits.update_liquid_twice = true;
(*biter)->setBlockFlags(bflags);
(*biter)->enableBlockUpdates(true);
}
else if(flowmode == "f-")
{
bflags.bits.update_liquid = false;
bflags.bits.update_liquid_twice = false;
(*biter)->setBlockFlags(bflags);
if (auto block = (*biter)->getRaw())
{
block->flags.bits.update_liquid = false;
block->flags.bits.update_liquid_twice = false;
}
}
else
{
auto bflags = (*biter)->BlockFlags();
out << "flow bit 1 = " << bflags.bits.update_liquid << endl;
out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl;
}

@ -2,9 +2,11 @@ local _ENV = mkmodule('plugins.sort')
local utils = require('utils')
local units = require('plugins.sort.units')
local items = require('plugins.sort.items')
orders = orders or {}
orders.units = units.orders
orders.items = items.orders
function parse_ordering_spec(type,...)
local group = orders[type]

@ -0,0 +1,55 @@
local _ENV = mkmodule('plugins.sort.items')
local utils = require('utils')
orders = orders or {}
-- Relies on NULL being auto-translated to NULL, and then sorted
orders.exists = {
key = function(item)
return 1
end
}
orders.type = {
key = function(item)
return item:getType()
end
}
orders.description = {
key = function(item)
return dfhack.items.getDescription(item,0)
end
}
orders.quality = {
key = function(item)
return item:getQuality()
end
}
orders.improvement = {
key = function(item)
return item:getImprovementQuality()
end
}
orders.wear = {
key = function(item)
return item:getWear()
end
}
orders.material = {
key = function(item)
local mattype = item:getActualMaterial()
local matindex = item:getActualMaterialIndex()
local info = dfhack.matinfo.decode(mattype, matindex)
if info then
return info:toString()
end
end
}
return _ENV

@ -109,4 +109,10 @@ orders.squad_position = {
end
}
orders.happiness = {
key = function(unit)
return unit.status.happiness
end
}
return _ENV

@ -124,7 +124,7 @@ command_result mapexport (color_ostream &out, std::vector <std::string> & parame
if (filename.rfind(".dfmap") == std::string::npos) filename += ".dfmap";
out << "Writing to " << filename << "..." << std::endl;
std::ofstream output_file(filename, std::ios::out | std::ios::trunc | std::ios::binary);
std::ofstream output_file(filename.c_str(), std::ios::out | std::ios::trunc | std::ios::binary);
if (!output_file.is_open())
{
out.printerr("Couldn't open the output file.\n");

@ -220,8 +220,26 @@ command_result df_probe (color_ostream &out, vector <string> & parameters)
out.print("temperature1: %d U\n",mc.temperature1At(cursor));
out.print("temperature2: %d U\n",mc.temperature2At(cursor));
int offset = block.region_offset[des.bits.biome];
df::coord2d region_pos = block.region_pos + df::coord2d ((offset % 3) - 1, (offset / 3) -1);
df::world_data::T_region_map* biome =
&world->world_data->region_map[region_pos.x][region_pos.y];
int sav = biome->savagery;
int evi = biome->evilness;
int sindex = sav > 65 ? 2 : sav < 33 ? 0 : 1;
int eindex = evi > 65 ? 2 : evi < 33 ? 0 : 1;
int surr = sindex + eindex * 3;
const char* surroundings[] = { "Serene", "Mirthful", "Joyous Wilds", "Calm", "Wilderness", "Untamed Wilds", "Sinister", "Haunted", "Terrifying" };
// biome, geolayer
out << "biome: " << des.bits.biome << std::endl;
out << "biome: " << des.bits.biome << " (" <<
"region id=" << biome->region_id << ", " <<
surroundings[surr] << ", " <<
"savagery " << biome->savagery << ", " <<
"evilness " << biome->evilness << ")" << std::endl;
out << "geolayer: " << des.bits.geolayer_index
<< std::endl;
int16_t base_rock = mc.layerMaterialAt(cursor);
@ -312,6 +330,39 @@ command_result df_probe (color_ostream &out, vector <string> & parameters)
out << "global feature idx: " << block.global_feature
<< endl;
out << std::endl;
if(block.occupancy[tileX][tileY].bits.no_grow)
out << "no grow" << endl;
for(size_t e=0; e<block.block_events.size(); e++)
{
df::block_square_event * blev = block.block_events[e];
df::block_square_event_type blevtype = blev->getType();
switch(blevtype)
{
case df::block_square_event_type::grass:
{
df::block_square_event_grassst * gr_ev = (df::block_square_event_grassst *)blev;
if(gr_ev->amount[tileX][tileY] > 0)
{
out << "amount of grass: " << (int)gr_ev->amount[tileX][tileY] << endl;
}
break;
}
case df::block_square_event_type::world_construction:
{
df::block_square_event_world_constructionst * co_ev = (df::block_square_event_world_constructionst*)blev;
uint16_t bits = co_ev->tile_bitmask[tileY];
out << "construction bits: " << bits << endl;
break;
}
default:
//out << "unhandled block event type!" << endl;
break;
}
}
return CR_OK;
}
@ -399,14 +450,10 @@ command_result df_bprobe (color_ostream &out, vector <string> & parameters)
break;
}
if(building.origin->is_room) //isRoom())
out << ", is room";
else
out << ", not a room";
out << ", room";
if(building.origin->getBuildStage()!=building.origin->getMaxBuildStage())
out << ", in construction";
out.print("\n");
}
return CR_OK;
}

@ -83,7 +83,12 @@ command_result df_regrass (color_ostream &out, vector <string> & parameters)
{
if ( tileShape(cur->tiletype[x][y]) != tiletype_shape::FLOOR
|| cur->designation[x][y].bits.subterranean
|| cur->occupancy[x][y].bits.building)
|| cur->occupancy[x][y].bits.building
|| cur->occupancy[x][y].bits.no_grow)
continue;
// don't touch furrowed tiles (dirt roads made on soil)
if(tileSpecial(cur->tiletype[x][y]) == tiletype_special::FURROWED)
continue;
int mat = tileMaterial(cur->tiletype[x][y]);
@ -93,6 +98,7 @@ command_result df_regrass (color_ostream &out, vector <string> & parameters)
)
continue;
// max = set amounts of all grass events on that tile to 100
if(max)
{

@ -0,0 +1,33 @@
OPTION(DL_RUBY "download libruby from the internet" ON)
IF (DL_RUBY)
IF (UNIX)
FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/libruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.tar.gz
EXPECTED_MD5 eb2adea59911f68e6066966c1352f291)
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf libruby187.tar.gz
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
FILE(RENAME libruby1.8.so.1.8.7 libruby.so)
SET(RUBYLIB libruby.so)
ELSE (UNIX)
FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/msvcrtruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz
EXPECTED_MD5 9f4a1659ac3a5308f32d3a1937bbeeae)
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf msvcrtruby187.tar.gz
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
FILE(RENAME msvcrt-ruby18.dll libruby.dll)
SET(RUBYLIB libruby.dll)
ENDIF(UNIX)
ENDIF(DL_RUBY)
ADD_CUSTOM_COMMAND(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl
COMMENT ruby-autogen.rb
)
ADD_CUSTOM_TARGET(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb)
INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread")
DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
ADD_DEPENDENCIES(ruby ruby-autogen-rb)
INSTALL(FILES ruby.rb ruby-autogen.rb ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION})

@ -0,0 +1,105 @@
This plugins embeds a ruby interpreter inside DFHack (ie inside Dwarf Fortress).
The plugin maps all the structures available in library/xml/ to ruby objects.
These objects are described in ruby-autogen.rb, they are all in the DFHack::
module. The toplevel 'df' method is a shortcut to the DFHack module.
The plugin does *not* map most of dfhack methods (MapCache, ...) ; only direct
access to the raw DF data structures in memory is provided.
Some library methods are stored in the ruby.rb file, with shortcuts to read a
map block, find an unit or an item, etc.
Global objects are accessible through the 'df' accessor (eg df.world).
The ruby plugin defines 2 dfhack console commands:
rb_load <filename> ; load a ruby script. Ex: rb_load hack/plants.rb (no quotes)
rb_eval <ruby expression> ; evaluate a ruby expression, show the result in the
console. Ex: rb_eval df.find_unit.name.first_name
You can use single-quotes for strings ; avoid double-quotes that are parsed
and removed by the dfhack console.
If dfhack reports 'rb_eval is not a recognized command', check stderr.log. You
need a valid 32-bit ruby library to work, and ruby1.8 is prefered (ruby1.9 may
crash DF on startup for now). Install the library in the df root folder (or
hack/ on linux), the library should be named 'libruby.dll' (.so on linux).
You can download a tested version at http://github.com/jjyg/dfhack/downloads/
The plugin also interfaces with dfhack 'onupdate' hook.
To register ruby code to be run every graphic frame, use:
handle = df.onupdate_register { puts 'i love flooding the console' }
To stop being called, use:
df.onupdate_unregister handle
The same mechanism is available for onstatechange.
Exemples
--------
For more complex exemples, check the ruby/plugins/ source folder.
Show info on the currently selected unit ('v' or 'k' DF menu)
p df.find_unit.flags1
Set a custom nickname to unit with id '123'
df.find_unit(123).name.nickname = 'moo'
Show current unit profession
p df.find_unit.profession
Change current unit profession
df.find_unit.profession = :MASON
Center the screen on unit '123'
df.center_viewscreen(df.find_unit(123))
Find an item at a given position, show its C++ classname
df.find_item(df.cursor)._rtti_classname
Find the raws name of the plant under cursor
plant = df.world.plants.all.find { |plt| df.at_cursor?(plt) }
p df.world.raws.plants.all[plant.mat_index].id
Dig a channel under the cursor
df.map_designation_at(df.cursor).dig = :Channel
df.map_block_at(df.cursor).flags.designated = true
Compilation
-----------
The plugin consists of the ruby.rb file including user comfort functions and
describing basic classes used by the autogenerated code, and ruby-autogen.rb,
the auto-generated code.
The generated code is generated by codegen.pl, which takes the codegen.out.xml
file as input.
For exemple,
<ld:global-type ld:meta="struct-type" type-name="unit">
<ld:field type-name="language_name" name="name" ld:meta="global"/>
<ld:field name="custom_profession" ld:meta="primitive" ld:subtype="stl-string"/>
<ld:field ld:subtype="enum" base-type="int16_t" name="profession" type-name="profession" ld:meta="global"/>
Will generate
class Unit < MemHack::Compound
field(:name, 0) { global :LanguageName }
field(:custom_profession, 60) { stl_string }
field(:profession, 64) { number 16, true }
The syntax for the 'field' method is:
1st argument = name of the method
2nd argument = offset of this field from the beginning of the struct.
The block argument describes the type of the field: uint32, ptr to global...
Primitive type access is done through native methods in ruby.cpp (vector length,
raw memory access, etc)
MemHack::Pointers are automatically dereferenced ; so a vector of pointer to
Units will yield Units directly. Null pointers yield the 'nil' value.
This allows to use code such as 'df.world.units.all[0].pos', with 'all' being
in fact a vector of *pointers* to DFHack::Unit objects.

@ -0,0 +1,912 @@
#!/usr/bin/perl
use strict;
use warnings;
use XML::LibXML;
our @lines_rb;
my $os;
if ($^O =~ /linux/i) {
$os = 'linux';
} else {
$os = 'windows';
}
sub indent_rb(&) {
my ($sub) = @_;
my @lines;
{
local @lines_rb;
$sub->();
@lines = map { " " . $_ } @lines_rb;
}
push @lines_rb, @lines
}
sub rb_ucase {
my ($name) = @_;
return $name if ($name eq uc($name));
return join("", map { ucfirst $_ } (split('_', $name)));
}
my %item_renderer = (
'global' => \&render_item_global,
'number' => \&render_item_number,
'container' => \&render_item_container,
'compound' => \&render_item_compound,
'pointer' => \&render_item_pointer,
'static-array' => \&render_item_staticarray,
'primitive' => \&render_item_primitive,
'bytes' => \&render_item_bytes,
);
my %global_types;
our $current_typename;
sub render_global_enum {
my ($name, $type) = @_;
my $rbname = rb_ucase($name);
push @lines_rb, "class $rbname < MemHack::Enum";
indent_rb {
render_enum_fields($type);
};
push @lines_rb, "end\n";
}
sub render_enum_fields {
my ($type) = @_;
push @lines_rb, "ENUM = Hash.new";
push @lines_rb, "NUME = Hash.new";
my %attr_type;
my %attr_list;
render_enum_initattrs($type, \%attr_type, \%attr_list);
my $value = -1;
for my $item ($type->findnodes('child::enum-item'))
{
$value = $item->getAttribute('value') || ($value+1);
my $elemname = $item->getAttribute('name'); # || "unk_$value";
if ($elemname)
{
my $rbelemname = rb_ucase($elemname);
push @lines_rb, "ENUM[$value] = :$rbelemname ; NUME[:$rbelemname] = $value";
for my $iattr ($item->findnodes('child::item-attr'))
{
my $attr = render_enum_attr($rbelemname, $iattr, \%attr_type, \%attr_list);
$lines_rb[$#lines_rb] .= ' ; ' . $attr;
}
}
}
}
sub render_enum_initattrs {
my ($type, $attr_type, $attr_list) = @_;
for my $attr ($type->findnodes('child::enum-attr'))
{
my $rbattr = rb_ucase($attr->getAttribute('name'));
my $typeattr = $attr->getAttribute('type-name');
# find how we need to encode the attribute values: string, symbol (for enums), raw (number, bool)
if ($typeattr) {
if ($global_types{$typeattr}) {
$attr_type->{$rbattr} = 'symbol';
} else {
$attr_type->{$rbattr} = 'naked';
}
} else {
$attr_type->{$rbattr} = 'quote';
}
my $def = $attr->getAttribute('default-value');
if ($attr->getAttribute('is-list'))
{
push @lines_rb, "$rbattr = Hash.new { |h, k| h[k] = [] }";
$attr_list->{$rbattr} = 1;
}
elsif ($def)
{
$def = ":$def" if ($attr_type->{$rbattr} eq 'symbol');
$def =~ s/'/\\'/g if ($attr_type->{$rbattr} eq 'quote');
$def = "'$def'" if ($attr_type->{$rbattr} eq 'quote');
push @lines_rb, "$rbattr = Hash.new($def)";
}
else
{
push @lines_rb, "$rbattr = Hash.new";
}
}
}
sub render_enum_attr {
my ($rbelemname, $iattr, $attr_type, $attr_list) = @_;
my $ian = $iattr->getAttribute('name');
my $iav = $iattr->getAttribute('value');
my $rbattr = rb_ucase($ian);
my $op = ($attr_list->{$rbattr} ? '<<' : '=');
$iav = ":$iav" if ($attr_type->{$rbattr} eq 'symbol');
$iav =~ s/'/\\'/g if ($attr_type->{$rbattr} eq 'quote');
$iav = "'$iav'" if ($attr_type->{$rbattr} eq 'quote');
return "${rbattr}[:$rbelemname] $op $iav";
}
sub render_global_bitfield {
my ($name, $type) = @_;
my $rbname = rb_ucase($name);
push @lines_rb, "class $rbname < MemHack::Compound";
indent_rb {
render_bitfield_fields($type);
};
push @lines_rb, "end\n";
}
sub render_bitfield_fields {
my ($type) = @_;
push @lines_rb, "field(:_whole, 0) {";
indent_rb {
render_item_number($type);
};
push @lines_rb, "}";
my $shift = 0;
for my $field ($type->findnodes('child::ld:field'))
{
my $count = $field->getAttribute('count') || 1;
my $name = $field->getAttribute('name');
my $type = $field->getAttribute('type-name');
my $enum = rb_ucase($type) if ($type and $global_types{$type});
$name = $field->getAttribute('ld:anon-name') if (!$name);
print "bitfield $name !number\n" if (!($field->getAttribute('ld:meta') eq 'number'));
if ($name)
{
if ($count == 1) {
push @lines_rb, "field(:$name, 0) { bit $shift }";
} elsif ($enum) {
push @lines_rb, "field(:$name, 0) { bits $shift, $count, $enum }";
} else {
push @lines_rb, "field(:$name, 0) { bits $shift, $count }";
}
}
$shift += $count;
}
}
my %seen_class;
our $compound_off;
our $compound_pointer;
sub render_global_class {
my ($name, $type) = @_;
my $meta = $type->getAttribute('ld:meta');
my $rbname = rb_ucase($name);
# ensure pre-definition of ancestors
my $parent = $type->getAttribute('inherits-from');
render_global_class($parent, $global_types{$parent}) if ($parent and !$seen_class{$parent});
return if $seen_class{$name};
$seen_class{$name}++;
local $compound_off = 0;
$compound_off = 4 if ($meta eq 'class-type');
$compound_off = sizeof($global_types{$parent}) if $parent;
local $current_typename = $rbname;
my $rtti_name;
if ($meta eq 'class-type')
{
$rtti_name = $type->getAttribute('original-name') ||
$type->getAttribute('type-name') ||
$name;
}
my $rbparent = ($parent ? rb_ucase($parent) : 'MemHack::Compound');
push @lines_rb, "class $rbname < $rbparent";
indent_rb {
my $sz = sizeof($type);
# see comment is sub sizeof ; but gcc has sizeof(cls) aligned
$sz = align_field($sz, 4) if $os eq 'linux' and $meta eq 'class-type';
push @lines_rb, "sizeof $sz\n";
push @lines_rb, "rtti_classname :$rtti_name\n" if $rtti_name;
render_struct_fields($type);
my $vms = $type->findnodes('child::virtual-methods')->[0];
render_class_vmethods($vms) if $vms;
};
push @lines_rb, "end\n";
}
sub render_struct_fields {
my ($type) = @_;
my $isunion = $type->getAttribute('is-union');
for my $field ($type->findnodes('child::ld:field'))
{
my $name = $field->getAttribute('name');
$name = $field->getAttribute('ld:anon-name') if (!$name);
if (!$name and $field->getAttribute('ld:anon-compound'))
{
render_struct_fields($field);
}
else
{
$compound_off = align_field($compound_off, get_field_align($field));
if ($name)
{
push @lines_rb, "field(:$name, $compound_off) {";
indent_rb {
render_item($field);
};
push @lines_rb, "}";
}
}
$compound_off += sizeof($field) if (!$isunion);
}
}
sub render_class_vmethods {
my ($vms) = @_;
my $voff = 0;
for my $meth ($vms->findnodes('child::vmethod'))
{
my $name = $meth->getAttribute('name');
if ($name)
{
my @argnames;
my @argargs;
# check if arguments need special treatment (eg auto-convert from symbol to enum value)
for my $arg ($meth->findnodes('child::ld:field'))
{
my $nr = $#argnames + 1;
my $argname = lcfirst($arg->getAttribute('name') || "arg$nr");
push @argnames, $argname;
if ($arg->getAttribute('ld:meta') eq 'global' and $arg->getAttribute('ld:subtype') eq 'enum') {
push @argargs, rb_ucase($arg->getAttribute('type-name')) . ".int($argname)";
} else {
push @argargs, $argname;
}
}
# write vmethod ruby wrapper
push @lines_rb, "def $name(" . join(', ', @argnames) . ')';
indent_rb {
my $args = join('', map { ", $_" } @argargs);
my $call = "DFHack.vmethod_call(self, $voff$args)";
my $ret = $meth->findnodes('child::ret-type')->[0];
render_class_vmethod_ret($call, $ret);
};
push @lines_rb, 'end';
}
# on linux, the destructor uses 2 entries
$voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux';
$voff += 4;
}
}
sub render_class_vmethod_ret {
my ($call, $ret) = @_;
if (!$ret)
{
# method returns void, hide return value
push @lines_rb, "$call ; nil";
return;
}
my $retmeta = $ret->getAttribute('ld:meta') || '';
if ($retmeta eq 'global')
{
# method returns an enum value: auto-convert to symbol
my $retname = $ret->getAttribute('type-name');
if ($retname and $global_types{$retname} and
$global_types{$retname}->getAttribute('ld:meta') eq 'enum-type')
{
push @lines_rb, rb_ucase($retname) . ".sym($call)";
}
else
{
print "vmethod global nonenum $call\n";
push @lines_rb, $call;
}
}
elsif ($retmeta eq 'number')
{
# raw method call returns an int32, mask according to actual return type
my $retsubtype = $ret->getAttribute('ld:subtype');
my $retbits = $ret->getAttribute('ld:bits');
push @lines_rb, "val = $call";
if ($retsubtype eq 'bool')
{
push @lines_rb, "(val & 1) != 0";
}
elsif ($ret->getAttribute('ld:unsigned'))
{
push @lines_rb, "val & ((1 << $retbits) - 1)";
}
elsif ($retbits != 32)
{
# manual sign extension
push @lines_rb, "val &= ((1 << $retbits) - 1)";
push @lines_rb, "((val >> ($retbits-1)) & 1) == 0 ? val : val - (1 << $retbits)";
}
}
elsif ($retmeta eq 'pointer')
{
# method returns a pointer to some struct, create the correct ruby wrapper
push @lines_rb, "ptr = $call";
push @lines_rb, "class << self";
indent_rb {
render_item($ret->findnodes('child::ld:item')->[0]);
};
push @lines_rb, "end._at(ptr) if ptr != 0";
}
else
{
print "vmethod unkret $call\n";
push @lines_rb, $call;
}
}
sub render_global_objects {
my (@objects) = @_;
my @global_objects;
local $compound_off = 0;
local $current_typename = 'Global';
# define all globals as 'fields' of a virtual globalobject wrapping the whole address space
push @lines_rb, 'class GlobalObjects < MemHack::Compound';
indent_rb {
for my $obj (@objects)
{
my $oname = $obj->getAttribute('name');
# check if the symbol is defined in xml to avoid NULL deref
my $addr = "DFHack.get_global_address('$oname')";
push @lines_rb, "addr = $addr";
push @lines_rb, "if addr != 0";
indent_rb {
push @lines_rb, "field(:$oname, addr) {";
my $item = $obj->findnodes('child::ld:item')->[0];
indent_rb {
render_item($item);
};
push @lines_rb, "}";
};
push @lines_rb, "end";
push @global_objects, $oname;
}
};
push @lines_rb, "end";
# define friendlier accessors, eg df.world -> DFHack::GlobalObjects.new._at(0).world
indent_rb {
push @lines_rb, "Global = GlobalObjects.new._at(0)";
for my $obj (@global_objects)
{
push @lines_rb, "def self.$obj ; Global.$obj ; end";
push @lines_rb, "def self.$obj=(v) ; Global.$obj = v ; end";
}
};
}
my %align_cache;
my %sizeof_cache;
sub align_field {
my ($off, $fldalign) = @_;
my $dt = $off % $fldalign;
$off += $fldalign - $dt if $dt > 0;
return $off;
}
sub get_field_align {
my ($field) = @_;
my $al = 4;
my $meta = $field->getAttribute('ld:meta');
if ($meta eq 'number') {
$al = $field->getAttribute('ld:bits')/8;
$al = 4 if $al > 4;
} elsif ($meta eq 'global') {
$al = get_global_align($field);
} elsif ($meta eq 'compound') {
$al = get_compound_align($field);
} elsif ($meta eq 'static-array') {
my $tg = $field->findnodes('child::ld:item')->[0];
$al = get_field_align($tg);
} elsif ($meta eq 'bytes') {
$al = $field->getAttribute('alignment') || 1;
}
return $al;
}
sub get_global_align {
my ($field) = @_;
my $typename = $field->getAttribute('type-name');
return $align_cache{$typename} if $align_cache{$typename};
my $g = $global_types{$typename};
my $st = $field->getAttribute('ld:subtype') || '';
if ($st eq 'bitfield' or $st eq 'enum' or $g->getAttribute('ld:meta') eq 'bitfield-type')
{
my $base = $field->getAttribute('base-type') || $g->getAttribute('base-type') || 'uint32_t';
print "$st type $base\n" if $base !~ /int(\d+)_t/;
# dont cache, field->base-type may differ
return $1/8;
}
my $al = 1;
for my $gf ($g->findnodes('child::ld:field')) {
my $fld_al = get_field_align($gf);
$al = $fld_al if $fld_al > $al;
}
$align_cache{$typename} = $al;
return $al;
}
sub get_compound_align {
my ($field) = @_;
my $st = $field->getAttribute('ld:subtype') || '';
if ($st eq 'bitfield' or $st eq 'enum')
{
my $base = $field->getAttribute('base-type') || 'uint32_t';
print "$st type $base\n" if $base !~ /int(\d+)_t/;
return $1/8;
}
my $al = 1;
for my $f ($field->findnodes('child::ld:field')) {
my $fal = get_field_align($f);
$al = $fal if $fal > $al;
}
return $al;
}
sub sizeof {
my ($field) = @_;
my $meta = $field->getAttribute('ld:meta');
if ($meta eq 'number') {
return $field->getAttribute('ld:bits')/8;
} elsif ($meta eq 'pointer') {
return 4;
} elsif ($meta eq 'static-array') {
my $count = $field->getAttribute('count');
my $tg = $field->findnodes('child::ld:item')->[0];
return $count * sizeof($tg);
} elsif ($meta eq 'bitfield-type' or $meta eq 'enum-type') {
my $base = $field->getAttribute('base-type') || 'uint32_t';
print "$meta type $base\n" if $base !~ /int(\d+)_t/;
return $1/8;
} elsif ($meta eq 'global') {
my $typename = $field->getAttribute('type-name');
return $sizeof_cache{$typename} if $sizeof_cache{$typename};
my $g = $global_types{$typename};
my $st = $field->getAttribute('ld:subtype') || '';
if ($st eq 'bitfield' or $st eq 'enum' or $g->getAttribute('ld:meta') eq 'bitfield-type')
{
my $base = $field->getAttribute('base-type') || $g->getAttribute('base-type') || 'uint32_t';
print "$st type $base\n" if $base !~ /int(\d+)_t/;
return $1/8;
}
return sizeof($g);
} elsif ($meta eq 'class-type' or $meta eq 'struct-type' or $meta eq 'compound') {
return sizeof_compound($field);
} elsif ($meta eq 'container') {
my $subtype = $field->getAttribute('ld:subtype');
if ($subtype eq 'stl-vector') {
if ($os eq 'linux') {
return 12;
} elsif ($os eq 'windows') {
return 16;
} else {
print "sizeof stl-vector on $os\n";
}
} elsif ($subtype eq 'stl-bit-vector') {
if ($os eq 'linux') {
return 20;
} elsif ($os eq 'windows') {
return 20;
} else {
print "sizeof stl-bit-vector on $os\n";
}
} elsif ($subtype eq 'stl-deque') {
if ($os eq 'linux') {
return 40;
} elsif ($os eq 'windows') {
return 24;
} else {
print "sizeof stl-deque on $os\n";
}
} elsif ($subtype eq 'df-linked-list') {
return 12;
} elsif ($subtype eq 'df-flagarray') {
return 8;
} elsif ($subtype eq 'df-array') {
return 8; # XXX 6 ?
} else {
print "sizeof container $subtype\n";
}
} elsif ($meta eq 'primitive') {
my $subtype = $field->getAttribute('ld:subtype');
if ($subtype eq 'stl-string') { if ($os eq 'linux') {
return 4;
} elsif ($os eq 'windows') {
return 28;
} else {
print "sizeof stl-string on $os\n";
}
print "sizeof stl-string\n";
} else {
print "sizeof primitive $subtype\n";
}
} elsif ($meta eq 'bytes') {
return $field->getAttribute('size');
} else {
print "sizeof $meta\n";
}
}
sub sizeof_compound {
my ($field) = @_;
my $typename = $field->getAttribute('type-name');
return $sizeof_cache{$typename} if $typename and $sizeof_cache{$typename};
my $meta = $field->getAttribute('ld:meta');
my $st = $field->getAttribute('ld:subtype') || '';
if ($st eq 'bitfield' or $st eq 'enum')
{
my $base = $field->getAttribute('base-type') || 'uint32_t';
print "$st type $base\n" if $base !~ /int(\d+)_t/;
$sizeof_cache{$typename} = $1/8 if $typename;
return $1/8;
}
if ($field->getAttribute('is-union'))
{
my $sz = 0;
for my $f ($field->findnodes('child::ld:field'))
{
my $fsz = sizeof($f);
$sz = $fsz if $fsz > $sz;
}
return $sz;
}
my $parent = $field->getAttribute('inherits-from');
my $off = 0;
$off = 4 if ($meta eq 'class-type');
$off = sizeof($global_types{$parent}) if ($parent);
my $al = 1;
$al = 4 if ($meta eq 'class-type');
for my $f ($field->findnodes('child::ld:field'))
{
my $fa = get_field_align($f);
$al = $fa if $fa > $al;
$off = align_field($off, $fa);
$off += sizeof($f);
}
# GCC: class a { vtable; char; } ; class b:a { char c2; } -> c2 has offset 5 (Windows MSVC: offset 8)
$al = 1 if ($meta eq 'class-type' and $os eq 'linux');
$off = align_field($off, $al);
$sizeof_cache{$typename} = $off if $typename;
return $off;
}
sub render_item {
my ($item) = @_;
return if (!$item);
my $meta = $item->getAttribute('ld:meta');
my $renderer = $item_renderer{$meta};
if ($renderer) {
$renderer->($item);
} else {
print "no render item $meta\n";
}
}
sub render_item_global {
my ($item) = @_;
my $typename = $item->getAttribute('type-name');
my $subtype = $item->getAttribute('ld:subtype');
if ($subtype and $subtype eq 'enum') {
render_item_number($item);
} else {
my $rbname = rb_ucase($typename);
push @lines_rb, "global :$rbname";
}
}
sub render_item_number {
my ($item, $classname) = @_;
my $subtype = $item->getAttribute('ld:subtype');
my $meta = $item->getAttribute('ld:meta');
my $initvalue = $item->getAttribute('init-value');
my $typename = $item->getAttribute('type-name');
undef $typename if ($meta and $meta eq 'bitfield-type');
$typename = rb_ucase($typename) if $typename;
$typename = $classname if (!$typename and $subtype and $subtype eq 'enum'); # compound enum
$initvalue = 1 if ($initvalue and $initvalue eq 'true');
$initvalue = ":$initvalue" if ($initvalue and $typename and $initvalue =~ /[a-zA-Z]/);
$initvalue ||= 'nil' if $typename;
$subtype = $item->getAttribute('base-type') if (!$subtype or $subtype eq 'bitfield' or $subtype eq 'enum');
$subtype = 'int32_t' if (!$subtype);
if ($subtype eq 'int64_t') {
push @lines_rb, 'number 64, true';
} elsif ($subtype eq 'uint32_t') {
push @lines_rb, 'number 32, false';
} elsif ($subtype eq 'int32_t') {
push @lines_rb, 'number 32, true';
} elsif ($subtype eq 'uint16_t') {
push @lines_rb, 'number 16, false';
} elsif ($subtype eq 'int16_t') {
push @lines_rb, 'number 16, true';
} elsif ($subtype eq 'uint8_t') {
push @lines_rb, 'number 8, false';
} elsif ($subtype eq 'int8_t') {
push @lines_rb, 'number 8, false';
} elsif ($subtype eq 'bool') {
push @lines_rb, 'number 8, true';
$initvalue ||= 'nil';
$typename ||= 'BooleanEnum';
} elsif ($subtype eq 's-float') {
push @lines_rb, 'float';
return;
} else {
print "no render number $subtype\n";
return;
}
$lines_rb[$#lines_rb] .= ", $initvalue" if ($initvalue);
$lines_rb[$#lines_rb] .= ", $typename" if ($typename);
}
sub render_item_compound {
my ($item) = @_;
my $subtype = $item->getAttribute('ld:subtype');
local $compound_off = 0;
my $classname = $current_typename . '_' . rb_ucase($item->getAttribute('ld:typedef-name'));
local $current_typename = $classname;
if (!$subtype || $subtype eq 'bitfield')
{
push @lines_rb, "compound(:$classname) {";
indent_rb {
# declare sizeof() only for useful compound, eg the one behind pointers
# that the user may want to allocate
my $sz = sizeof($item);
push @lines_rb, "sizeof $sz\n" if $compound_pointer;
if (!$subtype) {
local $compound_pointer = 0;
render_struct_fields($item);
} else {
render_bitfield_fields($item);
}
};
push @lines_rb, "}"
}
elsif ($subtype eq 'enum')
{
push @lines_rb, "class ::DFHack::$classname < MemHack::Enum";
indent_rb {
# declare constants
render_enum_fields($item);
};
push @lines_rb, "end\n";
# actual field
render_item_number($item, $classname);
}
else
{
print "no render compound $subtype\n";
}
}
sub render_item_container {
my ($item) = @_;
my $subtype = $item->getAttribute('ld:subtype');
my $rbmethod = join('_', split('-', $subtype));
my $tg = $item->findnodes('child::ld:item')->[0];
my $indexenum = $item->getAttribute('index-enum');
if ($tg)
{
if ($rbmethod eq 'df_linked_list') {
push @lines_rb, "$rbmethod {";
} else {
my $tglen = sizeof($tg) if $tg;
push @lines_rb, "$rbmethod($tglen) {";
}
indent_rb {
render_item($tg);
};
push @lines_rb, "}";
}
elsif ($indexenum)
{
$indexenum = rb_ucase($indexenum);
push @lines_rb, "$rbmethod($indexenum)";
}
else
{
push @lines_rb, "$rbmethod";
}
}
sub render_item_pointer {
my ($item) = @_;
my $tg = $item->findnodes('child::ld:item')->[0];
my $ary = $item->getAttribute('is-array') || '';
if ($ary eq 'true') {
my $tglen = sizeof($tg) if $tg;
push @lines_rb, "pointer_ary($tglen) {";
} else {
push @lines_rb, "pointer {";
}
indent_rb {
local $compound_pointer = 1;
render_item($tg);
};
push @lines_rb, "}";
}
sub render_item_staticarray {
my ($item) = @_;
my $count = $item->getAttribute('count');
my $tg = $item->findnodes('child::ld:item')->[0];
my $tglen = sizeof($tg) if $tg;
my $indexenum = $item->getAttribute('index-enum');
if ($indexenum) {
$indexenum = rb_ucase($indexenum);
push @lines_rb, "static_array($count, $tglen, $indexenum) {";
} else {
push @lines_rb, "static_array($count, $tglen) {";
}
indent_rb {
render_item($tg);
};
push @lines_rb, "}";
}
sub render_item_primitive {
my ($item) = @_;
my $subtype = $item->getAttribute('ld:subtype');
if ($subtype eq 'stl-string') {
push @lines_rb, "stl_string";
} else {
print "no render primitive $subtype\n";
}
}
sub render_item_bytes {
my ($item) = @_;
my $subtype = $item->getAttribute('ld:subtype');
if ($subtype eq 'padding') {
} elsif ($subtype eq 'static-string') {
my $size = $item->getAttribute('size');
push @lines_rb, "static_string($size)";
} else {
print "no render bytes $subtype\n";
}
}
my $input = $ARGV[0] or die "need input xml";
my $output = $ARGV[1] or die "need output file";
my $doc = XML::LibXML->new()->parse_file($input);
$global_types{$_->getAttribute('type-name')} = $_ foreach $doc->findnodes('/ld:data-definition/ld:global-type');
# render enums first, this allows later code to refer to them directly
my @nonenums;
for my $name (sort { $a cmp $b } keys %global_types)
{
my $type = $global_types{$name};
my $meta = $type->getAttribute('ld:meta');
if ($meta eq 'enum-type') {
render_global_enum($name, $type);
} else {
push @nonenums, $name;
}
}
# render other structs/bitfields/classes
for my $name (@nonenums)
{
my $type = $global_types{$name};
my $meta = $type->getAttribute('ld:meta');
if ($meta eq 'struct-type' or $meta eq 'class-type') {
render_global_class($name, $type);
} elsif ($meta eq 'bitfield-type') {
render_global_bitfield($name, $type);
} else {
print "no render global type $meta\n";
}
}
# render globals
render_global_objects($doc->findnodes('/ld:data-definition/ld:global-object'));
open FH, ">$output";
print FH "module DFHack\n";
print FH "$_\n" for @lines_rb;
print FH "end\n";
close FH;

@ -0,0 +1,266 @@
module DFHack
# allocate a new building object
def self.building_alloc(type, subtype=-1, custom=-1)
cls = rtti_n2c[BuildingType::Classname[type].to_sym]
raise "invalid building type #{type.inspect}" if not cls
bld = cls.cpp_new
bld.race = ui.race_id
bld.setSubtype(subtype) if subtype != -1
bld.setCustomType(custom) if custom != -1
case type
when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0
when :Coffin; bld.initBurialFlags
when :Trap; bld.unk_cc = 500 if bld.trap_type == :PressurePlate
end
bld
end
# used by building_setsize
def self.building_check_bridge_support(bld)
x1 = bld.x1-1
x2 = bld.x2+1
y1 = bld.y1-1
y2 = bld.y2+1
z = bld.z
(x1..x2).each { |x|
(y1..y2).each { |y|
next if ((x == x1 or x == x2) and
(y == y1 or y == y2))
if mb = map_block_at(x, y, z) and tile = mb.tiletype[x%16][y%16] and TiletypeShape::BasicShape[Tiletype::Shape[tile]] == :Open
bld.gate_flags.has_support = true
return
end
}
}
bld.gate_flags.has_support = false
end
# sets x2/centerx/y2/centery from x1/y1/bldtype
# x2/y2 preserved for :FarmPlot etc
def self.building_setsize(bld)
bld.x2 = bld.x1 if bld.x1 > bld.x2
bld.y2 = bld.y1 if bld.y1 > bld.y2
case bld.getType
when :Bridge
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
building_check_bridge_support(bld)
when :FarmPlot, :RoadDirt, :RoadPaved, :Stockpile, :Civzone
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
when :TradeDepot, :Shop
bld.x2 = bld.x1+4
bld.y2 = bld.y1+4
bld.centerx = bld.x1+2
bld.centery = bld.y1+2
when :SiegeEngine, :Windmill, :Wagon
bld.x2 = bld.x1+2
bld.y2 = bld.y1+2
bld.centerx = bld.x1+1
bld.centery = bld.y1+1
when :AxleHorizontal
if bld.is_vertical == 1
bld.x2 = bld.centerx = bld.x1
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
else
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
bld.y2 = bld.centery = bld.y1
end
when :WaterWheel
if bld.is_vertical == 1
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.y1+2
bld.centery = bld.y1+1
else
bld.x2 = bld.x1+2
bld.centerx = bld.x1+1
bld.y2 = bld.centery = bld.y1
end
when :Workshop, :Furnace
# Furnace = Custom or default case only
case bld.type
when :Quern, :Millstone, :Tool
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.centery = bld.y1
when :Siege, :Kennels
bld.x2 = bld.x1+4
bld.y2 = bld.y1+4
bld.centerx = bld.x1+2
bld.centery = bld.y1+2
when :Custom
if bdef = world.raws.buildings.all.binsearch(bld.getCustomType)
bld.x2 = bld.x1 + bdef.dim_x - 1
bld.y2 = bld.y1 + bdef.dim_y - 1
bld.centerx = bld.x1 + bdef.workloc_x
bld.centery = bld.y1 + bdef.workloc_y
end
else
bld.x2 = bld.x1+2
bld.y2 = bld.y1+2
bld.centerx = bld.x1+1
bld.centery = bld.y1+1
end
when :ScrewPump
case bld.direction
when :FromEast
bld.x2 = bld.centerx = bld.x1+1
bld.y2 = bld.centery = bld.y1
when :FromSouth
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.centery = bld.y1+1
when :FromWest
bld.x2 = bld.x1+1
bld.y2 = bld.centery = bld.y1
bld.centerx = bld.x1
else
bld.x2 = bld.x1+1
bld.y2 = bld.centery = bld.y1
bld.centerx = bld.x1
end
when :Well
bld.bucket_z = bld.z
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.centery = bld.y1
when :Construction
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.centery = bld.y1
bld.setMaterialAmount(1)
return
else
bld.x2 = bld.centerx = bld.x1
bld.y2 = bld.centery = bld.y1
end
bld.setMaterialAmount((bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1)
end
# set building at position, with optional width/height
def self.building_position(bld, pos, w=nil, h=nil)
bld.x1 = pos.x
bld.y1 = pos.y
bld.z = pos.z
bld.x2 = bld.x1+w-1 if w
bld.y2 = bld.y1+h-1 if h
building_setsize(bld)
end
# set map occupancy/stockpile/etc for a building
def self.building_setoccupancy(bld)
stockpile = (bld.getType == :Stockpile)
complete = (bld.getBuildStage >= bld.getMaxBuildStage)
extents = (bld.room.extents and bld.isExtentShaped)
z = bld.z
(bld.x1..bld.x2).each { |x|
(bld.y1..bld.y2).each { |y|
next if !extents or bld.room.extents[bld.room.width*(y-bld.room.y)+(x-bld.room.x)] == 0
next if not mb = map_block_at(x, y, z)
des = mb.designation[x%16][y%16]
des.pile = stockpile
des.dig = :No
if complete
bld.updateOccupancy(x, y)
else
mb.occupancy[x%16][y%16].building = :Planned
end
}
}
end
# link bld into other rooms if it is inside their extents
def self.building_linkrooms(bld)
didstuff = false
world.buildings.other[:ANY_FREE].each { |ob|
next if !ob.is_room or ob.z != bld.z
next if !ob.room.extents or !ob.isExtentShaped or ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)] == 0
didstuff = true
ob.children << bld
bld.parents << ob
}
ui.equipment.update.buildings = true if didstuff
end
# link the building into the world, set map data, link rooms, bld.id
def self.building_link(bld)
bld.id = df.building_next_id
df.building_next_id += 1
world.buildings.all << bld
bld.categorize(true)
building_setoccupancy(bld) if bld.isSettingOccupancy
building_linkrooms(bld)
end
# set a design for the building
def self.building_createdesign(bld, rough=true)
job = bld.jobs[0]
job.mat_type = bld.mat_type
job.mat_index = bld.mat_index
if bld.needsDesign
bld.design = BuildingDesign.cpp_new
bld.design.flags.rough = rough
end
end
# creates a job to build bld, return it
def self.building_linkforconstruct(bld)
building_link bld
ref = GeneralRefBuildingHolderst.cpp_new
ref.building_id = bld.id
job = Job.cpp_new
job.job_type = :ConstructBuilding
job.pos = [bld.centerx, bld.centery, bld.z]
job.references << ref
bld.jobs << job
job_link job
job
end
# construct a building with items or JobItems
def self.building_construct(bld, items)
job = building_linkforconstruct(bld)
rough = false
items.each { |item|
if items.kind_of?(JobItem)
item.quantity = (bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1 if item.quantity < 0
job.job_items << item
else
job_attachitem(job, item, :Hauled)
end
rough = true if item.getType == :BOULDER
bld.mat_type = item.getMaterial if bld.mat_type == -1
bld.mat_index = item.getMaterialIndex if bld.mat_index == -1
}
building_createdesign(bld, rough)
end
# creates a job to deconstruct the building
def self.building_deconstruct(bld)
job = Job.cpp_new
refbuildingholder = GeneralRefBuildingHolderst.cpp_new
job.job_type = :DestroyBuilding
refbuildingholder.building_id = building.id
job.references << refbuildingholder
building.jobs << job
job_link job
job
end
# exemple usage
def self.buildbed(pos=cursor)
suspend {
raise 'where to ?' if pos.x < 0
item = world.items.all.find { |i|
i.kind_of?(ItemBedst) and
i.itemrefs.empty? and
!i.flags.in_job
}
raise 'no free bed, build more !' if not item
bld = building_alloc(:Bed)
building_position(bld, pos)
building_construct(bld, [item])
}
end
end

@ -0,0 +1,152 @@
module DFHack
def self.each_tree(material=:any)
@raws_tree_name ||= {}
if @raws_tree_name.empty?
df.world.raws.plants.all.each_with_index { |p, idx|
@raws_tree_name[idx] = p.id if p.flags[:TREE]
}
end
if material != :any
mat = match_rawname(material, @raws_tree_name.values)
unless wantmat = @raws_tree_name.index(mat)
raise "invalid tree material #{material}"
end
end
world.plants.all.each { |plant|
next if not @raws_tree_name[plant.material]
next if wantmat and plant.material != wantmat
yield plant
}
end
def self.each_shrub(material=:any)
@raws_shrub_name ||= {}
if @raws_tree_name.empty?
df.world.raws.plants.all.each_with_index { |p, idx|
@raws_shrub_name[idx] = p.id if not p.flags[:GRASS] and not p.flags[:TREE]
}
end
if material != :any
mat = match_rawname(material, @raws_shrub_name.values)
unless wantmat = @raws_shrub_name.index(mat)
raise "invalid shrub material #{material}"
end
end
end
SaplingToTreeAge = 120960
def self.cuttrees(material=nil, count_max=100)
if !material
# list trees
cnt = Hash.new(0)
suspend {
each_tree { |plant|
next if plant.grow_counter < SaplingToTreeAge
next if map_designation_at(plant).hidden
cnt[plant.material] += 1
}
}
cnt.sort_by { |mat, c| c }.each { |mat, c|
name = @raws_tree_name[mat]
puts " #{name} #{c}"
}
else
cnt = 0
suspend {
each_tree(material) { |plant|
next if plant.grow_counter < SaplingToTreeAge
b = map_block_at(plant)
d = b.designation[plant.pos.x%16][plant.pos.y%16]
next if d.hidden
if d.dig == :No
d.dig = :Default
b.flags.designated = true
cnt += 1
break if cnt == count_max
end
}
}
puts "Updated #{cnt} plant designations"
end
end
def self.growtrees(material=nil, count_max=100)
if !material
# list plants
cnt = Hash.new(0)
suspend {
each_tree { |plant|
next if plant.grow_counter >= SaplingToTreeAge
next if map_designation_at(plant).hidden
cnt[plant.material] += 1
}
}
cnt.sort_by { |mat, c| c }.each { |mat, c|
name = @raws_tree_name[mat]
puts " #{name} #{c}"
}
else
cnt = 0
suspend {
each_tree(material) { |plant|
next if plant.grow_counter >= SaplingToTreeAge
next if map_designation_at(plant).hidden
plant.grow_counter = SaplingToTreeAge
cnt += 1
break if cnt == count_max
}
}
puts "Grown #{cnt} saplings"
end
end
def self.growcrops(material=nil, count_max=100)
@raws_plant_name ||= {}
@raws_plant_growdur ||= {}
if @raws_plant_name.empty?
df.world.raws.plants.all.each_with_index { |p, idx|
@raws_plant_name[idx] = p.id
@raws_plant_growdur[idx] = p.growdur
}
end
if !material
cnt = Hash.new(0)
suspend {
world.items.other[:SEEDS].each { |seed|
next if not seed.flags.in_building
next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
cnt[seed.mat_index] += 1
}
}
cnt.sort_by { |mat, c| c }.each { |mat, c|
name = world.raws.plants.all[mat].id
puts " #{name} #{c}"
}
else
if material != :any
mat = match_rawname(material, @raws_plant_name.values)
unless wantmat = @raws_plant_name.index(mat)
raise "invalid plant material #{material}"
end
end
cnt = 0
suspend {
world.items.other[:SEEDS].each { |seed|
next if wantmat and seed.mat_index != wantmat
next if not seed.flags.in_building
next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
cnt += 1
}
}
puts "Grown #{cnt} crops"
end
end
end

@ -0,0 +1,52 @@
module DFHack
# returns an Array of all units that are current fort citizen (dwarves, on map, not hostile)
def self.unit_citizens
race = ui.race_id
civ = ui.civ_id
world.units.active.find_all { |u|
u.race == race and u.civ_id == civ and !u.flags1.dead and !u.flags1.merchant and
!u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and
!u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and
u.mood != :Berserk
# TODO check curse ; currently this should keep vampires, but may include werebeasts
}
end
# list workers (citizen, not crazy / child / inmood / noble)
def self.unit_workers
unit_citizens.find_all { |u|
u.mood == :None and
u.profession != :CHILD and
u.profession != :BABY and
# TODO MENIAL_WORK_EXEMPTION_SPOUSE
!unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] }
}
end
# list currently idle workers
def self.unit_idlers
unit_workers.find_all { |u|
# current_job includes eat/drink/sleep/pickupequip
!u.job.current_job._getv and
# filter 'attend meeting'
u.meetings.length == 0 and
# filter soldiers (TODO check schedule)
u.military.squad_index == -1 and
# filter 'on break'
!u.status.misc_traits.find { |t| id == :OnBreak }
}
end
def self.unit_entitypositions(unit)
list = []
return list if not hf = world.history.figures.binsearch(unit.hist_figure_id)
hf.entity_links.each { |el|
next if el._rtti_classname != :histfig_entity_link_positionst
next if not ent = world.entities.all.binsearch(el.entity_id)
next if not pa = ent.positions.assignments.binsearch(el.assignment_id)
next if not pos = ent.positions.own.binsearch(pa.position_id)
list << pos
}
list
end
end

@ -0,0 +1,842 @@
// blindly copied imports from fastdwarf
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "VersionInfo.h"
#include "DataDefs.h"
#include "df/world.h"
#include "df/unit.h"
#include "tinythread.h"
using namespace DFHack;
// DFHack stuff
static int df_loadruby(void);
static void df_unloadruby(void);
static void df_rubythread(void*);
static command_result df_rubyload(color_ostream &out, std::vector <std::string> & parameters);
static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters);
static void ruby_bind_dfhack(void);
// inter-thread communication stuff
enum RB_command {
RB_IDLE,
RB_INIT,
RB_DIE,
RB_EVAL,
RB_CUSTOM,
};
tthread::mutex *m_irun;
tthread::mutex *m_mutex;
static RB_command r_type;
static const char *r_command;
static command_result r_result;
static tthread::thread *r_thread;
static int onupdate_active;
DFHACK_PLUGIN("ruby")
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
// fail silently instead of spamming the console with 'failed to initialize' if libruby is not present
// the error is still logged in stderr.log
if (!df_loadruby())
return CR_OK;
m_irun = new tthread::mutex();
m_mutex = new tthread::mutex();
r_type = RB_INIT;
r_thread = new tthread::thread(df_rubythread, 0);
while (r_type != RB_IDLE)
tthread::this_thread::yield();
m_irun->lock();
if (r_result == CR_FAILURE)
return CR_FAILURE;
onupdate_active = 0;
commands.push_back(PluginCommand("rb_load",
"Ruby interpreter. Loads the given ruby script.",
df_rubyload));
commands.push_back(PluginCommand("rb_eval",
"Ruby interpreter. Eval() a ruby string.",
df_rubyeval));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
if (!r_thread)
return CR_OK;
m_mutex->lock();
r_type = RB_DIE;
r_command = 0;
m_irun->unlock();
r_thread->join();
delete r_thread;
r_thread = 0;
delete m_irun;
m_mutex->unlock();
delete m_mutex;
df_unloadruby();
return CR_OK;
}
// send a single ruby line to be evaluated by the ruby thread
static command_result plugin_eval_rb(const char *command)
{
command_result ret;
// serialize 'accesses' to the ruby thread
m_mutex->lock();
if (!r_thread)
// raced with plugin_shutdown ?
return CR_OK;
r_type = RB_EVAL;
r_command = command;
m_irun->unlock();
// could use a condition_variable or something...
while (r_type != RB_IDLE)
tthread::this_thread::yield();
// XXX non-atomic with previous r_type change check
ret = r_result;
m_irun->lock();
m_mutex->unlock();
return ret;
}
static command_result plugin_eval_rb(std::string &command)
{
return plugin_eval_rb(command.c_str());
}
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
if (!r_thread)
return CR_OK;
if (!onupdate_active)
return CR_OK;
return plugin_eval_rb("DFHack.onupdate");
}
DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event e)
{
if (!r_thread)
return CR_OK;
std::string cmd = "DFHack.onstatechange ";
switch (e) {
#define SCASE(s) case SC_ ## s : cmd += ":" # s ; break
SCASE(WORLD_LOADED);
SCASE(WORLD_UNLOADED);
SCASE(MAP_LOADED);
SCASE(MAP_UNLOADED);
SCASE(VIEWSCREEN_CHANGED);
SCASE(CORE_INITIALIZED);
SCASE(BEGIN_UNLOAD);
#undef SCASE
}
return plugin_eval_rb(cmd);
}
static command_result df_rubyload(color_ostream &out, std::vector <std::string> & parameters)
{
if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?"))
{
out.print("This command loads the ruby script whose path is given as parameter, and run it.\n");
return CR_OK;
}
std::string cmd = "load '";
cmd += parameters[0]; // TODO escape singlequotes
cmd += "'";
return plugin_eval_rb(cmd);
}
static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters)
{
command_result ret;
if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?"))
{
out.print("This command executes an arbitrary ruby statement.\n");
return CR_OK;
}
std::string full = "";
for (unsigned i=0 ; i<parameters.size() ; ++i) {
full += parameters[i];
if (i != parameters.size()-1)
full += " ";
}
return plugin_eval_rb(full);
}
// ruby stuff
// ruby-dev on windows is messy
// ruby.h on linux 64 is broken
// so we dynamically load libruby instead of linking it at compile time
// lib path can be set in dfhack.ini to use the system libruby, but by
// default we'll embed our own (downloaded by cmake)
// these ruby definitions are invalid for windows 64bit
typedef unsigned long VALUE;
typedef unsigned long ID;
#define Qfalse ((VALUE)0)
#define Qtrue ((VALUE)2)
#define Qnil ((VALUE)4)
#define INT2FIX(i) ((VALUE)((((long)i) << 1) | 1))
#define FIX2INT(i) (((long)i) >> 1)
#define RUBY_METHOD_FUNC(func) ((VALUE(*)(...))func)
VALUE *rb_eRuntimeError;
void (*ruby_sysinit)(int *, const char ***);
void (*ruby_init)(void);
void (*ruby_init_loadpath)(void);
void (*ruby_script)(const char*);
void (*ruby_finalize)(void);
ID (*rb_intern)(const char*);
VALUE (*rb_raise)(VALUE, const char*, ...);
VALUE (*rb_funcall)(VALUE, ID, int, ...);
VALUE (*rb_define_module)(const char*);
void (*rb_define_singleton_method)(VALUE, const char*, VALUE(*)(...), int);
void (*rb_define_const)(VALUE, const char*, VALUE);
void (*rb_load_protect)(VALUE, int, int*);
VALUE (*rb_gv_get)(const char*);
VALUE (*rb_str_new)(const char*, long);
VALUE (*rb_str_new2)(const char*);
char* (*rb_string_value_ptr)(VALUE*);
VALUE (*rb_eval_string_protect)(const char*, int*);
VALUE (*rb_ary_shift)(VALUE);
VALUE (*rb_float_new)(double);
double (*rb_num2dbl)(VALUE);
VALUE (*rb_int2inum)(long);
VALUE (*rb_uint2inum)(unsigned long);
unsigned long (*rb_num2ulong)(VALUE);
// end of rip(ruby.h)
DFHack::DFLibrary *libruby_handle;
// load the ruby library, initialize function pointers
static int df_loadruby(void)
{
const char *libpath =
#ifdef WIN32
"./libruby.dll";
#else
"hack/libruby.so";
#endif
libruby_handle = OpenPlugin(libpath);
if (!libruby_handle) {
fprintf(stderr, "Cannot initialize ruby plugin: failed to load %s\n", libpath);
return 0;
}
if (!(rb_eRuntimeError = (VALUE*)LookupPlugin(libruby_handle, "rb_eRuntimeError")))
return 0;
// XXX does msvc support decltype ? might need a #define decltype typeof
// or just assign to *(void**)(&s) = ...
// ruby_sysinit is optional (ruby1.9 only)
ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit");
#define rbloadsym(s) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #s))) return 0
rbloadsym(ruby_init);
rbloadsym(ruby_init_loadpath);
rbloadsym(ruby_script);
rbloadsym(ruby_finalize);
rbloadsym(rb_intern);
rbloadsym(rb_raise);
rbloadsym(rb_funcall);
rbloadsym(rb_define_module);
rbloadsym(rb_define_singleton_method);
rbloadsym(rb_define_const);
rbloadsym(rb_load_protect);
rbloadsym(rb_gv_get);
rbloadsym(rb_str_new);
rbloadsym(rb_str_new2);
rbloadsym(rb_string_value_ptr);
rbloadsym(rb_eval_string_protect);
rbloadsym(rb_ary_shift);
rbloadsym(rb_float_new);
rbloadsym(rb_num2dbl);
rbloadsym(rb_int2inum);
rbloadsym(rb_uint2inum);
rbloadsym(rb_num2ulong);
#undef rbloadsym
return 1;
}
static void df_unloadruby(void)
{
if (libruby_handle) {
ClosePlugin(libruby_handle);
libruby_handle = 0;
}
}
// ruby thread code
static void dump_rb_error(void)
{
VALUE s, err;
err = rb_gv_get("$!");
s = rb_funcall(err, rb_intern("class"), 0);
s = rb_funcall(s, rb_intern("name"), 0);
Core::printerr("E: %s: ", rb_string_value_ptr(&s));
s = rb_funcall(err, rb_intern("message"), 0);
Core::printerr("%s\n", rb_string_value_ptr(&s));
err = rb_funcall(err, rb_intern("backtrace"), 0);
for (int i=0 ; i<8 ; ++i)
if ((s = rb_ary_shift(err)) != Qnil)
Core::printerr(" %s\n", rb_string_value_ptr(&s));
}
static color_ostream_proxy *console_proxy;
// ruby thread main loop
static void df_rubythread(void *p)
{
int state, running;
if (ruby_sysinit) {
// ruby1.9 specific API
static int argc;
static const char *argv[] = { "dfhack", 0 };
ruby_sysinit(&argc, (const char ***)&argv);
}
// initialize the ruby interpreter
ruby_init();
ruby_init_loadpath();
// default value for the $0 "current script name"
ruby_script("dfhack");
// create the ruby objects to map DFHack to ruby methods
ruby_bind_dfhack();
console_proxy = new color_ostream_proxy(Core::getInstance().getConsole());
r_result = CR_OK;
r_type = RB_IDLE;
running = 1;
while (running) {
// wait for new command
m_irun->lock();
switch (r_type) {
case RB_IDLE:
case RB_INIT:
break;
case RB_DIE:
running = 0;
ruby_finalize();
break;
case RB_EVAL:
state = 0;
rb_eval_string_protect(r_command, &state);
if (state)
dump_rb_error();
break;
case RB_CUSTOM:
// TODO handle ruby custom commands
break;
}
r_result = CR_OK;
r_type = RB_IDLE;
m_irun->unlock();
tthread::this_thread::yield();
}
}
#define BOOL_ISFALSE(v) ((v) == Qfalse || (v) == Qnil || (v) == INT2FIX(0))
// main DFHack ruby module
static VALUE rb_cDFHack;
// DFHack module ruby methods, binds specific dfhack methods
// enable/disable calls to DFHack.onupdate()
static VALUE rb_dfonupdateactive(VALUE self)
{
if (onupdate_active)
return Qtrue;
else
return Qfalse;
}
static VALUE rb_dfonupdateactiveset(VALUE self, VALUE val)
{
onupdate_active = (BOOL_ISFALSE(val) ? 0 : 1);
return Qtrue;
}
static VALUE rb_dfresume(VALUE self)
{
Core::getInstance().Resume();
return Qtrue;
}
static VALUE rb_dfsuspend(VALUE self)
{
Core::getInstance().Suspend();
return Qtrue;
}
// returns the delta to apply to dfhack xml addrs wrt actual memory addresses
// usage: real_addr = addr_from_xml + this_delta;
static VALUE rb_dfrebase_delta(void)
{
uint32_t expected_base_address;
uint32_t actual_base_address = 0;
#ifdef WIN32
expected_base_address = 0x00400000;
actual_base_address = (uint32_t)GetModuleHandle(0);
#else
expected_base_address = 0x08048000;
FILE *f = fopen("/proc/self/maps", "r");
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strstr(line, "libs/Dwarf_Fortress")) {
actual_base_address = strtoul(line, 0, 16);
break;
}
}
#endif
return rb_int2inum(actual_base_address - expected_base_address);
}
static VALUE rb_dfprint_str(VALUE self, VALUE s)
{
console_proxy->print("%s", rb_string_value_ptr(&s));
return Qnil;
}
static VALUE rb_dfprint_err(VALUE self, VALUE s)
{
Core::printerr("%s", rb_string_value_ptr(&s));
return Qnil;
}
/* TODO needs main dfhack support
this needs a custom DFHack::Plugin subclass to pass the cmdname to invoke(), to match the ruby callback
// register a ruby method as dfhack console command
// usage: DFHack.register("moo", "this commands prints moo on the console") { DFHack.puts "moo !" }
static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
{
commands.push_back(PluginCommand(rb_string_value_ptr(&name),
rb_string_value_ptr(&descr),
df_rubycustom));
return Qtrue;
}
*/
static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
{
rb_raise(*rb_eRuntimeError, "not implemented");
}
static VALUE rb_dfget_global_address(VALUE self, VALUE name)
{
return rb_uint2inum(Core::getInstance().vinfo->getAddress(rb_string_value_ptr(&name)));
}
static VALUE rb_dfget_vtable(VALUE self, VALUE name)
{
return rb_uint2inum((uint32_t)Core::getInstance().vinfo->getVTable(rb_string_value_ptr(&name)));
}
// read the c++ class name from a vtable pointer, inspired from doReadClassName
// XXX virtual classes only! dark pointer arithmetic, use with caution !
static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr)
{
char *ptr = (char*)rb_num2ulong(vptr);
#ifdef WIN32
char *rtti = *(char**)(ptr - 0x4);
char *typeinfo = *(char**)(rtti + 0xC);
// skip the .?AV, trim @@ from end
return rb_str_new(typeinfo+0xc, strlen(typeinfo+0xc)-2);
#else
char *typeinfo = *(char**)(ptr - 0x4);
char *typestring = *(char**)(typeinfo + 0x4);
while (*typestring >= '0' && *typestring <= '9')
typestring++;
return rb_str_new2(typestring);
#endif
}
static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr)
{
// actually, rb_dfmemory_read_int32
return rb_uint2inum(*(uint32_t*)rb_num2ulong(objptr));
}
// raw memory access
// used by the ruby class definitions
// XXX may cause game crash ! double-check your addresses !
static VALUE rb_dfmalloc(VALUE self, VALUE len)
{
char *ptr = (char*)malloc(FIX2INT(len));
if (!ptr)
rb_raise(*rb_eRuntimeError, "no memory");
memset(ptr, 0, FIX2INT(len));
return rb_uint2inum((long)ptr);
}
static VALUE rb_dffree(VALUE self, VALUE ptr)
{
free((void*)rb_num2ulong(ptr));
return Qtrue;
}
// memory reading (buffer)
static VALUE rb_dfmemory_read(VALUE self, VALUE addr, VALUE len)
{
return rb_str_new((char*)rb_num2ulong(addr), rb_num2ulong(len));
}
// memory reading (integers/floats)
static VALUE rb_dfmemory_read_int8(VALUE self, VALUE addr)
{
return rb_int2inum(*(char*)rb_num2ulong(addr));
}
static VALUE rb_dfmemory_read_int16(VALUE self, VALUE addr)
{
return rb_int2inum(*(short*)rb_num2ulong(addr));
}
static VALUE rb_dfmemory_read_int32(VALUE self, VALUE addr)
{
return rb_int2inum(*(int*)rb_num2ulong(addr));
}
static VALUE rb_dfmemory_read_float(VALUE self, VALUE addr)
{
return rb_float_new(*(float*)rb_num2ulong(addr));
}
// memory writing (buffer)
static VALUE rb_dfmemory_write(VALUE self, VALUE addr, VALUE raw)
{
// no stable api for raw.length between rb1.8/rb1.9 ...
int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0));
memcpy((void*)rb_num2ulong(addr), rb_string_value_ptr(&raw), strlen);
return Qtrue;
}
// memory writing (integers/floats)
static VALUE rb_dfmemory_write_int8(VALUE self, VALUE addr, VALUE val)
{
*(char*)rb_num2ulong(addr) = rb_num2ulong(val);
return Qtrue;
}
static VALUE rb_dfmemory_write_int16(VALUE self, VALUE addr, VALUE val)
{
*(short*)rb_num2ulong(addr) = rb_num2ulong(val);
return Qtrue;
}
static VALUE rb_dfmemory_write_int32(VALUE self, VALUE addr, VALUE val)
{
*(int*)rb_num2ulong(addr) = rb_num2ulong(val);
return Qtrue;
}
static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val)
{
*(float*)rb_num2ulong(addr) = rb_num2dbl(val);
return Qtrue;
}
// stl::string
static VALUE rb_dfmemory_stlstring_init(VALUE self, VALUE addr)
{
// XXX THIS IS TERRIBLE
std::string *ptr = new std::string;
memcpy((void*)rb_num2ulong(addr), (void*)ptr, sizeof(*ptr));
return Qtrue;
}
static VALUE rb_dfmemory_read_stlstring(VALUE self, VALUE addr)
{
std::string *s = (std::string*)rb_num2ulong(addr);
return rb_str_new(s->c_str(), s->length());
}
static VALUE rb_dfmemory_write_stlstring(VALUE self, VALUE addr, VALUE val)
{
std::string *s = (std::string*)rb_num2ulong(addr);
int strlen = FIX2INT(rb_funcall(val, rb_intern("length"), 0));
s->assign(rb_string_value_ptr(&val), strlen);
return Qtrue;
}
// vector access
static VALUE rb_dfmemory_vec_init(VALUE self, VALUE addr)
{
std::vector<uint8_t> *ptr = new std::vector<uint8_t>;
memcpy((void*)rb_num2ulong(addr), (void*)ptr, sizeof(*ptr));
return Qtrue;
}
// vector<uint8>
static VALUE rb_dfmemory_vec8_length(VALUE self, VALUE addr)
{
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
return rb_uint2inum(v->size());
}
static VALUE rb_dfmemory_vec8_ptrat(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
}
static VALUE rb_dfmemory_vec8_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
return Qtrue;
}
static VALUE rb_dfmemory_vec8_delete(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
v->erase(v->begin()+FIX2INT(idx));
return Qtrue;
}
// vector<uint16>
static VALUE rb_dfmemory_vec16_length(VALUE self, VALUE addr)
{
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
return rb_uint2inum(v->size());
}
static VALUE rb_dfmemory_vec16_ptrat(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
}
static VALUE rb_dfmemory_vec16_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
return Qtrue;
}
static VALUE rb_dfmemory_vec16_delete(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
v->erase(v->begin()+FIX2INT(idx));
return Qtrue;
}
// vector<uint32>
static VALUE rb_dfmemory_vec32_length(VALUE self, VALUE addr)
{
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
return rb_uint2inum(v->size());
}
static VALUE rb_dfmemory_vec32_ptrat(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
}
static VALUE rb_dfmemory_vec32_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
return Qtrue;
}
static VALUE rb_dfmemory_vec32_delete(VALUE self, VALUE addr, VALUE idx)
{
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
v->erase(v->begin()+FIX2INT(idx));
return Qtrue;
}
// vector<bool>
static VALUE rb_dfmemory_vecbool_length(VALUE self, VALUE addr)
{
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
return rb_uint2inum(v->size());
}
static VALUE rb_dfmemory_vecbool_at(VALUE self, VALUE addr, VALUE idx)
{
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
return v->at(FIX2INT(idx)) ? Qtrue : Qfalse;
}
static VALUE rb_dfmemory_vecbool_setat(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
v->at(FIX2INT(idx)) = (BOOL_ISFALSE(val) ? 0 : 1);
return Qtrue;
}
static VALUE rb_dfmemory_vecbool_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
v->insert(v->begin()+FIX2INT(idx), (BOOL_ISFALSE(val) ? 0 : 1));
return Qtrue;
}
static VALUE rb_dfmemory_vecbool_delete(VALUE self, VALUE addr, VALUE idx)
{
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
v->erase(v->begin()+FIX2INT(idx));
return Qtrue;
}
// BitArray
static VALUE rb_dfmemory_bitarray_length(VALUE self, VALUE addr)
{
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
return rb_uint2inum(b->size*8); // b->size is in bytes
}
static VALUE rb_dfmemory_bitarray_resize(VALUE self, VALUE addr, VALUE sz)
{
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
b->resize(rb_num2ulong(sz));
return Qtrue;
}
static VALUE rb_dfmemory_bitarray_isset(VALUE self, VALUE addr, VALUE idx)
{
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
return b->is_set(rb_num2ulong(idx)) ? Qtrue : Qfalse;
}
static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE val)
{
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
b->set(rb_num2ulong(idx), (BOOL_ISFALSE(val) ? 0 : 1));
return Qtrue;
}
/* call an arbitrary object virtual method */
static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE a1, VALUE a2, VALUE a3)
{
#ifdef WIN32
__thiscall
#endif
int (*fptr)(char **me, int, int, int, int);
char **that = (char**)rb_num2ulong(cppobj);
int ret;
fptr = (decltype(fptr))*(void**)(*that + rb_num2ulong(cppvoff));
ret = fptr(that, rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3));
return rb_int2inum(ret);
}
// define module DFHack and its methods
static void ruby_bind_dfhack(void) {
rb_cDFHack = rb_define_module("DFHack");
// global DFHack commands
rb_define_singleton_method(rb_cDFHack, "onupdate_active", RUBY_METHOD_FUNC(rb_dfonupdateactive), 0);
rb_define_singleton_method(rb_cDFHack, "onupdate_active=", RUBY_METHOD_FUNC(rb_dfonupdateactiveset), 1);
rb_define_singleton_method(rb_cDFHack, "resume", RUBY_METHOD_FUNC(rb_dfresume), 0);
rb_define_singleton_method(rb_cDFHack, "do_suspend", RUBY_METHOD_FUNC(rb_dfsuspend), 0);
rb_define_singleton_method(rb_cDFHack, "get_global_address", RUBY_METHOD_FUNC(rb_dfget_global_address), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1);
rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
rb_define_singleton_method(rb_cDFHack, "register_dfcommand", RUBY_METHOD_FUNC(rb_dfregister), 2);
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1);
rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1);
rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);
rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1);
rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 6);
rb_define_const(rb_cDFHack, "REBASE_DELTA", rb_dfrebase_delta());
rb_define_singleton_method(rb_cDFHack, "memory_read", RUBY_METHOD_FUNC(rb_dfmemory_read), 2);
rb_define_singleton_method(rb_cDFHack, "memory_read_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1);
rb_define_singleton_method(rb_cDFHack, "memory_read_int16", RUBY_METHOD_FUNC(rb_dfmemory_read_int16), 1);
rb_define_singleton_method(rb_cDFHack, "memory_read_int32", RUBY_METHOD_FUNC(rb_dfmemory_read_int32), 1);
rb_define_singleton_method(rb_cDFHack, "memory_read_float", RUBY_METHOD_FUNC(rb_dfmemory_read_float), 1);
rb_define_singleton_method(rb_cDFHack, "memory_write", RUBY_METHOD_FUNC(rb_dfmemory_write), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int8", RUBY_METHOD_FUNC(rb_dfmemory_write_int8), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2);
rb_define_singleton_method(rb_cDFHack, "memory_stlstring_init", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_init), 1);
rb_define_singleton_method(rb_cDFHack, "memory_read_stlstring", RUBY_METHOD_FUNC(rb_dfmemory_read_stlstring), 1);
rb_define_singleton_method(rb_cDFHack, "memory_write_stlstring", RUBY_METHOD_FUNC(rb_dfmemory_write_stlstring), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector_init", RUBY_METHOD_FUNC(rb_dfmemory_vec_init), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vector8_length", RUBY_METHOD_FUNC(rb_dfmemory_vec8_length), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vector8_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec8_ptrat), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector8_insert", RUBY_METHOD_FUNC(rb_dfmemory_vec8_insert), 3);
rb_define_singleton_method(rb_cDFHack, "memory_vector8_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec8_delete), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector16_length", RUBY_METHOD_FUNC(rb_dfmemory_vec16_length), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vector16_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec16_ptrat), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector16_insert", RUBY_METHOD_FUNC(rb_dfmemory_vec16_insert), 3);
rb_define_singleton_method(rb_cDFHack, "memory_vector16_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec16_delete), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector32_length", RUBY_METHOD_FUNC(rb_dfmemory_vec32_length), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vector32_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_ptrat), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vector32_insert", RUBY_METHOD_FUNC(rb_dfmemory_vec32_insert), 3);
rb_define_singleton_method(rb_cDFHack, "memory_vector32_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec32_delete), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_length", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_length), 1);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_at", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_at), 2);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_setat", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_setat), 3);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_insert", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_insert), 3);
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_delete", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_delete), 2);
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_length", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_length), 1);
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_resize", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_resize), 2);
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_isset", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_isset), 2);
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_set", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_set), 3);
// load the default ruby-level definitions
int state=0;
rb_load_protect(rb_str_new2("./hack/ruby.rb"), Qfalse, &state);
if (state)
dump_rb_error();
}

File diff suppressed because it is too large Load Diff

@ -100,37 +100,37 @@ command_result df_showmood (color_ostream &out, vector <string> & parameters)
switch (job->job_type)
{
case job_type::StrangeMoodCrafter:
out.print("become a Craftsdwarf (or Engraver)");
out.print("claim a Craftsdwarf's Workshop");
break;
case job_type::StrangeMoodJeweller:
out.print("become a Jeweler");
out.print("claim a Jeweler's Workshop");
break;
case job_type::StrangeMoodForge:
out.print("become a Metalworker");
out.print("claim a Metalsmith's Forge");
break;
case job_type::StrangeMoodMagmaForge:
out.print("become a Metalworker using a Magma Forge");
out.print("claim a Magma Forge");
break;
case job_type::StrangeMoodCarpenter:
out.print("become a Carpenter");
out.print("claim a Carpenter's Workshop");
break;
case job_type::StrangeMoodMason:
out.print("become a Mason (or Miner)");
out.print("claim a Mason's Workshop");
break;
case job_type::StrangeMoodBowyer:
out.print("become a Bowyer");
out.print("claim a Boywer's Workshop");
break;
case job_type::StrangeMoodTanner:
out.print("become a Leatherworker (or Tanner)");
out.print("claim a Leather Works");
break;
case job_type::StrangeMoodWeaver:
out.print("become a Clothier (or Weaver)");
out.print("claim a Clothier's Shop");
break;
case job_type::StrangeMoodGlassmaker:
out.print("become a Glassmaker");
out.print("claim a Glass Furnace");
break;
case job_type::StrangeMoodMechanics:
out.print("become a Mechanic");
out.print("claim a Mechanic's Workshop");
break;
case job_type::StrangeMoodBrooding:
out.print("enter a macabre mood?");
@ -142,20 +142,28 @@ command_result df_showmood (color_ostream &out, vector <string> & parameters)
out.print("do something else...");
break;
}
out.print(" and become a legendary %s", ENUM_ATTR_STR(job_skill, caption_noun, unit->job.mood_skill));
if (unit->mood == mood_type::Possessed)
out.print(" (but not really)");
break;
default:
out.print("insane?");
break;
}
out.print(".\n");
if (unit->sex)
out.print("He has ");
else
out.print("She has ");
if (building)
{
string name;
building->getName(&name);
out.print(" and has claimed a %s\n", name.c_str());
out.print("claimed a %s and wants", name.c_str());
}
else
out.print(" and has not yet claimed a workshop\n");
out.print("not yet claimed a workshop but will want");
out.print(" the following items:\n");
for (size_t i = 0; i < job->job_items.size(); i++)
{

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