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

develop
Timothy Collett 2012-06-14 09:59:53 -04:00
commit c6700585bc
47 changed files with 2798 additions and 1248 deletions

@ -60,7 +60,7 @@ endif()
# set up versioning.
set(DF_VERSION_MAJOR "0")
set(DF_VERSION_MINOR "34")
set(DF_VERSION_PATCH "10")
set(DF_VERSION_PATCH "11")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.")

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

@ -121,6 +121,12 @@ or as a result of calling the ``_field()`` method.
They behave as structs with one field ``value`` of the right type.
To make working with numeric buffers easier, they also allow
numeric indices. Note that other than excluding negative values
no bound checking is performed, since buffer length is not available.
Index 0 is equivalent to the ``value`` field.
Struct references
-----------------
@ -219,12 +225,21 @@ Bitfield references
-------------------
Bitfields behave like special fixed-size containers.
The ``_enum`` property points to the bitfield type.
Consider them to be something in between structs and
fixed-size vectors.
The ``_enum`` property points to the bitfield type.
Numerical indices correspond to the shift value,
and if a subfield occupies multiple bits, the
``ipairs`` order would have a gap.
Since currently there is no API to allocate a bitfield
object fully in GC-managed lua heap, consider using the
lua table assignment feature outlined below in order to
pass bitfield values to dfhack API functions that need
them, e.g. ``matinfo:matches{metal=true}``.
Named types
===========
@ -308,6 +323,24 @@ The ``df`` table itself contains the following functions and values:
Equivalent to the method, but also allows a reference as proxy for its type.
* ``df.new(ptype[,count])``
Allocate a new instance, or an array of built-in types.
The ``ptype`` argument is a string from the following list:
``string``, ``int8_t``, ``uint8_t``, ``int16_t``, ``uint16_t``,
``int32_t``, ``uint32_t``, ``int64_t``, ``uint64_t``, ``bool``,
``float``, ``double``. All of these except ``string`` can be
used with the count argument to allocate an array.
* ``df.reinterpret_cast(type,ptr)``
Converts ptr to a ref of specified type. The type may be anything
acceptable to ``df.is_instance``. Ptr may be *nil*, a ref,
a lightuserdata, or a number.
Returns *nil* if NULL, or a ref.
Recursive table assignment
==========================
@ -598,6 +631,22 @@ One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments
can be omitted.
* ``dfhack.getOSType()``
Returns the OS type string from ``symbols.xml``.
* ``dfhack.getDFVersion()``
Returns the DF version string from ``symbols.xml``.
* ``dfhack.getDFPath()``
Returns the DF directory path.
* ``dfhack.getHackPath()``
Returns the dfhack directory path, i.e. ``".../df/hack/"``.
* ``dfhack.isWorldLoaded()``
Checks if the world is loaded.
@ -726,7 +775,7 @@ Units module
* ``dfhack.units.isDead(unit)``
The unit is completely dead and passive.
The unit is completely dead and passive, or a ghost.
* ``dfhack.units.isAlive(unit)``
@ -734,7 +783,16 @@ Units module
* ``dfhack.units.isSane(unit)``
The unit is capable of rational action, i.e. not dead, insane or zombie.
The unit is capable of rational action, i.e. not dead, insane, zombie, or active werewolf.
* ``dfhack.units.isDwarf(unit)``
The unit is of the correct race of the fortress.
* ``dfhack.units.isCitizen(unit)``
The unit is an alive sane citizen of the fortress; wraps the
same checks the game uses to decide game-over by extinction.
* ``dfhack.units.getAge(unit[,true_age])``
@ -1084,6 +1142,29 @@ Constructions module
Returns *true, was_only_planned* if removed; or *false* if none found.
Internal API
------------
These functions are intended for the use by dfhack developers,
and are only documented here for completeness:
* ``dfhack.internal.getAddress(name)``
Returns the global address ``name``, or *nil*.
* ``dfhack.internal.setAddress(name, value)``
Sets the global address ``name``. Returns the value of ``getAddress`` before the change.
* ``dfhack.internal.getBase()``
Returns the base address of the process.
* ``dfhack.internal.getMemRanges()``
Returns a sequence of tables describing virtual memory ranges of the process.
Core interpreter context
========================

