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

develop
jj 2012-05-19 12:53:23 +02:00
commit b851ee2d22
35 changed files with 1620 additions and 194 deletions

@ -666,6 +666,14 @@ Job module
Returns the unit performing the job.
* ``dfhack.job.checkBuildingsNow()``
Instructs the game to check buildings for jobs next frame and assign workers.
* ``dfhack.job.checkDesignationsNow()``
Instructs the game to check designations for jobs next frame and assign workers.
* ``dfhack.job.is_equal(job1,job2)``
Compares important fields in the job and nested item structures.
@ -685,7 +693,7 @@ Units module
* ``dfhack.units.getPosition(unit)``
Returns true *x,y,z* of the unit; may be not equal to unit.pos if caged.
Returns true *x,y,z* of the unit, or *nil* if invalid; may be not equal to unit.pos if caged.
* ``dfhack.units.getContainer(unit)``
@ -744,7 +752,7 @@ Items module
* ``dfhack.items.getPosition(item)``
Returns true *x,y,z* of the item; may be not equal to item.pos if in inventory.
Returns true *x,y,z* of the item, or *nil* if invalid; may be not equal to item.pos if in inventory.
* ``dfhack.items.getGeneralRef(item, type)``
@ -799,10 +807,14 @@ Maps module
Returns a map block object for given df::coord or x,y,z in local tile coordinates.
* ``dfhack.maps.getRegionBiome(region_coord2d)``
* ``dfhack.maps.getRegionBiome(region_coord2d)``, or ``getRegionBiome(x,y)``
Returns the biome info struct for the given global map region.
* ``dfhack.maps.enableBlockUpdates(block[,flow,temperature])``
Enables updates for liquid flow or temperature, unless already active.
* ``dfhack.maps.getGlobalInitFeature(index)``
Returns the global feature object with the given index.
@ -811,6 +823,10 @@ Maps module
Returns the local feature object with the given region coords and index.
* ``dfhack.maps.getTileBiomeRgn(coords)``, or ``getTileBiomeRgn(x,y,z)``
Returns *x, y* for use with ``getRegionBiome``.
* ``dfhack.maps.canWalkBetween(pos1, pos2)``
Checks if a dwarf may be able to walk between the two tiles,
@ -955,6 +971,80 @@ Low-level building creation functions;
More high-level functions are implemented in lua and can be loaded by
``require('dfhack.buildings')``. See ``hack/lua/dfhack/buildings.lua``.
Among them are:
* ``dfhack.buildings.getFiltersByType(argtable,type,subtype,custom)``
Returns a sequence of lua structures, describing input item filters
suitable for the specified building type, or *nil* if unknown or invalid.
The returned sequence is suitable for use as the ``job_items`` argument
of ``constructWithFilters``.
Uses tables defined in ``buildings.lua``.
Argtable members ``material`` (the default name), ``bucket``, ``barrel``,
``chain``, ``mechanism``, ``screw``, ``pipe``, ``anvil``, ``weapon`` are used to
augment the basic attributes with more detailed information if the
building has input items with the matching name (see the tables for naming details).
Note that it is impossible to *override* any properties this way, only supply those that
are not mentioned otherwise; one exception is that flags2.non_economic
is automatically cleared if an explicit material is specified.
* ``dfhack.buildings.constructBuilding{...}``
Creates a building in one call, using options contained
in the argument table. Returns the building, or *nil, error*.
**NOTE:** Despite the name, unless the building is abstract,
the function creates it in an 'unconstructed' stage, with
a queued in-game job that will actually construct it. I.e.
the function replicates programmatically what can be done
through the construct building menu in the game ui, except
that it does less environment constraint checking.
The following options can be used:
- ``pos = coordinates``, or ``x = ..., y = ..., z = ...``
Mandatory. Specifies the left upper corner of the building.
- ``type = df.building_type.FOO, subtype = ..., custom = ...``
Mandatory. Specifies the type of the building. Obviously, subtype
and custom are only expected if the type requires them.
- ``fields = { ... }``
Initializes fields of the building object after creation with ``df.assign``.
- ``width = ..., height = ..., direction = ...``
Sets size and orientation of the building. If it is
fixed-size, specified dimensions are ignored.
- ``full_rectangle = true``
For buildings like stockpiles or farm plots that can normally
accomodate individual tile exclusion, forces an error if any
tiles within the specified width*height are obstructed.
- ``items = { item, item ... }``, or ``filters = { {...}, {...}... }``
Specifies explicit items or item filters to use in construction.
It is the job of the user to ensure they are correct for the building type.
- ``abstract = true``
Specifies that the building is abstract and does not require construction.
Required for stockpiles and civzones; an error otherwise.
- ``material = {...}, mechanism = {...}, ...``
If none of ``items``, ``filter``, or ``abstract`` is used,
the function uses ``getFiltersByType`` to compute the input
item filters, and passes the argument table through. If no filters
can be determined this way, ``constructBuilding`` throws an error.
Constructions module
--------------------
@ -997,6 +1087,13 @@ Core context specific functions:
Returns the timer id, or *nil* if unsuccessful due to
world being unloaded.
* ``dfhack.timeout_active(id[,new_callback])``
Returns the active callback with the given id, or *nil*
if inactive or nil id. If called with 2 arguments, replaces
the current callback with the given value, if still active.
Using ``timeout_active(id,nil)`` cancels the timer.
* ``dfhack.onStateChange.foo = function(code)``
Event. Receives the same codes as plugin_onstatechange in C++.
@ -1054,7 +1151,7 @@ Events:
Emitted when a burrow might have been renamed either through
the game UI, or ``renameBurrow()``.
* ``onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype)``
* ``onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype,worker)``
Emitted when a tile might have been dug out. Only tracked if the
auto-growing burrows feature is enabled.

