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

develop
Robert Heinrich 2012-04-05 11:41:01 +02:00
commit 511fceff0a
25 changed files with 2390 additions and 322 deletions

@ -140,7 +140,7 @@ add_subdirectory(depends)
IF(BUILD_LIBRARY) IF(BUILD_LIBRARY)
add_subdirectory (library) add_subdirectory (library)
## install the default documentation files ## install the default documentation files
install(FILES LICENSE Readme.html Compile.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES LICENSE Readme.html Compile.html Lua\ API.html DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif() endif()
#build the plugins #build the plugins

@ -0,0 +1,499 @@
##############
DFHack Lua API
##############
.. contents::
====================
DF structure wrapper
====================
DF structures described by the xml files in library/xml are exported
to lua code as a tree of objects and functions under the ``df`` global,
which broadly maps to the ``df`` namespace in C++.
**WARNING**: The wrapper provides almost raw access to the memory
of the game, so mistakes in manipulating objects are as likely to
crash the game as equivalent plain C++ code would be. E.g. NULL
pointer access is safely detected, but dangling pointers aren't.
Objects managed by the wrapper can be broadly classified into the following groups:
1. Typed object pointers (references).
References represent objects in DF memory with a known type.
In addition to fields and methods defined by the wrapped type,
every reference has some built-in properties and methods.
2. Untyped pointers
Represented as lightuserdata.
In assignment to a pointer NULL can be represented either as
``nil``, or a NULL lightuserdata; reading a NULL pointer field
returns ``nil``.
3. Named types
Objects in the ``df`` tree that represent identity of struct, class,
enum and bitfield types. They host nested named types, static
methods, builtin properties & methods, and, for enums and bitfields,
the bi-directional mapping between key names and values.
4. The ``global`` object
``df.global`` corresponds to the ``df::global`` namespace, and
behaves as a mix between a named type and a reference, containing
both nested types and fields corresponding to global symbols.
In addition to the ``global`` object and top-level types the ``df``
global also contains a few global builtin utility functions.
Typed object references
=======================
The underlying primitive lua object is userdata with a metatable.
Every structured field access produces a new userdata instance.
All typed objects have the following built-in features:
* ``ref1 == ref2``, ``tostring(ref)``
References implement equality by type & pointer value, and string conversion.
* ``pairs(ref)``
Returns an iterator for the sequence of actual C++ field names
and values. Fields are enumerated in memory order. Methods and
lua wrapper properties are not included in the iteration.
* ``ref._kind``
Returns one of: ``primitive``, ``struct``, ``container``,
or ``bitfield``, as appropriate for the referenced object.
* ``ref._type``
Returns the named type object or a string that represents
the referenced object type.
* ``ref:sizeof()``
Returns *size, address*
* ``ref:new()``
Allocates a new instance of the same type, and copies data
from the current object.
* ``ref:delete()``
Destroys the object with the C++ ``delete`` operator.
If destructor is not available, returns *false*.
**WARNING**: the lua reference object remains as a dangling
pointer, like a raw C++ pointer would.
* ``ref:assign(object)``
Assigns data from object to ref. Object must either be another
ref of a compatible type, or a lua table; in the latter case
special recursive assignment rules are applied.
* ``ref:_displace(index[,step])``
Returns a new reference with the pointer adjusted by index*step.
Step defaults to the natural object size.
Primitive references
--------------------
References of the *_kind* ``'primitive'`` are used for objects
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 ``_field()`` method.
They behave as structs with one field ``value`` of the right type.
Struct references
-----------------
Struct references are used for class and struct objects.
They implement the following features:
* ``ref.field``, ``ref.field = value``
Valid fields of the structure may be accessed by subscript.
Primitive typed fields, i.e. numbers & strings, are converted
to/from matching lua values. The value of a pointer is a reference
to the target, or nil/NULL. Complex types are represented by
a reference to the field within the structure; unless recursive
lua table assignment is used, such fields can only be read.
**NOTE:** In case of inheritance, *superclass* fields have precedence
over the subclass, but fields shadowed in this way can still
be accessed as ``ref['subclasstype.field']``.
This shadowing order is necessary because vtable-based classes
are automatically exposed in their exact type, and the reverse
rule would make access to superclass fields unreliable.
* ``ref._field(field)``
Returns a reference to a valid field. That is, unlike regular
subscript, it returns a reference to the field within the structure
even for primitive typed fields and pointers.
* ``ref:vmethod(args...)``
Named virtual methods are also exposed, subject to the same
shadowing rules.
* ``pairs(ref)``
Enumerates all real fields (but not methods) in memory
(= declaration) order.
Container references
--------------------
Containers represent vectors and arrays, possibly resizable.
A container field can associate an enum to the container
reference, which allows accessing elements using string keys
instead of numerical indices.
Implemented features:
* ``ref._enum``
If the container has an associated enum, returns the matching
named type object.
* ``#ref``
Returns the *length* of the container.
* ``ref[index]``
Accesses the container element, using either a *0-based* numerical
index, or, if an enum is associated, a valid enum key string.
Accessing an invalid index is an error, but some container types
may return a default value, or auto-resize instead for convenience.
Currently this relaxed mode is implemented by df-flagarray aka BitArray.
* ``ref._field(index)``
Like with structs, returns a pointer to the array element, if possible.
Flag and bit arrays cannot return such pointer, so it fails with an error.
* ``pairs(ref)``, ``ipairs(ref)``
If the container has no associated enum, both behave identically,
iterating over numerical indices in order. Otherwise, ipairs still
uses numbers, while pairs tries to substitute enum keys whenever
possible.
* ``ref:resize(new_size)``
Resizes the container if supported, or fails with an error.
* ``ref:insert(index,item)``
Inserts a new item at the specified index. To add at the end,
use ``#ref`` as index.
* ``ref:erase(index)``
Removes the element at the given valid index.
Bitfield references
-------------------
Bitfields behave like special fixed-size containers.
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.
Named types
===========
Named types are exposed in the ``df`` tree with names identical
to the C++ version, except for the ``::`` vs ``.`` difference.
All types and the global object have the following features:
* ``type._kind``
Evaluates to one of ``struct-type``, ``class-type``, ``enum-type``,
``bitfield-type`` or ``global``.
* ``type._identity``
Contains a lightuserdata pointing to the underlying
DFHack::type_instance object.
Types excluding the global object also support:
* ``type:sizeof()``
Returns the size of an object of the type.
* ``type:new()``
Creates a new instance of an object of the type.
* ``type:is_instance(object)``
Returns true if object is same or subclass type, or a reference
to an object of same or subclass type. It is permissible to pass
nil, NULL or non-wrapper value as object; in this case the
method returns nil.
In addition to this, enum and bitfield types contain a
bi-directional mapping between key strings and values, and
also map ``_first_item`` and ``_last_item`` to the min and
max values.
Struct and class types with instance-vector attribute in the
xml have a ``type.find(key)`` function that wraps the find
method provided in C++.
Global functions
================
The ``df`` table itself contains the following functions and values:
* ``NULL``, ``df.NULL``
Contains the NULL lightuserdata.
* ``df.isnull(obj)``
Evaluates to true if obj is nil or NULL; false otherwise.
* ``df.isvalid(obj[,allow_null])``
For supported objects returns one of ``type``, ``voidptr``, ``ref``.
If *allow_null* is true, and obj is nil or NULL, returns ``null``.
Otherwise returns *nil*.
* ``df.sizeof(obj)``
For types and refs identical to ``obj:sizeof()``.
For lightuserdata returns *nil, address*
* ``df.new(obj)``, ``df.delete(obj)``, ``df.assign(obj, obj2)``
Equivalent to using the matching methods of obj.
* ``df._displace(obj,index[,step])``
For refs equivalent to the method, but also works with
lightuserdata (step is mandatory then).
* ``df.is_instance(type,obj)``
Equivalent to the method, but also allows a reference as proxy for its type.
Recursive table assignment
==========================
Recursive assignment is invoked when a lua table is assigned
to a C++ object or field, i.e. one of:
* ``ref:assign{...}``
* ``ref.field = {...}``
The general mode of operation is that all fields of the table
are assigned to the fields of the target structure, roughly
emulating the following code::
function rec_assign(ref,table)
for key,value in pairs(table) do
ref[key] = value
end
end
Since assigning a table to a field using = invokes the same
process, it is recursive.
There are however some variations to this process depending
on the type of the field being assigned to:
1. If the table contains an ``assign`` field, it is
applied first, using the ``ref:assign(value)`` method.
It is never assigned as a usual field.
2. When a table is assigned to a non-NULL pointer field
using the ``ref.field = {...}`` syntax, it is applied
to the target of the pointer instead.
If the pointer is NULL, the table is checked for a ``new`` field:
a. If it is *nil* or *false*, assignment fails with an error.
b. If it is *true*, the pointer is initialized with a newly
allocated object of the declared target type of the pointer.
c. Otherwise, ``table.new`` must be a named type, or an
object of a type compatible with the pointer. The pointer
is initialized with the result of calling ``table.new:new()``.
After this auto-vivification process, assignment proceeds
as if the pointer wasn't NULL.
Obviously, the ``new`` field inside the table is always skipped
during the actual per-field assignment processing.
3. If the target of the assignment is a container, a separate
rule set is used:
a. If the table contains neither ``assign`` nor ``resize``
fields, it is interpreted as an ordinary *1-based* lua
array. The container is resized to the #-size of the
table, and elements are assigned in numeric order::
ref:resize(#table);
for i=1,#table do ref[i-1] = table[i] end
b. Otherwise, ``resize`` must be *true*, *false*, or
an explicit number. If it is not false, the container
is resized. After that the usual struct-like 'pairs'
assignment is performed.
In case ``resize`` is *true*, the size is computed
by scanning the table for the largest numeric key.
This means that in order to reassign only one element of
a container using this system, it is necessary to use::
{ resize=false, [idx]=value }
Since nil inside a table is indistinguishable from missing key,
it is necessary to use ``df.NULL`` as a null pointer value.
This system is intended as a way to define a nested object
tree using pure lua data structures, and then materialize it in
C++ memory in one go. Note that if pointer auto-vivification
is used, an error in the middle of the recursive walk would
not destroy any objects allocated in this way, so the user
should be prepared to catch the error and do the necessary
cleanup.
================
DFHack utilities
================
DFHack utility functions are placed in the ``dfhack`` global tree.
Currently it defines the following features:
* ``dfhack.print(args...)``
Output tab-separated args as standard lua print would do,
but without a newline.
* ``print(args...)``, ``dfhack.println(args...)``
A replacement of the standard library print function that
works with DFHack output infrastructure.
* ``dfhack.printerr(args...)``
Same as println; intended for errors. Uses red color and logs to stderr.log.
* ``dfhack.color([color])``
Sets the current output color. If color is *nil* or *-1*, resets to default.
* ``dfhack.is_interactive()``
Checks if the thread can access the interactive console and returns *true* or *false*.
* ``dfhack.lineedit([prompt[,history_filename]])``
If the thread owns the interactive console, shows a prompt
and returns the entered string. Otherwise returns *nil, error*.
* ``dfhack.interpreter([prompt[,env[,history_filename]]])``
Starts an interactive lua interpreter, using the specified prompt
string, global environment and command-line history file.
If the interactive console is not accessible, returns *nil, error*.
* ``dfhack.pcall(f[,args...])``
Invokes f via xpcall, using an error function that attaches
a stack trace to the error. The same function is used by SafeCall
in C++, and dfhack.safecall.
The returned error is a table with separate ``message`` and
``stacktrace`` string fields; it implements ``__tostring``.
* ``safecall(f[,args...])``, ``dfhack.safecall(f[,args...])``
Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function.
* ``dfhack.with_suspend(f[,args...])``
Calls ``f`` with arguments after grabbing the DF core suspend lock.
Suspending is necessary for accessing a consistent state of DF memory.
Returned values and errors are propagated through after releasing
the lock. It is safe to nest suspends.
Every thread is allowed only one suspend per DF frame, so it is best
to group operations together in one big critical section. A plugin
can choose to run all lua code inside a C++-side suspend lock.
Persistent configuration storage
================================
This api is intended for storing configuration options in the world itself.
It probably should be restricted to data that is world-dependent.
Entries are identified by a string ``key``, but it is also possible to manage
multiple entries with the same key; their identity is determined by ``entry_id``.
Every entry has a mutable string ``value``, and an array of 7 mutable ``ints``.
* ``dfhack.persistent.get(key)``, ``entry:get()``
Retrieves a persistent config record with the given string key,
or refreshes an already retrieved entry. If there are multiple
entries with the same key, it is undefined which one is retrieved
by the first version of the call.
Returns entry, or *nil* if not found.
* ``dfhack.persistent.delete(key)``, ``entry:delete()``
Removes an existing entry. Returns *true* if succeeded.
* ``dfhack.persistent.get_all(key[,match_prefix])``
Retrieves all entries with the same key, or starting with key..'/'.
Calling ``get_all('',true)`` will match all entries.
If none found, returns nil; otherwise returns an array of entries.
* ``dfhack.persistent.save({key=str1, ...}[,new])``, ``entry:save([new])``
Saves changes in an entry, or creates a new one. Passing true as
new forces creation of a new entry even if one already exists;
otherwise the existing one is simply updated.
Returns *entry, did_create_new*
Since the data is hidden in data structures owned by the DF world,
and automatically stored in the save game, these save and retrieval
functions can just copy values in memory without doing any actual I/O.
However, currently every entry has a 180+-byte dead-weight overhead.

@ -0,0 +1,759 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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/" />
<title>DFHack Lua API</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7056 2011-06-17 10:50:48Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math {
margin-left: 2em ;
margin-right: 2em }
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="dfhack-lua-api">
<h1 class="title">DFHack Lua API</h1>
<div class="contents topic" id="contents">
<p class="topic-title first">Contents</p>
<ul class="simple">
<li><a class="reference internal" href="#df-structure-wrapper" id="id1">DF structure wrapper</a><ul>
<li><a class="reference internal" href="#typed-object-references" id="id2">Typed object references</a><ul>
<li><a class="reference internal" href="#primitive-references" id="id3">Primitive references</a></li>
<li><a class="reference internal" href="#struct-references" id="id4">Struct references</a></li>
<li><a class="reference internal" href="#container-references" id="id5">Container references</a></li>
<li><a class="reference internal" href="#bitfield-references" id="id6">Bitfield references</a></li>
</ul>
</li>
<li><a class="reference internal" href="#named-types" id="id7">Named types</a></li>
<li><a class="reference internal" href="#global-functions" id="id8">Global functions</a></li>
<li><a class="reference internal" href="#recursive-table-assignment" id="id9">Recursive table assignment</a></li>
</ul>
</li>
<li><a class="reference internal" href="#dfhack-utilities" id="id10">DFHack utilities</a><ul>
<li><a class="reference internal" href="#persistent-configuration-storage" id="id11">Persistent configuration storage</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="df-structure-wrapper">
<h1><a class="toc-backref" href="#id1">DF structure wrapper</a></h1>
<p>DF structures described by the xml files in library/xml are exported
to lua code as a tree of objects and functions under the <tt class="docutils literal">df</tt> global,
which broadly maps to the <tt class="docutils literal">df</tt> namespace in C++.</p>
<p><strong>WARNING</strong>: The wrapper provides almost raw access to the memory
of the game, so mistakes in manipulating objects are as likely to
crash the game as equivalent plain C++ code would be. E.g. NULL
pointer access is safely detected, but dangling pointers aren't.</p>
<p>Objects managed by the wrapper can be broadly classified into the following groups:</p>
<ol class="arabic">
<li><p class="first">Typed object pointers (references).</p>
<p>References represent objects in DF memory with a known type.</p>
<p>In addition to fields and methods defined by the wrapped type,
every reference has some built-in properties and methods.</p>
</li>
<li><p class="first">Untyped pointers</p>
<p>Represented as lightuserdata.</p>
<p>In assignment to a pointer NULL can be represented either as
<tt class="docutils literal">nil</tt>, or a NULL lightuserdata; reading a NULL pointer field
returns <tt class="docutils literal">nil</tt>.</p>
</li>
<li><p class="first">Named types</p>
<p>Objects in the <tt class="docutils literal">df</tt> tree that represent identity of struct, class,
enum and bitfield types. They host nested named types, static
methods, builtin properties &amp; methods, and, for enums and bitfields,
the bi-directional mapping between key names and values.</p>
</li>
<li><p class="first">The <tt class="docutils literal">global</tt> object</p>
<p><tt class="docutils literal">df.global</tt> corresponds to the <tt class="docutils literal"><span class="pre">df::global</span></tt> namespace, and
behaves as a mix between a named type and a reference, containing
both nested types and fields corresponding to global symbols.</p>
</li>
</ol>
<p>In addition to the <tt class="docutils literal">global</tt> object and top-level types the <tt class="docutils literal">df</tt>
global also contains a few global builtin utility functions.</p>
<div class="section" id="typed-object-references">
<h2><a class="toc-backref" href="#id2">Typed object references</a></h2>
<p>The underlying primitive lua object is userdata with a metatable.
Every structured field access produces a new userdata instance.</p>
<p>All typed objects have the following built-in features:</p>
<ul>
<li><p class="first"><tt class="docutils literal">ref1 == ref2</tt>, <tt class="docutils literal">tostring(ref)</tt></p>
<p>References implement equality by type &amp; pointer value, and string conversion.</p>
</li>
<li><p class="first"><tt class="docutils literal">pairs(ref)</tt></p>
<p>Returns an iterator for the sequence of actual C++ field names
and values. Fields are enumerated in memory order. Methods and
lua wrapper properties are not included in the iteration.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref._kind</tt></p>
<p>Returns one of: <tt class="docutils literal">primitive</tt>, <tt class="docutils literal">struct</tt>, <tt class="docutils literal">container</tt>,
or <tt class="docutils literal">bitfield</tt>, as appropriate for the referenced object.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref._type</tt></p>
<p>Returns the named type object or a string that represents
the referenced object type.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref:sizeof()</tt></p>
<p>Returns <em>size, address</em></p>
</li>
<li><p class="first"><tt class="docutils literal">ref:new()</tt></p>
<p>Allocates a new instance of the same type, and copies data
from the current object.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref:delete()</tt></p>
<p>Destroys the object with the C++ <tt class="docutils literal">delete</tt> operator.
If destructor is not available, returns <em>false</em>.</p>
<p><strong>WARNING</strong>: the lua reference object remains as a dangling
pointer, like a raw C++ pointer would.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref:assign(object)</tt></p>
<p>Assigns data from object to ref. Object must either be another
ref of a compatible type, or a lua table; in the latter case
special recursive assignment rules are applied.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">ref:_displace(index[,step])</span></tt></p>
<p>Returns a new reference with the pointer adjusted by index*step.
Step defaults to the natural object size.</p>
</li>
</ul>
<div class="section" id="primitive-references">
<h3><a class="toc-backref" href="#id3">Primitive references</a></h3>
<p>References of the <em>_kind</em> <tt class="docutils literal">'primitive'</tt> are used for objects
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>
</div>
<div class="section" id="struct-references">
<h3><a class="toc-backref" href="#id4">Struct references</a></h3>
<p>Struct references are used for class and struct objects.</p>
<p>They implement the following features:</p>
<ul>
<li><p class="first"><tt class="docutils literal">ref.field</tt>, <tt class="docutils literal">ref.field = value</tt></p>
<p>Valid fields of the structure may be accessed by subscript.</p>
<p>Primitive typed fields, i.e. numbers &amp; strings, are converted
to/from matching lua values. The value of a pointer is a reference
to the target, or nil/NULL. Complex types are represented by
a reference to the field within the structure; unless recursive
lua table assignment is used, such fields can only be read.</p>
<p><strong>NOTE:</strong> In case of inheritance, <em>superclass</em> fields have precedence
over the subclass, but fields shadowed in this way can still
be accessed as <tt class="docutils literal"><span class="pre">ref['subclasstype.field']</span></tt>.
This shadowing order is necessary because vtable-based classes
are automatically exposed in their exact type, and the reverse
rule would make access to superclass fields unreliable.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref._field(field)</tt></p>
<p>Returns a reference to a valid field. That is, unlike regular
subscript, it returns a reference to the field within the structure
even for primitive typed fields and pointers.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">ref:vmethod(args...)</span></tt></p>
<p>Named virtual methods are also exposed, subject to the same
shadowing rules.</p>
</li>
<li><p class="first"><tt class="docutils literal">pairs(ref)</tt></p>
<p>Enumerates all real fields (but not methods) in memory
(= declaration) order.</p>
</li>
</ul>
</div>
<div class="section" id="container-references">
<h3><a class="toc-backref" href="#id5">Container references</a></h3>
<p>Containers represent vectors and arrays, possibly resizable.</p>
<p>A container field can associate an enum to the container
reference, which allows accessing elements using string keys
instead of numerical indices.</p>
<p>Implemented features:</p>
<ul>
<li><p class="first"><tt class="docutils literal">ref._enum</tt></p>
<p>If the container has an associated enum, returns the matching
named type object.</p>
</li>
<li><p class="first"><tt class="docutils literal">#ref</tt></p>
<p>Returns the <em>length</em> of the container.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref[index]</tt></p>
<p>Accesses the container element, using either a <em>0-based</em> numerical
index, or, if an enum is associated, a valid enum key string.</p>
<p>Accessing an invalid index is an error, but some container types
may return a default value, or auto-resize instead for convenience.
Currently this relaxed mode is implemented by df-flagarray aka BitArray.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref._field(index)</tt></p>
<p>Like with structs, returns a pointer to the array element, if possible.
Flag and bit arrays cannot return such pointer, so it fails with an error.</p>
</li>
<li><p class="first"><tt class="docutils literal">pairs(ref)</tt>, <tt class="docutils literal">ipairs(ref)</tt></p>
<p>If the container has no associated enum, both behave identically,
iterating over numerical indices in order. Otherwise, ipairs still
uses numbers, while pairs tries to substitute enum keys whenever
possible.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref:resize(new_size)</tt></p>
<p>Resizes the container if supported, or fails with an error.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref:insert(index,item)</tt></p>
<p>Inserts a new item at the specified index. To add at the end,
use <tt class="docutils literal">#ref</tt> as index.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref:erase(index)</tt></p>
<p>Removes the element at the given valid index.</p>
</li>
</ul>
</div>
<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,
and if a subfield occupies multiple bits, the
<tt class="docutils literal">ipairs</tt> order would have a gap.</p>
</div>
</div>
<div class="section" id="named-types">
<h2><a class="toc-backref" href="#id7">Named types</a></h2>
<p>Named types are exposed in the <tt class="docutils literal">df</tt> tree with names identical
to the C++ version, except for the <tt class="docutils literal">::</tt> vs <tt class="docutils literal">.</tt> difference.</p>
<p>All types and the global object have the following features:</p>
<ul>
<li><p class="first"><tt class="docutils literal">type._kind</tt></p>
<p>Evaluates to one of <tt class="docutils literal"><span class="pre">struct-type</span></tt>, <tt class="docutils literal"><span class="pre">class-type</span></tt>, <tt class="docutils literal"><span class="pre">enum-type</span></tt>,
<tt class="docutils literal"><span class="pre">bitfield-type</span></tt> or <tt class="docutils literal">global</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">type._identity</tt></p>
<p>Contains a lightuserdata pointing to the underlying
DFHack::type_instance object.</p>
</li>
</ul>
<p>Types excluding the global object also support:</p>
<ul>
<li><p class="first"><tt class="docutils literal">type:sizeof()</tt></p>
<p>Returns the size of an object of the type.</p>
</li>
<li><p class="first"><tt class="docutils literal">type:new()</tt></p>
<p>Creates a new instance of an object of the type.</p>
</li>
<li><p class="first"><tt class="docutils literal">type:is_instance(object)</tt></p>
<p>Returns true if object is same or subclass type, or a reference
to an object of same or subclass type. It is permissible to pass
nil, NULL or non-wrapper value as object; in this case the
method returns nil.</p>
</li>
</ul>
<p>In addition to this, enum and bitfield types contain a
bi-directional mapping between key strings and values, and
also map <tt class="docutils literal">_first_item</tt> and <tt class="docutils literal">_last_item</tt> to the min and
max values.</p>
<p>Struct and class types with instance-vector attribute in the
xml have a <tt class="docutils literal">type.find(key)</tt> function that wraps the find
method provided in C++.</p>
</div>
<div class="section" id="global-functions">
<h2><a class="toc-backref" href="#id8">Global functions</a></h2>
<p>The <tt class="docutils literal">df</tt> table itself contains the following functions and values:</p>
<ul>
<li><p class="first"><tt class="docutils literal">NULL</tt>, <tt class="docutils literal">df.NULL</tt></p>
<p>Contains the NULL lightuserdata.</p>
</li>
<li><p class="first"><tt class="docutils literal">df.isnull(obj)</tt></p>
<p>Evaluates to true if obj is nil or NULL; false otherwise.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">df.isvalid(obj[,allow_null])</span></tt></p>
<p>For supported objects returns one of <tt class="docutils literal">type</tt>, <tt class="docutils literal">voidptr</tt>, <tt class="docutils literal">ref</tt>.</p>
<p>If <em>allow_null</em> is true, and obj is nil or NULL, returns <tt class="docutils literal">null</tt>.</p>
<p>Otherwise returns <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">df.sizeof(obj)</tt></p>
<p>For types and refs identical to <tt class="docutils literal">obj:sizeof()</tt>.
For lightuserdata returns <em>nil, address</em></p>
</li>
<li><p class="first"><tt class="docutils literal">df.new(obj)</tt>, <tt class="docutils literal">df.delete(obj)</tt>, <tt class="docutils literal">df.assign(obj, obj2)</tt></p>
<p>Equivalent to using the matching methods of obj.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">df._displace(obj,index[,step])</span></tt></p>
<p>For refs equivalent to the method, but also works with
lightuserdata (step is mandatory then).</p>
</li>
<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>
</ul>
</div>
<div class="section" id="recursive-table-assignment">
<h2><a class="toc-backref" href="#id9">Recursive table assignment</a></h2>
<p>Recursive assignment is invoked when a lua table is assigned
to a C++ object or field, i.e. one of:</p>
<ul class="simple">
<li><tt class="docutils literal"><span class="pre">ref:assign{...}</span></tt></li>
<li><tt class="docutils literal">ref.field = <span class="pre">{...}</span></tt></li>
</ul>
<p>The general mode of operation is that all fields of the table
are assigned to the fields of the target structure, roughly
emulating the following code:</p>
<pre class="literal-block">
function rec_assign(ref,table)
for key,value in pairs(table) do
ref[key] = value
end
end
</pre>
<p>Since assigning a table to a field using = invokes the same
process, it is recursive.</p>
<p>There are however some variations to this process depending
on the type of the field being assigned to:</p>
<ol class="arabic">
<li><p class="first">If the table contains an <tt class="docutils literal">assign</tt> field, it is
applied first, using the <tt class="docutils literal">ref:assign(value)</tt> method.
It is never assigned as a usual field.</p>
</li>
<li><p class="first">When a table is assigned to a non-NULL pointer field
using the <tt class="docutils literal">ref.field = <span class="pre">{...}</span></tt> syntax, it is applied
to the target of the pointer instead.</p>
<p>If the pointer is NULL, the table is checked for a <tt class="docutils literal">new</tt> field:</p>
<ol class="loweralpha simple">
<li>If it is <em>nil</em> or <em>false</em>, assignment fails with an error.</li>
<li>If it is <em>true</em>, the pointer is initialized with a newly
allocated object of the declared target type of the pointer.</li>
<li>Otherwise, <tt class="docutils literal">table.new</tt> must be a named type, or an
object of a type compatible with the pointer. The pointer
is initialized with the result of calling <tt class="docutils literal">table.new:new()</tt>.</li>
</ol>
<p>After this auto-vivification process, assignment proceeds
as if the pointer wasn't NULL.</p>
<p>Obviously, the <tt class="docutils literal">new</tt> field inside the table is always skipped
during the actual per-field assignment processing.</p>
</li>
<li><p class="first">If the target of the assignment is a container, a separate
rule set is used:</p>
<ol class="loweralpha">
<li><p class="first">If the table contains neither <tt class="docutils literal">assign</tt> nor <tt class="docutils literal">resize</tt>
fields, it is interpreted as an ordinary <em>1-based</em> lua
array. The container is resized to the #-size of the
table, and elements are assigned in numeric order:</p>
<pre class="literal-block">
ref:resize(#table);
for i=1,#table do ref[i-1] = table[i] end
</pre>
</li>
<li><p class="first">Otherwise, <tt class="docutils literal">resize</tt> must be <em>true</em>, <em>false</em>, or
an explicit number. If it is not false, the container
is resized. After that the usual struct-like 'pairs'
assignment is performed.</p>
<p>In case <tt class="docutils literal">resize</tt> is <em>true</em>, the size is computed
by scanning the table for the largest numeric key.</p>
</li>
</ol>
<p>This means that in order to reassign only one element of
a container using this system, it is necessary to use:</p>
<pre class="literal-block">
{ resize=false, [idx]=value }
</pre>
</li>
</ol>
<p>Since nil inside a table is indistinguishable from missing key,
it is necessary to use <tt class="docutils literal">df.NULL</tt> as a null pointer value.</p>
<p>This system is intended as a way to define a nested object
tree using pure lua data structures, and then materialize it in
C++ memory in one go. Note that if pointer auto-vivification
is used, an error in the middle of the recursive walk would
not destroy any objects allocated in this way, so the user
should be prepared to catch the error and do the necessary
cleanup.</p>
</div>
</div>
<div class="section" id="dfhack-utilities">
<h1><a class="toc-backref" href="#id10">DFHack utilities</a></h1>
<p>DFHack utility functions are placed in the <tt class="docutils literal">dfhack</tt> global tree.</p>
<p>Currently it defines the following features:</p>
<ul>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.print(args...)</span></tt></p>
<p>Output tab-separated args as standard lua print would do,
but without a newline.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">print(args...)</span></tt>, <tt class="docutils literal"><span class="pre">dfhack.println(args...)</span></tt></p>
<p>A replacement of the standard library print function that
works with DFHack output infrastructure.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.printerr(args...)</span></tt></p>
<p>Same as println; intended for errors. Uses red color and logs to stderr.log.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.color([color])</span></tt></p>
<p>Sets the current output color. If color is <em>nil</em> or <em>-1</em>, resets to default.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.is_interactive()</tt></p>
<p>Checks if the thread can access the interactive console and returns <em>true</em> or <em>false</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.lineedit([prompt[,history_filename]])</span></tt></p>
<p>If the thread owns the interactive console, shows a prompt
and returns the entered string. Otherwise returns <em>nil, error</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.interpreter([prompt[,env[,history_filename]]])</span></tt></p>
<p>Starts an interactive lua interpreter, using the specified prompt
string, global environment and command-line history file.</p>
<p>If the interactive console is not accessible, returns <em>nil, error</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.pcall(f[,args...])</span></tt></p>
<p>Invokes f via xpcall, using an error function that attaches
a stack trace to the error. The same function is used by SafeCall
in C++, and dfhack.safecall.</p>
<p>The returned error is a table with separate <tt class="docutils literal">message</tt> and
<tt class="docutils literal">stacktrace</tt> string fields; it implements <tt class="docutils literal">__tostring</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">safecall(f[,args...])</span></tt>, <tt class="docutils literal"><span class="pre">dfhack.safecall(f[,args...])</span></tt></p>
<p>Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function.</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>
<p>Returned values and errors are propagated through after releasing
the lock. It is safe to nest suspends.</p>
<p>Every thread is allowed only one suspend per DF frame, so it is best
to group operations together in one big critical section. A plugin
can choose to run all lua code inside a C++-side suspend lock.</p>
</li>
</ul>
<div class="section" id="persistent-configuration-storage">
<h2><a class="toc-backref" href="#id11">Persistent configuration storage</a></h2>
<p>This api is intended for storing configuration options in the world itself.
It probably should be restricted to data that is world-dependent.</p>
<p>Entries are identified by a string <tt class="docutils literal">key</tt>, but it is also possible to manage
multiple entries with the same key; their identity is determined by <tt class="docutils literal">entry_id</tt>.
Every entry has a mutable string <tt class="docutils literal">value</tt>, and an array of 7 mutable <tt class="docutils literal">ints</tt>.</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.persistent.get(key)</tt>, <tt class="docutils literal">entry:get()</tt></p>
<p>Retrieves a persistent config record with the given string key,
or refreshes an already retrieved entry. If there are multiple
entries with the same key, it is undefined which one is retrieved
by the first version of the call.</p>
<p>Returns entry, or <em>nil</em> if not found.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.persistent.delete(key)</tt>, <tt class="docutils literal">entry:delete()</tt></p>
<p>Removes an existing entry. Returns <em>true</em> if succeeded.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.persistent.get_all(key[,match_prefix])</span></tt></p>
<p>Retrieves all entries with the same key, or starting with key..'/'.
Calling <tt class="docutils literal"><span class="pre">get_all('',true)</span></tt> will match all entries.</p>
<p>If none found, returns nil; otherwise returns an array of entries.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.persistent.save({key=str1,</span> <span class="pre">...}[,new])</span></tt>, <tt class="docutils literal"><span class="pre">entry:save([new])</span></tt></p>
<p>Saves changes in an entry, or creates a new one. Passing true as
new forces creation of a new entry even if one already exists;
otherwise the existing one is simply updated.
Returns <em>entry, did_create_new</em></p>
</li>
</ul>
<p>Since the data is hidden in data structures owned by the DF world,
and automatically stored in the save game, these save and retrieval
functions can just copy values in memory without doing any actual I/O.
However, currently every entry has a 180+-byte dead-weight overhead.</p>
</div>
</div>
</div>
</body>
</html>

@ -467,33 +467,35 @@ access DF memory and allow for easier development of new tools.</p>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#tubefill" id="id125">tubefill</a></li> <li><a class="reference internal" href="#tubefill" id="id125">tubefill</a></li>
<li><a class="reference internal" href="#vdig" id="id126">vdig</a></li> <li><a class="reference internal" href="#digv" id="id126">digv</a></li>
<li><a class="reference internal" href="#vdigx" id="id127">vdigx</a></li> <li><a class="reference internal" href="#digvx" id="id127">digvx</a></li>
<li><a class="reference internal" href="#expdig" id="id128">expdig</a><ul> <li><a class="reference internal" href="#digl" id="id128">digl</a></li>
<li><a class="reference internal" href="#patterns" id="id129">Patterns:</a></li> <li><a class="reference internal" href="#diglx" id="id129">diglx</a></li>
<li><a class="reference internal" href="#filters" id="id130">Filters:</a></li> <li><a class="reference internal" href="#digexp" id="id130">digexp</a><ul>
<li><a class="reference internal" href="#id23" id="id131">Examples:</a></li> <li><a class="reference internal" href="#patterns" id="id131">Patterns:</a></li>
<li><a class="reference internal" href="#filters" id="id132">Filters:</a></li>
<li><a class="reference internal" href="#id23" id="id133">Examples:</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#digcircle" id="id132">digcircle</a><ul> <li><a class="reference internal" href="#digcircle" id="id134">digcircle</a><ul>
<li><a class="reference internal" href="#shape" id="id133">Shape:</a></li> <li><a class="reference internal" href="#shape" id="id135">Shape:</a></li>
<li><a class="reference internal" href="#action" id="id134">Action:</a></li> <li><a class="reference internal" href="#action" id="id136">Action:</a></li>
<li><a class="reference internal" href="#designation-types" id="id135">Designation types:</a></li> <li><a class="reference internal" href="#designation-types" id="id137">Designation types:</a></li>
<li><a class="reference internal" href="#id24" id="id136">Examples:</a></li> <li><a class="reference internal" href="#id24" id="id138">Examples:</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#weather" id="id137">weather</a><ul> <li><a class="reference internal" href="#weather" id="id139">weather</a><ul>
<li><a class="reference internal" href="#id25" id="id138">Options:</a></li> <li><a class="reference internal" href="#id25" id="id140">Options:</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#workflow" id="id139">workflow</a><ul> <li><a class="reference internal" href="#workflow" id="id141">workflow</a><ul>
<li><a class="reference internal" href="#id26" id="id140">Usage</a></li> <li><a class="reference internal" href="#id26" id="id142">Usage</a></li>
<li><a class="reference internal" href="#function" id="id141">Function</a></li> <li><a class="reference internal" href="#function" id="id143">Function</a></li>
<li><a class="reference internal" href="#constraint-examples" id="id142">Constraint examples</a></li> <li><a class="reference internal" href="#constraint-examples" id="id144">Constraint examples</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#mapexport" id="id143">mapexport</a></li> <li><a class="reference internal" href="#mapexport" id="id145">mapexport</a></li>
<li><a class="reference internal" href="#dwarfexport" id="id144">dwarfexport</a></li> <li><a class="reference internal" href="#dwarfexport" id="id146">dwarfexport</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -1330,21 +1332,29 @@ You can also paint only over tiles that match a set of properties (filter)</p>
<h2><a class="toc-backref" href="#id125">tubefill</a></h2> <h2><a class="toc-backref" href="#id125">tubefill</a></h2>
<p>Fills all the adamantine veins again. Veins that were empty will be filled in too, but might still trigger a demon invasion (this is a known bug).</p> <p>Fills all the adamantine veins again. Veins that were empty will be filled in too, but might still trigger a demon invasion (this is a known bug).</p>
</div> </div>
<div class="section" id="vdig"> <div class="section" id="digv">
<h2><a class="toc-backref" href="#id126">vdig</a></h2> <h2><a class="toc-backref" href="#id126">digv</a></h2>
<p>Designates a whole vein for digging. Requires an active in-game cursor placed over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles).</p> <p>Designates a whole vein for digging. Requires an active in-game cursor placed over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles).</p>
</div> </div>
<div class="section" id="vdigx"> <div class="section" id="digvx">
<h2><a class="toc-backref" href="#id127">vdigx</a></h2> <h2><a class="toc-backref" href="#id127">digvx</a></h2>
<p>A permanent alias for 'vdig x'.</p> <p>A permanent alias for 'digv x'.</p>
</div> </div>
<div class="section" id="expdig"> <div class="section" id="digl">
<h2><a class="toc-backref" href="#id128">expdig</a></h2> <h2><a class="toc-backref" href="#id128">digl</a></h2>
<p>Designates layer stone for digging. Requires an active in-game cursor placed over a layer stone tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles). With the 'undo' option it will remove the dig designation instead (if you realize that digging out a 50 z-level deep layer was not such a good idea after all).</p>
</div>
<div class="section" id="diglx">
<h2><a class="toc-backref" href="#id129">diglx</a></h2>
<p>A permanent alias for 'digl x'.</p>
</div>
<div class="section" id="digexp">
<h2><a class="toc-backref" href="#id130">digexp</a></h2>
<p>This command can be used for exploratory mining.</p> <p>This command can be used for exploratory mining.</p>
<p>See: <a class="reference external" href="http://df.magmawiki.com/index.php/DF2010:Exploratory_mining">http://df.magmawiki.com/index.php/DF2010:Exploratory_mining</a></p> <p>See: <a class="reference external" href="http://df.magmawiki.com/index.php/DF2010:Exploratory_mining">http://df.magmawiki.com/index.php/DF2010:Exploratory_mining</a></p>
<p>There are two variables that can be set: pattern and filter.</p> <p>There are two variables that can be set: pattern and filter.</p>
<div class="section" id="patterns"> <div class="section" id="patterns">
<h3><a class="toc-backref" href="#id129">Patterns:</a></h3> <h3><a class="toc-backref" href="#id131">Patterns:</a></h3>
<table class="docutils field-list" frame="void" rules="none"> <table class="docutils field-list" frame="void" rules="none">
<col class="field-name" /> <col class="field-name" />
<col class="field-body" /> <col class="field-body" />
@ -1365,7 +1375,7 @@ You can also paint only over tiles that match a set of properties (filter)</p>
</table> </table>
</div> </div>
<div class="section" id="filters"> <div class="section" id="filters">
<h3><a class="toc-backref" href="#id130">Filters:</a></h3> <h3><a class="toc-backref" href="#id132">Filters:</a></h3>
<table class="docutils field-list" frame="void" rules="none"> <table class="docutils field-list" frame="void" rules="none">
<col class="field-name" /> <col class="field-name" />
<col class="field-body" /> <col class="field-body" />
@ -1381,7 +1391,7 @@ You can also paint only over tiles that match a set of properties (filter)</p>
<p>After you have a pattern set, you can use 'expdig' to apply it again.</p> <p>After you have a pattern set, you can use 'expdig' to apply it again.</p>
</div> </div>
<div class="section" id="id23"> <div class="section" id="id23">
<h3><a class="toc-backref" href="#id131">Examples:</a></h3> <h3><a class="toc-backref" href="#id133">Examples:</a></h3>
<dl class="docutils"> <dl class="docutils">
<dt>designate the diagonal 5 patter over all hidden tiles:</dt> <dt>designate the diagonal 5 patter over all hidden tiles:</dt>
<dd><ul class="first last simple"> <dd><ul class="first last simple">
@ -1402,11 +1412,11 @@ You can also paint only over tiles that match a set of properties (filter)</p>
</div> </div>
</div> </div>
<div class="section" id="digcircle"> <div class="section" id="digcircle">
<h2><a class="toc-backref" href="#id132">digcircle</a></h2> <h2><a class="toc-backref" href="#id134">digcircle</a></h2>
<p>A command for easy designation of filled and hollow circles. <p>A command for easy designation of filled and hollow circles.
It has several types of options.</p> It has several types of options.</p>
<div class="section" id="shape"> <div class="section" id="shape">
<h3><a class="toc-backref" href="#id133">Shape:</a></h3> <h3><a class="toc-backref" href="#id135">Shape:</a></h3>
<table class="docutils field-list" frame="void" rules="none"> <table class="docutils field-list" frame="void" rules="none">
<col class="field-name" /> <col class="field-name" />
<col class="field-body" /> <col class="field-body" />
@ -1421,7 +1431,7 @@ It has several types of options.</p>
</table> </table>
</div> </div>
<div class="section" id="action"> <div class="section" id="action">
<h3><a class="toc-backref" href="#id134">Action:</a></h3> <h3><a class="toc-backref" href="#id136">Action:</a></h3>
<table class="docutils field-list" frame="void" rules="none"> <table class="docutils field-list" frame="void" rules="none">
<col class="field-name" /> <col class="field-name" />
<col class="field-body" /> <col class="field-body" />
@ -1436,7 +1446,7 @@ It has several types of options.</p>
</table> </table>
</div> </div>
<div class="section" id="designation-types"> <div class="section" id="designation-types">
<h3><a class="toc-backref" href="#id135">Designation types:</a></h3> <h3><a class="toc-backref" href="#id137">Designation types:</a></h3>
<table class="docutils field-list" frame="void" rules="none"> <table class="docutils field-list" frame="void" rules="none">
<col class="field-name" /> <col class="field-name" />
<col class="field-body" /> <col class="field-body" />
@ -1459,7 +1469,7 @@ It has several types of options.</p>
repeats with the last selected parameters.</p> repeats with the last selected parameters.</p>
</div> </div>
<div class="section" id="id24"> <div class="section" id="id24">
<h3><a class="toc-backref" href="#id136">Examples:</a></h3> <h3><a class="toc-backref" href="#id138">Examples:</a></h3>
<ul class="simple"> <ul class="simple">
<li>'digcircle filled 3' = Dig a filled circle with radius = 3.</li> <li>'digcircle filled 3' = Dig a filled circle with radius = 3.</li>
<li>'digcircle' = Do it again.</li> <li>'digcircle' = Do it again.</li>
@ -1467,11 +1477,11 @@ repeats with the last selected parameters.</p>
</div> </div>
</div> </div>
<div class="section" id="weather"> <div class="section" id="weather">
<h2><a class="toc-backref" href="#id137">weather</a></h2> <h2><a class="toc-backref" href="#id139">weather</a></h2>
<p>Prints the current weather map by default.</p> <p>Prints the current weather map by default.</p>
<p>Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.</p> <p>Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.</p>
<div class="section" id="id25"> <div class="section" id="id25">
<h3><a class="toc-backref" href="#id138">Options:</a></h3> <h3><a class="toc-backref" href="#id140">Options:</a></h3>
<table class="docutils field-list" frame="void" rules="none"> <table class="docutils field-list" frame="void" rules="none">
<col class="field-name" /> <col class="field-name" />
<col class="field-body" /> <col class="field-body" />
@ -1487,10 +1497,10 @@ repeats with the last selected parameters.</p>
</div> </div>
</div> </div>
<div class="section" id="workflow"> <div class="section" id="workflow">
<h2><a class="toc-backref" href="#id139">workflow</a></h2> <h2><a class="toc-backref" href="#id141">workflow</a></h2>
<p>Manage control of repeat jobs.</p> <p>Manage control of repeat jobs.</p>
<div class="section" id="id26"> <div class="section" id="id26">
<h3><a class="toc-backref" href="#id140">Usage</a></h3> <h3><a class="toc-backref" href="#id142">Usage</a></h3>
<dl class="docutils"> <dl class="docutils">
<dt><tt class="docutils literal">workflow enable <span class="pre">[option...],</span> workflow disable <span class="pre">[option...]</span></tt></dt> <dt><tt class="docutils literal">workflow enable <span class="pre">[option...],</span> workflow disable <span class="pre">[option...]</span></tt></dt>
<dd><p class="first">If no options are specified, enables or disables the plugin. <dd><p class="first">If no options are specified, enables or disables the plugin.
@ -1511,7 +1521,7 @@ Otherwise, enables or disables any of the following options:</p>
</dl> </dl>
</div> </div>
<div class="section" id="function"> <div class="section" id="function">
<h3><a class="toc-backref" href="#id141">Function</a></h3> <h3><a class="toc-backref" href="#id143">Function</a></h3>
<p>When the plugin is enabled, it protects all repeat jobs from removal. <p>When the plugin is enabled, it protects all repeat jobs from removal.
If they do disappear due to any cause, they are immediately re-added to their If they do disappear due to any cause, they are immediately re-added to their
workshop and suspended.</p> workshop and suspended.</p>
@ -1522,7 +1532,7 @@ the amount has to drop before jobs are resumed; this is intended to reduce
the frequency of jobs being toggled.</p> the frequency of jobs being toggled.</p>
</div> </div>
<div class="section" id="constraint-examples"> <div class="section" id="constraint-examples">
<h3><a class="toc-backref" href="#id142">Constraint examples</a></h3> <h3><a class="toc-backref" href="#id144">Constraint examples</a></h3>
<p>Keep metal bolts within 900-1000, and wood/bone within 150-200.</p> <p>Keep metal bolts within 900-1000, and wood/bone within 150-200.</p>
<pre class="literal-block"> <pre class="literal-block">
workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100 workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
@ -1559,11 +1569,11 @@ the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material' command
</div> </div>
</div> </div>
<div class="section" id="mapexport"> <div class="section" id="mapexport">
<h2><a class="toc-backref" href="#id143">mapexport</a></h2> <h2><a class="toc-backref" href="#id145">mapexport</a></h2>
<p>Export the current loaded map as a file. This will be eventually usable with visualizers.</p> <p>Export the current loaded map as a file. This will be eventually usable with visualizers.</p>
</div> </div>
<div class="section" id="dwarfexport"> <div class="section" id="dwarfexport">
<h2><a class="toc-backref" href="#id144">dwarfexport</a></h2> <h2><a class="toc-backref" href="#id146">dwarfexport</a></h2>
<p>Export dwarves to RuneSmith-compatible XML.</p> <p>Export dwarves to RuneSmith-compatible XML.</p>
</div> </div>
</div> </div>