@ -3,7 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.8.1: http://docutils.sourceforge.net/" />
<meta name="generator" content="Docutils 0.9: http://docutils.sourceforge.net/" />
<title>DFHack Lua API</title>
<style type="text/css">
@ -345,17 +345,18 @@ ul.auto-toc {
<li><a class="reference internal" href="#burrows-module" id="id19">Burrows module</a></li>
<li><a class="reference internal" href="#buildings-module" id="id20">Buildings module</a></li>
<li><a class="reference internal" href="#constructions-module" id="id21">Constructions module</a></li>
<li><a class="reference internal" href="#internal-api" id="id22">Internal API</a></li>
</ul>
</li>
<li><a class="reference internal" href="#core-interpreter-context" id="id22">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id23">Event type</a></li>
<li><a class="reference internal" href="#core-interpreter-context" id="id23">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id24">Event type</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#plugins" id="id24">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id25">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id26">sort</a></li>
<li><a class="reference internal" href="#plugins" id="id25">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id26">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id27">sort</a></li>
</ul>
</li>
</ul>
@ -452,6 +453,10 @@ that don't fit any of the other reference types. Such
references can only appear as a value of a pointer field,
or as a result of calling the <tt class="docutils literal">_field()</tt> method.</p>
<p>They behave as structs with one field <tt class="docutils literal">value</tt> of the right type.</p>
<p>To make working with numeric buffers easier, they also allow
numeric indices. Note that other than excluding negative values
no bound checking is performed, since buffer length is not available.
Index 0 is equivalent to the <tt class="docutils literal">value</tt> field.</p>
</div>
<div class="section" id="struct-references">
<h3><a class="toc-backref" href="#id4">Struct references</a></h3>
@ -534,10 +539,17 @@ use <tt class="docutils literal">#ref</tt>, or just <tt class="docutils literal"
<div class="section" id="bitfield-references">
<h3><a class="toc-backref" href="#id6">Bitfield references</a></h3>
<p>Bitfields behave like special fixed-size containers.
The <tt class="docutils literal">_enum</tt> property points to the bitfield type.</p>
<p>Numerical indices correspond to the shift value,
Consider them to be something in between structs and
fixed-size vectors.</p>
<p>The <tt class="docutils literal">_enum</tt> property points to the bitfield type.
Numerical indices correspond to the shift value,
and if a subfield occupies multiple bits, the
<tt class="docutils literal">ipairs</tt> order would have a gap.</p>
<p>Since currently there is no API to allocate a bitfield
object fully in GC-managed lua heap, consider using the
lua table assignment feature outlined below in order to
pass bitfield values to dfhack API functions that need
them, e.g. <tt class="docutils literal">matinfo:matches{metal=true}</tt>.</p>
</div>
</div>
<div class="section" id="named-types">
@ -607,6 +619,20 @@ lightuserdata (step is mandatory then).</p>
<li><p class="first"><tt class="docutils literal">df.is_instance(type,obj)</tt></p>
<p>Equivalent to the method, but also allows a reference as proxy for its type.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">df.new(ptype[,count])</span></tt></p>
<p>Allocate a new instance, or an array of built-in types.
The <tt class="docutils literal">ptype</tt> argument is a string from the following list:
<tt class="docutils literal">string</tt>, <tt class="docutils literal">int8_t</tt>, <tt class="docutils literal">uint8_t</tt>, <tt class="docutils literal">int16_t</tt>, <tt class="docutils literal">uint16_t</tt>,
<tt class="docutils literal">int32_t</tt>, <tt class="docutils literal">uint32_t</tt>, <tt class="docutils literal">int64_t</tt>, <tt class="docutils literal">uint64_t</tt>, <tt class="docutils literal">bool</tt>,
<tt class="docutils literal">float</tt>, <tt class="docutils literal">double</tt>. All of these except <tt class="docutils literal">string</tt> can be
used with the count argument to allocate an array.</p>
</li>
<li><p class="first"><tt class="docutils literal">df.reinterpret_cast(type,ptr)</tt></p>
<p>Converts ptr to a ref of specified type. The type may be anything
acceptable to <tt class="docutils literal">df.is_instance</tt>. Ptr may be <em>nil</em>, a ref,
a lightuserdata, or a number.</p>
<p>Returns <em>nil</em> if NULL, or a ref.</p>
</li>
</ul>
</div>
<div class="section" id="recursive-table-assignment">
@ -855,6 +881,18 @@ One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments
can be omitted.</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.getOSType()</tt></p>
<p>Returns the OS type string from <tt class="docutils literal">symbols.xml</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.getDFVersion()</tt></p>
<p>Returns the DF version string from <tt class="docutils literal">symbols.xml</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.getDFPath()</tt></p>
<p>Returns the DF directory path.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.getHackPath()</tt></p>
<p>Returns the dfhack directory path, i.e. <tt class="docutils literal"><span class="pre">&quot;.../df/hack/&quot;</span></tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.isWorldLoaded()</tt></p>
<p>Checks if the world is loaded.</p>
</li>
@ -960,13 +998,20 @@ a lua list containing them.</p>
<p>Returns the nemesis record of the unit if it has one, or <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isDead(unit)</tt></p>
<p>The unit is completely dead and passive.</p>
<p>The unit is completely dead and passive, or a ghost.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isAlive(unit)</tt></p>
<p>The unit isn't dead or undead.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isSane(unit)</tt></p>
<p>The unit is capable of rational action, i.e. not dead, insane or zombie.</p>
<p>The unit is capable of rational action, i.e. not dead, insane, zombie, or active werewolf.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isDwarf(unit)</tt></p>
<p>The unit is of the correct race of the fortress.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isCitizen(unit)</tt></p>
<p>The unit is an alive sane citizen of the fortress; wraps the
same checks the game uses to decide game-over by extinction.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.units.getAge(unit[,true_age])</span></tt></p>
<p>Returns the age of the unit in years as a floating-point value.
@ -1260,9 +1305,28 @@ Returns <em>true, was_only_planned</em> if removed; or <em>false</em> if none fo
</li>
</ul>
</div>
<div class="section" id="internal-api">
<h3><a class="toc-backref" href="#id22">Internal API</a></h3>
<p>These functions are intended for the use by dfhack developers,
and are only documented here for completeness:</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getAddress(name)</tt></p>
<p>Returns the global address <tt class="docutils literal">name</tt>, or <em>nil</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.setAddress(name, value)</tt></p>
<p>Sets the global address <tt class="docutils literal">name</tt>. Returns the value of <tt class="docutils literal">getAddress</tt> before the change.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getBase()</tt></p>
<p>Returns the base address of the process.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.internal.getMemRanges()</tt></p>
<p>Returns a sequence of tables describing virtual memory ranges of the process.</p>
</li>
</ul>
</div>
</div>
<div class="section" id="core-interpreter-context">
<h2><a class="toc-backref" href="#id22">Core interpreter context</a></h2>
<h2><a class="toc-backref" href="#id23">Core interpreter context</a></h2>
<p>While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.</p>
@ -1293,7 +1357,7 @@ Using <tt class="docutils literal">timeout_active(id,nil)</tt> cancels the timer
</li>
</ul>
<div class="section" id="event-type">
<h3><a class="toc-backref" href="#id23">Event type</a></h3>
<h3><a class="toc-backref" href="#id24">Event type</a></h3>
<p>An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
@ -1319,14 +1383,14 @@ order using <tt class="docutils literal">dfhack.safecall</tt>.</p>
</div>
</div>
<div class="section" id="plugins">
<h1><a class="toc-backref" href="#id24">Plugins</a></h1>
<h1><a class="toc-backref" href="#id25">Plugins</a></h1>
<p>DFHack plugins may export native functions and events
to lua contexts. They are automatically imported by
<tt class="docutils literal"><span class="pre">mkmodule('plugins.&lt;name&gt;')</span></tt>; this means that a lua
module file is still necessary for <tt class="docutils literal">require</tt> to read.</p>
<p>The following plugins have lua support.</p>
<div class="section" id="burrows">
<h2><a class="toc-backref" href="#id25">burrows</a></h2>
<h2><a class="toc-backref" href="#id26">burrows</a></h2>
<p>Implements extended burrow manipulations.</p>
<p>Events:</p>
<ul>
@ -1364,7 +1428,7 @@ set is the same as used by the command line.</p>
<p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p>
</div>
<div class="section" id="sort">
<h2><a class="toc-backref" href="#id26">sort</a></h2>
<h2><a class="toc-backref" href="#id27">sort</a></h2>
<p>Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.</p>
</div>

@ -27,7 +27,7 @@ Compatibility
DFHack works on Windows XP, Vista, 7 or any modern Linux distribution.
OSX is not supported due to lack of developers with a Mac.
Currently, only versions 0.34.06 and 0.34.07 are supported. If you need DFHack
Currently, versions 0.34.08 - 0.34.11 are supported. If you need DFHack
for older versions, look for older releases.
On Windows, you have to use the SDL version of DF.
@ -633,8 +633,14 @@ produce undesirable results. There are a few good ones though.
You are in fort game mode, managing your fortress and paused.
You switch to the arena game mode, *assume control of a creature* and then
switch to adventure game mode(1).
switch to adventure game mode(1).
You just lost a fortress and gained an adventurer.
You could also do this.
You are in fort game mode, managing your fortress and paused at the esc menu.
You switch to the adventure game mode, then use Dfusion to *assume control of a creature* and then
save or retire.
You just created a returnable mountain home and gained an adventurer.
I take no responsibility of anything that happens as a result of using this tool

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

@ -1 +1 @@
Subproject commit 27216d9a4be418729cb4671371b7309f0af558f1
Subproject commit c85e9fb35d3510c5dcc367056cda3237d77a7add

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

@ -264,7 +264,7 @@ namespace DFHack
void color(Console::color_value index)
{
if(!rawmode)
fprintf(dfout_C,getANSIColor(index));
fprintf(dfout_C, "%s", getANSIColor(index));
else
{
const char * colstr = getANSIColor(index);
@ -770,4 +770,4 @@ void Console::msleep (unsigned int msec)
{
if (msec > 1000) sleep(msec/1000000);
usleep((msec % 1000000) * 1000);
}
}

@ -68,6 +68,8 @@ using namespace DFHack;
#include <fstream>
#include "tinythread.h"
#include "SDL_events.h"
using namespace tthread;
using namespace df::enums;
using df::global::init;
@ -1218,29 +1220,30 @@ bool Core::ncurses_wgetch(int in, int & out)
return true;
}
int Core::UnicodeAwareSym(const SDL_KeyboardEvent& ke)
int UnicodeAwareSym(const SDL::KeyboardEvent& ke)
{
// Assume keyboard layouts don't change the order of numbers:
if( '0' <= ke.keysym.sym && ke.keysym.sym <= '9') return ke.keysym.sym;
if(SDLK_F1 <= ke.keysym.sym && ke.keysym.sym <= SDLK_F12) return ke.keysym.sym;
if( '0' <= ke.ksym.sym && ke.ksym.sym <= '9') return ke.ksym.sym;
if(SDL::K_F1 <= ke.ksym.sym && ke.ksym.sym <= SDL::K_F12) return ke.ksym.sym;
// These keys are mapped to the same control codes as Ctrl-?
switch (ke.keysym.sym) {
case SDLK_RETURN:
case SDLK_KP_ENTER:
case SDLK_TAB:
case SDLK_ESCAPE:
case SDLK_DELETE:
return ke.keysym.sym;
default:
break;
switch (ke.ksym.sym)
{
case SDL::K_RETURN:
case SDL::K_KP_ENTER:
case SDL::K_TAB:
case SDL::K_ESCAPE:
case SDL::K_DELETE:
return ke.ksym.sym;
default:
break;
}
int unicode = ke.keysym.unicode;
int unicode = ke.ksym.unicode;
// convert Ctrl characters to their 0x40-0x5F counterparts:
if (unicode < ' ')
{
{
unicode += 'A' - 1;
}
@ -1248,7 +1251,7 @@ int Core::UnicodeAwareSym(const SDL_KeyboardEvent& ke)
if('A' < unicode && unicode < 'Z')
{
unicode += 'a' - 'A';
}
}
// convert various other punctuation marks:
if('\"' == unicode) unicode = '\'';
@ -1265,29 +1268,30 @@ int Core::UnicodeAwareSym(const SDL_KeyboardEvent& ke)
return unicode;
}
//MEMO: return false if event is consumed
int Core::DFH_SDL_Event(SDL_Event* ev)
int Core::DFH_SDL_Event(SDL::Event* ev)
{
// do NOT process events before we are ready.
if(!started) return true;
if(!ev)
return true;
if(ev && (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP))
if(ev && (ev->type == SDL::ET_KEYDOWN || ev->type == SDL::ET_KEYUP))
{
SDL_KeyboardEvent * ke = (SDL_KeyboardEvent *)ev;
auto ke = (SDL::KeyboardEvent *)ev;
if(ke->state == SDL_PRESSED && !hotkey_states[ke->keysym.sym])
if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym])
{
hotkey_states[ke->keysym.sym] = true;
hotkey_states[ke->ksym.sym] = true;
int mod = 0;
if (ke->keysym.mod & KMOD_SHIFT) mod |= 1;
if (ke->keysym.mod & KMOD_CTRL) mod |= 2;
if (ke->keysym.mod & KMOD_ALT) mod |= 4;
if (ke->ksym.mod & SDL::KMOD_SHIFT) mod |= 1;
if (ke->ksym.mod & SDL::KMOD_CTRL) mod |= 2;
if (ke->ksym.mod & SDL::KMOD_ALT) mod |= 4;
// Use unicode so Windows gives the correct value for the
// user's Input Language
if((ke->keysym.unicode & 0xff80) == 0)
if((ke->ksym.unicode & 0xff80) == 0)
{
int key = UnicodeAwareSym(*ke);
SelectHotkey(key, mod);
@ -1295,12 +1299,12 @@ int Core::DFH_SDL_Event(SDL_Event* ev)
else
{
// Pretend non-ascii characters don't happen:
SelectHotkey(ke->keysym.sym, mod);
SelectHotkey(ke->ksym.sym, mod);
}
}
else if(ke->state == SDL_RELEASED)
else if(ke->state == SDL::BTN_RELEASED)
{
hotkey_states[ke->keysym.sym] = false;
hotkey_states[ke->ksym.sym] = false;
}
}
return true;
@ -1317,8 +1321,8 @@ bool Core::SelectHotkey(int sym, int modifiers)
while (screen->child)
screen = screen->child;
if (sym == SDLK_KP_ENTER)
sym = SDLK_RETURN;
if (sym == SDL::K_KP_ENTER)
sym = SDL::K_RETURN;
std::string cmd;
@ -1341,7 +1345,7 @@ bool Core::SelectHotkey(int sym, int modifiers)
if (cmd.empty()) {
// Check the hotkey keybindings
int idx = sym - SDLK_F1;
int idx = sym - SDL::K_F1;
if(idx >= 0 && idx < 8)
{
if (modifiers & 1)
@ -1396,13 +1400,13 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string
}
if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') {
*psym = SDLK_a + (keyspec[0]-'A');
*psym = SDL::K_a + (keyspec[0]-'A');
return true;
} else if (keyspec.size() == 2 && keyspec[0] == 'F' && keyspec[1] >= '1' && keyspec[1] <= '9') {
*psym = SDLK_F1 + (keyspec[1]-'1');
*psym = SDL::K_F1 + (keyspec[1]-'1');
return true;
} else if (keyspec == "Enter") {
*psym = SDLK_RETURN;
*psym = SDL::K_RETURN;
return true;
} else
return false;

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

@ -84,6 +84,8 @@ distribution.
using namespace DFHack;
using namespace DFHack::LuaWrapper;
void dfhack_printerr(lua_State *S, const std::string &str);
void Lua::Push(lua_State *state, const Units::NoblePosition &pos)
{
lua_createtable(state, 0, 3);
@ -640,10 +642,37 @@ static void OpenModule(lua_State *state, const char *mname,
/***** DFHack module *****/
static std::string getOSType()
{
switch (Core::getInstance().vinfo->getOS())
{
case OS_WINDOWS:
return "windows";
case OS_LINUX:
return "linux";
case OS_APPLE:
return "darwin";
default:
return "unknown";
}
}
static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); }
static std::string getDFPath() { return Core::getInstance().p->getPath(); }
static std::string getHackPath() { return Core::getInstance().getHackPath(); }
static bool isWorldLoaded() { return Core::getInstance().isWorldLoaded(); }
static bool isMapLoaded() { return Core::getInstance().isMapLoaded(); }
static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(getOSType),
WRAP(getDFVersion),
WRAP(getDFPath),
WRAP(getHackPath),
WRAP(isWorldLoaded),
WRAP(isMapLoaded),
WRAPM(Translation, TranslateName),
@ -715,6 +744,8 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, isDead),
WRAPM(Units, isAlive),
WRAPM(Units, isSane),
WRAPM(Units, isDwarf),
WRAPM(Units, isCitizen),
WRAPM(Units, getAge),
WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName),
@ -988,6 +1019,91 @@ static const luaL_Reg dfhack_constructions_funcs[] = {
{ NULL, NULL }
};
/***** Internal module *****/
static uint32_t getBase() { return Core::getInstance().p->getBase(); }
static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
WRAP(getBase),
{ NULL, NULL }
};
static int internal_getAddress(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
uint32_t addr = Core::getInstance().vinfo->getAddress(name);
if (addr)
lua_pushnumber(L, addr);
else
lua_pushnil(L);
return 1;
}
static int internal_setAddress(lua_State *L)
{
std::string name = luaL_checkstring(L, 1);
uint32_t addr = luaL_checkint(L, 2);
internal_getAddress(L);
// Set the address
Core::getInstance().vinfo->setAddress(name, addr);
auto fields = df::global::_identity.getFields();
for (int i = 0; fields && fields[i].mode != struct_field_info::END; ++i)
{
if (fields[i].name != name)
continue;
*(void**)fields[i].offset = (void*)addr;
}
// Print via printerr, so that it is definitely logged to stderr.log.
std::string msg = stl_sprintf("<global-address name='%s' value='0x%x'/>", name.c_str(), addr);
dfhack_printerr(L, msg);
return 1;
}
static int internal_getMemRanges(lua_State *L)
{
std::vector<DFHack::t_memrange> ranges;
Core::getInstance().p->getMemRanges(ranges);
lua_newtable(L);
for(size_t i = 0; i < ranges.size(); i++)
{
lua_newtable(L);
lua_pushnumber(L, (uint32_t)ranges[i].start);
lua_setfield(L, -2, "start");
lua_pushnumber(L, (uint32_t)ranges[i].end);
lua_setfield(L, -2, "end");
lua_pushstring(L, ranges[i].name);
lua_setfield(L, -2, "name");
lua_pushboolean(L, ranges[i].read);
lua_setfield(L, -2, "read");
lua_pushboolean(L, ranges[i].write);
lua_setfield(L, -2, "write");
lua_pushboolean(L, ranges[i].execute);
lua_setfield(L, -2, "execute");
lua_pushboolean(L, ranges[i].shared);
lua_setfield(L, -2, "shared");
lua_pushboolean(L, ranges[i].valid);
lua_setfield(L, -2, "valid");
lua_rawseti(L, -2, i+1);
}
return 1;
}
static const luaL_Reg dfhack_internal_funcs[] = {
{ "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress },
{ "getMemRanges", internal_getMemRanges },
{ NULL, NULL }
};
/************************
* Main Open function *
@ -1007,4 +1123,5 @@ void OpenDFHackApi(lua_State *state)
OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs);
OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs);
OpenModule(state, "constructions", dfhack_constructions_module);
OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs);
}

@ -66,6 +66,8 @@ using namespace DFHack::LuaWrapper;
lua_State *DFHack::Lua::Core::State = NULL;
void dfhack_printerr(lua_State *S, const std::string &str);
inline void AssertCoreSuspend(lua_State *state)
{
assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended());
@ -96,8 +98,6 @@ static void check_valid_ptr_index(lua_State *state, int val_index)
}
}
static void dfhack_printerr(lua_State *S, const std::string &str);
static void signal_typeid_error(color_ostream *out, lua_State *state,
type_identity *type, const char *msg,
int val_index, bool perr, bool signal)
@ -233,7 +233,7 @@ static int lua_dfhack_println(lua_State *S)
return 0;
}
static void dfhack_printerr(lua_State *S, const std::string &str)
void dfhack_printerr(lua_State *S, const std::string &str)
{
if (color_ostream *out = Lua::GetOutput(S))
out->printerr("%s\n", str.c_str());

@ -635,6 +635,28 @@ static int meta_struct_next(lua_State *state)
return 2;
}
/**
* Field lookup for primitive refs: behave as a quasi-array with numeric indices.
*/
static type_identity *find_primitive_field(lua_State *state, int field, const char *mode, uint8_t **ptr)
{
if (lua_type(state, field) == LUA_TNUMBER)
{
int idx = lua_tointeger(state, field);
if (idx < 0)
field_error(state, 2, "negative index", mode);
lua_rawgetp(state, UPVAL_METATABLE, &DFHACK_IDENTITY_FIELD_TOKEN);
auto id = (type_identity *)lua_touserdata(state, -1);
lua_pop(state, 1);
*ptr += int(id->byte_size()) * idx;
return id;
}
return (type_identity*)find_field(state, field, mode);
}
/**
* Metamethod: __index for primitives, i.e. simple object references.
* Fields point to identity, or NULL for metafields.
@ -642,7 +664,7 @@ static int meta_struct_next(lua_State *state)
static int meta_primitive_index(lua_State *state)
{
uint8_t *ptr = get_object_addr(state, 1, 2, "read");
auto type = (type_identity*)find_field(state, 2, "read");
auto type = find_primitive_field(state, 2, "read", &ptr);
if (!type)
return 1;
type->lua_read(state, 2, ptr);
@ -655,7 +677,7 @@ static int meta_primitive_index(lua_State *state)
static int meta_primitive_newindex(lua_State *state)
{
uint8_t *ptr = get_object_addr(state, 1, 2, "write");
auto type = (type_identity*)find_field(state, 2, "write");
auto type = find_primitive_field(state, 2, "write", &ptr);
if (!type)
field_error(state, 2, "builtin property or method", "write");
type->lua_write(state, 2, ptr, 3);

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

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

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

@ -33,7 +33,6 @@ distribution.
#include <stdint.h>
#include "Console.h"
#include "modules/Graphic.h"
#include "SDL_events.h"
#include "RemoteClient.h"
@ -86,14 +85,14 @@ namespace DFHack
{
friend int ::SDL_NumJoysticks(void);
friend void ::SDL_Quit(void);
friend int ::SDL_PollEvent(SDL_Event *);
friend int ::SDL_PollEvent(SDL::Event *);
friend int ::SDL_Init(uint32_t flags);
friend int ::wgetch(WINDOW * w);
friend int ::egg_init(void);
friend int ::egg_shutdown(void);
friend int ::egg_tick(void);
friend int ::egg_prerender(void);
friend int ::egg_sdl_event(SDL_Event* event);
friend int ::egg_sdl_event(SDL::Event* event);
friend int ::egg_curses_event(int orig_return);
public:
/// Get the single Core instance or make one.
@ -170,7 +169,7 @@ namespace DFHack
int Update (void);
int TileUpdate (void);
int Shutdown (void);
int DFH_SDL_Event(SDL_Event* event);
int DFH_SDL_Event(SDL::Event* event);
bool ncurses_wgetch(int in, int & out);
void onUpdate(color_ostream &out);
@ -215,7 +214,6 @@ namespace DFHack
tthread::mutex * HotkeyMutex;
tthread::condition_variable * HotkeyCond;
int UnicodeAwareSym(const SDL_KeyboardEvent& ke);
bool SelectHotkey(int key, int modifiers);
// for state change tracking

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

@ -33,8 +33,6 @@ distribution.
#include <string>
#include <stdint.h>
#include "SDL.h"
// function and variable pointer... we don't try to understand what SDL does here
typedef void * fPtr;
typedef void * vPtr;
@ -48,7 +46,7 @@ namespace SDL
// be declared as friend functions/known
DFhackCExport int SDL_NumJoysticks(void);
DFhackCExport void SDL_Quit(void);
DFhackCExport int SDL_PollEvent(SDL_Event* event);
DFhackCExport int SDL_PollEvent(SDL::Event* event);
DFhackCExport int SDL_Init(uint32_t flags);
DFhackCExport int wgetch(WINDOW * win);
@ -65,7 +63,7 @@ DFhackCExport int egg_tick(void);
DFhackCExport int egg_prerender(void);
// hook - called for each SDL event, can filter both the event and the return value
DFhackCExport int egg_sdl_event(SDL_Event* event);
DFhackCExport int egg_sdl_event(SDL::Event* event);
// hook - ncurses event. return -1 to consume
DFhackCExport int egg_curses_event(int orig_return);

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

@ -61,11 +61,11 @@ namespace DFHack
struct DFLibrary;
// Open a plugin library
DFLibrary * OpenPlugin (const char * filename);
DFHACK_EXPORT DFLibrary * OpenPlugin (const char * filename);
// find a symbol inside plugin
void * LookupPlugin (DFLibrary * plugin ,const char * function);
DFHACK_EXPORT void * LookupPlugin (DFLibrary * plugin ,const char * function);
// Close a plugin library
void ClosePlugin (DFLibrary * plugin);
DFHACK_EXPORT void ClosePlugin (DFLibrary * plugin);
struct DFHACK_EXPORT CommandReg {
const char *name;

@ -0,0 +1,210 @@
/*
SDL - Simple DirectMedia Layer
Copyright (C) 1997-2009 Sam Lantinga
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Sam Lantinga
slouken@libsdl.org
*/
// Fake - only structs. Shamelessly pilfered from the SDL library.
// Needed for processing its event types without polluting our namespaces with C garbage
#pragma once
#include "SDL_keyboard.h"
namespace SDL
{
enum ButtonState
{
BTN_RELEASED = 0,
BTN_PRESSED = 1
};
/** Event enumerations */
enum EventType
{
ET_NOEVENT = 0, /**< Unused (do not remove) */
ET_ACTIVEEVENT, /**< Application loses/gains visibility */
ET_KEYDOWN, /**< Keys pressed */
ET_KEYUP, /**< Keys released */
ET_MOUSEMOTION, /**< Mouse moved */
ET_MOUSEBUTTONDOWN, /**< Mouse button pressed */
ET_MOUSEBUTTONUP, /**< Mouse button released */
ET_JOYAXISMOTION, /**< Joystick axis motion */
ET_JOYBALLMOTION, /**< Joystick trackball motion */
ET_JOYHATMOTION, /**< Joystick hat position change */
ET_JOYBUTTONDOWN, /**< Joystick button pressed */
ET_JOYBUTTONUP, /**< Joystick button released */
ET_QUIT, /**< User-requested quit */
ET_SYSWMEVENT, /**< System specific event */
ET_EVENT_RESERVEDA, /**< Reserved for future use.. */
ET_EVENT_RESERVEDB, /**< Reserved for future use.. */
ET_VIDEORESIZE, /**< User resized video mode */
ET_VIDEOEXPOSE, /**< Screen needs to be redrawn */
ET_EVENT_RESERVED2, /**< Reserved for future use.. */
ET_EVENT_RESERVED3, /**< Reserved for future use.. */
ET_EVENT_RESERVED4, /**< Reserved for future use.. */
ET_EVENT_RESERVED5, /**< Reserved for future use.. */
ET_EVENT_RESERVED6, /**< Reserved for future use.. */
ET_EVENT_RESERVED7, /**< Reserved for future use.. */
/** Events ET_USEREVENT through ET_MAXEVENTS-1 are for your use */
ET_USEREVENT = 24,
/** This last event is only for bounding internal arrays
* It is the number of bits in the event mask datatype -- Uint32
*/
ET_NUMEVENTS = 32
};
/** Application visibility event structure */
struct ActiveEvent
{
uint8_t type; /**< ET_ACTIVEEVENT */
uint8_t gain; /**< Whether given states were gained or lost (1/0) */
uint8_t state; /**< A mask of the focus states */
};
/** Keyboard event structure */
struct KeyboardEvent
{
uint8_t type; /**< ET_KEYDOWN or ET_KEYUP */
uint8_t which; /**< The keyboard device index */
uint8_t state; /**< BTN_PRESSED or BTN_RELEASED */
keysym ksym;
};
/** Mouse motion event structure */
struct MouseMotionEvent
{
uint8_t type; /**< ET_MOUSEMOTION */
uint8_t which; /**< The mouse device index */
uint8_t state; /**< The current button state */
uint16_t x, y; /**< The X/Y coordinates of the mouse */
int16_t xrel; /**< The relative motion in the X direction */
int16_t yrel; /**< The relative motion in the Y direction */
};
/** Mouse button event structure */
struct MouseButtonEvent
{
uint8_t type; /**< ET_MOUSEBUTTONDOWN or ET_MOUSEBUTTONUP */
uint8_t which; /**< The mouse device index */
uint8_t button; /**< The mouse button index */
uint8_t state; /**< BTN_PRESSED or BTN_RELEASED */
uint16_t x, y; /**< The X/Y coordinates of the mouse at press time */
};
/** Joystick axis motion event structure */
struct JoyAxisEvent
{
uint8_t type; /**< ET_JOYAXISMOTION */
uint8_t which; /**< The joystick device index */
uint8_t axis; /**< The joystick axis index */
int16_t value; /**< The axis value (range: -32768 to 32767) */
};
/** Joystick trackball motion event structure */
struct JoyBallEvent
{
uint8_t type; /**< ET_JOYBALLMOTION */
uint8_t which; /**< The joystick device index */
uint8_t ball; /**< The joystick trackball index */
int16_t xrel; /**< The relative motion in the X direction */
int16_t yrel; /**< The relative motion in the Y direction */
};
/** Joystick hat position change event structure */
struct JoyHatEvent
{
uint8_t type; /**< ET_JOYHATMOTION */
uint8_t which; /**< The joystick device index */
uint8_t hat; /**< The joystick hat index */
uint8_t value; /**< The hat position value:
* SDL_HAT_LEFTUP SDL_HAT_UP SDL_HAT_RIGHTUP
* SDL_HAT_LEFT SDL_HAT_CENTERED SDL_HAT_RIGHT
* SDL_HAT_LEFTDOWN SDL_HAT_DOWN SDL_HAT_RIGHTDOWN
* Note that zero means the POV is centered.
*/
};
/** Joystick button event structure */
struct JoyButtonEvent
{
uint8_t type; /**< ET_JOYBUTTONDOWN or ET_JOYBUTTONUP */
uint8_t which; /**< The joystick device index */
uint8_t button; /**< The joystick button index */
uint8_t state; /**< BTN_PRESSED or BTN_RELEASED */
};
/** The "window resized" event
* When you get this event, you are responsible for setting a new video
* mode with the new width and height.
*/
struct ResizeEvent
{
uint8_t type; /**< ET_VIDEORESIZE */
int w; /**< New width */
int h; /**< New height */
};
/** The "screen redraw" event */
struct ExposeEvent
{
uint8_t type; /**< ET_VIDEOEXPOSE */
};
/** The "quit requested" event */
struct QuitEvent
{
uint8_t type; /**< ET_QUIT */
};
/** A user-defined event type */
struct UserEvent
{
uint8_t type; /**< ETL_USEREVENT through ET_NUMEVENTS-1 */
int code; /**< User defined event code */
void *data1; /**< User defined data pointer */
void *data2; /**< User defined data pointer */
};
/** If you want to use this event, you should include SDL_syswm.h */
struct SysWMmsg;
struct SysWMEvent
{
uint8_t type;
SysWMmsg *msg;
};
/** General event structure */
union Event
{
uint8_t type;
ActiveEvent active;
KeyboardEvent key;
MouseMotionEvent motion;
MouseButtonEvent button;
JoyAxisEvent jaxis;
JoyBallEvent jball;
JoyHatEvent jhat;
JoyButtonEvent jbutton;
ResizeEvent resize;
ExposeEvent expose;
QuitEvent quit;
UserEvent user;
SysWMEvent syswm;
};
}

@ -0,0 +1,61 @@
/*
SDL - Simple DirectMedia Layer
Copyright (C) 1997-2009 Sam Lantinga
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Sam Lantinga
slouken@libsdl.org
*/
// Fake - only structs. Shamelessly pilfered from the SDL library.
// Needed for processing its event types without polluting our namespaces with C garbage
#pragma once
#include "SDL_keysym.h"
#include <stdint.h>
namespace SDL
{
/** Keysym structure
*
* - The scancode is hardware dependent, and should not be used by general
* applications. If no hardware scancode is available, it will be 0.
*
* - The 'unicode' translated character is only available when character
* translation is enabled by the SDL_EnableUNICODE() API. If non-zero,
* this is a UNICODE character corresponding to the keypress. If the
* high 9 bits of the character are 0, then this maps to the equivalent
* ASCII character:
* @code
* char ch;
* if ( (keysym.unicode & 0xFF80) == 0 ) {
* ch = keysym.unicode & 0x7F;
* } else {
* An international character..
* }
* @endcode
*/
typedef struct keysym
{
uint8_t scancode; /**< hardware specific scancode */
Key sym; /**< SDL virtual keysym */
Mod mod; /**< current key modifiers */
uint16_t unicode; /**< translated character */
} keysym;
/** This is the mask which refers to all hotkey bindings */
#define ALL_HOTKEYS 0xFFFFFFFF
}

@ -0,0 +1,329 @@
/*
SDL - Simple DirectMedia Layer
Copyright (C) 1997-2009 Sam Lantinga
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Sam Lantinga
slouken@libsdl.org
*/
// Fake - only structs. Shamelessly pilfered from the SDL library.
// Needed for processing its event types without polluting our namespaces with C garbage
#pragma once
namespace SDL
{
/** What we really want is a mapping of every raw key on the keyboard.
* To support international keyboards, we use the range 0xA1 - 0xFF
* as international virtual keycodes. We'll follow in the footsteps of X11...
* @brief The names of the keys
*/
enum Key
{
/** @name ASCII mapped keysyms
* The keyboard syms have been cleverly chosen to map to ASCII
*/
/*@{*/
K_UNKNOWN = 0,
K_FIRST = 0,
K_BACKSPACE = 8,
K_TAB = 9,
K_CLEAR = 12,
K_RETURN = 13,
K_PAUSE = 19,
K_ESCAPE = 27,
K_SPACE = 32,
K_EXCLAIM = 33,
K_QUOTEDBL = 34,
K_HASH = 35,
K_DOLLAR = 36,
K_AMPERSAND = 38,
K_QUOTE = 39,
K_LEFTPAREN = 40,
K_RIGHTPAREN = 41,
K_ASTERISK = 42,
K_PLUS = 43,
K_COMMA = 44,
K_MINUS = 45,
K_PERIOD = 46,
K_SLASH = 47,
K_0 = 48,
K_1 = 49,
K_2 = 50,
K_3 = 51,
K_4 = 52,
K_5 = 53,
K_6 = 54,
K_7 = 55,
K_8 = 56,
K_9 = 57,
K_COLON = 58,
K_SEMICOLON = 59,
K_LESS = 60,
K_EQUALS = 61,
K_GREATER = 62,
K_QUESTION = 63,
K_AT = 64,
/*
Skip uppercase letters
*/
K_LEFTBRACKET = 91,
K_BACKSLASH = 92,
K_RIGHTBRACKET = 93,
K_CARET = 94,
K_UNDERSCORE = 95,
K_BACKQUOTE = 96,
K_a = 97,
K_b = 98,
K_c = 99,
K_d = 100,
K_e = 101,
K_f = 102,
K_g = 103,
K_h = 104,
K_i = 105,
K_j = 106,
K_k = 107,
K_l = 108,
K_m = 109,
K_n = 110,
K_o = 111,
K_p = 112,
K_q = 113,
K_r = 114,
K_s = 115,
K_t = 116,
K_u = 117,
K_v = 118,
K_w = 119,
K_x = 120,
K_y = 121,
K_z = 122,
K_DELETE = 127,
/* End of ASCII mapped keysyms */
/*@}*/
/** @name International keyboard syms */
/*@{*/
K_WORLD_0 = 160, /* 0xA0 */
K_WORLD_1 = 161,
K_WORLD_2 = 162,
K_WORLD_3 = 163,
K_WORLD_4 = 164,
K_WORLD_5 = 165,
K_WORLD_6 = 166,
K_WORLD_7 = 167,
K_WORLD_8 = 168,
K_WORLD_9 = 169,
K_WORLD_10 = 170,
K_WORLD_11 = 171,
K_WORLD_12 = 172,
K_WORLD_13 = 173,
K_WORLD_14 = 174,
K_WORLD_15 = 175,
K_WORLD_16 = 176,
K_WORLD_17 = 177,
K_WORLD_18 = 178,
K_WORLD_19 = 179,
K_WORLD_20 = 180,
K_WORLD_21 = 181,
K_WORLD_22 = 182,
K_WORLD_23 = 183,
K_WORLD_24 = 184,
K_WORLD_25 = 185,
K_WORLD_26 = 186,
K_WORLD_27 = 187,
K_WORLD_28 = 188,
K_WORLD_29 = 189,
K_WORLD_30 = 190,
K_WORLD_31 = 191,
K_WORLD_32 = 192,
K_WORLD_33 = 193,
K_WORLD_34 = 194,
K_WORLD_35 = 195,
K_WORLD_36 = 196,
K_WORLD_37 = 197,
K_WORLD_38 = 198,
K_WORLD_39 = 199,
K_WORLD_40 = 200,
K_WORLD_41 = 201,
K_WORLD_42 = 202,
K_WORLD_43 = 203,
K_WORLD_44 = 204,
K_WORLD_45 = 205,
K_WORLD_46 = 206,
K_WORLD_47 = 207,
K_WORLD_48 = 208,
K_WORLD_49 = 209,
K_WORLD_50 = 210,
K_WORLD_51 = 211,
K_WORLD_52 = 212,
K_WORLD_53 = 213,
K_WORLD_54 = 214,
K_WORLD_55 = 215,
K_WORLD_56 = 216,
K_WORLD_57 = 217,
K_WORLD_58 = 218,
K_WORLD_59 = 219,
K_WORLD_60 = 220,
K_WORLD_61 = 221,
K_WORLD_62 = 222,
K_WORLD_63 = 223,
K_WORLD_64 = 224,
K_WORLD_65 = 225,
K_WORLD_66 = 226,
K_WORLD_67 = 227,
K_WORLD_68 = 228,
K_WORLD_69 = 229,
K_WORLD_70 = 230,
K_WORLD_71 = 231,
K_WORLD_72 = 232,
K_WORLD_73 = 233,
K_WORLD_74 = 234,
K_WORLD_75 = 235,
K_WORLD_76 = 236,
K_WORLD_77 = 237,
K_WORLD_78 = 238,
K_WORLD_79 = 239,
K_WORLD_80 = 240,
K_WORLD_81 = 241,
K_WORLD_82 = 242,
K_WORLD_83 = 243,
K_WORLD_84 = 244,
K_WORLD_85 = 245,
K_WORLD_86 = 246,
K_WORLD_87 = 247,
K_WORLD_88 = 248,
K_WORLD_89 = 249,
K_WORLD_90 = 250,
K_WORLD_91 = 251,
K_WORLD_92 = 252,
K_WORLD_93 = 253,
K_WORLD_94 = 254,
K_WORLD_95 = 255, /* 0xFF */
/*@}*/
/** @name Numeric keypad */
/*@{*/
K_KP0 = 256,
K_KP1 = 257,
K_KP2 = 258,
K_KP3 = 259,
K_KP4 = 260,
K_KP5 = 261,
K_KP6 = 262,
K_KP7 = 263,
K_KP8 = 264,
K_KP9 = 265,
K_KP_PERIOD = 266,
K_KP_DIVIDE = 267,
K_KP_MULTIPLY = 268,
K_KP_MINUS = 269,
K_KP_PLUS = 270,
K_KP_ENTER = 271,
K_KP_EQUALS = 272,
/*@}*/
/** @name Arrows + Home/End pad */
/*@{*/
K_UP = 273,
K_DOWN = 274,
K_RIGHT = 275,
K_LEFT = 276,
K_INSERT = 277,
K_HOME = 278,
K_END = 279,
K_PAGEUP = 280,
K_PAGEDOWN = 281,
/*@}*/
/** @name Function keys */
/*@{*/
K_F1 = 282,
K_F2 = 283,
K_F3 = 284,
K_F4 = 285,
K_F5 = 286,
K_F6 = 287,
K_F7 = 288,
K_F8 = 289,
K_F9 = 290,
K_F10 = 291,
K_F11 = 292,
K_F12 = 293,
K_F13 = 294,
K_F14 = 295,
K_F15 = 296,
/*@}*/
/** @name Key state modifier keys */
/*@{*/
K_NUMLOCK = 300,
K_CAPSLOCK = 301,
K_SCROLLOCK = 302,
K_RSHIFT = 303,
K_LSHIFT = 304,
K_RCTRL = 305,
K_LCTRL = 306,
K_RALT = 307,
K_LALT = 308,
K_RMETA = 309,
K_LMETA = 310,
K_LSUPER = 311, /**< Left "Windows" key */
K_RSUPER = 312, /**< Right "Windows" key */
K_MODE = 313, /**< "Alt Gr" key */
K_COMPOSE = 314, /**< Multi-key compose key */
/*@}*/
/** @name Miscellaneous function keys */
/*@{*/
K_HELP = 315,
K_PRINT = 316,
K_SYSREQ = 317,
K_BREAK = 318,
K_MENU = 319,
K_POWER = 320, /**< Power Macintosh power key */
K_EURO = 321, /**< Some european keyboards */
K_UNDO = 322, /**< Atari keyboard has Undo */
/*@}*/
/* Add any other keys here */
K_LAST
};
/** Enumeration of valid key mods (possibly OR'd together) */
enum Mod {
KMOD_NONE = 0x0000,
KMOD_LSHIFT= 0x0001,
KMOD_RSHIFT= 0x0002,
KMOD_LCTRL = 0x0040,
KMOD_RCTRL = 0x0080,
KMOD_LALT = 0x0100,
KMOD_RALT = 0x0200,
KMOD_LMETA = 0x0400,
KMOD_RMETA = 0x0800,
KMOD_NUM = 0x1000,
KMOD_CAPS = 0x2000,
KMOD_MODE = 0x4000,
KMOD_RESERVED = 0x8000,
KMOD_CTRL = (KMOD_LCTRL|KMOD_RCTRL),
KMOD_SHIFT = (KMOD_LSHIFT|KMOD_RSHIFT),
KMOD_ALT = (KMOD_LALT|KMOD_RALT),
KMOD_META = (KMOD_LMETA|KMOD_RMETA)
};
}

@ -58,12 +58,14 @@ using namespace DFHack;
#include "df/viewscreen_layer_overall_healthst.h"
#include "df/viewscreen_layer_assigntradest.h"
#include "df/viewscreen_layer_militaryst.h"
#include "df/viewscreen_layer_stockpilest.h"
#include "df/viewscreen_petst.h"
#include "df/viewscreen_tradegoodsst.h"
#include "df/viewscreen_storesst.h"
#include "df/ui_unit_view_mode.h"
#include "df/ui_sidebar_menus.h"
#include "df/ui_look_list.h"
#include "df/ui_advmode.h"
#include "df/job.h"
#include "df/ui_build_selector.h"
#include "df/building_workshopst.h"
@ -273,7 +275,9 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
break;
case Burrows:
if (ui->burrows.in_add_units_mode)
if (ui->burrows.in_confirm_delete)
focus += "/ConfirmDelete";
else if (ui->burrows.in_add_units_mode)
focus += "/AddUnits";
else if (ui->burrows.in_edit_name_mode)
focus += "/EditName";
@ -288,6 +292,16 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
}
}
DEFINE_GET_FOCUS_STRING_HANDLER(dungeonmode)
{
using df::global::ui_advmode;
if (!ui_advmode)
return;
focus += "/" + enum_item_key(ui_advmode->menu);
}
DEFINE_GET_FOCUS_STRING_HANDLER(unitlist)
{
focus += "/" + enum_item_key(screen->page);
@ -407,6 +421,35 @@ DEFINE_GET_FOCUS_STRING_HANDLER(stores)
focus += "/Items";
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_stockpile)
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
auto list3 = getLayerList(screen, 2);
if (!list1 || !list2 || !list3 || !screen->settings) return;
auto group = screen->cur_group;
if (group != vector_get(screen->group_ids, list1->cursor))
return;
focus += "/" + enum_item_key(group);
auto bits = vector_get(screen->group_bits, list1->cursor);
if (bits.whole && !(bits.whole & screen->settings->flags.whole))
{
focus += "/Off";
return;
}
focus += "/On";
if (list2->bright || list3->bright || screen->list_ids.empty()) {
focus += "/" + enum_item_key(screen->cur_list);
if (list3->bright)
focus += (screen->item_names.empty() ? "/None" : "/Item");
}
}
std::string Gui::getFocusString(df::viewscreen *top)
{

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

@ -613,12 +613,45 @@ df::nemesis_record *Units::getNemesis(df::unit *unit)
return NULL;
}
static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag)
{
auto creature = df::creature_raw::find(race);
if (!creature)
return false;
auto craw = vector_get(creature->caste, caste);
if (!craw)
return false;
return craw->flags.is_set(flag);
}
static bool isCrazed(df::unit *unit)
{
if (unit->flags3.bits.scuttle)
return false;
if (unit->curse.rem_tags1.bits.CRAZED)
return false;
if (unit->curse.add_tags1.bits.CRAZED)
return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED);
}
static bool isOpposedToLife(df::unit *unit)
{
if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE)
return false;
if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE)
return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CANNOT_UNDEAD);
}
bool DFHack::Units::isDead(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
return unit->flags1.bits.dead;
return unit->flags1.bits.dead ||
unit->flags3.bits.ghostly;
}
bool DFHack::Units::isAlive(df::unit *unit)
@ -636,8 +669,11 @@ bool DFHack::Units::isSane(df::unit *unit)
if (unit->flags1.bits.dead ||
unit->flags3.bits.ghostly ||
unit->curse.add_tags1.bits.OPPOSED_TO_LIFE ||
unit->curse.add_tags1.bits.CRAZED)
isOpposedToLife(unit) ||
unit->unknown8.unk2)
return false;
if (unit->unknown8.normal_race == unit->unknown8.were_race && isCrazed(unit))
return false;
switch (unit->mood)
@ -657,19 +693,38 @@ bool DFHack::Units::isCitizen(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
// Copied from the conditions used to decide game over,
// except that the game appears to let melancholy/raving
// dwarves count as citizens.
if (!isDwarf(unit) || !isSane(unit))
return false;
if (unit->flags1.bits.marauder ||
unit->flags1.bits.invader_origin ||
unit->flags1.bits.active_invader ||
unit->flags1.bits.forest ||
unit->flags1.bits.merchant ||
unit->flags1.bits.diplomat)
return false;
if (unit->flags1.bits.tame)
return true;
return unit->civ_id == ui->civ_id &&
!unit->flags1.bits.merchant &&
!unit->flags1.bits.diplomat &&
unit->civ_id != -1 &&
!unit->flags2.bits.underworld &&
!unit->flags2.bits.resident &&
!unit->flags1.bits.dead &&
!unit->flags3.bits.ghostly;
!unit->flags2.bits.visitor_uninvited &&
!unit->flags2.bits.visitor;
}
bool DFHack::Units::isDwarf(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
return unit->race == ui->race_id;
return unit->race == ui->race_id ||
unit->unknown8.normal_race == ui->race_id;
}
double DFHack::Units::getAge(df::unit *unit, bool true_age)