@ -912,6 +912,12 @@ The is_bright boolean actually seems to invert the brightness.</p>
<li><p class="first"><tt class="docutils literal">dfhack.job.getWorker(job)</tt></p>
<p>Returns the unit performing the job.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.checkBuildingsNow()</tt></p>
<p>Instructs the game to check buildings for jobs next frame and assign workers.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.checkDesignationsNow()</tt></p>
<p>Instructs the game to check designations for jobs next frame and assign workers.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.is_equal(job1,job2)</tt></p>
<p>Compares important fields in the job and nested item structures.</p>
</li>
@ -929,7 +935,7 @@ a lua list containing them.</p>
<h3><a class="toc-backref" href="#id16">Units module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.units.getPosition(unit)</tt></p>
<p>Returns true <em>x,y,z</em> of the unit; may be not equal to unit.pos if caged.</p>
<p>Returns true <em>x,y,z</em> of the unit, or <em>nil</em> if invalid; may be not equal to unit.pos if caged.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getContainer(unit)</tt></p>
<p>Returns the container (cage) item or <em>nil</em>.</p>
@ -976,7 +982,7 @@ or raws. The <tt class="docutils literal">ignore_noble</tt> boolean disables the
<h3><a class="toc-backref" href="#id17">Items module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.items.getPosition(item)</tt></p>
<p>Returns true <em>x,y,z</em> of the item; may be not equal to item.pos if in inventory.</p>
<p>Returns true <em>x,y,z</em> of the item, or <em>nil</em> if invalid; may be not equal to item.pos if in inventory.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getGeneralRef(item, type)</tt></p>
<p>Searches for a general_ref with the given type.</p>
@ -1020,15 +1026,21 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBlock(coords)</tt>, or <tt class="docutils literal">getTileBlock(x,y,z)</tt></p>
<p>Returns a map block object for given df::coord or x,y,z in local tile coordinates.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getRegionBiome(region_coord2d)</tt></p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getRegionBiome(region_coord2d)</tt>, or <tt class="docutils literal">getRegionBiome(x,y)</tt></p>
<p>Returns the biome info struct for the given global map region.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.maps.enableBlockUpdates(block[,flow,temperature])</span></tt></p>
<p>Enables updates for liquid flow or temperature, unless already active.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getGlobalInitFeature(index)</tt></p>
<p>Returns the global feature object with the given index.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getLocalInitFeature(region_coord2d,index)</tt></p>
<p>Returns the local feature object with the given region coords and index.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBiomeRgn(coords)</tt>, or <tt class="docutils literal">getTileBiomeRgn(x,y,z)</tt></p>
<p>Returns <em>x, y</em> for use with <tt class="docutils literal">getRegionBiome</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.canWalkBetween(pos1, pos2)</tt></p>
<p>Checks if a dwarf may be able to walk between the two tiles,
using a pathfinding cache maintained by the game. Note that
@ -1151,6 +1163,69 @@ Returns <em>true</em> if the building was destroyed and deallocated immediately.
</ul>
<p>More high-level functions are implemented in lua and can be loaded by
<tt class="docutils literal"><span class="pre">require('dfhack.buildings')</span></tt>. See <tt class="docutils literal">hack/lua/dfhack/buildings.lua</tt>.</p>
<p>Among them are:</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getFiltersByType(argtable,type,subtype,custom)</tt></p>
<p>Returns a sequence of lua structures, describing input item filters
suitable for the specified building type, or <em>nil</em> if unknown or invalid.
The returned sequence is suitable for use as the <tt class="docutils literal">job_items</tt> argument
of <tt class="docutils literal">constructWithFilters</tt>.
Uses tables defined in <tt class="docutils literal">buildings.lua</tt>.</p>
<p>Argtable members <tt class="docutils literal">material</tt> (the default name), <tt class="docutils literal">bucket</tt>, <tt class="docutils literal">barrel</tt>,
<tt class="docutils literal">chain</tt>, <tt class="docutils literal">mechanism</tt>, <tt class="docutils literal">screw</tt>, <tt class="docutils literal">pipe</tt>, <tt class="docutils literal">anvil</tt>, <tt class="docutils literal">weapon</tt> are used to
augment the basic attributes with more detailed information if the
building has input items with the matching name (see the tables for naming details).
Note that it is impossible to <em>override</em> any properties this way, only supply those that
are not mentioned otherwise; one exception is that flags2.non_economic
is automatically cleared if an explicit material is specified.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.buildings.constructBuilding{...}</span></tt></p>
<p>Creates a building in one call, using options contained
in the argument table. Returns the building, or <em>nil, error</em>.</p>
<p><strong>NOTE:</strong> Despite the name, unless the building is abstract,
the function creates it in an 'unconstructed' stage, with
a queued in-game job that will actually construct it. I.e.
the function replicates programmatically what can be done
through the construct building menu in the game ui, except
that it does less environment constraint checking.</p>
<p>The following options can be used:</p>
<ul>
<li><p class="first"><tt class="docutils literal">pos = coordinates</tt>, or <tt class="docutils literal">x = <span class="pre">...,</span> y = <span class="pre">...,</span> z = ...</tt></p>
<p>Mandatory. Specifies the left upper corner of the building.</p>
</li>
<li><p class="first"><tt class="docutils literal">type = df.building_type.FOO, subtype = <span class="pre">...,</span> custom = ...</tt></p>
<p>Mandatory. Specifies the type of the building. Obviously, subtype
and custom are only expected if the type requires them.</p>
</li>
<li><p class="first"><tt class="docutils literal">fields = { ... }</tt></p>
<p>Initializes fields of the building object after creation with <tt class="docutils literal">df.assign</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">width = <span class="pre">...,</span> height = <span class="pre">...,</span> direction = ...</tt></p>
<p>Sets size and orientation of the building. If it is
fixed-size, specified dimensions are ignored.</p>
</li>
<li><p class="first"><tt class="docutils literal">full_rectangle = true</tt></p>
<p>For buildings like stockpiles or farm plots that can normally
accomodate individual tile exclusion, forces an error if any
tiles within the specified width*height are obstructed.</p>
</li>
<li><p class="first"><tt class="docutils literal">items = { item, item ... }</tt>, or <tt class="docutils literal">filters = { <span class="pre">{...},</span> <span class="pre">{...}...</span> }</tt></p>
<p>Specifies explicit items or item filters to use in construction.
It is the job of the user to ensure they are correct for the building type.</p>
</li>
<li><p class="first"><tt class="docutils literal">abstract = true</tt></p>
<p>Specifies that the building is abstract and does not require construction.
Required for stockpiles and civzones; an error otherwise.</p>
</li>
<li><p class="first"><tt class="docutils literal">material = <span class="pre">{...},</span> mechanism = <span class="pre">{...},</span> ...</tt></p>
<p>If none of <tt class="docutils literal">items</tt>, <tt class="docutils literal">filter</tt>, or <tt class="docutils literal">abstract</tt> is used,
the function uses <tt class="docutils literal">getFiltersByType</tt> to compute the input
item filters, and passes the argument table through. If no filters
can be determined this way, <tt class="docutils literal">constructBuilding</tt> throws an error.</p>
</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="constructions-module">
<h3><a class="toc-backref" href="#id21">Constructions module</a></h3>
@ -1190,6 +1265,12 @@ and cannot be queued until it is loaded again.
Returns the timer id, or <em>nil</em> if unsuccessful due to
world being unloaded.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.timeout_active(id[,new_callback])</span></tt></p>
<p>Returns the active callback with the given id, or <em>nil</em>
if inactive or nil id. If called with 2 arguments, replaces
the current callback with the given value, if still active.
Using <tt class="docutils literal">timeout_active(id,nil)</tt> cancels the timer.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.onStateChange.foo = function(code)</tt></p>
<p>Event. Receives the same codes as plugin_onstatechange in C++.</p>
</li>
@ -1236,7 +1317,7 @@ module file is still necessary for <tt class="docutils literal">require</tt> to
<p>Emitted when a burrow might have been renamed either through
the game UI, or <tt class="docutils literal">renameBurrow()</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype)</tt></p>
<li><p class="first"><tt class="docutils literal">onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype,worker)</tt></p>
<p>Emitted when a tile might have been dug out. Only tracked if the
auto-growing burrows feature is enabled.</p>
</li>