@ -2,3 +2,4 @@
rst2html README.rst > Readme.html rst2html README.rst > Readme.html
rst2html COMPILE.rst > Compile.html rst2html COMPILE.rst > Compile.html
rst2html DEVEL.rst > Devel.html rst2html DEVEL.rst > Devel.html
rst2html LUA_API.rst > Lua\ API.html

@ -868,6 +868,15 @@ int Core::Update()
color_ostream_proxy out(con); color_ostream_proxy out(con);
// Pretend this thread has suspended the core in the usual way
{
lock_guard<mutex> lock(d->AccessMutex);
assert(d->df_suspend_depth == 0);
d->df_suspend_thread = this_thread::get_id();
d->df_suspend_depth = 1000;
}
// detect if the game was loaded or unloaded in the meantime // detect if the game was loaded or unloaded in the meantime
void *new_wdata = NULL; void *new_wdata = NULL;
void *new_mapdata = NULL; void *new_mapdata = NULL;
@ -922,6 +931,14 @@ int Core::Update()
// notify all the plugins that a game tick is finished // notify all the plugins that a game tick is finished
plug_mgr->OnUpdate(out); plug_mgr->OnUpdate(out);
// Release the fake suspend lock
{
lock_guard<mutex> lock(d->AccessMutex);
assert(d->df_suspend_depth == 1000);
d->df_suspend_depth = 0;
}
out << std::flush; out << std::flush;
// wake waiting tools // wake waiting tools