@ -1 +1 @@
Subproject commit 234d0f57a927f306f2052fc2f45d38b3201ddee6
Subproject commit c381884664c71adefbec44258a734def2c88dacc

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

@ -89,13 +89,13 @@ static void element(const char* name, const uint32_t content, ostream& out, cons
static void printAttributes(color_ostream &con, df::unit* cre, ostream& out) {
out << " <Attributes>" << endl;
for (int i = 0; i < NUM_CREATURE_PHYSICAL_ATTRIBUTES; i++) {
element(physicals[i], cre->body.physical_attrs[i].unk1, out, " ");
element(physicals[i], cre->body.physical_attrs[i].value, out, " ");
}
df::unit_soul * s = cre->status.current_soul;
if (s) {
for (int i = 0; i < NUM_CREATURE_MENTAL_ATTRIBUTES; i++) {
element(mentals[i], s->mental_attrs[i].unk1, out, " ");
element(mentals[i], s->mental_attrs[i].value, out, " ");
}
}
out << " </Attributes>" << endl;

@ -28,7 +28,7 @@ using namespace std;
#include "df/body_part_raw.h"
#include "MiscUtils.h"
#include "df/unit_inventory_item.h"
#include "df/body_part_template_flags.h"
#include "df/body_part_raw_flags.h"
#include "df/creature_raw.h"
#include "df/caste_raw.h"
#include "df/body_detail_plan.h"
@ -292,7 +292,7 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u
else if (bpIndex < unit->body.body_plan->body_parts.size())
{
// The current body part is not the one that was specified in the function call, but we can keep searching
if (verbose) { Core::printerr("Found bodypart %s; not a match; continuing search.\n", currPart->part_code.c_str()); }
if (verbose) { Core::printerr("Found bodypart %s; not a match; continuing search.\n", currPart->token.c_str()); }
continue;
}
else
@ -302,35 +302,35 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u
return false;
}
if (verbose) { Core::print("Inspecting bodypart %s.\n", currPart->part_code.c_str()); }
if (verbose) { Core::print("Inspecting bodypart %s.\n", currPart->token.c_str()); }
// Inspect the current bodypart
if (item->getType() == df::enums::item_type::GLOVES && currPart->flags.is_set(df::body_part_template_flags::GRASP) &&
((item->getGloveHandedness() == const_GloveLeftHandedness && currPart->flags.is_set(df::body_part_template_flags::LEFT)) ||
(item->getGloveHandedness() == const_GloveRightHandedness && currPart->flags.is_set(df::body_part_template_flags::RIGHT))))
if (item->getType() == df::enums::item_type::GLOVES && currPart->flags.is_set(df::body_part_raw_flags::GRASP) &&
((item->getGloveHandedness() == const_GloveLeftHandedness && currPart->flags.is_set(df::body_part_raw_flags::LEFT)) ||
(item->getGloveHandedness() == const_GloveRightHandedness && currPart->flags.is_set(df::body_part_raw_flags::RIGHT))))
{
if (verbose) { Core::print("Hand found (%s)...", currPart->part_code.c_str()); }
if (verbose) { Core::print("Hand found (%s)...", currPart->token.c_str()); }
}
else if (item->getType() == df::enums::item_type::HELM && currPart->flags.is_set(df::body_part_template_flags::HEAD))
else if (item->getType() == df::enums::item_type::HELM && currPart->flags.is_set(df::body_part_raw_flags::HEAD))
{
if (verbose) { Core::print("Head found (%s)...", currPart->part_code.c_str()); }
if (verbose) { Core::print("Head found (%s)...", currPart->token.c_str()); }
}
else if (item->getType() == df::enums::item_type::ARMOR && currPart->flags.is_set(df::body_part_template_flags::UPPERBODY))
else if (item->getType() == df::enums::item_type::ARMOR && currPart->flags.is_set(df::body_part_raw_flags::UPPERBODY))
{
if (verbose) { Core::print("Upper body found (%s)...", currPart->part_code.c_str()); }
if (verbose) { Core::print("Upper body found (%s)...", currPart->token.c_str()); }
}
else if (item->getType() == df::enums::item_type::PANTS && currPart->flags.is_set(df::body_part_template_flags::LOWERBODY))
else if (item->getType() == df::enums::item_type::PANTS && currPart->flags.is_set(df::body_part_raw_flags::LOWERBODY))
{
if (verbose) { Core::print("Lower body found (%s)...", currPart->part_code.c_str()); }
if (verbose) { Core::print("Lower body found (%s)...", currPart->token.c_str()); }
}
else if (item->getType() == df::enums::item_type::SHOES && currPart->flags.is_set(df::body_part_template_flags::STANCE))
else if (item->getType() == df::enums::item_type::SHOES && currPart->flags.is_set(df::body_part_raw_flags::STANCE))
{
if (verbose) { Core::print("Foot found (%s)...", currPart->part_code.c_str()); }
if (verbose) { Core::print("Foot found (%s)...", currPart->token.c_str()); }
}
else if (targetBodyPart && ignoreRestrictions)
{
// The BP in question would normally be considered ineligible for equipment. But since it was deliberately specified by the user, we'll proceed anyways.
if (verbose) { Core::print("Non-standard bodypart found (%s)...", targetBodyPart->part_code.c_str()); }
if (verbose) { Core::print("Non-standard bodypart found (%s)...", targetBodyPart->token.c_str()); }
}
else if (targetBodyPart)
{
@ -538,16 +538,16 @@ command_result df_forceequip(color_ostream &out, vector <string> & parameters)
{
// Tentatively assume that the part is a match
targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex);
if (targetBodyPart->part_code.compare(targetBodyPartCode) == 0)
if (targetBodyPart->token.compare(targetBodyPartCode) == 0)
{
// It is indeed a match; exit the loop (while leaving the variable populated)
if (verbose) { out.print("Matching bodypart (%s) found.\n", targetBodyPart->part_name.c_str()); }
if (verbose) { out.print("Matching bodypart (%s) found.\n", targetBodyPart->token.c_str()); }
break;
}
else
{
// Not a match; nullify the variable (it will get re-populated on the next pass through the loop)
if (verbose) { out.printerr("Bodypart \"%s\" does not match \"%s\".\n", targetBodyPart->part_code.c_str(), targetBodyPartCode.c_str()); }
if (verbose) { out.printerr("Bodypart \"%s\" does not match \"%s\".\n", targetBodyPart->token.c_str(), targetBodyPartCode.c_str()); }
targetBodyPart = NULL;
}
}

@ -1,29 +1,33 @@
find_package(Ruby)
if(RUBY_FOUND)
ADD_CUSTOM_COMMAND(
OUTPUT ruby-autogen.cpp
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.cpp
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl
)
ADD_EXECUTABLE(ruby-autogen ruby-autogen.cpp)
if(CMAKE_COMPILER_IS_GNUCC)
set_target_properties (ruby-autogen PROPERTIES COMPILE_FLAGS "-Wno-invalid-offsetof")
endif(CMAKE_COMPILER_IS_GNUCC)
ADD_CUSTOM_COMMAND(
OUTPUT ruby-autogen.offsets
COMMAND ruby-autogen ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.offsets
DEPENDS ruby-autogen
)
ADD_CUSTOM_COMMAND(
OUTPUT ruby-autogen.rb
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.offsets ${CMAKE_CURRENT_SOURCE_DIR}/ruby-memstruct.rb
DEPENDS ruby-autogen.offsets ruby-memstruct.rb
)
ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb)
include_directories("${dfhack_SOURCE_DIR}/depends/tthread" ${RUBY_INCLUDE_PATH})
DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
target_link_libraries(ruby ${RUBY_LIBRARY})
install(FILES ruby.rb ruby-autogen.rb DESTINATION ${DFHACK_LIBRARY_DESTINATION})
else(RUBY_FOUND)
MESSAGE(STATUS "Required library (ruby) not found - ruby plugin can't be built.")
endif(RUBY_FOUND)
OPTION(DL_RUBY "download libruby from the internet" ON)
IF (DL_RUBY)
IF (UNIX)
FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/libruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.tar.gz
EXPECTED_MD5 eb2adea59911f68e6066966c1352f291)
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf libruby187.tar.gz
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
FILE(RENAME libruby1.8.so.1.8.7 libruby.so)
SET(RUBYLIB libruby.so)
ELSE (UNIX)
FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/msvcrtruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz
EXPECTED_MD5 9f4a1659ac3a5308f32d3a1937bbeeae)
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf msvcrtruby187.tar.gz
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
FILE(RENAME msvcrt-ruby18.dll libruby.dll)
SET(RUBYLIB libruby.dll)
ENDIF(UNIX)
ENDIF(DL_RUBY)
ADD_CUSTOM_COMMAND(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl
COMMENT ruby-autogen.rb
)
ADD_CUSTOM_TARGET(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb)
INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread")
DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
ADD_DEPENDENCIES(ruby ruby-autogen-rb)
INSTALL(FILES ruby.rb ruby-autogen.rb ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION})

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