@ -71,6 +71,7 @@ using namespace DFHack;
using namespace tthread;
using namespace df::enums;
using df::global::init;
using df::global::world;
// FIXME: A lot of code in one file, all doing different things... there's something fishy about it.
@ -1017,7 +1018,7 @@ int Core::Update()
Lua::Core::Reset(out, "DF code execution");
if (first_update)
plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED);
onStateChange(out, SC_CORE_INITIALIZED);
// detect if the game was loaded or unloaded in the meantime
void *new_wdata = NULL;
@ -1043,11 +1044,11 @@ int Core::Update()
// and if the world is going away, we report the map change first
if(had_map)
plug_mgr->OnStateChange(out, SC_MAP_UNLOADED);
onStateChange(out, SC_MAP_UNLOADED);
// and if the world is appearing, we report map change after that
plug_mgr->OnStateChange(out, new_wdata ? SC_WORLD_LOADED : SC_WORLD_UNLOADED);
onStateChange(out, new_wdata ? SC_WORLD_LOADED : SC_WORLD_UNLOADED);
if(isMapLoaded())
plug_mgr->OnStateChange(out, SC_MAP_LOADED);
onStateChange(out, SC_MAP_LOADED);
}
// otherwise just check for map change...
else if (new_mapdata != last_local_map_ptr)
@ -1058,7 +1059,7 @@ int Core::Update()
if (isMapLoaded() != had_map)
{
getWorld()->ClearPersistentCache();
plug_mgr->OnStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED);
onStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED);
}
}
@ -1071,15 +1072,12 @@ int Core::Update()
if (screen != top_viewscreen)
{
top_viewscreen = screen;
plug_mgr->OnStateChange(out, SC_VIEWSCREEN_CHANGED);
onStateChange(out, SC_VIEWSCREEN_CHANGED);
}
}
// notify all the plugins that a game tick is finished
plug_mgr->OnUpdate(out);
// process timers in lua
Lua::Core::onUpdate(out);
// Execute per-frame handlers
onUpdate(out);
// Release the fake suspend lock
{
@ -1116,6 +1114,34 @@ int Core::Update()
return 0;
};
extern bool buildings_do_onupdate;
void buildings_onStateChange(color_ostream &out, state_change_event event);
void buildings_onUpdate(color_ostream &out);
static int buildings_timer = 0;
void Core::onUpdate(color_ostream &out)
{
// convert building reagents
if (buildings_do_onupdate && (++buildings_timer & 1))
buildings_onUpdate(out);
// notify all the plugins that a game tick is finished
plug_mgr->OnUpdate(out);
// process timers in lua
Lua::Core::onUpdate(out);
}
void Core::onStateChange(color_ostream &out, state_change_event event)
{
buildings_onStateChange(out, event);
plug_mgr->OnStateChange(out, event);
Lua::Core::onStateChange(out, event);
}
// FIXME: needs to terminate the IO threads and properly dismantle all the machinery involved.
int Core::Shutdown ( void )
{
@ -1180,6 +1206,18 @@ int Core::UnicodeAwareSym(const SDL::KeyboardEvent& ke)
if( '0' <= ke.ksym.sym && ke.ksym.sym <= '9') return ke.ksym.sym;
if(SDL::K_F1 <= ke.ksym.sym && ke.ksym.sym <= SDL::K_F12) return ke.ksym.sym;
// These keys are mapped to the same control codes as Ctrl-?
switch (ke.ksym.sym) {
case SDL::K_RETURN:
case SDL::K_KP_ENTER:
case SDL::K_TAB:
case SDL::K_ESCAPE:
case SDL::K_DELETE:
return ke.ksym.sym;
default:
break;
}
int unicode = ke.ksym.unicode;
// convert Ctrl characters to their 0x40-0x5F counterparts:

@ -94,6 +94,26 @@ void Lua::Push(lua_State *state, const Units::NoblePosition &pos)
lua_setfield(state, -2, "position");
}
void Lua::Push(lua_State *state, df::coord pos)
{
lua_createtable(state, 0, 3);
lua_pushinteger(state, pos.x);
lua_setfield(state, -2, "x");
lua_pushinteger(state, pos.y);
lua_setfield(state, -2, "y");
lua_pushinteger(state, pos.z);
lua_setfield(state, -2, "z");
}
void Lua::Push(lua_State *state, df::coord2d pos)
{
lua_createtable(state, 0, 2);
lua_pushinteger(state, pos.x);
lua_setfield(state, -2, "x");
lua_pushinteger(state, pos.y);
lua_setfield(state, -2, "y");
}
int Lua::PushPosXYZ(lua_State *state, df::coord pos)
{
if (!pos.isValid())
@ -110,6 +130,21 @@ int Lua::PushPosXYZ(lua_State *state, df::coord pos)
}
}
int Lua::PushPosXY(lua_State *state, df::coord2d pos)
{
if (!pos.isValid())
{
lua_pushnil(state);
return 1;
}
else
{
lua_pushinteger(state, pos.x);
lua_pushinteger(state, pos.y);
return 2;
}
}
static df::coord2d CheckCoordXY(lua_State *state, int base, bool vararg = false)
{
df::coord2d p;
@ -637,6 +672,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,printJobDetails),
WRAPM(Job,getHolder),
WRAPM(Job,getWorker),
WRAPM(Job,checkBuildingsNow),
WRAPM(Job,checkDesignationsNow),
WRAPN(is_equal, jobEqual),
WRAPN(is_item_equal, jobItemEqual),
{ NULL, NULL }
@ -718,6 +755,12 @@ static bool items_moveToContainer(df::item *item, df::item *container)
return Items::moveToContainer(mc, item, container);
}
static bool items_moveToBuilding(df::item *item, df::building_actual *building, int use_mode)
{
MapExtras::MapCache mc;
return Items::moveToBuilding(mc, item, building,use_mode);
}
static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getGeneralRef),
WRAPM(Items, getSpecificRef),
@ -726,6 +769,7 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getContainer),
WRAPN(moveToGround, items_moveToGround),
WRAPN(moveToContainer, items_moveToContainer),
WRAPN(moveToBuilding, items_moveToBuilding),
{ NULL, NULL }
};
@ -752,7 +796,7 @@ static const luaL_Reg dfhack_items_funcs[] = {
static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock),
WRAPM(Maps, getRegionBiome),
WRAPM(Maps, enableBlockUpdates),
WRAPM(Maps, getGlobalInitFeature),
WRAPM(Maps, getLocalInitFeature),
WRAPM(Maps, canWalkBetween),
@ -766,8 +810,24 @@ static int maps_getTileBlock(lua_State *L)
return 1;
}
static int maps_getRegionBiome(lua_State *L)
{
auto pos = CheckCoordXY(L, 1, true);
Lua::PushDFObject(L, Maps::getRegionBiome(pos));
return 1;
}
static int maps_getTileBiomeRgn(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos));
return 1;
}
static const luaL_Reg dfhack_maps_funcs[] = {
{ "getTileBlock", maps_getTileBlock },
{ "getRegionBiome", maps_getRegionBiome },
{ "getTileBiomeRgn", maps_getTileBiomeRgn },
{ NULL, NULL }
};

@ -1429,6 +1429,7 @@ int dfhack_timeout(lua_State *L)
using df::global::world;
using df::global::enabler;
// Parse arguments
lua_Number time = luaL_checknumber(L, 1);
int mode = luaL_checkoption(L, 2, NULL, timeout_modes);
luaL_checktype(L, 3, LUA_TFUNCTION);
@ -1440,6 +1441,7 @@ int dfhack_timeout(lua_State *L)
return 1;
}
// Compute timeout value
switch (mode)
{
case 2:
@ -1454,12 +1456,13 @@ int dfhack_timeout(lua_State *L)
default:;
}
int id = next_timeout_id++;
int delta = time;
if (delta <= 0)
luaL_error(L, "Invalid timeout: %d", delta);
// Queue the timeout
int id = next_timeout_id++;
if (mode)
tick_timers.insert(std::pair<int,int>(world->frame_counter+delta, id));
else
@ -1473,20 +1476,44 @@ int dfhack_timeout(lua_State *L)
return 1;
}
static void cancel_tick_timers()
int dfhack_timeout_active(lua_State *L)
{
int id = luaL_optint(L, 1, -1);
bool set_cb = (lua_gettop(L) >= 2);
lua_settop(L, 2);
if (!lua_isnil(L, 2))
luaL_checktype(L, 2, LUA_TFUNCTION);
if (id < 0)
{
lua_pushnil(L);
return 1;
}
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
lua_rawgeti(L, 3, id);
if (set_cb && !lua_isnil(L, -1))
{
lua_pushvalue(L, 2);
lua_rawseti(L, 3, id);
}
return 1;
}
static void cancel_timers(std::multimap<int,int> &timers)
{
using Lua::Core::State;
Lua::StackUnwinder frame(State);
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
for (auto it = tick_timers.begin(); it != tick_timers.end(); ++it)
for (auto it = timers.begin(); it != timers.end(); ++it)
{
lua_pushnil(State);
lua_rawseti(State, frame[1], it->second);
}
tick_timers.clear();
timers.clear();
}
void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) {
@ -1496,7 +1523,7 @@ void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) {
{
case SC_MAP_UNLOADED:
case SC_WORLD_UNLOADED:
cancel_tick_timers();
cancel_timers(tick_timers);
break;
default:;
@ -1506,40 +1533,40 @@ void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) {
Lua::InvokeEvent(out, State, (void*)onStateChange, 1);
}
void DFHack::Lua::Core::onUpdate(color_ostream &out)
static void run_timers(color_ostream &out, lua_State *L,
std::multimap<int,int> &timers, int table, int bound)
{
using df::global::world;
Lua::StackUnwinder frame(State);
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
frame_idx++;
while (!frame_timers.empty() &&
frame_timers.begin()->first <= frame_idx)
while (!timers.empty() && timers.begin()->first <= bound)
{
int id = frame_timers.begin()->second;
frame_timers.erase(frame_timers.begin());
int id = timers.begin()->second;
timers.erase(timers.begin());
lua_rawgeti(State, frame[1], id);
lua_pushnil(State);
lua_rawseti(State, frame[1], id);
lua_rawgeti(L, table, id);
if (lua_isnil(L, -1))
lua_pop(L, 1);
else
{
lua_pushnil(L);
lua_rawseti(L, table, id);
Lua::SafeCall(out, State, 0, 0);
Lua::SafeCall(out, L, 0, 0);
}
}
}
while (!tick_timers.empty() &&
tick_timers.begin()->first <= world->frame_counter)
{
int id = tick_timers.begin()->second;
tick_timers.erase(tick_timers.begin());
void DFHack::Lua::Core::onUpdate(color_ostream &out)
{
using df::global::world;
lua_rawgeti(State, frame[1], id);
lua_pushnil(State);
lua_rawseti(State, frame[1], id);
if (frame_timers.empty() && tick_timers.empty())
return;
Lua::SafeCall(out, State, 0, 0);
}
Lua::StackUnwinder frame(State);
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
run_timers(out, State, frame_timers, frame[1], ++frame_idx);
run_timers(out, State, tick_timers, frame[1], world->frame_counter);
}
void DFHack::Lua::Core::Init(color_ostream &out)
@ -1562,6 +1589,8 @@ void DFHack::Lua::Core::Init(color_ostream &out)
lua_pushcfunction(State, dfhack_timeout);
lua_setfield(State, -2, "timeout");
lua_pushcfunction(State, dfhack_timeout_active);
lua_setfield(State, -2, "timeout_active");
lua_pop(State, 1);
}

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