@ -76,6 +76,27 @@ static void set_dfhack_output(lua_State *L, color_ostream *p)
lua_rawsetp(L, LUA_REGISTRYINDEX, &DFHACK_OSTREAM_TOKEN); lua_rawsetp(L, LUA_REGISTRYINDEX, &DFHACK_OSTREAM_TOKEN);
} }
static Console *get_console(lua_State *state)
{
color_ostream *pstream = Lua::GetOutput(state);
if (!pstream)
{
lua_pushnil(state);
lua_pushstring(state, "no output stream");
return NULL;
}
if (!pstream->is_console())
{
lua_pushnil(state);
lua_pushstring(state, "not an interactive console");
return NULL;
}
return static_cast<Console*>(pstream);
}
static std::string lua_print_fmt(lua_State *L) static std::string lua_print_fmt(lua_State *L)
{ {
/* Copied from lua source to fully replicate builtin print */ /* Copied from lua source to fully replicate builtin print */
@ -120,37 +141,233 @@ static int lua_dfhack_println(lua_State *S)
return 0; return 0;
} }
static int lua_dfhack_printerr(lua_State *S) static void dfhack_printerr(lua_State *S, const std::string &str)
{ {
std::string str = lua_print_fmt(S);
if (color_ostream *out = Lua::GetOutput(S)) if (color_ostream *out = Lua::GetOutput(S))
out->printerr("%s\n", str.c_str()); out->printerr("%s\n", str.c_str());
else else
Core::printerr("%s\n", str.c_str()); Core::printerr("%s\n", str.c_str());
}
static int lua_dfhack_printerr(lua_State *S)
{
std::string str = lua_print_fmt(S);
dfhack_printerr(S, str);
return 0; return 0;
} }
static int traceback (lua_State *L) { static int lua_dfhack_color(lua_State *S)
const char *msg = lua_tostring(L, 1); {
if (msg) int cv = luaL_optint(S, 1, -1);
luaL_traceback(L, L, msg, 1);
else if (!lua_isnoneornil(L, 1)) { /* is there an error object? */ if (cv < -1 || cv > color_ostream::COLOR_MAX)
if (!luaL_callmeta(L, 1, "__tostring")) /* try its 'tostring' metamethod */ luaL_argerror(S, 1, "invalid color value");
lua_pushliteral(L, "(no error message)");
} color_ostream *out = Lua::GetOutput(S);
if (out)
out->color(color_ostream::color_value(cv));
return 0;
}
static int lua_dfhack_is_interactive(lua_State *S)
{
lua_pushboolean(S, get_console(S) != NULL);
return 1; return 1;
} }
static void report_error(color_ostream &out, lua_State *L) static int lua_dfhack_lineedit(lua_State *S)
{ {
const char *prompt = luaL_optstring(S, 1, ">> ");
const char *hfile = luaL_optstring(S, 2, NULL);
Console *pstream = get_console(S);
if (!pstream)
return 2;
DFHack::CommandHistory hist;
if (hfile)
hist.load(hfile);
std::string ret;
int rv = pstream->lineedit(prompt, ret, hist);
if (rv < 0)
{
lua_pushnil(S);
lua_pushstring(S, "input error");
return 2;
}
else
{
if (hfile)
hist.save(hfile);
lua_pushlstring(S, ret.data(), ret.size());
return 1;
}
}
static int DFHACK_EXCEPTION_META_TOKEN = 0;
static void error_tostring(lua_State *L)
{
lua_getglobal(L, "tostring");
lua_pushvalue(L, -2);
bool ok = lua_pcall(L, 1, 1, 0) == LUA_OK;
const char *msg = lua_tostring(L, -1);
if (!msg)
{
msg = "tostring didn't return a string";
ok = false;
}
if (!ok)
{
lua_pushfstring(L, "(invalid error: %s)", msg);
lua_remove(L, -2);
}
}
static void report_error(lua_State *L, color_ostream *out = NULL)
{
lua_dup(L);
error_tostring(L);
const char *msg = lua_tostring(L, -1); const char *msg = lua_tostring(L, -1);
if (msg) assert(msg);
out.printerr("%s\n", msg);
if (out)
out->printerr("%s\n", msg);
else else
out.printerr("In Lua::SafeCall: error message is not a string.\n", msg); dfhack_printerr(L, msg);
lua_pop(L, 1); lua_pop(L, 1);
} }
static bool convert_to_exception(lua_State *L)
{
int base = lua_gettop(L);
bool force_unknown = false;
if (lua_istable(L, base) && lua_getmetatable(L, base))
{
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
bool is_exception = lua_rawequal(L, -1, -2);
lua_settop(L, base);
// If it is an exception, return as is
if (is_exception)
return false;
force_unknown = true;
}
if (!lua_istable(L, base) || force_unknown)
{
lua_newtable(L);
lua_swap(L);
if (lua_isstring(L, -1))
lua_setfield(L, base, "message");
else
{
error_tostring(L);
lua_setfield(L, base, "message");
lua_setfield(L, base, "object");
}
}
else
{
lua_getfield(L, base, "message");
if (!lua_isstring(L, -1))
{
error_tostring(L);
lua_setfield(L, base, "message");
}
lua_settop(L, base);
}
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
lua_setmetatable(L, base);
return true;
}
static int dfhack_onerror(lua_State *L)
{
luaL_checkany(L, 1);
lua_settop(L, 1);
bool changed = convert_to_exception(L);
if (!changed)
return 1;
luaL_traceback(L, L, NULL, 1);
lua_setfield(L, 1, "stacktrace");
return 1;
}
static int dfhack_exception_tostring(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
int base = lua_gettop(L);
lua_getfield(L, 1, "message");
if (!lua_isstring(L, -1))
{
lua_pop(L, 1);
lua_pushstring(L, "(error message is not a string)");
}
lua_pushstring(L, "\n");
lua_getfield(L, 1, "stacktrace");
if (!lua_isstring(L, -1))
lua_pop(L, 2);
lua_concat(L, lua_gettop(L) - base);
return 1;
}
static int finish_dfhack_safecall (lua_State *L, bool success)
{
if (!lua_checkstack(L, 2))
{
lua_settop(L, 0); /* create space for return values */
lua_pushboolean(L, 0);
lua_pushstring(L, "stack overflow in dfhack.safecall()");
success = false;
}
else
{
lua_pushboolean(L, success);
lua_replace(L, 1); /* put first result in first slot */
}
if (!success)
report_error(L);
return lua_gettop(L);
}
static int safecall_cont (lua_State *L)
{
int status = lua_getctx(L, NULL);
return finish_dfhack_safecall(L, (status == LUA_YIELD));
}
static int lua_dfhack_safecall (lua_State *L)
{
luaL_checkany(L, 1);
lua_pushcfunction(L, dfhack_onerror);
lua_insert(L, 1);
int status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 1, 0, safecall_cont);
return finish_dfhack_safecall(L, (status == LUA_OK));
}
bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres, bool perr) bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres, bool perr)
{ {
int base = lua_gettop(L) - nargs; int base = lua_gettop(L) - nargs;
@ -158,17 +375,20 @@ bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres
color_ostream *cur_out = Lua::GetOutput(L); color_ostream *cur_out = Lua::GetOutput(L);
set_dfhack_output(L, &out); set_dfhack_output(L, &out);
lua_pushcfunction(L, traceback); lua_pushcfunction(L, dfhack_onerror);
lua_insert(L, base); lua_insert(L, base);
bool ok = lua_pcall(L, nargs, nres, base) == LUA_OK; bool ok = lua_pcall(L, nargs, nres, base) == LUA_OK;
if (!ok && perr)
{
report_error(L, &out);
lua_pop(L, 1);
}
lua_remove(L, base); lua_remove(L, base);
set_dfhack_output(L, cur_out); set_dfhack_output(L, cur_out);
if (!ok && perr)
report_error(out, L);
return ok; return ok;
} }
@ -189,24 +409,51 @@ bool DFHack::Lua::Require(color_ostream &out, lua_State *state,
return true; return true;
} }
static bool load_with_env(color_ostream &out, lua_State *state, const std::string &code, int eidx) bool DFHack::Lua::AssignDFObject(color_ostream &out, lua_State *state,
type_identity *type, void *target, int val_index, bool perr)
{ {
if (luaL_loadbuffer(state, code.data(), code.size(), "=(interactive)") != LUA_OK) 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);
}
bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std::string &code,
int nargs, int nres, bool perr,
const char *debug_tag, int env_idx)
{
if (!debug_tag)
debug_tag = code.c_str();
if (env_idx)
env_idx = lua_absindex(state, env_idx);
int base = lua_gettop(state);
// Parse the code
if (luaL_loadbuffer(state, code.data(), code.size(), debug_tag) != LUA_OK)
{ {
report_error(out, state); if (perr)
{
report_error(state, &out);
lua_pop(state, 1);
}
return false; return false;
} }
// Replace _ENV // Replace _ENV
lua_pushvalue(state, eidx); if (env_idx)
if (!lua_setupvalue(state, -2, 1))
{ {
out.printerr("No _ENV upvalue.\n"); lua_pushvalue(state, env_idx);
return false; lua_setupvalue(state, -2, 1);
assert(lua_gettop(state) == base+1);
} }
return true; if (nargs > 0)
lua_insert(state, -1-nargs);
return Lua::SafeCall(out, state, nargs, nres, perr);
} }
bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state,
@ -240,6 +487,8 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state,
// Make a proxy global environment. // Make a proxy global environment.
lua_newtable(state); lua_newtable(state);
int base = lua_gettop(state);
lua_newtable(state); lua_newtable(state);
if (env) if (env)
lua_pushvalue(state, env); lua_pushvalue(state, env);
@ -249,7 +498,6 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state,
lua_setmetatable(state, -2); lua_setmetatable(state, -2);
// Main interactive loop // Main interactive loop
int base = lua_gettop(state);
int vcnt = 1; int vcnt = 1;
string curline; string curline;
string prompt_str = "[" + string(prompt) + "]# "; string prompt_str = "[" + string(prompt) + "]# ";
@ -272,9 +520,7 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state,
{ {
curline = "return " + curline.substr(1); curline = "return " + curline.substr(1);
if (!load_with_env(out, state, curline, base)) if (!Lua::SafeCallString(out, state, curline, 0, LUA_MULTRET, true, "=(interactive)", base))
continue;
if (!SafeCall(out, state, 0, LUA_MULTRET))
continue; continue;
int numret = lua_gettop(state) - base; int numret = lua_gettop(state) - base;
@ -308,9 +554,7 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state,
} }
else else
{ {
if (!load_with_env(out, state, curline, base)) if (!Lua::SafeCallString(out, state, curline, 0, LUA_MULTRET, true, "=(interactive)", base))
continue;
if (!SafeCall(out, state, 0, 0))
continue; continue;
} }
} }
@ -323,9 +567,9 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state,
static int lua_dfhack_interpreter(lua_State *state) static int lua_dfhack_interpreter(lua_State *state)
{ {
color_ostream *pstream = Lua::GetOutput(state); Console *pstream = get_console(state);
if (!pstream) if (!pstream)
luaL_error(state, "Cannot use dfhack.interpreter() without output."); return 2;
int argc = lua_gettop(state); int argc = lua_gettop(state);
@ -339,8 +583,7 @@ static int lua_dfhack_interpreter(lua_State *state)
static int lua_dfhack_with_suspend(lua_State *L) static int lua_dfhack_with_suspend(lua_State *L)
{ {
int ctx; int rv = lua_getctx(L, NULL);
int rv = lua_getctx(L, &ctx);
// Non-resume entry point: // Non-resume entry point:
if (rv == LUA_OK) if (rv == LUA_OK)
@ -351,7 +594,7 @@ static int lua_dfhack_with_suspend(lua_State *L)
Core::getInstance().Suspend(); Core::getInstance().Suspend();
lua_pushcfunction(L, traceback); lua_pushcfunction(L, dfhack_onerror);
lua_insert(L, 1); lua_insert(L, 1);
rv = lua_pcallk(L, nargs-1, LUA_MULTRET, 1, 0, lua_dfhack_with_suspend); rv = lua_pcallk(L, nargs-1, LUA_MULTRET, 1, 0, lua_dfhack_with_suspend);
@ -372,8 +615,12 @@ static const luaL_Reg dfhack_funcs[] = {
{ "print", lua_dfhack_print }, { "print", lua_dfhack_print },
{ "println", lua_dfhack_println }, { "println", lua_dfhack_println },
{ "printerr", lua_dfhack_printerr }, { "printerr", lua_dfhack_printerr },
{ "traceback", traceback }, { "color", lua_dfhack_color },
{ "is_interactive", lua_dfhack_is_interactive },
{ "lineedit", lua_dfhack_lineedit },
{ "interpreter", lua_dfhack_interpreter }, { "interpreter", lua_dfhack_interpreter },
{ "safecall", lua_dfhack_safecall },
{ "onerror", dfhack_onerror },
{ "with_suspend", lua_dfhack_with_suspend }, { "with_suspend", lua_dfhack_with_suspend },
{ NULL, NULL } { NULL, NULL }
}; };
@ -459,6 +706,8 @@ static PersistentDataItem get_persistent(lua_State *state)
static int dfhack_persistent_get(lua_State *state) static int dfhack_persistent_get(lua_State *state)
{ {
CoreSuspender suspend;
auto ref = get_persistent(state); auto ref = get_persistent(state);
return read_persistent(state, ref, !lua_istable(state, 1)); return read_persistent(state, ref, !lua_istable(state, 1));
@ -466,6 +715,8 @@ static int dfhack_persistent_get(lua_State *state)
static int dfhack_persistent_delete(lua_State *state) static int dfhack_persistent_delete(lua_State *state)
{ {
CoreSuspender suspend;
auto ref = get_persistent(state); auto ref = get_persistent(state);
bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref);
@ -476,6 +727,8 @@ static int dfhack_persistent_delete(lua_State *state)
static int dfhack_persistent_get_all(lua_State *state) static int dfhack_persistent_get_all(lua_State *state)
{ {
CoreSuspender suspend;
const char *str = luaL_checkstring(state, 1); const char *str = luaL_checkstring(state, 1);
bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false);
@ -501,6 +754,8 @@ static int dfhack_persistent_get_all(lua_State *state)
static int dfhack_persistent_save(lua_State *state) static int dfhack_persistent_save(lua_State *state)
{ {
CoreSuspender suspend;
lua_settop(state, 2); lua_settop(state, 2);
luaL_checktype(state, 1, LUA_TTABLE); luaL_checktype(state, 1, LUA_TTABLE);
bool add = lua_toboolean(state, 2); bool add = lua_toboolean(state, 2);
@ -597,8 +852,18 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_pushcfunction(state, lua_dfhack_println); lua_pushcfunction(state, lua_dfhack_println);
lua_setglobal(state, "print"); lua_setglobal(state, "print");
// Create and initialize the dfhack global // Create the dfhack global
lua_newtable(state); lua_newtable(state);
// Create the metatable for exceptions
lua_newtable(state);
lua_pushcfunction(state, dfhack_exception_tostring);
lua_setfield(state, -2, "__tostring");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
lua_setfield(state, -2, "exception");
// Initialize the dfhack global
luaL_setfuncs(state, dfhack_funcs, 0); luaL_setfuncs(state, dfhack_funcs, 0);
OpenPersistent(state); OpenPersistent(state);

@ -36,6 +36,7 @@ distribution.
#include "DataDefs.h" #include "DataDefs.h"
#include "DataIdentity.h" #include "DataIdentity.h"
#include "LuaWrapper.h" #include "LuaWrapper.h"
#include "LuaTools.h"
#include "MiscUtils.h" #include "MiscUtils.h"
@ -402,6 +403,46 @@ static bool is_valid_metatable(lua_State *state, int objidx, int metaidx)
return ok; return ok;
} }
bool Lua::IsDFNull(lua_State *state, int val_index)
{
if (lua_isnil(state, val_index))
return true;
if (lua_islightuserdata(state, val_index))
return lua_touserdata(state, val_index) == NULL;
return false;
}
Lua::ObjectClass Lua::IsDFObject(lua_State *state, int val_index)
{
if (lua_isnil(state, val_index))
return Lua::OBJ_NULL;
if (lua_islightuserdata(state, val_index))
return lua_touserdata(state, val_index) ? Lua::OBJ_VOIDPTR : OBJ_NULL;
Lua::ObjectClass cls;
if (lua_istable(state, val_index))
{
cls = Lua::OBJ_TYPE;
lua_pushvalue(state, val_index);
LookupInTable(state, &DFHACK_TYPEID_TABLE_TOKEN);
}
else if (lua_isuserdata(state, val_index))
{
if (!lua_getmetatable(state, val_index))
return Lua::OBJ_INVALID;
cls = Lua::OBJ_REF;
LookupInTable(state, &DFHACK_TYPETABLE_TOKEN);
}
else
return Lua::OBJ_INVALID;
bool ok = !lua_isnil(state, -1);
lua_pop(state, 1);
return ok ? cls : Lua::OBJ_INVALID;
}
/** /**
* Given a DF object reference or type, safely retrieve its identity pointer. * Given a DF object reference or type, safely retrieve its identity pointer.
*/ */
@ -865,6 +906,52 @@ static int meta_enum_attr_index(lua_State *state)
return 1; return 1;
} }
/**
* Metamethod: df.isvalid(obj[,allow_null])
*/
static int meta_isvalid(lua_State *state)
{
luaL_checkany(state, 1);
switch (Lua::IsDFObject(state, 1))
{
case Lua::OBJ_NULL:
lua_settop(state, 2);
if (lua_toboolean(state, 2))
lua_pushvalue(state, lua_upvalueindex(1));
else
lua_pushnil(state);
return 1;
case Lua::OBJ_TYPE:
lua_pushvalue(state, lua_upvalueindex(2));
return 1;
case Lua::OBJ_VOIDPTR:
lua_pushvalue(state, lua_upvalueindex(3));
return 1;
case Lua::OBJ_REF:
lua_pushvalue(state, lua_upvalueindex(4));
return 1;
case Lua::OBJ_INVALID:
default:
lua_pushnil(state);
return 1;
}
}
/**
* Metamethod: df.isnull(obj)
*/
static int meta_isnull(lua_State *state)
{
luaL_checkany(state, 1);
lua_pushboolean(state, Lua::IsDFNull(state, 1));
return 1;
}
static int meta_nodata(lua_State *state) static int meta_nodata(lua_State *state)
{ {
return 0; return 0;
@ -1221,9 +1308,15 @@ static void RenderType(lua_State *state, compound_identity *node)
break; break;
case IDTYPE_GLOBAL: case IDTYPE_GLOBAL:
lua_pushstring(state, "global");
lua_setfield(state, ftable, "_kind");
{ {
RenderTypeChildren(state, node->getScopeChildren()); RenderTypeChildren(state, node->getScopeChildren());
lua_pushlightuserdata(state, node);
lua_setfield(state, ftable, "_identity");
BuildTypeMetatable(state, node); BuildTypeMetatable(state, node);
lua_dup(state); lua_dup(state);
@ -1244,6 +1337,9 @@ static void RenderType(lua_State *state, compound_identity *node)
RenderTypeChildren(state, node->getScopeChildren()); RenderTypeChildren(state, node->getScopeChildren());
lua_pushlightuserdata(state, node);
lua_setfield(state, ftable, "_identity");
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME);
lua_setfield(state, ftable, "sizeof"); lua_setfield(state, ftable, "sizeof");
@ -1341,6 +1437,16 @@ static int DoAttach(lua_State *state)
lua_pushlightuserdata(state, NULL); lua_pushlightuserdata(state, NULL);
lua_setglobal(state, "NULL"); lua_setglobal(state, "NULL");
lua_pushstring(state, "null");
lua_pushstring(state, "type");
lua_pushstring(state, "voidptr");
lua_pushstring(state, "ref");
lua_pushcclosure(state, meta_isvalid, 4);
lua_setfield(state, -2, "isvalid");
lua_pushcfunction(state, meta_isnull);
lua_setfield(state, -2, "isnull");
freeze_table(state, false, "df"); freeze_table(state, false, "df");
} }

@ -445,6 +445,48 @@ static command_result ListEnums(color_ostream &stream,
#undef BITFIELD #undef BITFIELD
} }
static command_result ListJobSkills(color_ostream &stream, const EmptyMessage *, ListJobSkillsOut *out)
{
auto pf_skill = out->mutable_skill();
FOR_ENUM_ITEMS(job_skill, skill)
{
auto item = pf_skill->Add();
item->set_id(skill);
item->set_key(ENUM_KEY_STR(job_skill, skill));
item->set_caption(ENUM_ATTR_STR(job_skill, caption, skill));
item->set_caption_noun(ENUM_ATTR_STR(job_skill, caption_noun, skill));
item->set_profession(ENUM_ATTR(job_skill, profession, skill));
item->set_labor(ENUM_ATTR(job_skill, labor, skill));
item->set_type(ENUM_KEY_STR(job_skill_class, ENUM_ATTR(job_skill, type, skill)));
}
auto pf_profession = out->mutable_profession();
FOR_ENUM_ITEMS(profession, p)
{
auto item = pf_profession->Add();
item->set_id(p);
item->set_key(ENUM_KEY_STR(profession, p));
item->set_caption(ENUM_ATTR_STR(profession, caption, p));
item->set_military(ENUM_ATTR(profession, military, p));
item->set_can_assign_labor(ENUM_ATTR(profession, can_assign_labor, p));
item->set_parent(ENUM_ATTR(profession, parent, p));
}
auto pf_labor = out->mutable_labor();
FOR_ENUM_ITEMS(unit_labor, labor)
{
auto item = pf_labor->Add();
item->set_id(labor);
item->set_key(ENUM_KEY_STR(unit_labor, labor));
item->set_caption(ENUM_ATTR_STR(unit_labor, caption, labor));
}
return CR_OK;
}
static void listMaterial(ListMaterialsOut *out, int type, int index, const BasicMaterialInfoMask *mask) static void listMaterial(ListMaterialsOut *out, int type, int index, const BasicMaterialInfoMask *mask)
{ {
MaterialInfo info(type, index); MaterialInfo info(type, index);
@ -590,6 +632,7 @@ CoreService::CoreService() {
addFunction("GetWorldInfo", GetWorldInfo); addFunction("GetWorldInfo", GetWorldInfo);
addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND); addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND);
addFunction("ListJobSkills", ListJobSkills, SF_CALLED_ONCE | SF_DONT_SUSPEND);
addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE); addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE);
addFunction("ListUnits", ListUnits); addFunction("ListUnits", ListUnits);

@ -46,6 +46,29 @@ namespace DFHack { namespace Lua {
DFHACK_EXPORT bool Require(color_ostream &out, lua_State *state, DFHACK_EXPORT bool Require(color_ostream &out, lua_State *state,
const std::string &module, bool setglobal = false); const std::string &module, bool setglobal = false);
/**
* Check if the object at the given index is NIL or NULL.
*/
DFHACK_EXPORT bool IsDFNull(lua_State *state, int val_index);
enum ObjectClass {
/** Not a DF wrapper object */
OBJ_INVALID = 0,
/** NIL or NULL */
OBJ_NULL,
/** A named type identity object */
OBJ_TYPE,
/** A void* reference, i.e. non-null lightuserdata */
OBJ_VOIDPTR,
/** A typed object reference */
OBJ_REF
};
/**
* Check if the object at the given index is a valid wrapper object.
*/
DFHACK_EXPORT ObjectClass IsDFObject(lua_State *state, int val_index);
/** /**
* Push the pointer onto the stack as a wrapped DF object of the given type. * Push the pointer onto the stack as a wrapped DF object of the given type.
*/ */
@ -56,6 +79,13 @@ namespace DFHack { namespace Lua {
*/ */
DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false); DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false);
/**
* Assign the value at val_index to the target of given identity using df.assign().
* 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);
/** /**
* Push the pointer onto the stack as a wrapped DF object of a specific type. * Push the pointer onto the stack as a wrapped DF object of a specific type.
*/ */
@ -72,12 +102,28 @@ namespace DFHack { namespace Lua {
return (T*)GetDFObject(state, df::identity_traits<T>::get(), val_index, exact_type); return (T*)GetDFObject(state, df::identity_traits<T>::get(), val_index, exact_type);
} }
/**
* 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);
}
/** /**
* Invoke lua function via pcall. Returns true if success. * Invoke lua function via pcall. Returns true if success.
* If an error is signalled, and perr is true, it is printed and popped from the stack. * If an error is signalled, and perr is true, it is printed and popped from the stack.
*/ */
DFHACK_EXPORT bool SafeCall(color_ostream &out, lua_State *state, int nargs, int nres, bool perr = true); DFHACK_EXPORT bool SafeCall(color_ostream &out, lua_State *state, int nargs, int nres, bool perr = true);
/**
* Parse code from string with debug_tag and env_idx, then call it using SafeCall.
* In case of error, it is either left on the stack, or printed like SafeCall does.
*/
DFHACK_EXPORT bool SafeCallString(color_ostream &out, lua_State *state, const std::string &code,
int nargs, int nres, bool perr = true,
const char *debug_tag = NULL, int env_idx = 0);
/** /**
* Returns the ostream passed to SafeCall. * Returns the ostream passed to SafeCall.
*/ */

@ -1,6 +1,12 @@
-- Common startup file for all dfhack plugins with lua support -- Common startup file for all dfhack plugins with lua support
-- The global dfhack table is already created by C++ init code. -- The global dfhack table is already created by C++ init code.
safecall = dfhack.safecall
function dfhack.pcall(f, ...)
return xpcall(f, dfhack.onerror, ...)
end
function mkmodule(module,env) function mkmodule(module,env)
local pkg = package.loaded[module] local pkg = package.loaded[module]
if pkg == nil then if pkg == nil then

@ -69,6 +69,36 @@ message BasicMaterialInfoMask {
optional bool reaction = 3 [default = false]; optional bool reaction = 3 [default = false];
}; };
message JobSkillAttr {
required int32 id = 1;
required string key = 2;
optional string caption = 3;
optional string caption_noun = 4;
optional int32 profession = 5;
optional int32 labor = 6;
optional string type = 7;
};
message ProfessionAttr {
required int32 id = 1;
required string key = 2;
optional string caption = 3;
optional bool military = 4;
optional bool can_assign_labor = 5;
optional int32 parent = 6;
};
message UnitLaborAttr {
required int32 id = 1;
required string key = 2;
optional string caption = 3;
};
message NameInfo { message NameInfo {
optional string first_name = 1; optional string first_name = 1;
optional string nickname = 2; optional string nickname = 2;

@ -51,6 +51,13 @@ message ListEnumsOut {
repeated EnumItemName profession = 11; repeated EnumItemName profession = 11;
}; };
// RPC ListJobSkills : EmptyMessage -> ListJobSkillsOut
message ListJobSkillsOut {
repeated JobSkillAttr skill = 1;
repeated ProfessionAttr profession = 2;
repeated UnitLaborAttr labor = 3;
};
// RPC ListMaterials : ListMaterialsIn -> ListMaterialsOut // RPC ListMaterials : ListMaterialsIn -> ListMaterialsOut
message ListMaterialsIn { message ListMaterialsIn {
optional BasicMaterialInfoMask mask = 1; optional BasicMaterialInfoMask mask = 1;

@ -8,6 +8,7 @@
#include "luamain.h" #include "luamain.h"
#include "OutFile.h" #include "OutFile.h"
#include "functioncall.h" #include "functioncall.h"
#include "LuaTools.h"
namespace lua namespace lua
{ {

@ -1,5 +1,53 @@
adv_tools=adv_tools or {} adv_tools= {}
adv_tools.menu=adv_tools.menu or MakeMenu() adv_tools.menu=MakeMenu()
--TODO make every tool generic (work for both modes)
function adv_tools.reincarnate(swap_soul) --only for adventurer i guess
if swap_soul==nil then
swap_soul=true
end
local adv=df.global.world.units.other[0][0]
if adv.flags1.dead==false then
error("You are not dead (yet)!")
end
local hist_fig=getNemesis(adv).figure
if hist_fig==nil then
error("No historical figure for adventurer...")
end
local events=df.global.world.history.events
local trg_hist_fig
for i=#events-1,0,-1 do -- reverse search because almost always it will be last entry
if df.history_event_hist_figure_diedst:is_instance(events[i]) then
--print("is instance:"..i)
if events[i].hfid==hist_fig.id then
--print("Is same id:"..i)
trg_hist_fig=events[i].slayer
if trg_hist_fig then
trg_hist_fig=df.historical_figure.find(trg_hist_fig)
end
break
end
end
end
if trg_hist_fig ==nil then
error("Slayer not found")
end
local trg_unit=trg_hist_fig.unit_id
if trg_unit==nil then
error("Unit id not found!")
end
local trg_unit_final=df.unit.find(trg_unit)
tools.change_adv(trg_unit_final)
if swap_soul then --actually add a soul...
t_soul=adv.status.current_soul
adv.status.current_soul=df.NULL
adv.status.souls:resize(0)
trg_unit_final.status.current_soul=t_soul
trg_unit_final.status.souls:insert(#trg_unit_final.status.souls,t_soul)
end
end
adv_tools.menu:add("Reincarnate",adv_tools.reincarnate)
function adv_tools.ressurect() function adv_tools.ressurect()
v2=engine.peek(vector:getval(indx),ptr_Creature.hurt1) v2=engine.peek(vector:getval(indx),ptr_Creature.hurt1)

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

@ -254,11 +254,11 @@ function it_menu:display()
if r=='q' then return end if r=='q' then return end
ans=tonumber(r) ans=tonumber(r)
if ans==nil or not(ans<=table.maxn(self.items) and ans>0) then if ans==nil or not(ans<=#self.items and ans>0) then
print("incorrect choice") print("incorrect choice")
end end
until ans~=nil and (ans<=table.maxn(self.items) and ans>0) until ans~=nil and (ans<=#self.items and ans>0)
self.items[ans][1]() self.items[ans][1]()
end end
function MakeMenu() function MakeMenu()
@ -468,6 +468,9 @@ function ParseNames(path)
return ret return ret
end end
function getSelectedUnit() function getSelectedUnit()
if df.global.ui.main.mode~=23 then
return nil
end
local unit_indx=df.global.ui_selected_unit local unit_indx=df.global.ui_selected_unit
if unit_indx<#df.global.world.units.other[0]-1 then if unit_indx<#df.global.world.units.other[0]-1 then
return df.global.world.units.other[0][unit_indx] return df.global.world.units.other[0][unit_indx]
@ -493,10 +496,34 @@ function getCreatureAtPos(x,y,z) -- gets the creature index @ x,y,z coord
return vector[i] --return index return vector[i] --return index
end end
end end
print("Creature not found!") --print("Creature not found!")
return nil return nil
end end
function getCreatureAtPointer()
return getCreatureAtPos(getxyz())
end
function getCreature()
local unit=getSelectedUnit()
if unit==nil then
unit=getCreatureAtPointer()
end
--any other selection methods...
return unit
end
function getNemesisId(unit)
for k,v in pairs(unit.refs) do
if df.general_ref_is_nemesisst:is_instance(v) then
return v.nemesis_id
end
end
end
function getNemesis(unit)
local id=getNemesisId(unit)
if id then
return df.nemesis_record.find(id)
end
end
function Allocate(size) function Allocate(size)
local ptr=engine.getmod('General_Space') local ptr=engine.getmod('General_Space')
if ptr==nil then if ptr==nil then
@ -509,14 +536,6 @@ function Allocate(size)
engine.poked(ptr,curptr) engine.poked(ptr,curptr)
return curptr-size+ptr return curptr-size+ptr
end end
function initType(object,...)
local m=getmetatable(object)
if m~=nil and m.__setup~=nil then
m.__setup(object,...)
else
error("This object does not have __setup function")
end
end
dofile("dfusion/patterns.lua") dofile("dfusion/patterns.lua")
dofile("dfusion/patterns2.lua") dofile("dfusion/patterns2.lua")
dofile("dfusion/itempatterns.lua") dofile("dfusion/itempatterns.lua")

@ -73,7 +73,7 @@ table.insert(plugins,{"friendship","Multi race fort enabler"})
table.insert(plugins,{"offsets","Find all offsets"}) table.insert(plugins,{"offsets","Find all offsets"})
table.insert(plugins,{"friendship_civ","Multi civ fort enabler"}) table.insert(plugins,{"friendship_civ","Multi civ fort enabler"})
table.insert(plugins,{"adv_tools","some tools for (mainly) advneturer hacking"})
table.insert(plugins,{"triggers","a function calling plug (discontinued...)"}) table.insert(plugins,{"triggers","a function calling plug (discontinued...)"})
table.insert(plugins,{"migrants","multi race imigrations"}) table.insert(plugins,{"migrants","multi race imigrations"})
@ -81,10 +81,19 @@ table.insert(plugins,{"migrants","multi race imigrations"})
--table.insert(plugins,{"onfunction","run lua on some df function"}) --table.insert(plugins,{"onfunction","run lua on some df function"})
--table.insert(plugins,{"editor","edit internals of df",EditDF}) --table.insert(plugins,{"editor","edit internals of df",EditDF})
table.insert(plugins,{"saves","run current worlds's init.lua",RunSaved}) table.insert(plugins,{"saves","run current worlds's init.lua",RunSaved})
table.insert(plugins,{"adv_tools","some tools for (mainly) advneturer hacking"})
loadall(plugins) loadall(plugins)
dofile_silent("dfusion/initcustom.lua") dofile_silent("dfusion/initcustom.lua")
local args={...}
for k,v in pairs(args) do
local f,err=load(v)
if f then
f()
else
Console.printerr(err)
end
end
if not INIT then if not INIT then
mainmenu(plugins) mainmenu(plugins)
end end

@ -1,7 +1,7 @@
offsets=offsets or {} offsets=offsets or {}
function offsets.find(startoffset,...) function offsets.find(startoffset,...)
local endadr=GetTextRegion()["end"]; -- [=[
--[=[if startoffset== 0 then if startoffset== 0 then
local text=GetTextRegion() local text=GetTextRegion()
--print("searching in:"..text.name) --print("searching in:"..text.name)
startoffset=text.start startoffset=text.start
@ -14,7 +14,8 @@ function offsets.find(startoffset,...)
return 0 return 0
end end
endadr=reg["end"] endadr=reg["end"]
end--]=] end
--]=]
--print(string.format("Searching (%x->%x)",startoffset,endadr)) --print(string.format("Searching (%x->%x)",startoffset,endadr))
local h=hexsearch(startoffset,endadr,...) local h=hexsearch(startoffset,endadr,...)
local pos=h:find() local pos=h:find()

@ -8,7 +8,8 @@ if WINDOWS then --windows function defintions
onfunction.AddFunction(0x5af826+offsets.base(),"Hurt",{target="esi",attacker={off=0x74,rtype=DWORD,reg="esp"}}) onfunction.AddFunction(0x5af826+offsets.base(),"Hurt",{target="esi",attacker={off=0x74,rtype=DWORD,reg="esp"}})
onfunction.AddFunction(0x3D5886+offsets.base(),"Flip",{building="esi"}) onfunction.AddFunction(0x3D5886+offsets.base(),"Flip",{building="esi"})
onfunction.AddFunction(0x35E340+offsets.base(),"ItemCreate")--]=] onfunction.AddFunction(0x35E340+offsets.base(),"ItemCreate")--]=]
onfunction.AddFunction(4B34B6+offsets.base(),"ReactionFinish") --esp item. Ecx creature, edx? --onfunction.AddFunction(0x4B34B6+offsets.base(),"ReactionFinish") --esp item. Ecx creature, edx? 0.34.07
onfunction.AddFunction(0x72aB6+offsets.base(),"Die",{creature="edi"}) --0.34.07
else --linux else --linux
--[=[onfunction.AddFunction(0x899befe+offsets.base(),"Move") -- found out by attaching watch... --[=[onfunction.AddFunction(0x899befe+offsets.base(),"Move") -- found out by attaching watch...
onfunction.AddFunction(0x850eecd+offsets.base(),"Die",{creature="ebx"}) -- same--]=] onfunction.AddFunction(0x850eecd+offsets.base(),"Die",{creature="ebx"}) -- same--]=]

@ -1,8 +1,9 @@
mypos=engine.getmod("functions") mypos=engine.getmod("functions")
function DeathMsg(values) function DeathMsg(values)
local name local name
name=engine.peek(values[onfunction.hints["Die"].creature],ptt_dfstring) local u=engine.cast(df.unit,values[onfunction.hints["Die"].creature])
print(name:getval().." died")
print(u.name.first_name.." died")
end end
if mypos then if mypos then
print("Onfunction already installed") print("Onfunction already installed")

@ -1,20 +1,28 @@
tools={} tools={}
tools.menu=MakeMenu() tools.menu=MakeMenu()
function tools.setrace() function tools.setrace(name)
RaceTable=BuildNameTable() RaceTable=BuildNameTable()
print("Your current race is:"..GetRaceToken(df.global.ui.race_id)) print("Your current race is:"..GetRaceToken(df.global.ui.race_id))
print("Type new race's token name in full caps (q to quit):") local id
repeat if name == nil then
entry=getline() print("Type new race's token name in full caps (q to quit):")
if entry=="q" then repeat
return entry=getline()
if entry=="q" then
return
end
id=RaceTable[entry]
until id~=nil
else
id=RaceTable[name]
if id==nil then
error("Name not found!")
end end
id=RaceTable[entry] end
until id~=nil
df.global.ui.race_id=id df.global.ui.race_id=id
end end
tools.menu:add("Set current race",tools.setrace) tools.menu:add("Set current race",tools.setrace)
function tools.GiveSentience(names) --TODO make pattern... function tools.GiveSentience(names)
RaceTable=RaceTable or BuildNameTable() --slow.If loaded don't load again RaceTable=RaceTable or BuildNameTable() --slow.If loaded don't load again
if names ==nil then if names ==nil then
ids={} ids={}
@ -63,23 +71,7 @@ function tools.embark()
end end
end end
tools.menu:add("Embark anywhere",tools.embark) tools.menu:add("Embark anywhere",tools.embark)
function tools.getlegendsid(croff) function tools.getCreatureId(vector) --redo it to getcreature by name/id or something
local vec=engine.peek(croff,ptr_Creature.legends)
if vec:size()==0 then
return 0
end
for i =0,vector:size()-1 do
--if engine.peekd(vec:getval(i))~=0 then
-- print(string.format("%x",engine.peekd(vec:getval(i))-offsets.base()))
--end
if(engine.peekd(vec:getval(i))==offsets.getEx("vtableLegends")) then --easy to get.. just copy from player's-base
return engine.peekd(vec:getval(i)+4)
end
end
return 0
end
function tools.getCreatureId(vector)
tnames={} tnames={}
rnames={} rnames={}
--[[print("vector1 size:"..vector:size()) --[[print("vector1 size:"..vector:size())
@ -111,48 +103,70 @@ function tools.getCreatureId(vector)
end end
return indx return indx
end end
function tools.change_adv() function tools.change_adv(unit,nemesis)
myoff=offsets.getEx("AdvCreatureVec") if nemesis==nil then
vector=engine.peek(myoff,ptr_vector) nemesis=true --default value is nemesis switch too.
indx=tools.getCreatureId(vector)
print("Swaping, press enter when done or 'q' to stay, 's' to stay with legends id change")
tval=vector:getval(0)
vector:setval(0,vector:getval(indx))
vector:setval(indx,tval)
r=getline()
if r=='q' then
return
end end
if r~='s' then if unit==nil then
tval=vector:getval(0) unit=getCreatureAtPointer()
vector:setval(0,vector:getval(indx))
vector:setval(indx,tval)
end end
local lid=tools.getlegendsid(vector:getval(0)) if unit==nil then
if lid~=0 then error("Invalid unit!")
engine.poked(offsets.getEx("PlayerLegend"),lid) end
else local other=df.global.world.units.other[0]
print("Warning target does not have a valid legends id!") local unit_indx
for k,v in pairs(other) do
if v==unit then
unit_indx=k
break
end
end
if unit_indx==nil then
error("Unit not found in array?!") --should not happen
end
other[unit_indx]=other[0]
other[0]=unit
if nemesis then --basicly copied from advtools plugin...
local nem=getNemesis(unit)
local other_nem=getNemesis(other[unit_indx])
if other_nem then
other_nem.flags[0]=false
other_nem.flags[1]=true
end
if nem then
nem.flags[0]=true
nem.flags[2]=true
for k,v in pairs(df.global.world.nemesis.all) do
if v.id==nem.id then
df.global.ui_advmode.player_id=k
end
end
else
error("Current unit does not have nemesis record, further working not guaranteed")
end
end end
end end
tools.menu:add("Change Adventurer",tools.change_adv) tools.menu:add("Change Adventurer",tools.change_adv)
function tools.MakeFollow() function tools.MakeFollow(unit,trgunit)
myoff=offsets.getEx("AdvCreatureVec")
vector=engine.peek(myoff,ptr_vector)
indx=tools.getCreatureId(vector)
print(string.format("current creature:%x",vector:getval(indx)))
trgid=engine.peek(vector:getval(0)+ptr_Creature.ID.off,DWORD) if unit == nil then
lfollow=engine.peek(vector:getval(indx)+ptr_Creature.followID.off,DWORD) unit=getCreature()
if lfollow ~=0xFFFFFFFF then end
print("Already following, unfollow? y/N") if unit== nil then
r=getline() error("Invalid creature")
if r== "y" then end
engine.poke(vector:getval(indx)+ptr_Creature.followID.off,DWORD,0) if trgunit==nil then
end trgunit=df.global.world.units.other[0][0]
else end
engine.poke(vector:getval(indx)+ptr_Creature.followID.off,DWORD,trgid) unit.relations.group_leader_id=trgunit.id
local u_nem=getNemesis(unit)
local t_nem=getNemesis(trgunit)
if u_nem then
u_nem.group_leader_id=t_nem.id
end
if t_nem and u_nem then
t_nem.companions:insert(#t_nem.companions,u_nem.id)
end end
end end
tools.menu:add("Make creature follow",tools.MakeFollow) tools.menu:add("Make creature follow",tools.MakeFollow)

@ -151,8 +151,6 @@ static size_t __stdcall PushValue(size_t ret,uint32_t eax,uint32_t ebx,uint32_t
#endif #endif
{ {
lua::state st=lua::glua::Get(); lua::state st=lua::glua::Get();
st.getglobal("err");
int perr=st.gettop();
st.getglobal("OnFunction"); st.getglobal("OnFunction");
if(st.is<lua::nil>()) if(st.is<lua::nil>())
return 0; return 0;
@ -175,7 +173,7 @@ static size_t __stdcall PushValue(size_t ret,uint32_t eax,uint32_t ebx,uint32_t
st.setfield("ebp"); st.setfield("ebp");
st.push(ret); st.push(ret);
st.setfield("ret"); st.setfield("ret");
st.pcall(1,1,perr); DFHack::Lua::SafeCall(DFHack::Core::getInstance().getConsole(),st,1,1);
return st.as<uint32_t>(); return st.as<uint32_t>();
} }
static int Get_PushValue(lua_State *L) static int Get_PushValue(lua_State *L)
@ -210,6 +208,17 @@ static int Resume_Df(lua_State *L)
DFHack::Core::getInstance().Resume(); DFHack::Core::getInstance().Resume();
return 0; return 0;
} }
static int Cast(lua_State *L)
{
lua::state st(L);
if(DFHack::Lua::IsDFObject(st,1)!=DFHack::Lua::OBJ_TYPE)
st.error("First argument must be df type!");
if(!st.is<lua::number>(2)) //todo maybe lightuserdata?
st.error("Second argument must be pointer as a number!");
st.getfield("_identity",1);
DFHack::Lua::PushDFObject(st,(DFHack::type_identity*)lua_touserdata(st,-1),(void*)st.as<int>(2));
return 1;
}
const luaL_Reg lua_misc_func[]= const luaL_Reg lua_misc_func[]=
{ {
{"alloc",lua_malloc}, {"alloc",lua_malloc},
@ -224,6 +233,7 @@ const luaL_Reg lua_misc_func[]=
{"calldf",Call_Df}, {"calldf",Call_Df},
{"suspend",Suspend_Df}, {"suspend",Suspend_Df},
{"resume",Resume_Df}, {"resume",Resume_Df},
{"cast",Cast},
{NULL,NULL} {NULL,NULL}
}; };
void lua::RegisterMisc(lua::state &st) void lua::RegisterMisc(lua::state &st)

@ -22,6 +22,8 @@
#include <df/workshop_type.h> #include <df/workshop_type.h>
#include <df/unit_misc_trait.h> #include <df/unit_misc_trait.h>
using std::string;
using std::endl;
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
using df::global::ui; using df::global::ui;
@ -29,8 +31,36 @@ using df::global::world;
#define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) #define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0]))
/*
* Autolabor module for dfhack
*
* The idea behind this module is to constantly adjust labors so that the right dwarves
* are assigned to new tasks. The key is that, for almost all labors, once a dwarf begins
* a job it will finish that job even if the associated labor is removed. Thus the
* strategy is to frequently decide, for each labor, which dwarves should possibly take
* a new job for that labor if it comes in and which shouldn't, and then set the labors
* appropriately. The updating should happen as often as can be reasonably done without
* causing lag.
*
* The obvious thing to do is to just set each labor on a single idle dwarf who is best
* suited to doing new jobs of that labor. This works in a way, but it leads to a lot
* of idle dwarves since only one dwarf will be dispatched for each labor in an update
* cycle, and dwarves that finish tasks will wait for the next update before being
* dispatched. An improvement is to also set some labors on dwarves that are currently
* doing a job, so that they will immediately take a new job when they finish. The
* details of which dwarves should have labors set is mostly a heuristic.
*
* A complication to the above simple scheme is labors that have associated equipment.
* Enabling/disabling these labors causes dwarves to change equipment, and disabling
* them in the middle of a job may cause the job to be abandoned. Those labors
* (mining, hunting, and woodcutting) need to be handled carefully to minimize churn.
*/
static int enable_autolabor = 0; static int enable_autolabor = 0;
static bool print_debug = 0;
static std::vector<int> state_count(5);
// Here go all the command declarations... // Here go all the command declarations...
// mostly to allow having the mandatory stuff on top of the file and commands on the bottom // mostly to allow having the mandatory stuff on top of the file and commands on the bottom
@ -41,10 +71,9 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
DFHACK_PLUGIN("autolabor"); DFHACK_PLUGIN("autolabor");
enum labor_mode { enum labor_mode {
FIXED, DISABLE,
AUTOMATIC,
EVERYONE,
HAULERS, HAULERS,
AUTOMATIC,
}; };
enum dwarf_state { enum dwarf_state {
@ -64,6 +93,16 @@ enum dwarf_state {
OTHER OTHER
}; };
const int NUM_STATE = 5;
static const char *state_names[] = {
"IDLE",
"BUSY",
"MILITARY",
"CHILD",
"OTHER",
};
static const dwarf_state dwarf_states[] = { static const dwarf_state dwarf_states[] = {
BUSY /* CarveFortification */, BUSY /* CarveFortification */,
BUSY /* DetailWall */, BUSY /* DetailWall */,
@ -298,84 +337,86 @@ struct labor_info
labor_mode mode; labor_mode mode;
bool is_exclusive; bool is_exclusive;
int minimum_dwarfs; int minimum_dwarfs;
int maximum_dwarfs;
int active_dwarfs;
}; };
static struct labor_info* labor_infos; static struct labor_info* labor_infos;
static const struct labor_info default_labor_infos[] = { static const struct labor_info default_labor_infos[] = {
/* MINE */ {AUTOMATIC, true, 2}, /* MINE */ {AUTOMATIC, true, 2, 200, 0},
/* HAUL_STONE */ {HAULERS, false, 1}, /* HAUL_STONE */ {HAULERS, false, 1, 200, 0},
/* HAUL_WOOD */ {HAULERS, false, 1}, /* HAUL_WOOD */ {HAULERS, false, 1, 200, 0},
/* HAUL_BODY */ {HAULERS, false, 1}, /* HAUL_BODY */ {HAULERS, false, 1, 200, 0},
/* HAUL_FOOD */ {HAULERS, false, 1}, /* HAUL_FOOD */ {HAULERS, false, 1, 200, 0},
/* HAUL_REFUSE */ {HAULERS, false, 1}, /* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0},
/* HAUL_ITEM */ {HAULERS, false, 1}, /* HAUL_ITEM */ {HAULERS, false, 1, 200, 0},
/* HAUL_FURNITURE */ {HAULERS, false, 1}, /* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0},
/* HAUL_ANIMAL */ {HAULERS, false, 1}, /* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0},
/* CLEAN */ {HAULERS, false, 1}, /* CLEAN */ {HAULERS, false, 1, 200, 0},
/* CUTWOOD */ {AUTOMATIC, true, 1}, /* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0},
/* CARPENTER */ {AUTOMATIC, false, 1}, /* CARPENTER */ {AUTOMATIC, false, 1, 200, 0},
/* DETAIL */ {AUTOMATIC, false, 1}, /* DETAIL */ {AUTOMATIC, false, 1, 200, 0},
/* MASON */ {AUTOMATIC, false, 1}, /* MASON */ {AUTOMATIC, false, 1, 200, 0},
/* ARCHITECT */ {AUTOMATIC, false, 1}, /* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0},
/* ANIMALTRAIN */ {AUTOMATIC, false, 1}, /* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0},
/* ANIMALCARE */ {AUTOMATIC, false, 1}, /* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0},
/* DIAGNOSE */ {AUTOMATIC, false, 1}, /* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0},
/* SURGERY */ {AUTOMATIC, false, 1}, /* SURGERY */ {AUTOMATIC, false, 1, 200, 0},
/* BONE_SETTING */ {AUTOMATIC, false, 1}, /* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0},
/* SUTURING */ {AUTOMATIC, false, 1}, /* SUTURING */ {AUTOMATIC, false, 1, 200, 0},
/* DRESSING_WOUNDS */ {AUTOMATIC, false, 1}, /* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0},
/* FEED_WATER_CIVILIANS */ {EVERYONE, false, 1}, /* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0},
/* RECOVER_WOUNDED */ {HAULERS, false, 1}, /* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0},
/* BUTCHER */ {AUTOMATIC, false, 1}, /* BUTCHER */ {AUTOMATIC, false, 1, 200, 0},
/* TRAPPER */ {AUTOMATIC, false, 1}, /* TRAPPER */ {AUTOMATIC, false, 1, 200, 0},
/* DISSECT_VERMIN */ {AUTOMATIC, false, 1}, /* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0},
/* LEATHER */ {AUTOMATIC, false, 1}, /* LEATHER */ {AUTOMATIC, false, 1, 200, 0},
/* TANNER */ {AUTOMATIC, false, 1}, /* TANNER */ {AUTOMATIC, false, 1, 200, 0},
/* BREWER */ {AUTOMATIC, false, 1}, /* BREWER */ {AUTOMATIC, false, 1, 200, 0},
/* ALCHEMIST */ {AUTOMATIC, false, 1}, /* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0},
/* SOAP_MAKER */ {AUTOMATIC, false, 1}, /* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0},
/* WEAVER */ {AUTOMATIC, false, 1}, /* WEAVER */ {AUTOMATIC, false, 1, 200, 0},
/* CLOTHESMAKER */ {AUTOMATIC, false, 1}, /* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0},
/* MILLER */ {AUTOMATIC, false, 1}, /* MILLER */ {AUTOMATIC, false, 1, 200, 0},
/* PROCESS_PLANT */ {AUTOMATIC, false, 1}, /* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0},
/* MAKE_CHEESE */ {AUTOMATIC, false, 1}, /* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0},
/* MILK */ {AUTOMATIC, false, 1}, /* MILK */ {AUTOMATIC, false, 1, 200, 0},
/* COOK */ {AUTOMATIC, false, 1}, /* COOK */ {AUTOMATIC, false, 1, 200, 0},
/* PLANT */ {AUTOMATIC, false, 1}, /* PLANT */ {AUTOMATIC, false, 1, 200, 0},
/* HERBALIST */ {AUTOMATIC, false, 1}, /* HERBALIST */ {AUTOMATIC, false, 1, 200, 0},
/* FISH */ {FIXED, false, 1}, /* FISH */ {AUTOMATIC, false, 1, 1, 0},
/* CLEAN_FISH */ {AUTOMATIC, false, 1}, /* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0},
/* DISSECT_FISH */ {AUTOMATIC, false, 1}, /* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0},
/* HUNT */ {FIXED, true, 1}, /* HUNT */ {AUTOMATIC, true, 1, 1, 0},
/* SMELT */ {AUTOMATIC, false, 1}, /* SMELT */ {AUTOMATIC, false, 1, 200, 0},
/* FORGE_WEAPON */ {AUTOMATIC, false, 1}, /* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0},
/* FORGE_ARMOR */ {AUTOMATIC, false, 1}, /* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0},
/* FORGE_FURNITURE */ {AUTOMATIC, false, 1}, /* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0},
/* METAL_CRAFT */ {AUTOMATIC, false, 1}, /* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* CUT_GEM */ {AUTOMATIC, false, 1}, /* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0},
/* ENCRUST_GEM */ {AUTOMATIC, false, 1}, /* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0},
/* WOOD_CRAFT */ {AUTOMATIC, false, 1}, /* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* STONE_CRAFT */ {AUTOMATIC, false, 1}, /* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* BONE_CARVE */ {AUTOMATIC, false, 1}, /* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0},
/* GLASSMAKER */ {AUTOMATIC, false, 1}, /* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0},
/* EXTRACT_STRAND */ {AUTOMATIC, false, 1}, /* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0},
/* SIEGECRAFT */ {AUTOMATIC, false, 1}, /* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* SIEGEOPERATE */ {AUTOMATIC, false, 1}, /* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0},
/* BOWYER */ {AUTOMATIC, false, 1}, /* BOWYER */ {AUTOMATIC, false, 1, 200, 0},
/* MECHANIC */ {AUTOMATIC, false, 1}, /* MECHANIC */ {AUTOMATIC, false, 1, 200, 0},
/* POTASH_MAKING */ {AUTOMATIC, false, 1}, /* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0},
/* LYE_MAKING */ {AUTOMATIC, false, 1}, /* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0},
/* DYER */ {AUTOMATIC, false, 1}, /* DYER */ {AUTOMATIC, false, 1, 200, 0},
/* BURN_WOOD */ {AUTOMATIC, false, 1}, /* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0},
/* OPERATE_PUMP */ {AUTOMATIC, false, 1}, /* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0},
/* SHEARER */ {AUTOMATIC, false, 1}, /* SHEARER */ {AUTOMATIC, false, 1, 200, 0},
/* SPINNER */ {AUTOMATIC, false, 1}, /* SPINNER */ {AUTOMATIC, false, 1, 200, 0},
/* POTTERY */ {AUTOMATIC, false, 1}, /* POTTERY */ {AUTOMATIC, false, 1, 200, 0},
/* GLAZING */ {AUTOMATIC, false, 1}, /* GLAZING */ {AUTOMATIC, false, 1, 200, 0},
/* PRESSING */ {AUTOMATIC, false, 1}, /* PRESSING */ {AUTOMATIC, false, 1, 200, 0},
/* BEEKEEPING */ {AUTOMATIC, false, 1}, /* BEEKEEPING */ {AUTOMATIC, false, 1, 200, 0},
/* WAX_WORKING */ {AUTOMATIC, false, 1}, /* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0},
}; };
static const df::job_skill noble_skills[] = { static const df::job_skill noble_skills[] = {
@ -412,14 +453,29 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
" autolabor enable\n" " autolabor enable\n"
" autolabor disable\n" " autolabor disable\n"
" Enables or disables the plugin.\n" " Enables or disables the plugin.\n"
" autolabor miners <n>\n" " autolabor <labor> <minimum> [<maximum>]\n"
" Set number of desired miners (defaults to 2)\n" " Set number of dwarves assigned to a labor.\n"
" autolabor <labor> haulers\n"
" Set a labor to be handled by hauler dwarves.\n"
" autolabor <labor> disable\n"
" Turn off autolabor for a specific labor.\n"
" autolabor list\n"
" List current status of all labors.\n"
"Function:\n" "Function:\n"
" When enabled, autolabor periodically checks your dwarves and enables or\n" " When enabled, autolabor periodically checks your dwarves and enables or\n"
" disables labors. It tries to keep as many dwarves as possible busy but\n" " disables labors. It tries to keep as many dwarves as possible busy but\n"
" also tries to have dwarves specialize in specific skills.\n" " also tries to have dwarves specialize in specific skills.\n"
" Warning: autolabor will override any manual changes you make to labors\n" " Warning: autolabor will override any manual changes you make to labors\n"
" while it is enabled.\n" " while it is enabled.\n"
"Examples:\n"
" autolabor MINE 2\n"
" Keep at least 2 dwarves with mining enabled.\n"
" autolabor CUT_GEM 1 1\n"
" Keep exactly 1 dwarf with gemcutting enabled.\n"
" autolabor FEED_WATER_CIVILIANS haulers\n"
" Have haulers feed and water wounded dwarves.\n"
" autolabor CUTWOOD disable\n"
" Turn off autolabor for wood cutting.\n"
)); ));
return CR_OK; return CR_OK;
} }
@ -607,7 +663,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// Find the activity state for each dwarf. It's important to get this right - a dwarf who we think is IDLE but // Find the activity state for each dwarf. It's important to get this right - a dwarf who we think is IDLE but
// can't work will gum everything up. In the future I might add code to auto-detect slacker dwarves. // can't work will gum everything up. In the future I might add code to auto-detect slacker dwarves.
std::vector<int> state_count(5); state_count.clear();
state_count.resize(NUM_STATE);
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{ {
@ -627,11 +684,11 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
dwarf_info[dwarf].state = CHILD; dwarf_info[dwarf].state = CHILD;
} }
else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession))
dwarf_info[dwarf].state = MILITARY;
else if (dwarfs[dwarf]->job.current_job == NULL) else if (dwarfs[dwarf]->job.current_job == NULL)
{ {
if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession)) if (is_on_break)
dwarf_info[dwarf].state = MILITARY;
else if (is_on_break)
dwarf_info[dwarf].state = OTHER; dwarf_info[dwarf].state = OTHER;
else if (dwarfs[dwarf]->meetings.size() > 0) else if (dwarfs[dwarf]->meetings.size() > 0)
dwarf_info[dwarf].state = OTHER; dwarf_info[dwarf].state = OTHER;
@ -651,6 +708,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
} }
state_count[dwarf_info[dwarf].state]++; state_count[dwarf_info[dwarf].state]++;
if (print_debug)
out.print("Dwarf %i \"%s\": penalty %i, state %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), dwarf_info[dwarf].mastery_penalty, state_names[dwarf_info[dwarf].state]);
} }
// Generate labor -> skill mapping // Generate labor -> skill mapping
@ -685,11 +745,33 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
assert(labor < ARRAY_COUNT(labor_infos)); assert(labor < ARRAY_COUNT(labor_infos));
*/ */
labor_infos[labor].active_dwarfs = 0;
labors.push_back(labor); labors.push_back(labor);
} }
laborinfo_sorter lasorter; laborinfo_sorter lasorter;
std::sort(labors.begin(), labors.end(), lasorter); std::sort(labors.begin(), labors.end(), lasorter);
// Handle DISABLED skills (just bookkeeping)
for (auto lp = labors.begin(); lp != labors.end(); ++lp)
{
auto labor = *lp;
if (labor_infos[labor].mode != DISABLE)
continue;
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{
if (dwarfs[dwarf]->status.labors[labor])
{
if (labor_infos[labor].is_exclusive)
dwarf_info[dwarf].has_exclusive_labor = true;
dwarf_info[dwarf].assigned_jobs++;
}
}
}
// Handle all skills except those marked HAULERS // Handle all skills except those marked HAULERS
for (auto lp = labors.begin(); lp != labors.end(); ++lp) for (auto lp = labors.begin(); lp != labors.end(); ++lp)
@ -703,7 +785,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
df::job_skill skill = labor_to_skill[labor]; df::job_skill skill = labor_to_skill[labor];
if (labor_infos[labor].mode == HAULERS) if (labor_infos[labor].mode != AUTOMATIC)
continue; continue;
int best_dwarf = 0; int best_dwarf = 0;
@ -711,22 +793,23 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
std::vector<int> values(n_dwarfs); std::vector<int> values(n_dwarfs);
std::vector<int> candidates; std::vector<int> candidates;
std::vector<int> backup_candidates;
std::map<int, int> dwarf_skill; std::map<int, int> dwarf_skill;
std::vector<bool> previously_enabled(n_dwarfs);
auto mode = labor_infos[labor].mode; auto mode = labor_infos[labor].mode;
if (AUTOMATIC == mode && state_count[IDLE] == 0)
mode = FIXED;
// Find candidate dwarfs, and calculate a preference value for each dwarf
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{ {
if (dwarf_info[dwarf].state != IDLE && dwarf_info[dwarf].state != BUSY && mode != EVERYONE) if (dwarf_info[dwarf].state == CHILD)
continue;
if (dwarf_info[dwarf].state == MILITARY)
continue; continue;
if (labor_infos[labor].is_exclusive && dwarf_info[dwarf].has_exclusive_labor) if (labor_infos[labor].is_exclusive && dwarf_info[dwarf].has_exclusive_labor)
continue; continue;
int value = dwarf_info[dwarf].mastery_penalty - dwarf_info[dwarf].assigned_jobs * 50; int value = dwarf_info[dwarf].mastery_penalty;
if (skill != df::enums::job_skill::NONE) if (skill != df::enums::job_skill::NONE)
{ {
@ -766,77 +849,66 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
values[dwarf] = value; values[dwarf] = value;
if (mode == AUTOMATIC && dwarf_info[dwarf].state != IDLE) candidates.push_back(dwarf);
backup_candidates.push_back(dwarf);
else
candidates.push_back(dwarf);
} }
if (candidates.size() == 0) // Sort candidates by preference value
{ values_sorter ivs(values);
candidates = backup_candidates; std::sort(candidates.begin(), candidates.end(), ivs);
mode = FIXED;
}
if (labor_infos[labor].mode != EVERYONE)
{
values_sorter ivs(values);
std::sort(candidates.begin(), candidates.end(), ivs);
}
// Disable the labor on everyone
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{ {
bool allow_labor = false; if (dwarf_info[dwarf].state == CHILD)
continue;
if (dwarf_info[dwarf].state == BUSY &&
mode == AUTOMATIC &&
(labor_infos[labor].is_exclusive || dwarf_skill[dwarf] > 0))
{
allow_labor = true;
}
if (dwarf_info[dwarf].state == OTHER &&
mode == AUTOMATIC &&
dwarf_skill[dwarf] > 0 &&
!dwarf_info[dwarf].is_best_noble)
{
allow_labor = true;
}
if (dwarfs[dwarf]->status.labors[labor] &&
allow_labor &&
!(labor_infos[labor].is_exclusive && dwarf_info[dwarf].has_exclusive_labor))
{
if (labor_infos[labor].is_exclusive)
dwarf_info[dwarf].has_exclusive_labor = true;
dwarf_info[dwarf].assigned_jobs++; previously_enabled[dwarf] = dwarfs[dwarf]->status.labors[labor];
} dwarfs[dwarf]->status.labors[labor] = false;
else
{
dwarfs[dwarf]->status.labors[labor] = false;
}
} }
int minimum_dwarfs = labor_infos[labor].minimum_dwarfs; int min_dwarfs = labor_infos[labor].minimum_dwarfs;
int max_dwarfs = labor_infos[labor].maximum_dwarfs;
if (labor_infos[labor].mode == EVERYONE)
minimum_dwarfs = n_dwarfs;
// Special - don't assign hunt without a butchers, or fish without a fishery // Special - don't assign hunt without a butchers, or fish without a fishery
if (df::enums::unit_labor::HUNT == labor && !has_butchers) if (df::enums::unit_labor::HUNT == labor && !has_butchers)
minimum_dwarfs = 0; min_dwarfs = max_dwarfs = 0;
if (df::enums::unit_labor::FISH == labor && !has_fishery) if (df::enums::unit_labor::FISH == labor && !has_fishery)
minimum_dwarfs = 0; min_dwarfs = max_dwarfs = 0;
for (int i = 0; i < candidates.size() && i < minimum_dwarfs; i++) bool want_idle_dwarf = true;
if (state_count[IDLE] < 2)
want_idle_dwarf = false;
/*
* Assign dwarfs to this labor. We assign at least the minimum number of dwarfs, in
* order of preference, and then assign additional dwarfs that meet any of these conditions:
* - The dwarf is idle and there are no idle dwarves assigned to this labor
* - The dwarf has nonzero skill associated with the labor
* - The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled.
* We stop assigning dwarfs when we reach the maximum allowed.
* 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.
*/
for (int i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++)
{ {
int dwarf = candidates[i]; int dwarf = candidates[i];
assert(dwarf >= 0); assert(dwarf >= 0);
assert(dwarf < n_dwarfs); assert(dwarf < n_dwarfs);
bool preferred_dwarf = false;
if (want_idle_dwarf && dwarf_info[dwarf].state == IDLE)
preferred_dwarf = true;
if (dwarf_skill[dwarf] > 0)
preferred_dwarf = true;
if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive)
preferred_dwarf = true;
if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf)
continue;
if (!dwarfs[dwarf]->status.labors[labor]) if (!dwarfs[dwarf]->status.labors[labor])
dwarf_info[dwarf].assigned_jobs++; dwarf_info[dwarf].assigned_jobs++;
@ -848,6 +920,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// all the exclusive labors require equipment so this should force the dorf to reequip if needed // all the exclusive labors require equipment so this should force the dorf to reequip if needed
dwarfs[dwarf]->military.pickup_flags.bits.update = 1; dwarfs[dwarf]->military.pickup_flags.bits.update = 1;
} }
if (print_debug)
out.print("Dwarf %i \"%s\" assigned %s: value %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), values[dwarf]);
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY)
labor_infos[labor].active_dwarfs++;
if (dwarf_info[dwarf].state == IDLE)
want_idle_dwarf = false;
} }
} }
@ -896,6 +977,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
assert(dwarf < n_dwarfs); assert(dwarf < n_dwarfs);
dwarfs[dwarf]->status.labors[labor] = true; dwarfs[dwarf]->status.labors[labor] = true;
dwarf_info[dwarf].assigned_jobs++; dwarf_info[dwarf].assigned_jobs++;
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY)
labor_infos[labor].active_dwarfs++;
if (print_debug)
out.print("Dwarf %i \"%s\" assigned %s: hauler\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str());
} }
for (int i = num_haulers; i < hauler_ids.size(); i++) for (int i = num_haulers; i < hauler_ids.size(); i++)
@ -910,10 +997,30 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarfs[dwarf]->status.labors[labor] = false; dwarfs[dwarf]->status.labors[labor] = false;
} }
} }
print_debug = 0;
return CR_OK; return CR_OK;
} }
// A command! It sits around and looks pretty. And it's nice and friendly. void print_labor (df::enums::unit_labor::unit_labor labor, color_ostream &out)
{
string labor_name = ENUM_KEY_STR(unit_labor, labor);
out << labor_name << ": ";
for (int i = 0; i < 20 - (int)labor_name.length(); i++)
out << ' ';
if (labor_infos[labor].mode == DISABLE)
out << "disabled" << endl;
else
{
if (labor_infos[labor].mode == HAULERS)
out << "haulers";
else
out << "minimum " << labor_infos[labor].minimum_dwarfs << ", maximum " << labor_infos[labor].maximum_dwarfs;
out << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl;
}
}
command_result autolabor (color_ostream &out, std::vector <std::string> & parameters) command_result autolabor (color_ostream &out, std::vector <std::string> & parameters)
{ {
if (parameters.size() == 1 && if (parameters.size() == 1 &&
@ -926,16 +1033,81 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
enable_autolabor = 1; enable_autolabor = 1;
out.print("autolabor %sactivated.\n", (enable_autolabor ? "" : "de")); out.print("autolabor %sactivated.\n", (enable_autolabor ? "" : "de"));
} }
else if (parameters.size() == 2 && parameters[0] == "miners") { else if (parameters.size() == 2 || parameters.size() == 3) {
int nminers = atoi (parameters[1].c_str()); df::enums::unit_labor::unit_labor labor = df::enums::unit_labor::NONE;
if (nminers >= 0) {
labor_infos[0].minimum_dwarfs = nminers; FOR_ENUM_ITEMS(unit_labor, test_labor)
out.print("miner count set to %d.\n", nminers); {
} else { if (parameters[0] == ENUM_KEY_STR(unit_labor, test_labor))
out.print("Syntax: autolabor miners <n>, where n is 0 or more.\n" labor = test_labor;
"Current miner count: %d\n", labor_infos[0].minimum_dwarfs); }
if (labor == df::enums::unit_labor::NONE)
{
out.printerr("Could not find labor %s.", parameters[0].c_str());
return CR_WRONG_USAGE;
} }
} else
if (parameters[1] == "haulers")
{
labor_infos[labor].mode = HAULERS;
print_labor(labor, out);
return CR_OK;
}
if (parameters[1] == "disable")
{
labor_infos[labor].mode = DISABLE;
print_labor(labor, out);
return CR_OK;
}
int minimum = atoi (parameters[1].c_str());
int maximum = 200;
if (parameters.size() == 3)
maximum = atoi (parameters[2].c_str());
if (maximum < minimum || maximum < 0 || minimum < 0)
{
out.printerr("Syntax: autolabor <labor> <minimum> [<maximum>]\n", maximum, minimum);
return CR_WRONG_USAGE;
}
labor_infos[labor].minimum_dwarfs = minimum;
labor_infos[labor].maximum_dwarfs = maximum;
labor_infos[labor].mode = AUTOMATIC;
print_labor(labor, out);
}
else if (parameters.size() == 1 && parameters[0] == "list") {
if (!enable_autolabor)
{
out << "autolabor not activated." << endl;
return CR_OK;
}
bool need_comma = 0;
for (int i = 0; i < NUM_STATE; i++)
{
if (state_count[i] == 0)
continue;
if (need_comma)
out << ", ";
out << state_count[i] << ' ' << state_names[i];
need_comma = 1;
}
out << endl;
FOR_ENUM_ITEMS(unit_labor, labor)
{
if (labor == df::enums::unit_labor::NONE)
continue;
print_labor(labor, out);
}
}
else if (parameters.size() == 1 && parameters[0] == "debug") {
print_debug = 1;
}
else
{ {
out.print("Automatically assigns labors to dwarves.\n" out.print("Automatically assigns labors to dwarves.\n"
"Activate with 'autolabor 1', deactivate with 'autolabor 0'.\n" "Activate with 'autolabor 1', deactivate with 'autolabor 0'.\n"

@ -832,12 +832,12 @@ df::unit * findFreeEgglayer()
bool unassignUnitFromZone(df::unit* unit) bool unassignUnitFromZone(df::unit* unit)
{ {
bool success = false; bool success = false;
for (size_t or = 0; or < unit->refs.size(); or++) for (std::size_t idx = 0; idx < unit->refs.size(); idx++)
{ {
df::general_ref * oldref = unit->refs[or]; df::general_ref * oldref = unit->refs[idx];
if(oldref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) if(oldref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED)
{ {
unit->refs.erase(unit->refs.begin() + or); unit->refs.erase(unit->refs.begin() + idx);
df::building_civzonest * oldciv = (df::building_civzonest *) oldref->getBuilding(); df::building_civzonest * oldciv = (df::building_civzonest *) oldref->getBuilding();
for(size_t oc=0; oc<oldciv->assigned_creature.size(); oc++) for(size_t oc=0; oc<oldciv->assigned_creature.size(); oc++)
{ {