File diff suppressed because it is too large Load Diff

@ -2,7 +2,6 @@ module DFHack
# allocate a new building object
def self.building_alloc(type, subtype=-1, custom=-1)
type = BuildingType.to_sym(type)
cls = rtti_n2c[BuildingType::Classname[type].to_sym]
raise "invalid building type #{type.inspect}" if not cls
bld = cls.cpp_new

@ -1,747 +0,0 @@
module DFHack
module MemHack
INSPECT_SIZE_LIMIT=16384
class MemStruct
attr_accessor :_memaddr
def _at(addr) ; d = dup ; d._memaddr = addr ; d ; end
def _get ; self ; end
def _cpp_init ; end
end
class Compound < MemStruct
class << self
attr_accessor :_fields, :_rtti_classname, :_sizeof
def field(name, offset)
struct = yield
return if not struct
@_fields ||= []
@_fields << [name, offset, struct]
define_method(name) { struct._at(@_memaddr+offset)._get }
define_method("#{name}=") { |v| struct._at(@_memaddr+offset)._set(v) }
end
def _fields_ancestors
if superclass.respond_to?(:_fields_ancestors)
superclass._fields_ancestors + _fields.to_a
else
_fields.to_a
end
end
def number(bits, signed, initvalue=nil, enum=nil)
Number.new(bits, signed, initvalue, enum)
end
def float
Float.new
end
def bit(shift)
BitField.new(shift, 1)
end
def bits(shift, len, enum=nil)
BitField.new(shift, len, enum)
end
def pointer
Pointer.new((yield if block_given?))
end
def pointer_ary(tglen)
PointerAry.new(tglen, yield)
end
def static_array(len, tglen, indexenum=nil)
StaticArray.new(tglen, len, indexenum, yield)
end
def static_string(len)
StaticString.new(len)
end
def stl_vector(tglen=nil)
tg = yield if tglen
case tglen
when 1; StlVector8.new(tg)
when 2; StlVector16.new(tg)
else StlVector32.new(tg)
end
end
def stl_string
StlString.new
end
def stl_bit_vector
StlBitVector.new
end
def stl_deque(tglen)
StlDeque.new(tglen, yield)
end
def df_flagarray(indexenum=nil)
DfFlagarray.new(indexenum)
end
def df_array(tglen)
DfArray.new(tglen, yield)
end
def df_linked_list
DfLinkedList.new(yield)
end
def global(glob)
Global.new(glob)
end
def compound(name=nil, &b)
m = Class.new(Compound)
DFHack.const_set(name, m) if name
m.instance_eval(&b)
m.new
end
def rtti_classname(n)
DFHack.rtti_register(n, self)
@_rtti_classname = n
end
def sizeof(n)
@_sizeof = n
end
# allocate a new c++ object, return its ruby wrapper
def cpp_new
ptr = DFHack.malloc(_sizeof)
if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname)
DFHack.memory_write_int32(ptr, vt)
# TODO call constructor
end
o = new._at(ptr)
o._cpp_init
o
end
end
def _cpp_init
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
end
def _set(h)
case h
when Hash; h.each { |k, v| send("_#{k}=", v) }
when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) }
when Compound; _field_names.each { |n| send("#{n}=", h.send(n)) }
else raise 'wut?'
end
end
def _fields ; self.class._fields.to_a ; end
def _fields_ancestors ; self.class._fields_ancestors.to_a ; end
def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end
def _rtti_classname ; self.class._rtti_classname ; end
def _sizeof ; self.class._sizeof ; end
@@inspecting = {} # avoid infinite recursion on mutually-referenced objects
def inspect
cn = self.class.name.sub(/^DFHack::/, '')
cn << ' @' << ('0x%X' % _memaddr) if cn != ''
out = "#<#{cn}"
return out << ' ...>' if @@inspecting[_memaddr]
@@inspecting[_memaddr] = true
_fields_ancestors.each { |n, o, s|
out << ' ' if out.length != 0 and out[-1, 1] != ' '
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << inspect_field(n, o, s)
}
out.chomp!(' ')
@@inspecting.delete _memaddr
out << '>'
end
def inspect_field(n, o, s)
if s.kind_of?(BitField) and s._len == 1
send(n) ? n.to_s : ''
elsif s.kind_of?(Pointer)
"#{n}=#{s._at(@_memaddr+o).inspect}"
elsif n == :_whole
"_whole=0x#{_whole.to_s(16)}"
else
v = send(n).inspect
"#{n}=#{v}"
end
rescue
"#{n}=ERR(#{$!})"
end
end
class Enum
# number -> symbol
def self.enum
# ruby weirdness, needed to make the constants 'virtual'
@enum ||= const_get(:ENUM)
end
# symbol -> number
def self.nume
@nume ||= const_get(:NUME)
end
def self.to_i(i)
nume[i] || i
end
def self.to_sym(i)
enum[i] || i
end
end
class Number < MemStruct
attr_accessor :_bits, :_signed, :_initvalue, :_enum
def initialize(bits, signed, initvalue, enum)
@_bits = bits
@_signed = signed
@_initvalue = initvalue
@_enum = enum
end
def _get
v = case @_bits
when 32; DFHack.memory_read_int32(@_memaddr)
when 16; DFHack.memory_read_int16(@_memaddr)
when 8; DFHack.memory_read_int8( @_memaddr)
when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32)
end
v &= (1 << @_bits) - 1 if not @_signed
v = @_enum.to_sym(v) if @_enum
v
end
def _set(v)
v = @_enum.to_i(v) if @_enum
case @_bits
when 32; DFHack.memory_write_int32(@_memaddr, v)
when 16; DFHack.memory_write_int16(@_memaddr, v)
when 8; DFHack.memory_write_int8( @_memaddr, v)
when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32)
end
end
def _cpp_init
_set(@_initvalue) if @_initvalue
end
end
class Float < MemStruct
def _get
DFHack.memory_read_float(@_memaddr)
end
def _set(v)
DFHack.memory_write_float(@_memaddr, v)
end
def _cpp_init
_set(0.0)
end
end
class BitField < MemStruct
attr_accessor :_shift, :_len, :_enum
def initialize(shift, len, enum=nil)
@_shift = shift
@_len = len
@_enum = enum
end
def _mask
(1 << @_len) - 1
end
def _get
v = DFHack.memory_read_int32(@_memaddr) >> @_shift
if @_len == 1
((v & 1) == 0) ? false : true
else
v &= _mask
v = @_enum.to_sym(v) if @_enum
v
end
end
def _set(v)
if @_len == 1
# allow 'bit = 0'
v = (v && v != 0 ? 1 : 0)
end
v = @_enum.to_i(v) if @_enum
v = (v & _mask) << @_shift
ori = DFHack.memory_read_int32(@_memaddr) & 0xffffffff
DFHack.memory_write_int32(@_memaddr, ori - (ori & ((-1 & _mask) << @_shift)) + v)
end
end
class Pointer < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def _getp
DFHack.memory_read_int32(@_memaddr) & 0xffffffff
end
def _get
addr = _getp
return if addr == 0
@_tg._at(addr)._get
end
# XXX shaky...
def _set(v)
if v.kind_of?(Pointer)
DFHack.memory_write_int32(@_memaddr, v._getp)
elsif v.kind_of?(MemStruct)
DFHack.memory_write_int32(@_memaddr, v._memaddr)
else
_get._set(v)
end
end
def inspect
ptr = _getp
if ptr == 0
'NULL'
else
cn = ''
cn = @_tg.class.name.sub(/^DFHack::/, '').sub(/^MemHack::/, '') if @_tg
cn = @_tg._glob if cn == 'Global'
"#<Pointer #{cn} #{'0x%X' % _getp}>"
end
end
end
class PointerAry < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
def _getp(i=0)
delta = (i != 0 ? i*@_tglen : 0)
(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta
end
def _get
addr = _getp
return if addr == 0
self
end
def [](i)
addr = _getp(i)
return if addr == 0
@_tg._at(addr)._get
end
def []=(i, v)
addr = _getp(i)
raise 'null pointer' if addr == 0
@_tg._at(addr)._set(v)
end
def inspect ; ptr = _getp ; (ptr == 0) ? 'NULL' : "#<PointerAry #{'0x%X' % ptr}>" ; end
end
module Enumerable
include ::Enumerable
attr_accessor :_indexenum
def each ; (0...length).each { |i| yield self[i] } ; end
def inspect
out = '['
each_with_index { |e, idx|
out << ', ' if out.length > 1
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << "#{_indexenum.to_sym(idx)}=" if _indexenum
out << e.inspect
}
out << ']'
end
def empty? ; length == 0 ; end
def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end
end
class StaticArray < MemStruct
attr_accessor :_tglen, :_length, :_indexenum, :_tg
def initialize(tglen, length, indexenum, tg)
@_tglen = tglen
@_length = length
@_indexenum = indexenum
@_tg = tg
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
def _cpp_init
_length.times { |i| _tgat(i)._cpp_init }
end
alias length _length
alias size _length
def _tgat(i)
@_tg._at(@_memaddr + i*@_tglen) if i >= 0 and i < @_length
end
def [](i)
i = _indexenum.to_i(i) if _indexenum
i += @_length if i < 0
_tgat(i)._get
end
def []=(i, v)
i = _indexenum.to_i(i) if _indexenum
i += @_length if i < 0
_tgat(i)._set(v)
end
include Enumerable
end
class StaticString < MemStruct
attr_accessor :_length
def initialize(length)
@_length = length
end
def _get
DFHack.memory_read(@_memaddr, @_length)
end
def _set(v)
DFHack.memory_write(@_memaddr, v[0, @_length])
end
end
class StlVector32 < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def length
DFHack.memory_vector32_length(@_memaddr)
end
def size ; length ; end # alias wouldnt work for subclasses
def valueptr_at(idx)
DFHack.memory_vector32_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector32_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector32_delete(@_memaddr, idx)
end
def _set(v)
delete_at(length-1) while length > v.length # match lengthes
v.each_with_index { |e, i| self[i] = e } # patch entries
end
def _cpp_init
DFHack.memory_vector_init(@_memaddr)
end
def clear
delete_at(length-1) while length > 0
end
def [](idx)
idx += length if idx < 0
@_tg._at(valueptr_at(idx))._get if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, 0)
elsif idx < 0
raise 'invalid idx'
end
@_tg._at(valueptr_at(idx))._set(v)
end
def push(v)
self[length] = v
self
end
def <<(v) ; push(v) ; end
def pop
l = length
if l > 0
v = self[l-1]
delete_at(l-1)
end
v
end
include Enumerable
# do a binary search in an ordered vector for a specific target attribute
# ex: world.history.figures.binsearch(unit.hist_figure_id)
def binsearch(target, field=:id)
o_start = 0
o_end = length - 1
while o_end >= o_start
o_half = o_start + (o_end-o_start)/2
obj = self[o_half]
oval = obj.send(field)
if oval == target
return obj
elsif oval < target
o_start = o_half+1
else
o_end = o_half-1
end
end
end
end
class StlVector16 < StlVector32
def length
DFHack.memory_vector16_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector16_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector16_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector16_delete(@_memaddr, idx)
end
end
class StlVector8 < StlVector32
def length
DFHack.memory_vector8_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector8_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector8_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector8_delete(@_memaddr, idx)
end
end
class StlBitVector < StlVector32
def initialize ; end
def length
DFHack.memory_vectorbool_length(@_memaddr)
end
def insert_at(idx, val)
DFHack.memory_vectorbool_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vectorbool_delete(@_memaddr, idx)
end
def [](idx)
idx += length if idx < 0
DFHack.memory_vectorbool_at(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, v)
elsif idx < 0
raise 'invalid idx'
else
DFHack.memory_vectorbool_setat(@_memaddr, idx, v)
end
end
end
class StlString < MemStruct
def _get
DFHack.memory_read_stlstring(@_memaddr)
end
def _set(v)
DFHack.memory_write_stlstring(@_memaddr, v)
end
def _cpp_init
DFHack.memory_stlstring_init(@_memaddr)
end
end
class StlDeque < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every
# possible struct size, like for StlVector. Just ignore it for now, deque are rare enough.
def inspect ; "#<StlDeque>" ; end
end
class DfFlagarray < MemStruct
attr_accessor :_indexenum
def initialize(indexenum)
@_indexenum = indexenum
end
def length
DFHack.memory_bitarray_length(@_memaddr)
end
# TODO _cpp_init
def size ; length ; end
def resize(len)
DFHack.memory_bitarray_resize(@_memaddr, len)
end
def [](idx)
idx = _indexenum.to_i(idx) if _indexenum
idx += length if idx < 0
DFHack.memory_bitarray_isset(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx = _indexenum.to_i(idx) if _indexenum
idx += length if idx < 0
if idx >= length or idx < 0
raise 'invalid idx'
else
DFHack.memory_bitarray_set(@_memaddr, idx, v)
end
end
include Enumerable
end
class DfArray < Compound
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_length, 4) { number 16, false }
def length ; _length ; end
def size ; _length ; end
# TODO _cpp_init
def _tgat(i)
@_tg._at(_ptr + i*@_tglen) if i >= 0 and i < _length
end
def [](i)
i += _length if i < 0
_tgat(i)._get
end
def []=(i, v)
i += _length if i < 0
_tgat(i)._set(v)
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
include Enumerable
end
class DfLinkedList < Compound
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_prev, 4) { number 32, false }
field(:_next, 8) { number 32, false }
def item
# With the current xml structure, currently _tg designate
# the type of the 'next' and 'prev' fields, not 'item'.
# List head has item == NULL, so we can safely return nil.
#addr = _ptr
#return if addr == 0
#@_tg._at(addr)._get
end
def item=(v)
#addr = _ptr
#raise 'null pointer' if addr == 0
#@_tg.at(addr)._set(v)
raise 'null pointer'
end
def prev
addr = _prev
return if addr == 0
@_tg._at(addr)._get
end
def next
addr = _next
return if addr == 0
@_tg._at(addr)._get
end
include Enumerable
def each
o = self
while o
yield o.item if o.item
o = o.next
end
end
def inspect ; "#<DfLinkedList #{item.inspect} prev=#{'0x%X' % _prev} next=#{'0x%X' % _next}>" ; end
end
class Global < MemStruct
attr_accessor :_glob
def initialize(glob)
@_glob = glob
end
def _at(addr)
g = DFHack.const_get(@_glob)
g = DFHack.rtti_getclassat(g, addr)
g.new._at(addr)
end
def inspect ; "#<#{@_glob}>" ; end
end
end # module MemHack
# cpp rtti name -> rb class
@rtti_n2c = {}
@rtti_c2n = {}
# cpp rtti name -> vtable ptr
@rtti_n2v = {}
@rtti_v2n = {}
def self.rtti_n2c ; @rtti_n2c ; end
def self.rtti_c2n ; @rtti_c2n ; end
def self.rtti_n2v ; @rtti_n2v ; end
def self.rtti_v2n ; @rtti_v2n ; end
# register a ruby class with a cpp rtti class name
def self.rtti_register(cppname, cls)
@rtti_n2c[cppname] = cls
@rtti_c2n[cls] = cppname
end
# return the ruby class to use for the cpp object at address if rtti info is available
def self.rtti_getclassat(cls, addr)
if addr != 0 and @rtti_c2n[cls]
# rtti info exist for class => cpp object has a vtable
@rtti_n2c[rtti_readclassname(get_vtable_ptr(addr))] || cls
else
cls
end
end
# try to read the rtti classname from an object vtable pointer
def self.rtti_readclassname(vptr)
unless n = @rtti_v2n[vptr]
n = @rtti_v2n[vptr] = get_rtti_classname(vptr).to_sym
@rtti_n2v[n] = vptr
end
n
end
# return the vtable pointer from the cpp rtti name
def self.rtti_getvtable(cppname)
unless v = @rtti_n2v[cppname]
v = get_vtable(cppname.to_s)
@rtti_n2v[cppname] = v
@rtti_v2n[v] = cppname if v != 0
end
v if v != 0
end
def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0)
vmethod_do_call(obj._memaddr, voff, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3))
end
def self.vmethod_arg(arg)
case arg
when nil, false; 0
when true; 1
when Integer; arg
#when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer
when MemHack::Compound; arg._memaddr
else raise "bad vmethod arg #{arg.class}"
end
end
end