@ -609,8 +609,6 @@ void PluginManager::OnStateChange(color_ostream &out, state_change_event event)
{
all_plugins[i]->on_state_change(out, event);
}
Lua::Core::onStateChange(out, event);
}
// FIXME: doesn't check name collisions!

@ -68,6 +68,17 @@ namespace DFHack
class df_window;
}
enum state_change_event
{
SC_WORLD_LOADED = 0,
SC_WORLD_UNLOADED = 1,
SC_MAP_LOADED = 2,
SC_MAP_UNLOADED = 3,
SC_VIEWSCREEN_CHANGED = 4,
SC_CORE_INITIALIZED = 5,
SC_BEGIN_UNLOAD = 6
};
// Core is a singleton. Why? Because it is closely tied to SDL calls. It tracks the global state of DF.
// There should never be more than one instance
// Better than tracking some weird variables all over the place.
@ -162,6 +173,9 @@ namespace DFHack
int SDL_Event(SDL::Event* event);
bool ncurses_wgetch(int in, int & out);
void onUpdate(color_ostream &out);
void onStateChange(color_ostream &out, state_change_event event);
Core(Core const&); // Don't Implement
void operator=(Core const&); // Don't implement

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

@ -264,11 +264,14 @@ namespace DFHack {namespace Lua {
inline void Push(lua_State *state, bool value) {
lua_pushboolean(state, value);
}
inline void Push(lua_State *state, const char *str) {
lua_pushstring(state, str);
}
inline void Push(lua_State *state, const std::string &str) {
lua_pushlstring(state, str.data(), str.size());
}
inline void Push(lua_State *state, df::coord &obj) { PushDFObject(state, &obj); }
inline void Push(lua_State *state, df::coord2d &obj) { PushDFObject(state, &obj); }
DFHACK_EXPORT void Push(lua_State *state, df::coord obj);
DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj);
void Push(lua_State *state, const Units::NoblePosition &pos);
template<class T> inline void Push(lua_State *state, T *ptr) {
PushDFObject(state, ptr);
@ -293,6 +296,7 @@ namespace DFHack {namespace Lua {
}
DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos);
DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos);
DFHACK_EXPORT bool IsCoreContext(lua_State *state);
@ -424,3 +428,17 @@ namespace DFHack {namespace Lua {
name##_event.invoke(out, 4); \
} \
}
#define DEFINE_LUA_EVENT_5(name, handler, arg_type1, arg_type2, arg_type3, arg_type4, arg_type5) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4, arg_type5 arg5) { \
handler(out, arg1, arg2, arg3, arg4, arg5); \
if (auto state = name##_event.get_state()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
DFHack::Lua::Push(state, arg3); \
DFHack::Lua::Push(state, arg4); \
DFHack::Lua::Push(state, arg5); \
name##_event.invoke(out, 5); \
} \
}

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

@ -37,6 +37,7 @@ distribution.
#include "df/item_type.h"
#include "df/general_ref.h"
#include "df/specific_ref.h"
#include "df/building_actual.h"
namespace df
{
@ -147,5 +148,6 @@ DFHACK_EXPORT df::coord getPosition(df::item *item);
DFHACK_EXPORT bool moveToGround(MapExtras::MapCache &mc, df::item *item, df::coord pos);
DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container);
DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode);
}
}

@ -57,6 +57,10 @@ namespace DFHack
DFHACK_EXPORT df::building *getHolder(df::job *job);
DFHACK_EXPORT df::unit *getWorker(df::job *job);
// Instruct the game to check and assign workers
DFHACK_EXPORT void checkBuildingsNow();
DFHACK_EXPORT void checkDesignationsNow();
DFHACK_EXPORT bool linkIntoWorld(df::job *job, bool new_id = true);
// lists jobs with ids >= *id_var, and sets *id_var = *job_next_id;

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

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

@ -111,9 +111,11 @@ function copyall(table)
end
function pos2xyz(pos)
local x = pos.x
if x and x ~= -30000 then
return x, pos.y, pos.z
if pos then
local x = pos.x
if x and x ~= -30000 then
return x, pos.y, pos.z
end
end
end