@ -11,8 +11,6 @@
#include "tinythread.h"
#include <ruby.h>
using namespace DFHack;
@ -20,6 +18,8 @@ using namespace DFHack;
// DFHack stuff
static int df_loadruby(void);
static void df_unloadruby(void);
static void df_rubythread(void*);
static command_result df_rubyload(color_ostream &out, std::vector <std::string> & parameters);
static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters);
@ -45,6 +45,11 @@ DFHACK_PLUGIN("ruby")
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
// fail silently instead of spamming the console with 'failed to initialize' if libruby is not present
// the error is still logged in stderr.log
if (!df_loadruby())
return CR_OK;
m_irun = new tthread::mutex();
m_mutex = new tthread::mutex();
r_type = RB_INIT;
@ -74,10 +79,11 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
m_mutex->lock();
if (!r_thread)
return CR_OK;
m_mutex->lock();
r_type = RB_DIE;
r_command = 0;
m_irun->unlock();
@ -90,6 +96,8 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
m_mutex->unlock();
delete m_mutex;
df_unloadruby();
return CR_OK;
}
@ -128,6 +136,9 @@ static command_result plugin_eval_rb(std::string &command)
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
if (!r_thread)
return CR_OK;
if (!onupdate_active)
return CR_OK;
@ -136,6 +147,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event e)
{
if (!r_thread)
return CR_OK;
std::string cmd = "DFHack.onstatechange ";
switch (e) {
#define SCASE(s) case SC_ ## s : cmd += ":" # s ; break
@ -146,6 +160,7 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch
SCASE(VIEWSCREEN_CHANGED);
SCASE(CORE_INITIALIZED);
SCASE(BEGIN_UNLOAD);
#undef SCASE
}
return plugin_eval_rb(cmd);
@ -191,6 +206,113 @@ static command_result df_rubyeval(color_ostream &out, std::vector <std::string>
// ruby stuff
// ruby-dev on windows is messy
// ruby.h on linux 64 is broken
// so we dynamically load libruby instead of linking it at compile time
// lib path can be set in dfhack.ini to use the system libruby, but by
// default we'll embed our own (downloaded by cmake)
// these ruby definitions are invalid for windows 64bit
typedef unsigned long VALUE;
typedef unsigned long ID;
#define Qfalse ((VALUE)0)
#define Qtrue ((VALUE)2)
#define Qnil ((VALUE)4)
#define INT2FIX(i) ((VALUE)((((long)i) << 1) | 1))
#define FIX2INT(i) (((long)i) >> 1)
#define RUBY_METHOD_FUNC(func) ((VALUE(*)(...))func)
VALUE *rb_eRuntimeError;
void (*ruby_sysinit)(int *, const char ***);
void (*ruby_init)(void);
void (*ruby_init_loadpath)(void);
void (*ruby_script)(const char*);
void (*ruby_finalize)(void);
ID (*rb_intern)(const char*);
VALUE (*rb_raise)(VALUE, const char*, ...);
VALUE (*rb_funcall)(VALUE, ID, int, ...);
VALUE (*rb_define_module)(const char*);
void (*rb_define_singleton_method)(VALUE, const char*, VALUE(*)(...), int);
void (*rb_define_const)(VALUE, const char*, VALUE);
void (*rb_load_protect)(VALUE, int, int*);
VALUE (*rb_gv_get)(const char*);
VALUE (*rb_str_new)(const char*, long);
VALUE (*rb_str_new2)(const char*);
char* (*rb_string_value_ptr)(VALUE*);
VALUE (*rb_eval_string_protect)(const char*, int*);
VALUE (*rb_ary_shift)(VALUE);
VALUE (*rb_float_new)(double);
double (*rb_num2dbl)(VALUE);
VALUE (*rb_int2inum)(long);
VALUE (*rb_uint2inum)(unsigned long);
unsigned long (*rb_num2ulong)(VALUE);
// end of rip(ruby.h)
DFHack::DFLibrary *libruby_handle;
// load the ruby library, initialize function pointers
static int df_loadruby(void)
{
const char *libpath =
#ifdef WIN32
"./libruby.dll";
#else
"hack/libruby.so";
#endif
libruby_handle = OpenPlugin(libpath);
if (!libruby_handle) {
fprintf(stderr, "Cannot initialize ruby plugin: failed to load %s\n", libpath);
return 0;
}
if (!(rb_eRuntimeError = (VALUE*)LookupPlugin(libruby_handle, "rb_eRuntimeError")))
return 0;
// XXX does msvc support decltype ? might need a #define decltype typeof
// or just assign to *(void**)(&s) = ...
// ruby_sysinit is optional (ruby1.9 only)
ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit");
#define rbloadsym(s) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #s))) return 0
rbloadsym(ruby_init);
rbloadsym(ruby_init_loadpath);
rbloadsym(ruby_script);
rbloadsym(ruby_finalize);
rbloadsym(rb_intern);
rbloadsym(rb_raise);
rbloadsym(rb_funcall);
rbloadsym(rb_define_module);
rbloadsym(rb_define_singleton_method);
rbloadsym(rb_define_const);
rbloadsym(rb_load_protect);
rbloadsym(rb_gv_get);
rbloadsym(rb_str_new);
rbloadsym(rb_str_new2);
rbloadsym(rb_string_value_ptr);
rbloadsym(rb_eval_string_protect);
rbloadsym(rb_ary_shift);
rbloadsym(rb_float_new);
rbloadsym(rb_num2dbl);
rbloadsym(rb_int2inum);
rbloadsym(rb_uint2inum);
rbloadsym(rb_num2ulong);
#undef rbloadsym
return 1;
}
static void df_unloadruby(void)
{
if (libruby_handle) {
ClosePlugin(libruby_handle);
libruby_handle = 0;
}
}
// ruby thread code
static void dump_rb_error(void)
{
@ -218,6 +340,13 @@ static void df_rubythread(void *p)
{
int state, running;
if (ruby_sysinit) {
// ruby1.9 specific API
static int argc;
static const char *argv[] = { "dfhack", 0 };
ruby_sysinit(&argc, (const char ***)&argv);
}
// initialize the ruby interpreter
ruby_init();
ruby_init_loadpath();
@ -353,7 +482,7 @@ static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
*/
static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
{
rb_raise(rb_eRuntimeError, "not implemented");
rb_raise(*rb_eRuntimeError, "not implemented");
}
static VALUE rb_dfget_global_address(VALUE self, VALUE name)
@ -402,7 +531,7 @@ static VALUE rb_dfmalloc(VALUE self, VALUE len)
{
char *ptr = (char*)malloc(FIX2INT(len));
if (!ptr)
rb_raise(rb_eRuntimeError, "no memory");
rb_raise(*rb_eRuntimeError, "no memory");
memset(ptr, 0, FIX2INT(len));
return rb_uint2inum((long)ptr);
}
@ -641,7 +770,7 @@ static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE
int ret;
fptr = (decltype(fptr))*(void**)(*that + rb_num2ulong(cppvoff));
ret = fptr(that, rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3));
return rb_uint2inum(ret);
return rb_int2inum(ret);
}

@ -1,5 +1,3 @@
require 'hack/ruby-autogen'
module DFHack
class << self
# update the ruby.cpp version to accept a block
@ -298,5 +296,759 @@ def df
DFHack
end
# load user-specified startup file
# following: definitions used by ruby-autogen.rb
module DFHack
module MemHack
INSPECT_SIZE_LIMIT=16384
class MemStruct
attr_accessor :_memaddr
def _at(addr) ; d = dup ; d._memaddr = addr ; d ; end
def _get ; self ; end
def _cpp_init ; end
end
class Compound < MemStruct
class << self
attr_accessor :_fields, :_rtti_classname, :_sizeof
def field(name, offset)
struct = yield
return if not struct
@_fields ||= []
@_fields << [name, offset, struct]
define_method(name) { struct._at(@_memaddr+offset)._get }
define_method("#{name}=") { |v| struct._at(@_memaddr+offset)._set(v) }
end
def _fields_ancestors
if superclass.respond_to?(:_fields_ancestors)
superclass._fields_ancestors + _fields.to_a
else
_fields.to_a
end
end
def number(bits, signed, initvalue=nil, enum=nil)
Number.new(bits, signed, initvalue, enum)
end
def float
Float.new
end
def bit(shift)
BitField.new(shift, 1)
end
def bits(shift, len, enum=nil)
BitField.new(shift, len, enum)
end
def pointer
Pointer.new((yield if block_given?))
end
def pointer_ary(tglen)
PointerAry.new(tglen, yield)
end
def static_array(len, tglen, indexenum=nil)
StaticArray.new(tglen, len, indexenum, yield)
end
def static_string(len)
StaticString.new(len)
end
def stl_vector(tglen=nil)
tg = yield if tglen
case tglen
when 1; StlVector8.new(tg)
when 2; StlVector16.new(tg)
else StlVector32.new(tg)
end
end
def stl_string
StlString.new
end
def stl_bit_vector
StlBitVector.new
end
def stl_deque(tglen)
StlDeque.new(tglen, yield)
end
def df_flagarray(indexenum=nil)
DfFlagarray.new(indexenum)
end
def df_array(tglen)
DfArray.new(tglen, yield)
end
def df_linked_list
DfLinkedList.new(yield)
end
def global(glob)
Global.new(glob)
end
def compound(name=nil, &b)
m = Class.new(Compound)
DFHack.const_set(name, m) if name
m.instance_eval(&b)
m.new
end
def rtti_classname(n)
DFHack.rtti_register(n, self)
@_rtti_classname = n
end
def sizeof(n)
@_sizeof = n
end
# allocate a new c++ object, return its ruby wrapper
def cpp_new
ptr = DFHack.malloc(_sizeof)
if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname)
DFHack.memory_write_int32(ptr, vt)
# TODO call constructor
end
o = new._at(ptr)
o._cpp_init
o
end
end
def _cpp_init
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
end
def _set(h)
case h
when Hash; h.each { |k, v| send("_#{k}=", v) }
when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) }
when Compound; _field_names.each { |n| send("#{n}=", h.send(n)) }
else raise 'wut?'
end
end
def _fields ; self.class._fields.to_a ; end
def _fields_ancestors ; self.class._fields_ancestors.to_a ; end
def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end
def _rtti_classname ; self.class._rtti_classname ; end
def _sizeof ; self.class._sizeof ; end
@@inspecting = {} # avoid infinite recursion on mutually-referenced objects
def inspect
cn = self.class.name.sub(/^DFHack::/, '')
cn << ' @' << ('0x%X' % _memaddr) if cn != ''
out = "#<#{cn}"
return out << ' ...>' if @@inspecting[_memaddr]
@@inspecting[_memaddr] = true
_fields_ancestors.each { |n, o, s|
out << ' ' if out.length != 0 and out[-1, 1] != ' '
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << inspect_field(n, o, s)
}
out.chomp!(' ')
@@inspecting.delete _memaddr
out << '>'
end
def inspect_field(n, o, s)
if s.kind_of?(BitField) and s._len == 1
send(n) ? n.to_s : ''
elsif s.kind_of?(Pointer)
"#{n}=#{s._at(@_memaddr+o).inspect}"
elsif n == :_whole
"_whole=0x#{_whole.to_s(16)}"
else
v = send(n).inspect
"#{n}=#{v}"
end
rescue
"#{n}=ERR(#{$!})"
end
end
class Enum
# number -> symbol
def self.enum
# ruby weirdness, needed to make the constants 'virtual'
@enum ||= const_get(:ENUM)
end
# symbol -> number
def self.nume
@nume ||= const_get(:NUME)
end
def self.int(i)
nume[i] || i
end
def self.sym(i)
enum[i] || i
end
end
class Number < MemStruct
attr_accessor :_bits, :_signed, :_initvalue, :_enum
def initialize(bits, signed, initvalue, enum)
@_bits = bits
@_signed = signed
@_initvalue = initvalue
@_enum = enum
end
def _get
v = case @_bits
when 32; DFHack.memory_read_int32(@_memaddr)
when 16; DFHack.memory_read_int16(@_memaddr)
when 8; DFHack.memory_read_int8( @_memaddr)
when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32)
end
v &= (1 << @_bits) - 1 if not @_signed
v = @_enum.sym(v) if @_enum
v
end
def _set(v)
v = @_enum.int(v) if @_enum
case @_bits
when 32; DFHack.memory_write_int32(@_memaddr, v)
when 16; DFHack.memory_write_int16(@_memaddr, v)
when 8; DFHack.memory_write_int8( @_memaddr, v)
when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32)
end
end
def _cpp_init
_set(@_initvalue) if @_initvalue
end
end
class Float < MemStruct
def _get
DFHack.memory_read_float(@_memaddr)
end
def _set(v)
DFHack.memory_write_float(@_memaddr, v)
end
def _cpp_init
_set(0.0)
end
end
class BitField < MemStruct
attr_accessor :_shift, :_len, :_enum
def initialize(shift, len, enum=nil)
@_shift = shift
@_len = len
@_enum = enum
end
def _mask
(1 << @_len) - 1
end
def _get
v = DFHack.memory_read_int32(@_memaddr) >> @_shift
if @_len == 1
((v & 1) == 0) ? false : true
else
v &= _mask
v = @_enum.sym(v) if @_enum
v
end
end
def _set(v)
if @_len == 1
# allow 'bit = 0'
v = (v && v != 0 ? 1 : 0)
end
v = @_enum.int(v) if @_enum
v = (v & _mask) << @_shift
ori = DFHack.memory_read_int32(@_memaddr) & 0xffffffff
DFHack.memory_write_int32(@_memaddr, ori - (ori & ((-1 & _mask) << @_shift)) + v)
end
end
class Pointer < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def _getp
DFHack.memory_read_int32(@_memaddr) & 0xffffffff
end
def _get
addr = _getp
return if addr == 0
@_tg._at(addr)._get
end
# XXX shaky...
def _set(v)
if v.kind_of?(Pointer)
DFHack.memory_write_int32(@_memaddr, v._getp)
elsif v.kind_of?(MemStruct)
DFHack.memory_write_int32(@_memaddr, v._memaddr)
else
_get._set(v)
end
end
def inspect
ptr = _getp
if ptr == 0
'NULL'
else
cn = ''
cn = @_tg.class.name.sub(/^DFHack::/, '').sub(/^MemHack::/, '') if @_tg
cn = @_tg._glob if cn == 'Global'
"#<Pointer #{cn} #{'0x%X' % _getp}>"
end
end
end
class PointerAry < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
def _getp(i=0)
delta = (i != 0 ? i*@_tglen : 0)
(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta
end
def _get
addr = _getp
return if addr == 0
self
end
def [](i)
addr = _getp(i)
return if addr == 0
@_tg._at(addr)._get
end
def []=(i, v)
addr = _getp(i)
raise 'null pointer' if addr == 0
@_tg._at(addr)._set(v)
end
def inspect ; ptr = _getp ; (ptr == 0) ? 'NULL' : "#<PointerAry #{'0x%X' % ptr}>" ; end
end
module Enumerable
include ::Enumerable
attr_accessor :_indexenum
def each ; (0...length).each { |i| yield self[i] } ; end
def inspect
out = '['
each_with_index { |e, idx|
out << ', ' if out.length > 1
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << "#{_indexenum.sym(idx)}=" if _indexenum
out << e.inspect
}
out << ']'
end
def empty? ; length == 0 ; end
def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end
end
class StaticArray < MemStruct
attr_accessor :_tglen, :_length, :_indexenum, :_tg
def initialize(tglen, length, indexenum, tg)
@_tglen = tglen
@_length = length
@_indexenum = indexenum
@_tg = tg
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
def _cpp_init
_length.times { |i| _tgat(i)._cpp_init }
end
alias length _length
alias size _length
def _tgat(i)
@_tg._at(@_memaddr + i*@_tglen) if i >= 0 and i < @_length
end
def [](i)
i = _indexenum.int(i) if _indexenum
i += @_length if i < 0
_tgat(i)._get
end
def []=(i, v)
i = _indexenum.int(i) if _indexenum
i += @_length if i < 0
_tgat(i)._set(v)
end
include Enumerable
end
class StaticString < MemStruct
attr_accessor :_length
def initialize(length)
@_length = length
end
def _get
DFHack.memory_read(@_memaddr, @_length)
end
def _set(v)
DFHack.memory_write(@_memaddr, v[0, @_length])
end
end
class StlVector32 < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def length
DFHack.memory_vector32_length(@_memaddr)
end
def size ; length ; end # alias wouldnt work for subclasses
def valueptr_at(idx)
DFHack.memory_vector32_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector32_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector32_delete(@_memaddr, idx)
end
def _set(v)
delete_at(length-1) while length > v.length # match lengthes
v.each_with_index { |e, i| self[i] = e } # patch entries
end
def _cpp_init
DFHack.memory_vector_init(@_memaddr)
end
def clear
delete_at(length-1) while length > 0
end
def [](idx)
idx += length if idx < 0
@_tg._at(valueptr_at(idx))._get if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, 0)
elsif idx < 0
raise 'invalid idx'
end
@_tg._at(valueptr_at(idx))._set(v)
end
def push(v)
self[length] = v
self
end
def <<(v) ; push(v) ; end
def pop
l = length
if l > 0
v = self[l-1]
delete_at(l-1)
end
v
end
include Enumerable
# do a binary search in an ordered vector for a specific target attribute
# ex: world.history.figures.binsearch(unit.hist_figure_id)
def binsearch(target, field=:id)
o_start = 0
o_end = length - 1
while o_end >= o_start
o_half = o_start + (o_end-o_start)/2
obj = self[o_half]
oval = obj.send(field)
if oval == target
return obj
elsif oval < target
o_start = o_half+1
else
o_end = o_half-1
end
end
end
end
class StlVector16 < StlVector32
def length
DFHack.memory_vector16_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector16_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector16_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector16_delete(@_memaddr, idx)
end
end
class StlVector8 < StlVector32
def length
DFHack.memory_vector8_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector8_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector8_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector8_delete(@_memaddr, idx)
end
end
class StlBitVector < StlVector32
def initialize ; end
def length
DFHack.memory_vectorbool_length(@_memaddr)
end
def insert_at(idx, val)
DFHack.memory_vectorbool_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vectorbool_delete(@_memaddr, idx)
end
def [](idx)
idx += length if idx < 0
DFHack.memory_vectorbool_at(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, v)
elsif idx < 0
raise 'invalid idx'
else
DFHack.memory_vectorbool_setat(@_memaddr, idx, v)
end
end
end
class StlString < MemStruct
def _get
DFHack.memory_read_stlstring(@_memaddr)
end
def _set(v)
DFHack.memory_write_stlstring(@_memaddr, v)
end
def _cpp_init
DFHack.memory_stlstring_init(@_memaddr)
end
end
class StlDeque < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every
# possible struct size, like for StlVector. Just ignore it for now, deque are rare enough.
def inspect ; "#<StlDeque>" ; end
end
class DfFlagarray < MemStruct
attr_accessor :_indexenum
def initialize(indexenum)
@_indexenum = indexenum
end
def length
DFHack.memory_bitarray_length(@_memaddr)
end
# TODO _cpp_init
def size ; length ; end
def resize(len)
DFHack.memory_bitarray_resize(@_memaddr, len)
end
def [](idx)
idx = _indexenum.int(idx) if _indexenum
idx += length if idx < 0
DFHack.memory_bitarray_isset(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx = _indexenum.int(idx) if _indexenum
idx += length if idx < 0
if idx >= length or idx < 0
raise 'invalid idx'
else
DFHack.memory_bitarray_set(@_memaddr, idx, v)
end
end
include Enumerable
end
class DfArray < Compound
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_length, 4) { number 16, false }
def length ; _length ; end
def size ; _length ; end
# TODO _cpp_init
def _tgat(i)
@_tg._at(_ptr + i*@_tglen) if i >= 0 and i < _length
end
def [](i)
i += _length if i < 0
_tgat(i)._get
end
def []=(i, v)
i += _length if i < 0
_tgat(i)._set(v)
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
include Enumerable
end
class DfLinkedList < Compound
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_prev, 4) { number 32, false }
field(:_next, 8) { number 32, false }
def item
# With the current xml structure, currently _tg designate
# the type of the 'next' and 'prev' fields, not 'item'.
# List head has item == NULL, so we can safely return nil.
#addr = _ptr
#return if addr == 0
#@_tg._at(addr)._get
end
def item=(v)
#addr = _ptr
#raise 'null pointer' if addr == 0
#@_tg.at(addr)._set(v)
raise 'null pointer'
end
def prev
addr = _prev
return if addr == 0
@_tg._at(addr)._get
end
def next
addr = _next
return if addr == 0
@_tg._at(addr)._get
end
include Enumerable
def each
o = self
while o
yield o.item if o.item
o = o.next
end
end
def inspect ; "#<DfLinkedList #{item.inspect} prev=#{'0x%X' % _prev} next=#{'0x%X' % _next}>" ; end
end
class Global < MemStruct
attr_accessor :_glob
def initialize(glob)
@_glob = glob
end
def _at(addr)
g = DFHack.const_get(@_glob)
g = DFHack.rtti_getclassat(g, addr)
g.new._at(addr)
end
def inspect ; "#<#{@_glob}>" ; end
end
end # module MemHack
class BooleanEnum
def self.int(v) ; ((v == true) || (v == 1)) ? 1 : 0 ; end
def self.sym(v) ; (!v || (v == 0)) ? false : true ; end
end
# cpp rtti name -> rb class
@rtti_n2c = {}
@rtti_c2n = {}
# cpp rtti name -> vtable ptr
@rtti_n2v = {}
@rtti_v2n = {}
def self.rtti_n2c ; @rtti_n2c ; end
def self.rtti_c2n ; @rtti_c2n ; end
def self.rtti_n2v ; @rtti_n2v ; end
def self.rtti_v2n ; @rtti_v2n ; end
# register a ruby class with a cpp rtti class name
def self.rtti_register(cppname, cls)
@rtti_n2c[cppname] = cls
@rtti_c2n[cls] = cppname
end
# return the ruby class to use for the cpp object at address if rtti info is available
def self.rtti_getclassat(cls, addr)
if addr != 0 and @rtti_c2n[cls]
# rtti info exist for class => cpp object has a vtable
@rtti_n2c[rtti_readclassname(get_vtable_ptr(addr))] || cls
else
cls
end
end
# try to read the rtti classname from an object vtable pointer
def self.rtti_readclassname(vptr)
unless n = @rtti_v2n[vptr]
n = @rtti_v2n[vptr] = get_rtti_classname(vptr).to_sym
@rtti_n2v[n] = vptr
end
n
end
# return the vtable pointer from the cpp rtti name
def self.rtti_getvtable(cppname)
unless v = @rtti_n2v[cppname]
v = get_vtable(cppname.to_s)
@rtti_n2v[cppname] = v
@rtti_v2n[v] = cppname if v != 0
end
v if v != 0
end
def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0)
vmethod_do_call(obj._memaddr, voff, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3))
end
def self.vmethod_arg(arg)
case arg
when nil, false; 0
when true; 1
when Integer; arg
#when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer
when MemHack::Compound; arg._memaddr
else raise "bad vmethod arg #{arg.class}"
end
end
end
# load autogen'd file
require 'hack/ruby-autogen'
# load optional user-specified startup file
load 'ruby_custom.rb' if File.exist?('ruby_custom.rb')

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