@ -4,7 +4,32 @@ local buildings = dfhack.buildings
local utils = require 'utils'
--[[ Building input material tables. ]]
-- Uninteresting values for filter attributes when reading them from DF memory.
-- Differs from the actual defaults of the job_item constructor in allow_artifact.
buildings.input_filter_defaults = {
item_type = -1,
item_subtype = -1,
mat_type = -1,
mat_index = -1,
flags1 = {},
-- Instead of noting those that allow artifacts, mark those that forbid them.
-- Leaves actually enabling artifacts to the discretion of the API user,
-- which is the right thing because unlike the game UI these filters are
-- used in a way that does not give the user a chance to choose manually.
flags2 = { allow_artifact = true },
flags3 = {},
flags4 = 0,
flags5 = 0,
reaction_class = '',
has_material_reaction_product = '',
metal_ore = -1,
min_dimension = -1,
has_tool_use = -1,
quantity = 1
}
--[[ Building input material table. ]]
local building_inputs = {
[df.building_type.Chair] = { { item_type=df.item_type.CHAIR, vector_id=df.job_item_vector_id.CHAIR } },
@ -12,7 +37,6 @@ local building_inputs = {
[df.building_type.Table] = { { item_type=df.item_type.TABLE, vector_id=df.job_item_vector_id.TABLE } },
[df.building_type.Coffin] = { { item_type=df.item_type.COFFIN, vector_id=df.job_item_vector_id.COFFIN } },
[df.building_type.FarmPlot] = { },
[df.building_type.Furnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
[df.building_type.TradeDepot] = { { flags2={ building_material=true, non_economic=true }, quantity=3 } },
[df.building_type.Door] = { { item_type=df.item_type.DOOR, vector_id=df.job_item_vector_id.DOOR } },
[df.building_type.Floodgate] = {
@ -40,7 +64,6 @@ local building_inputs = {
vector_id=df.job_item_vector_id.ARMORSTAND
}
},
[df.building_type.Workshop] = { { flags2={ building_material=true, non_economic=true } } },
[df.building_type.Cabinet] = {
{ item_type=df.item_type.CABINET, vector_id=df.job_item_vector_id.CABINET }
},
@ -89,7 +112,7 @@ local building_inputs = {
[df.building_type.ArcheryTarget] = { { flags2={ building_material=true, non_economic=true } } },
[df.building_type.Chain] = { { item_type=df.item_type.CHAIN, vector_id=df.job_item_vector_id.CHAIN } },
[df.building_type.Cage] = { { item_type=df.item_type.CAGE, vector_id=df.job_item_vector_id.CAGE } },
[df.building_type.Weapon] = { { vector_id=df.job_item_vector_id.ANY_SPIKE } },
[df.building_type.Weapon] = { { name='weapon', vector_id=df.job_item_vector_id.ANY_SPIKE } },
[df.building_type.ScrewPump] = {
{
item_type=df.item_type.BLOCKS,
@ -155,9 +178,24 @@ local building_inputs = {
},
[df.building_type.Slab] = { { item_type=df.item_type.SLAB } },
[df.building_type.NestBox] = { { has_tool_use=df.tool_uses.NEST_BOX, item_type=df.item_type.TOOL } },
[df.building_type.Hive] = { { has_tool_use=df.tool_uses.HIVE, item_type=df.item_type.TOOL } }
[df.building_type.Hive] = { { has_tool_use=df.tool_uses.HIVE, item_type=df.item_type.TOOL } },
[df.building_type.Rollers] = {
{
name='mechanism',
item_type=df.item_type.TRAPPARTS,
quantity=-1,
vector_id=df.job_item_vector_id.TRAPPARTS
},
{
name='chain',
item_type=df.item_type.CHAIN,
vector_id=df.job_item_vector_id.CHAIN
}
}
}
--[[ Furnace building input material table. ]]
local furnace_inputs = {
[df.furnace_type.WoodFurnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
[df.furnace_type.Smelter] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
@ -168,6 +206,8 @@ local furnace_inputs = {
[df.furnace_type.MagmaKiln] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } }
}
--[[ Workshop building input material table. ]]
local workshop_inputs = {
[df.workshop_type.Carpenters] = { { flags2={ building_material=true, non_economic=true } } },
[df.workshop_type.Farmers] = { { flags2={ building_material=true, non_economic=true } } },
@ -250,6 +290,8 @@ local workshop_inputs = {
}
}
--[[ Trap building input material table. ]]
local trap_inputs = {
[df.trap_type.StoneFallTrap] = {
{
@ -264,7 +306,10 @@ local trap_inputs = {
item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.TRAPPARTS
},
{ vector_id=df.job_item_vector_id.ANY_WEAPON }
{
name='weapon',
vector_id=df.job_item_vector_id.ANY_WEAPON
}
},
[df.trap_type.Lever] = {
{
@ -286,14 +331,32 @@ local trap_inputs = {
item_type=df.item_type.TRAPPARTS,
vector_id=df.job_item_vector_id.TRAPPARTS
}
}
},
[df.trap_type.TrackStop] = { { flags2={ building_material=true, non_economic=true } } }
}
--[[ Functions for lookup in tables. ]]
local function get_custom_inputs(custom)
local defn = df.building_def.find(custom)
if defn ~= nil then
return utils.clone_with_default(defn.build_items, buildings.input_filter_defaults)
end
end
local function get_inputs_by_type(type,subtype,custom)
if type == df.building_type.Workshop then
return workshop_inputs[subtype]
if subtype == df.workshop_type.Custom then
return get_custom_inputs(custom)
else
return workshop_inputs[subtype]
end
elseif type == df.building_type.Furnace then
return furnace_inputs[subtype]
if subtype == df.furnace_type.Custom then
return get_custom_inputs(custom)
else
return furnace_inputs[subtype]
end
elseif type == df.building_type.Trap then
return trap_inputs[subtype]
else
@ -357,7 +420,7 @@ end
-- Materials:
items = { item, item ... },
-- OR
filter = { { ... }, { ... }... }
filters = { { ... }, { ... }... }
-- OR
abstract = true
-- OR

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

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

@ -66,6 +66,7 @@ using namespace DFHack;
#include "df/building_screw_pumpst.h"
#include "df/building_water_wheelst.h"
#include "df/building_wellst.h"
#include "df/building_rollersst.h"
using namespace df::enums;
using df::global::ui;
@ -86,6 +87,58 @@ static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile)
return &extent.extents[dx + dy*extent.width];
}
/*
* A monitor to work around this bug, in its application to buildings:
*
* http://www.bay12games.com/dwarves/mantisbt/view.php?id=1416
*/
bool buildings_do_onupdate = false;
void buildings_onStateChange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
buildings_do_onupdate = true;
break;
case SC_MAP_UNLOADED:
buildings_do_onupdate = false;
break;
default:
break;
}
}
void buildings_onUpdate(color_ostream &out)
{
buildings_do_onupdate = false;
df::job_list_link *link = world->job_list.next;
for (; link; link = link->next) {
df::job *job = link->item;
if (job->job_type != job_type::ConstructBuilding)
continue;
if (job->job_items.empty())
continue;
buildings_do_onupdate = true;
for (size_t i = 0; i < job->items.size(); i++)
{
df::job_item_ref *iref = job->items[i];
if (iref->role != df::job_item_ref::Reagent)
continue;
df::job_item *item = vector_get(job->job_items, iref->job_item_idx);
if (!item)
continue;
// Convert Reagent to Hauled, while decrementing quantity
item->quantity = std::max(0, item->quantity-1);
iref->role = df::job_item_ref::Hauled;
iref->job_item_idx = -1;
}
}
}
uint32_t Buildings::getNumBuildings()
{
return world->buildings.all.size();
@ -289,6 +342,10 @@ bool Buildings::getCorrectSize(df::coord2d &size, df::coord2d &center,
makeOneDim(size, center, direction);
return true;
case Rollers:
makeOneDim(size, center, (direction&1) == 0);
return true;
case WaterWheel:
size = df::coord2d(3,3);
makeOneDim(size, center, direction);
@ -592,6 +649,12 @@ bool Buildings::setSize(df::building *bld, df::coord2d size, int direction)
obj->direction = (df::screw_pump_direction)direction;
break;
}
case Rollers:
{
auto obj = (df::building_rollersst*)bld;
obj->direction = (df::screw_pump_direction)direction;
break;
}
case Bridge:
{
auto obj = (df::building_bridgest*)bld;
@ -709,8 +772,7 @@ static void linkBuilding(df::building *bld)
linkRooms(bld);
if (process_jobs)
*process_jobs = true;
Job::checkBuildingsNow();
}
static void createDesign(df::building *bld, bool rough)
@ -882,7 +944,11 @@ bool Buildings::constructWithFilters(df::building *bld, std::vector<df::job_item
if (items[i]->quantity < 0)
items[i]->quantity = computeMaterialAmount(bld);
job->job_items.push_back(items[i]);
/* The game picks up explicitly listed items in reverse
* order, but processes filters straight. This reverses
* the order of filters so as to produce the same final
* contained_items ordering as the normal ui way. */
vector_insert_at(job->job_items, 0, items[i]);
if (items[i]->item_type == item_type::BOULDER)
rough = true;
@ -892,6 +958,8 @@ bool Buildings::constructWithFilters(df::building *bld, std::vector<df::job_item
bld->mat_index = items[i]->mat_index;
}
buildings_do_onupdate = true;
createDesign(bld, rough);
return true;
}
@ -900,8 +968,6 @@ bool Buildings::deconstruct(df::building *bld)
{
using df::global::ui;
using df::global::world;
using df::global::process_jobs;
using df::global::process_dig;
using df::global::ui_look_list;
CHECK_NULL_POINTER(bld);
@ -952,8 +1018,9 @@ bool Buildings::deconstruct(df::building *bld)
}
}
if (process_dig) *process_dig = true;
if (process_jobs) *process_jobs = true;
Job::checkBuildingsNow();
Job::checkDesignationsNow();
return true;
}

@ -54,7 +54,9 @@ using namespace DFHack;
#include "df/viewscreen_layer_workshop_profilest.h"
#include "df/viewscreen_layer_noblelistst.h"
#include "df/viewscreen_layer_overall_healthst.h"
#include "df/viewscreen_layer_assigntradest.h"
#include "df/viewscreen_petst.h"
#include "df/viewscreen_tradegoodsst.h"
#include "df/ui_unit_view_mode.h"
#include "df/ui_sidebar_menus.h"
#include "df/ui_look_list.h"
@ -69,6 +71,7 @@ using namespace DFHack;
#include "df/interfacest.h"
#include "df/graphic.h"
#include "df/layer_object_listst.h"
#include "df/assign_trade_status.h"
using namespace df::enums;
using df::global::gview;
@ -420,6 +423,33 @@ static df::item *getAnyItem(df::viewscreen *top)
return ref ? ref->getItem() : NULL;
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_assigntradest, top))
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
if (!list1 || !list2 || !list2->bright)
return NULL;
int list_idx = vector_get(screen->visible_lists, list1->cursor, (int16_t)-1);
unsigned num_lists = sizeof(screen->lists)/sizeof(std::vector<int32_t>);
if (unsigned(list_idx) >= num_lists)
return NULL;
int idx = vector_get(screen->lists[list_idx], list2->cursor, -1);
if (auto info = vector_get(screen->info, idx))
return info->item;
return NULL;
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_tradegoodsst, top))
{
if (screen->in_right_pane)
return vector_get(screen->broker_items, screen->broker_cursor);
else
return vector_get(screen->trader_items, screen->trader_cursor);
}
if (!Gui::dwarfmode_hotkey(top))
return NULL;

@ -48,6 +48,7 @@ using namespace std;
#include "df/world.h"
#include "df/item.h"
#include "df/building.h"
#include "df/building_actual.h"
#include "df/tool_uses.h"
#include "df/itemdef_weaponst.h"
#include "df/itemdef_trapcompst.h"
@ -69,6 +70,8 @@ using namespace std;
#include "df/general_ref_unit_itemownerst.h"
#include "df/general_ref_contains_itemst.h"
#include "df/general_ref_contained_in_itemst.h"
#include "df/general_ref_building_holderst.h"
#include "df/vermin.h"
using namespace DFHack;
using namespace df::enums;
@ -512,9 +515,12 @@ df::coord Items::getPosition(df::item *item)
{
CHECK_NULL_POINTER(item);
if (item->flags.bits.in_inventory ||
item->flags.bits.in_chest ||
item->flags.bits.in_building)
/* Function reverse-engineered from DF code. */
if (item->flags.bits.removed)
return df::coord();
if (item->flags.bits.in_inventory)
{
for (size_t i = 0; i < item->itemrefs.size(); i++)
{
@ -532,15 +538,31 @@ df::coord Items::getPosition(df::item *item)
return Units::getPosition(unit);
break;
case general_ref_type::BUILDING_HOLDER:
/*case general_ref_type::BUILDING_HOLDER:
if (auto bld = ref->getBuilding())
return df::coord(bld->centerx, bld->centery, bld->z);
break;*/
default:
break;
}
}
for (size_t i = 0; i < item->specific_refs.size(); i++)
{
df::specific_ref *ref = item->specific_refs[i];
switch (ref->type)
{
case specific_ref_type::VERMIN_ESCAPED_PET:
return ref->vermin->pos;
default:
break;
}
}
return df::coord();
}
return item->pos;
@ -625,6 +647,10 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
CHECK_NULL_POINTER(item);
CHECK_NULL_POINTER(container);
auto cpos = getPosition(container);
if (!cpos.isValid())
return false;
if (!detachItem(mc, item))
return false;
@ -635,7 +661,7 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
{
delete ref1; delete ref2;
Core::printerr("Could not allocate container refs.\n");
putOnGround(mc, item, getPosition(container));
putOnGround(mc, item, cpos);
return false;
}
@ -652,3 +678,34 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
return true;
}
DFHACK_EXPORT bool DFHack::Items::moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode)
{
CHECK_NULL_POINTER(item);
CHECK_NULL_POINTER(building);
auto ref = df::allocate<df::general_ref_building_holderst>();
if(!ref)
{
delete ref;
Core::printerr("Could not allocate building holder refs.\n");
return false;
}
if (!detachItem(mc, item))
{
delete ref;
return false;
}
item->pos.x=building->centerx;
item->pos.y=building->centery;
item->pos.z=building->z;
item->flags.bits.in_building=true;
ref->building_id=building->id;
item->itemrefs.push_back(ref);
auto con=new df::building_actual::T_contained_items;
con->item=item;
con->use_mode=use_mode;
building->contained_items.push_back(con);
return true;
}