@ -1 +1 @@
Subproject commit 37a823541538023b9f3d0d1e8039cf32851de68d
Subproject commit 17b653665567a5f1df628217820f76bb0b9c70a5

@ -965,6 +965,9 @@ static void compute_job_outputs(color_ostream &out, ProtectedJob *pj)
mat.decode(mat.plant->material_defs.type_##tag, \
mat.plant->material_defs.idx_##tag); \
else mat.decode(-1);
case BrewDrink:
PLANT_PROCESS_MAT(DRINK, drink);
break;
case MillPlants:
PLANT_PROCESS_MAT(MILL, mill);
break;

@ -0,0 +1,16 @@
-- Deletes ALL items not held by units, buildings or jobs.
--
-- Intended solely for lag investigation.
local count = 0
for _,v in ipairs(df.global.world.items.all) do
if not (v.flags.in_building or v.flags.construction or v.flags.in_job
or dfhack.items.getGeneralRef(v,df.general_ref_type.UNIT_HOLDER)) then
count = count + 1
v.flags.forbid = true
v.flags.garbage_collect = true
end
end
print('Deletion requested: '..count)

@ -0,0 +1,29 @@
-- Remove uninteresting dead units from the unit list.
local units = df.global.world.units.active
local dwarf_race = df.global.ui.race_id
local dwarf_civ = df.global.ui.civ_id
local count = 0
for i=#units-1,0,-1 do
local unit = units[i]
local flags1 = unit.flags1
local flags2 = unit.flags2
if flags1.dead and unit.race ~= dwarf_race then
local remove = false
if flags2.slaughter then
remove = true
elseif not unit.name.has_name then
remove = true
elseif unit.civ_id ~= dwarf_civ and
not (flags1.merchant or flags1.diplomat) then
remove = true
end
if remove then
count = count + 1
units:erase(i)
end
end
end
print('Units removed from active: '..count)

@ -0,0 +1,24 @@
-- Makes fat dwarves non-fat.
--
-- See for more info:
-- http://www.bay12games.com/dwarves/mantisbt/view.php?id=5971
local num_fat = 0
local num_lagging = 0
for _,v in ipairs(df.global.world.units.all) do
local fat = v.counters2.stored_fat
if fat > 850000 then
v.counters2.stored_fat = 500000
if v.race == df.global.ui.race_id then
print(fat,dfhack.TranslateName(dfhack.units.getVisibleName(v)))
num_fat = num_fat + 1
if fat > 999990 then
num_lagging = num_lagging + 1
end
end
end
end
print("Fat dwarves cured: "..num_fat)
print("Lag sources: "..num_lagging)

@ -0,0 +1,49 @@
-- Reset item temperature to the value of their tile.
local count = 0
local types = {}
local function update_temp(item,btemp)
if item.temperature ~= btemp then
count = count + 1
local tid = item:getType()
types[tid] = (types[tid] or 0) + 1
end
item.temperature = btemp
item.temperature_fraction = 0
if item.contaminants then
for _,c in ipairs(item.contaminants) do
c.temperature = btemp
c.temperature_fraction = 0
end
end
for _,sub in ipairs(dfhack.items.getContainedItems(item)) do
update_temp(sub,btemp)
end
item:checkTemperatureDamage()
end
local last_frame = df.global.world.frame_counter-1
for _,item in ipairs(df.global.world.items.all) do
if item.flags.on_ground and df.item_actual:is_instance(item) and
item.temp_updated_frame == last_frame then
local pos = item.pos
local block = dfhack.maps.getTileBlock(pos)
if block then
update_temp(item, block.temperature_1[pos.x%16][pos.y%16])
end
end
end
print('Items updated: '..count)
local tlist = {}
for k,_ in pairs(types) do tlist[#tlist+1] = k end
table.sort(tlist, function(a,b) return types[a] > types[b] end)
for _,k in ipairs(tlist) do
print(' '..df.item_type[k]..':', types[k])
end

@ -23,5 +23,5 @@ if #items==0 then
error("No items found!")
end
for k,v in pairs(items) do
dfhack.items.moveToBuilding(v,build,1)
dfhack.items.moveToBuilding(v,build,0)
end