@ -256,6 +256,18 @@ df::unit *DFHack::Job::getWorker(df::job *job)
return NULL;
}
void DFHack::Job::checkBuildingsNow()
{
if (df::global::process_jobs)
*df::global::process_jobs = true;
}
void DFHack::Job::checkDesignationsNow()
{
if (df::global::process_dig)
*df::global::process_dig = true;
}
bool DFHack::Job::linkIntoWorld(df::job *job, bool new_id)
{
using df::global::world;

@ -54,6 +54,7 @@ using namespace std;
#include "df/world_region_details.h"
#include "df/builtin_mats.h"
#include "df/block_square_event_grassst.h"
#include "df/z_level_flags.h"
using namespace DFHack;
using namespace df::enums;
@ -176,6 +177,30 @@ df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos)
return &data->region_map[rgn_pos.x][rgn_pos.y];
}
void Maps::enableBlockUpdates(df::map_block *blk, bool flow, bool temperature)
{
if (!blk || !(flow || temperature)) return;
if (temperature)
blk->flags.bits.update_temperature = true;
if (flow)
{
blk->flags.bits.update_liquid = true;
blk->flags.bits.update_liquid_twice = true;
}
auto z_flags = world->map.z_level_flags;
int z_level = blk->map_pos.z;
if (z_flags && z_level >= 0 && z_level < world->map.z_count_block)
{
z_flags += z_level;
z_flags->bits.update = true;
z_flags->bits.update_twice = true;
}
}
df::feature_init *Maps::getGlobalInitFeature(int32_t index)
{
auto data = world->world_data;
@ -344,6 +369,39 @@ bool Maps::RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square
return false;
}
static df::coord2d biome_offsets[9] = {
df::coord2d(-1,-1), df::coord2d(0,-1), df::coord2d(1,-1),
df::coord2d(-1,0), df::coord2d(0,0), df::coord2d(1,0),
df::coord2d(-1,1), df::coord2d(0,1), df::coord2d(1,1)
};
inline df::coord2d getBiomeRgnPos(df::coord2d base, int idx)
{
auto r = base + biome_offsets[idx];
int world_width = world->world_data->world_width;
int world_height = world->world_data->world_height;
return df::coord2d(clip_range(r.x,0,world_width-1),clip_range(r.y,0,world_height-1));
}
df::coord2d Maps::getBlockTileBiomeRgn(df::map_block *block, df::coord2d pos)
{
if (!block || !world->world_data)
return df::coord2d();
auto des = MapExtras::index_tile<df::tile_designation>(block->designation,pos);
unsigned idx = des.bits.biome;
if (idx < 9)
{
idx = block->region_offset[idx];
if (idx < 9)
return getBiomeRgnPos(block->region_pos, idx);
}
return df::coord2d();
}
/*
* Layer geology
*/
@ -361,20 +419,14 @@ bool Maps::ReadGeology(vector<vector<int16_t> > *layer_mats, vector<df::coord2d>
(*geoidx)[i] = df::coord2d(-30000,-30000);
}
int world_width = world->world_data->world_width;
int world_height = world->world_data->world_height;
// regionX is in embark squares
// regionX/16 is in 16x16 embark square regions
df::coord2d map_region(world->map.region_x / 16, world->map.region_y / 16);
// iterate over 8 surrounding regions + local region
for (int i = eNorthWest; i < eBiomeCount; i++)
{
// check against worldmap boundaries, fix if needed
// regionX is in embark squares
// regionX/16 is in 16x16 embark square regions
// i provides -1 .. +1 offset from the current region
int bioRX = world->map.region_x / 16 + ((i % 3) - 1);
int bioRY = world->map.region_y / 16 + ((i / 3) - 1);
df::coord2d rgn_pos(clip_range(bioRX,0,world_width-1),clip_range(bioRY,0,world_height-1));
df::coord2d rgn_pos = getBiomeRgnPos(map_region, i);
(*geoidx)[i] = rgn_pos;
@ -426,7 +478,6 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
dirty_designations = false;
dirty_tiles = false;
dirty_temperatures = false;
dirty_blockflags = false;
dirty_occupancies = false;
valid = false;
bcoord = _bcoord;
@ -440,7 +491,6 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
{
COPY(designation, block->designation);
COPY(occupancy, block->occupancy);
blockflags = block->flags;
COPY(temp1, block->temperature_1);
COPY(temp2, block->temperature_2);
@ -449,7 +499,6 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
}
else
{
blockflags.whole = 0;
memset(designation,0,sizeof(designation));
memset(occupancy,0,sizeof(occupancy));
memset(temp1,0,sizeof(temp1));
@ -634,11 +683,6 @@ bool MapExtras::Block::Write ()
{
if(!valid) return false;
if(dirty_blockflags)
{
block->flags = blockflags;
dirty_blockflags = false;
}
if(dirty_designations)
{
COPY(block->designation, designation);

@ -49,6 +49,7 @@ using namespace std;
#include "df/world.h"
#include "df/ui.h"
#include "df/job.h"
#include "df/unit_inventory_item.h"
#include "df/unit_soul.h"
#include "df/nemesis_record.h"
@ -215,24 +216,18 @@ void Units::CopyCreature(df::unit * source, t_unit & furball)
}
}
*/
/*
furball.current_job.occupationPtr = p->readDWord (addr_cr + offs.current_job_offset);
if(furball.current_job.occupationPtr)
{
furball.current_job.active = true;
furball.current_job.jobType = p->readByte (furball.current_job.occupationPtr + offs.job_type_offset );
furball.current_job.jobId = p->readWord (furball.current_job.occupationPtr + offs.job_id_offset);
}
else
if(source->job.current_job == NULL)
{
furball.current_job.active = false;
}
*/
// no jobs for now...
else
{
furball.current_job.active = false;
furball.current_job.active = true;
furball.current_job.jobType = source->job.current_job->job_type;
furball.current_job.jobId = source->job.current_job->id;
}
}
int32_t Units::FindIndexById(int32_t creature_id)
{
return df::unit::binsearch_index(world->units.all, creature_id);

@ -1 +1 @@
Subproject commit 970bb0ae9530e9c0522cb0ca8fe357815d72fab1
Subproject commit f004804fd655efcb4bf9ea31bd95ae4f759937b4

@ -348,7 +348,12 @@ static const dwarf_state dwarf_states[] = {
OTHER /* CauseTrouble */,
OTHER /* DrinkBlood */,
OTHER /* ReportCrime */,
OTHER /* ExecuteCriminal */
OTHER /* ExecuteCriminal */,
BUSY /* TrainAnimal */,
BUSY /* CarveTrack */,
BUSY /* PushTrackVehicle */,
BUSY /* PlaceTrackVehicle */,
BUSY /* StoreItemInVehicle */
};
struct labor_info
@ -452,8 +457,9 @@ static const struct labor_default default_labor_infos[] = {
/* POTTERY */ {AUTOMATIC, false, 1, 200, 0},
/* GLAZING */ {AUTOMATIC, false, 1, 200, 0},
/* PRESSING */ {AUTOMATIC, false, 1, 200, 0},
/* BEEKEEPING */ {AUTOMATIC, false, 1, 200, 0},
/* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0},
/* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981)
/* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0},
/* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0}
};
static const int responsibility_penalties[] = {
@ -886,8 +892,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
assert(job >= 0);
assert(job < ARRAY_COUNT(dwarf_states));
*/
dwarf_info[dwarf].state = dwarf_states[job];
if (job >= 0 && job < ARRAY_COUNT(dwarf_states))
dwarf_info[dwarf].state = dwarf_states[job];
else
{
out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job);
dwarf_info[dwarf].state = OTHER;
}
}
state_count[dwarf_info[dwarf].state]++;
@ -1261,7 +1272,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
if (labor == df::enums::unit_labor::NONE)
{
out.printerr("Could not find labor %s.", parameters[0].c_str());
out.printerr("Could not find labor %s.\n", parameters[0].c_str());
return CR_WRONG_USAGE;
}

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

@ -418,10 +418,7 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setDesignationAt(*iter,a);
Block * b = mcache.BlockAt((*iter)/16);
DFHack::t_blockflags bf = b->BlockFlags();
bf.bits.update_liquid = true;
bf.bits.update_liquid_twice = true;
b->setBlockFlags(bf);
b->enableBlockUpdates(true);
iter++;
}
@ -448,7 +445,8 @@ command_result df_liquids_execute(color_ostream &out)
DFHack::DFCoord current = *iter; // current tile coord
DFHack::DFCoord curblock = current /16; // current block coord
// check if the block is actually there
if(!mcache.BlockAt(curblock))
auto block = mcache.BlockAt(curblock);
if(!block)
{
iter ++;
continue;
@ -463,64 +461,77 @@ command_result df_liquids_execute(color_ostream &out)
}
if(mode != "flowbits")
{
unsigned old_amount = des.bits.flow_size;
unsigned new_amount = old_amount;
df::tile_liquid old_liquid = des.bits.liquid_type;
df::tile_liquid new_liquid = old_liquid;
// Compute new liquid type and amount
if(setmode == "s.")
{
des.bits.flow_size = amount;
new_amount = amount;
}
else if(setmode == "s+")
{
if(des.bits.flow_size < amount)
des.bits.flow_size = amount;
if(old_amount < amount)
new_amount = amount;
}
else if(setmode == "s-")
{
if (des.bits.flow_size > amount)
des.bits.flow_size = amount;
if (old_amount > amount)
new_amount = amount;
}
if(amount != 0 && mode == "magma")
if (mode == "magma")
new_liquid = tile_liquid::Magma;
else if (mode == "water")
new_liquid = tile_liquid::Water;
// Store new amount and type
des.bits.flow_size = new_amount;
des.bits.liquid_type = new_liquid;
// Compute temperature
if (!old_amount)
old_liquid = tile_liquid::Water;
if (!new_amount)
new_liquid = tile_liquid::Water;
if (old_liquid != new_liquid)
{
des.bits.liquid_type = tile_liquid::Magma;
mcache.setTemp1At(current,12000);
mcache.setTemp2At(current,12000);
}
else if(amount != 0 && mode == "water")
{
des.bits.liquid_type = tile_liquid::Water;
mcache.setTemp1At(current,10015);
mcache.setTemp2At(current,10015);
}
else if(amount == 0 && (mode == "water" || mode == "magma"))
{
// reset temperature to sane default
mcache.setTemp1At(current,10015);
mcache.setTemp2At(current,10015);
if (new_liquid == tile_liquid::Water)
{
mcache.setTemp1At(current,10015);
mcache.setTemp2At(current,10015);
}
else
{
mcache.setTemp1At(current,12000);
mcache.setTemp2At(current,12000);
}
}
// mark the tile passable or impassable like the game does
des.bits.flow_forbid = des.bits.flow_size &&
(des.bits.liquid_type == tile_liquid::Magma || des.bits.flow_size > 3);
des.bits.flow_forbid = (new_liquid == tile_liquid::Magma || new_amount > 3);
mcache.setDesignationAt(current,des);
// request flow engine updates
block->enableBlockUpdates(new_amount != old_amount, new_liquid != old_liquid);
}
seen_blocks.insert(mcache.BlockAt(current / 16));
seen_blocks.insert(block);
iter++;
}
set <Block *>::iterator biter = seen_blocks.begin();
while (biter != seen_blocks.end())
{
DFHack::t_blockflags bflags = (*biter)->BlockFlags();
if(flowmode == "f+")
{
bflags.bits.update_liquid = true;
bflags.bits.update_liquid_twice = true;
(*biter)->setBlockFlags(bflags);
(*biter)->enableBlockUpdates(true);
}
else if(flowmode == "f-")
{
bflags.bits.update_liquid = false;
bflags.bits.update_liquid_twice = false;
(*biter)->setBlockFlags(bflags);
if (auto block = (*biter)->getRaw())
{
block->flags.bits.update_liquid = false;
block->flags.bits.update_liquid_twice = false;
}
}
else
{
auto bflags = (*biter)->BlockFlags();
out << "flow bit 1 = " << bflags.bits.update_liquid << endl;
out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl;
}

@ -1 +1 @@
Subproject commit 15e3b726c3e68d2985aecd95d2a33bf4550caaa1
Subproject commit 37a823541538023b9f3d0d1e8039cf32851de68d

@ -0,0 +1,42 @@
-- Lists and/or compares two tiletype material groups.
-- Usage: devel/cmptiles material1 [material2]
local nmat1,nmat2=...
local mat1 = df.tiletype_material[nmat1]
local mat2 = df.tiletype_material[nmat2]
local tmat1 = {}
local tmat2 = {}
local attrs = df.tiletype.attrs
for i=df.tiletype._first_item,df.tiletype._last_item do
local shape = df.tiletype_shape[attrs[i].shape] or ''
local variant = df.tiletype_variant[attrs[i].variant] or ''
local special = df.tiletype_special[attrs[i].special] or ''
local direction = attrs[i].direction or ''
local code = shape..':'..variant..':'..special..':'..direction
if attrs[i].material == mat1 then
tmat1[code] = true
end
if attrs[i].material == mat2 then
tmat2[code] = true
end
end
local function list_diff(n, t1, t2)
local lst = {}
for k,v in pairs(t1) do
if not t2[k] then
lst[#lst+1] = k
end
end
table.sort(lst)
for k,v in ipairs(lst) do
print(n, v)
end
end
list_diff(nmat1,tmat1,tmat2)
list_diff(nmat2,tmat2,tmat1)

@ -0,0 +1,72 @@
-- List input items for the building currently being built.
--
-- This is where the filters in lua/dfhack/buildings.lua come from.
local dumper = require 'dumper'
local utils = require 'utils'
local buildings = require 'dfhack.buildings'
local function name_enum(tgt,name,ename,enum)
if tgt[name] ~= nil then
tgt[name] = ename..'.'..enum[tgt[name]]
end
end
local lookup = {}
local items = df.global.world.items
for i=df.job_item_vector_id._first_item,df.job_item_vector_id._last_item do
local id = df.job_item_vector_id.attrs[i].other
local ptr
if id == df.items_other_id.ANY then
ptr = items.all
elseif id == df.items_other_id.BAD then
ptr = items.bad
else
ptr = items.other[id]
end
if ptr then
local _,addr = df.sizeof(ptr)
lookup[addr] = 'df.job_item_vector_id.'..df.job_item_vector_id[i]
end
end
local function clone_filter(src,quantity)
local tgt = utils.clone_with_default(src, buildings.input_filter_defaults, true)
if quantity ~= 1 then
tgt.quantity = quantity
end
name_enum(tgt, 'item_type', 'df.item_type', df.item_type)
name_enum(tgt, 'has_tool_use', 'df.tool_uses', df.tool_uses)
local ptr = src.item_vector
if ptr and ptr ~= df.global.world.items.other[0] then
local _,addr = df.sizeof(ptr)
tgt.vector_id = lookup[addr]
end
return tgt
end
local function dump(name)
local out = {}
for i,v in ipairs(df.global.ui_build_selector.requirements) do
out[#out+1] = clone_filter(v.filter, v.count_required)
end
local fmt = dumper.DataDumper(out,name,false,1,4)
fmt = string.gsub(fmt, '"(df%.[^"]+)"','%1')
fmt = string.gsub(fmt, '%s+$', '')
print(fmt)
end
local itype = df.global.ui_build_selector.building_type
local stype = df.global.ui_build_selector.building_subtype
if itype == df.building_type.Workshop then
dump(' [df.workshop_type.'..df.workshop_type[stype]..'] = ')
elseif itype == df.building_type.Furnace then
dump(' [df.furnace_type.'..df.furnace_type[stype]..'] = ')
elseif itype == df.building_type.Trap then
dump(' [df.trap_type.'..df.trap_type[stype]..'] = ')
else
dump(' [df.building_type.'..df.building_type[itype]..'] = ')
end

@ -0,0 +1,74 @@
-- Logs minecart coordinates and speeds to console.
last_stats = last_stats or {}
function compare_one(vehicle)
local last = last_stats[vehicle.id]
local item = df.item.find(vehicle.item_id)
local ipos = item.pos
local new = {
ipos.x*100000 + vehicle.offset_x, vehicle.speed_x,
ipos.y*100000 + vehicle.offset_y, vehicle.speed_y,
ipos.z*100000 + vehicle.offset_z, vehicle.speed_z
}
if (last == nil) or item.flags.on_ground then
local delta = { vehicle.id }
local show = (last == nil)
for i=1,6 do
local rv = 0
if last then
rv = last[i]
end
delta[i*2] = new[i]/100000
local dv = new[i] - rv
delta[i*2+1] = dv/100000
if dv ~= 0 then
show = true
end
end
if show then
print(table.unpack(delta))
end
end
last_stats[vehicle.id] = new
end
function compare_all()
local seen = {}
for _,v in ipairs(df.global.world.vehicles.all) do
seen[v.id] = true
compare_one(v)
end
for k,v in pairs(last_stats) do
if not seen[k] then
print(k,'DEAD')
end
end
end
function start_timer()
if not dfhack.timeout_active(timeout_id) then
timeout_id = dfhack.timeout(1, 'ticks', function()
compare_all()
start_timer()
end);
if not timeout_id then
dfhack.printerr('Could not start timer in watch-minecarts')
end
end
end
compare_all()
local cmd = ...
if cmd == 'start' then
start_timer()
elseif cmd == 'stop' then
dfhack.timeout_active(timeout_id, nil)
end

@ -0,0 +1,125 @@
-- Verify item occupancy and block item list integrity.
--
-- Checks:
-- 1) Item has flags.on_ground <=> it is in the correct block item list
-- 2) A tile has items in block item list <=> it has occupancy.item
-- 3) The block item lists are sorted.
local utils = require 'utils'
function check_block_items(fix)
local cnt = 0
local icnt = 0
local found = {}
local found_somewhere = {}
local should_fix = false
local can_fix = true
for _,block in ipairs(df.global.world.map.map_blocks) do
local itable = {}
local bx,by,bz = pos2xyz(block.map_pos)
-- Scan the block item vector
local last_id = nil
local resort = false
for _,id in ipairs(block.items) do
local item = df.item.find(id)
local ix,iy,iz = pos2xyz(item.pos)
local dx,dy,dz = ix-bx,iy-by,iz-bz
-- Check sorted order
if last_id and last_id >= id then
print(bx,by,bz,last_id,id,'block items not sorted')
resort = true
else
last_id = id
end
-- Check valid coordinates and flags
if not item.flags.on_ground then
print(bx,by,bz,id,dx,dy,'in block & not on ground')
elseif dx < 0 or dx >= 16 or dy < 0 or dy >= 16 or dz ~= 0 then
found_somewhere[id] = true
print(bx,by,bz,id,dx,dy,dz,'invalid pos')
can_fix = false
else
found[id] = true
itable[dx + dy*16] = true;
-- Check missing occupancy
if not block.occupancy[dx][dy].item then
print(bx,by,bz,dx,dy,'item & not occupied')
if fix then
block.occupancy[dx][dy].item = true
else
should_fix = true
end
end
end
end
-- Sort the vector if needed
if resort then
if fix then
utils.sort_vector(block.items)
else
should_fix = true
end
end
icnt = icnt + #block.items
-- Scan occupancy for spurious marks
for x=0,15 do
local ocx = block.occupancy[x]
for y=0,15 do
if ocx[y].item and not itable[x + y*16] then
print(bx,by,bz,x,y,'occupied & no item')
if fix then
ocx[y].item = false
else
should_fix = true
end
end
end
end
cnt = cnt + 256
end
-- Check if any items are missing from blocks
for _,item in ipairs(df.global.world.items.all) do
if item.flags.on_ground and not found[item.id] then
can_fix = false
if not found_somewhere[item.id] then
print(id,item.pos.x,item.pos.y,item.pos.z,'on ground & not in block')
end
end
end
-- Report
print(cnt.." tiles and "..icnt.." items checked.")
if should_fix and can_fix then
print("Use 'fix/item-occupancy --fix' to fix the listed problems.")
elseif should_fix then
print("The problems are too severe to be fixed by this script.")
end
end
local opt = ...
local fix = false
if opt then
if opt == '--fix' then
fix = true
else
dfhack.printerr('Invalid option: '..opt)
return
end
end
print("Checking item occupancy - this will take a few seconds.")
check_block_items(fix)