develop
Warmist 2012-04-15 18:17:00 +03:00
commit 35981e8146
37 changed files with 1962 additions and 158 deletions

@ -63,9 +63,9 @@ set(DF_VERSION_MINOR "34")
set(DF_VERSION_PATCH "07")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
set(DFHACK_RELEASE "1")
SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.")
set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-r${DFHACK_RELEASE}")
set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")
## where to install things (after the build is done, classic 'make install' or package structure)

@ -663,6 +663,14 @@ Job module
Units module
------------
* ``dfhack.units.getPosition(unit)``
Returns true *x,y,z* of the unit; may be not equal to unit.pos if caged.
* ``dfhack.units.getContainer(unit)``
Returns the container (cage) item or *nil*.
* ``dfhack.units.setNickname(unit,nick)``
Sets the unit's nickname properly.
@ -687,6 +695,10 @@ Units module
The unit is capable of rational action, i.e. not dead, insane or zombie.
* ``dfhack.units.clearBurrowMembers(burrow)``
Removes all units from the burrow.
* ``dfhack.units.isInBurrow(unit,burrow)``
Checks if the unit is in the burrow.
@ -701,7 +713,7 @@ Items module
* ``dfhack.items.getPosition(item)``
Returns true *x,y,z* of the item.
Returns true *x,y,z* of the item; may be not equal to item.pos if in inventory.
* ``dfhack.items.getOwner(item)``
@ -768,6 +780,10 @@ Maps module
Returns a table of map block pointers.
* ``dfhack.maps.clearBurrowTiles(burrow)``
Removes all tiles from the burrow.
* ``dfhack.maps.isBurrowTile(burrow,tile_coord)``
Checks if the tile is in burrow.
@ -783,3 +799,17 @@ Maps module
* ``dfhack.maps.setBlockBurrowTile(burrow,block,x,y,enable)``
Adds or removes the tile from the burrow. Returns *false* if invalid coords.
Core interpreter context
========================
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.
Core context specific functions:
* ``dfhack.is_core_context``
Boolean value; *true* in the core context.

@ -344,6 +344,7 @@ ul.auto-toc {
<li><a class="reference internal" href="#maps-module" id="id18">Maps module</a></li>
</ul>
</li>
<li><a class="reference internal" href="#core-interpreter-context" id="id19">Core interpreter context</a></li>
</ul>
</li>
</ul>
@ -900,6 +901,12 @@ a lua list containing them.</p>
<div class="section" id="units-module">
<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>
</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>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.setNickname(unit,nick)</tt></p>
<p>Sets the unit's nickname properly.</p>
</li>
@ -918,6 +925,9 @@ a lua list containing them.</p>
<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>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.clearBurrowMembers(burrow)</tt></p>
<p>Removes all units from the burrow.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isInBurrow(unit,burrow)</tt></p>
<p>Checks if the unit is in the burrow.</p>
</li>
@ -930,7 +940,7 @@ a lua list containing them.</p>
<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.</p>
<p>Returns true <em>x,y,z</em> of the item; may be not equal to item.pos if in inventory.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.getOwner(item)</tt></p>
<p>Returns the owner unit or <em>nil</em>.</p>
@ -983,6 +993,9 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.listBurrowBlocks(burrow)</tt></p>
<p>Returns a table of map block pointers.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.clearBurrowTiles(burrow)</tt></p>
<p>Removes all tiles from the burrow.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.isBurrowTile(burrow,tile_coord)</tt></p>
<p>Checks if the tile is in burrow.</p>
</li>
@ -998,6 +1011,18 @@ Returns <em>false</em> in case of error.</p>
</ul>
</div>
</div>
<div class="section" id="core-interpreter-context">
<h2><a class="toc-backref" href="#id19">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>
<p>Core context specific functions:</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.is_core_context</tt></p>
<p>Boolean value; <em>true</em> in the core context.</p>
</li>
</ul>
</div>
</div>
</div>
</body>

@ -615,8 +615,7 @@ Options
-------
:tweak clear-missing: Remove the missing status from the selected unit. This allows engraving slabs for ghostly, but not yet found, creatures.
:tweak clear-ghostly: Remove the ghostly status from the selected unit and mark it as dead. This allows getting rid of bugged ghosts which do not show up in the engraving slab menu at all, even after using clear-missing. It works, but is potentially very dangerous - so use with care. Probably (almost certainly) it does not have the same effects like a proper burial. You've been warned.
:tweak clear-resident: Remove the resident flag from the selected unit. Intended to fix bugged migrants who stay at the map edge and don't enter your fort. Only works for dwarves of the own civilization.
:tweak clear-merchant: Remove the merchant flag from the selected unit. Assimilates bugged merchants who don't leave the map into your fort. Only works for dwarves of the own civilization.
:tweak fixmigrant: Remove the resident/merchant flag from the selected unit. Intended to fix bugged migrants/traders who stay at the map edge and don't enter your fort. Only works for dwarves (or generally the player's race in modded games). Can be abused to grab caravan merchants, but that might result into weirdness during trading.
tubefill
========

@ -49,6 +49,8 @@ using namespace std;
#include "modules/Graphic.h"
#include "modules/Windows.h"
#include "RemoteServer.h"
#include "LuaTools.h"
using namespace DFHack;
#include "df/ui.h"
@ -701,6 +703,9 @@ bool Core::Init()
virtual_identity::Init(this);
df::global::InitGlobals();
// initialize common lua context
Lua::Core::Init(con);
// create mutex for syncing with interactive tasks
misc_data_mutex=new mutex();
cerr << "Initializing Plugins.\n";
@ -803,6 +808,13 @@ void *Core::GetData( std::string key )
}
}
bool Core::isSuspended(void)
{
lock_guard<mutex> lock(d->AccessMutex);
return (d->df_suspend_depth > 0 && d->df_suspend_thread == this_thread::get_id());
}
void Core::Suspend()
{
auto tid = this_thread::get_id();
@ -861,13 +873,9 @@ int Core::TileUpdate()
// should always be from simulation thread!
int Core::Update()
{
if(!started)
Init();
if(errorstate)
return -1;
color_ostream_proxy out(con);
// Pretend this thread has suspended the core in the usual way
{
lock_guard<mutex> lock(d->AccessMutex);
@ -877,6 +885,25 @@ int Core::Update()
d->df_suspend_depth = 1000;
}
// Initialize the core
bool first_update = false;
if(!started)
{
first_update = true;
Init();
if(errorstate)
return -1;
Lua::Core::Reset(con, "core init");
}
color_ostream_proxy out(con);
Lua::Core::Reset(out, "DF code execution");
if (first_update)
plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED);
// detect if the game was loaded or unloaded in the meantime
void *new_wdata = NULL;
void *new_mapdata = NULL;
@ -964,6 +991,8 @@ int Core::Update()
assert(d->df_suspend_depth == 0);
// destroy condition
delete nc;
// check lua stack depth
Lua::Core::Reset(con, "suspend");
}
return 0;

@ -74,30 +74,7 @@ distribution.
using namespace DFHack;
using namespace DFHack::LuaWrapper;
template<class T>
void push_pointer_vector(lua_State *state, const std::vector<T*> &pvec)
{
lua_createtable(state,pvec.size(),0);
for (size_t i = 0; i < pvec.size(); i++)
{
Lua::PushDFObject(state, pvec[i]);
lua_rawseti(state, -2, i+1);
}
}
template<class T>
T *get_checked_arg(lua_State *state, int arg)
{
luaL_checkany(state, arg);
auto ptr = Lua::GetDFObject<T>(state, arg);
if (!ptr)
luaL_argerror(state, arg, "invalid type");
return ptr;
}
int push_pos(lua_State *state, df::coord pos)
int Lua::PushPosXYZ(lua_State *state, df::coord pos)
{
if (!pos.isValid())
{
@ -570,9 +547,9 @@ static void OpenModule(lua_State *state, const char *mname,
lua_pop(state, 1);
}
#define WRAPM(module, function) { #function, df::wrap_function(module::function) }
#define WRAP(function) { #function, df::wrap_function(function) }
#define WRAPN(name, function) { #name, df::wrap_function(function) }
#define WRAPM(module, function) { #function, df::wrap_function(module::function,true) }
#define WRAP(function) { #function, df::wrap_function(function,true) }
#define WRAPN(name, function) { #name, df::wrap_function(function,true) }
static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAPM(Translation, TranslateName),
@ -613,7 +590,7 @@ static int job_listNewlyCreated(lua_State *state)
if (Job::listNewlyCreated(&pvec, &nxid))
{
lua_pushinteger(state, nxid);
push_pointer_vector(state, pvec);
Lua::PushVector(state, pvec);
return 2;
}
else
@ -627,17 +604,29 @@ static const luaL_Reg dfhack_job_funcs[] = {
static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getContainer),
WRAPM(Units, setNickname),
WRAPM(Units, getVisibleName),
WRAPM(Units, getNemesis),
WRAPM(Units, isDead),
WRAPM(Units, isAlive),
WRAPM(Units, isSane),
WRAPM(Units, clearBurrowMembers),
WRAPM(Units, isInBurrow),
WRAPM(Units, setInBurrow),
{ NULL, NULL }
};
static int units_getPosition(lua_State *state)
{
return Lua::PushPosXYZ(state, Units::getPosition(Lua::CheckDFObject<df::unit>(state,1)));
}
static const luaL_Reg dfhack_units_funcs[] = {
{ "getPosition", units_getPosition },
{ NULL, NULL }
};
static bool items_moveToGround(df::item *item, df::coord pos)
{
MapExtras::MapCache mc;
@ -661,14 +650,14 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
static int items_getPosition(lua_State *state)
{
return push_pos(state, Items::getPosition(get_checked_arg<df::item>(state,1)));
return Lua::PushPosXYZ(state, Items::getPosition(Lua::CheckDFObject<df::item>(state,1)));
}
static int items_getContainedItems(lua_State *state)
{
std::vector<df::item*> pvec;
Items::getContainedItems(get_checked_arg<df::item>(state,1),&pvec);
push_pointer_vector(state, pvec);
Items::getContainedItems(Lua::CheckDFObject<df::item>(state,1),&pvec);
Lua::PushVector(state, pvec);
return 1;
}
@ -696,6 +685,7 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPM(Maps, getGlobalInitFeature),
WRAPM(Maps, getLocalInitFeature),
WRAPM(Maps, findBurrowByName),
WRAPM(Maps, clearBurrowTiles),
WRAPN(isBlockBurrowTile, maps_isBlockBurrowTile),
WRAPN(setBlockBurrowTile, maps_setBlockBurrowTile),
WRAPM(Maps, isBurrowTile),
@ -706,8 +696,8 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
static int maps_listBurrowBlocks(lua_State *state)
{
std::vector<df::map_block*> pvec;
Maps::listBurrowBlocks(&pvec, get_checked_arg<df::burrow>(state,1));
push_pointer_vector(state, pvec);
Maps::listBurrowBlocks(&pvec, Lua::CheckDFObject<df::burrow>(state,1));
Lua::PushVector(state, pvec);
return 1;
}
@ -729,7 +719,7 @@ void OpenDFHackApi(lua_State *state)
LuaWrapper::SetFunctionWrappers(state, dfhack_module);
OpenModule(state, "gui", dfhack_gui_module);
OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs);
OpenModule(state, "units", dfhack_units_module);
OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs);
OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs);
OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs);
}

@ -58,9 +58,18 @@ distribution.
#include <lauxlib.h>
#include <lualib.h>
#include <lstate.h>
using namespace DFHack;
using namespace DFHack::LuaWrapper;
lua_State *DFHack::Lua::Core::State = NULL;
inline void AssertCoreSuspend(lua_State *state)
{
assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended());
}
void DFHack::Lua::PushDFObject(lua_State *state, type_identity *type, void *ptr)
{
push_object_internal(state, type, ptr, false);
@ -71,6 +80,36 @@ void *DFHack::Lua::GetDFObject(lua_State *state, type_identity *type, int val_in
return get_object_internal(state, type, val_index, exact_type, false);
}
void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type)
{
if (lua_type(state, val_index) == LUA_TNONE)
{
if (val_index > 0)
luaL_argerror(state, val_index, "pointer expected");
else
luaL_error(state, "at index %d: pointer expected", val_index);
}
if (lua_isnil(state, val_index))
return NULL;
void *rv = get_object_internal(state, type, val_index, exact_type, false);
if (!rv)
{
std::string error = "invalid pointer type";
if (type)
error += "; expected: " + type->getFullName();
if (val_index > 0)
luaL_argerror(state, val_index, error.c_str());
else
luaL_error(state, "at index %d: %s", val_index, error.c_str());
}
return rv;
}
static int DFHACK_OSTREAM_TOKEN = 0;
color_ostream *DFHack::Lua::GetOutput(lua_State *L)
@ -418,6 +457,8 @@ static int lua_dfhack_safecall (lua_State *L)
bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres, bool perr)
{
AssertCoreSuspend(L);
int base = lua_gettop(L) - nargs;
color_ostream *cur_out = Lua::GetOutput(L);
@ -440,13 +481,52 @@ bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres
return ok;
}
bool DFHack::Lua::Require(color_ostream &out, lua_State *state,
const std::string &module, bool setglobal)
static int DFHACK_LOADED_TOKEN = 0;
bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *module)
{
AssertCoreSuspend(state);
// Check if it is already loaded
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_LOADED_TOKEN);
lua_pushstring(state, module);
lua_rawget(state, -2);
if (lua_toboolean(state, -1))
{
lua_remove(state, -2);
return true;
}
lua_pop(state, 2);
lua_getglobal(state, "require");
lua_pushstring(state, module.c_str());
lua_pushstring(state, module);
if (!Lua::SafeCall(out, state, 1, 1))
return Lua::SafeCall(out, state, 1, 1);
}
bool DFHack::Lua::PushModulePublic(color_ostream &out, lua_State *state,
const char *module, const char *name)
{
if (!PushModule(out, state, module))
return false;
if (!lua_istable(state, -1))
{
lua_pop(state, 1);
return false;
}
lua_pushstring(state, name);
lua_rawget(state, -2);
lua_remove(state, -2);
return true;
}
bool DFHack::Lua::Require(color_ostream &out, lua_State *state,
const std::string &module, bool setglobal)
{
if (!PushModule(out, state, module.c_str()))
return false;
if (setglobal)
@ -471,6 +551,8 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std
int nargs, int nres, bool perr,
const char *debug_tag, int env_idx)
{
AssertCoreSuspend(state);
if (!debug_tag)
debug_tag = code.c_str();
if (env_idx)
@ -507,6 +589,8 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std
bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state,
const char *prompt, int env, const char *hfile)
{
AssertCoreSuspend(state);
if (!out.is_console())
return false;
if (!lua_checkstack(state, 20))
@ -745,6 +829,31 @@ static int lua_dfhack_with_suspend(lua_State *L)
return lua_gettop(L);
}
static int dfhack_open_plugin(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
luaL_checktype(L, 2, LUA_TSTRING);
const char *name = lua_tostring(L, 2);
PluginManager *pmgr = Core::getInstance().getPluginManager();
Plugin *plugin = pmgr->getPluginByName(name);
if (!plugin)
luaL_error(L, "plugin not found: '%s'", name);
plugin->open_lua(L, 1);
return 0;
}
bool Lua::IsCoreContext(lua_State *state)
{
// This uses a private field of the lua state to
// evaluate the condition without accessing the lua
// stack, and thus requiring a lock on the core state.
return state && Lua::Core::State &&
state->l_G == Lua::Core::State->l_G;
}
static const luaL_Reg dfhack_funcs[] = {
{ "print", lua_dfhack_print },
{ "println", lua_dfhack_println },
@ -757,9 +866,136 @@ static const luaL_Reg dfhack_funcs[] = {
{ "onerror", dfhack_onerror },
{ "call_with_finalizer", dfhack_call_with_finalizer },
{ "with_suspend", lua_dfhack_with_suspend },
{ "open_plugin", dfhack_open_plugin },
{ NULL, NULL }
};
/************
* Events *
************/
static int DFHACK_EVENT_META_TOKEN = 0;
int DFHack::Lua::NewEvent(lua_State *state)
{
lua_newtable(state);
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN);
lua_setmetatable(state, -2);
return 1;
}
static void dfhack_event_invoke(lua_State *L, int base, bool from_c)
{
int event = base+1;
int num_args = lua_gettop(L)-event;
int errorfun = base+2;
lua_pushcfunction(L, dfhack_onerror);
lua_insert(L, errorfun);
int argbase = base+3;
lua_pushnil(L);
// stack: |base| event errorfun (args) key cb (args)
while (lua_next(L, event))
{
if (from_c && lua_islightuserdata(L, -1) && !lua_touserdata(L, -1))
continue;
for (int i = 0; i < num_args; i++)
lua_pushvalue(L, argbase+i);
if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK)
{
report_error(L);
lua_pop(L, 1);
}
}
lua_settop(L, base);
}
static int dfhack_event_call(lua_State *state)
{
luaL_checktype(state, 1, LUA_TTABLE);
luaL_checkstack(state, lua_gettop(state)+2, "stack overflow in event dispatch");
dfhack_event_invoke(state, 0, false);
return 0;
}
void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args)
{
AssertCoreSuspend(state);
int base = lua_gettop(state) - num_args;
if (!lua_checkstack(state, num_args+4))
{
out.printerr("Stack overflow in Lua::InvokeEvent");
lua_settop(state, base);
return;
}
lua_rawgetp(state, LUA_REGISTRYINDEX, key);
if (!lua_istable(state, -1))
{
if (!lua_isnil(state, -1))
out.printerr("Invalid event object in Lua::InvokeEvent");
lua_settop(state, base);
return;
}
lua_insert(state, base+1);
color_ostream *cur_out = Lua::GetOutput(state);
set_dfhack_output(state, &out);
dfhack_event_invoke(state, base, true);
set_dfhack_output(state, cur_out);
}
void DFHack::Lua::CreateEvent(lua_State *state, void *key)
{
lua_rawgetp(state, LUA_REGISTRYINDEX, key);
if (lua_isnil(state, -1))
{
lua_pop(state, 1);
NewEvent(state);
}
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, key);
}
void DFHack::Lua::Notification::invoke(color_ostream &out, int nargs)
{
assert(state);
InvokeEvent(out, state, key, nargs);
}
void DFHack::Lua::Notification::bind(lua_State *state, void *key)
{
this->state = state;
this->key = key;
}
void DFHack::Lua::Notification::bind(lua_State *state, const char *name)
{
CreateEvent(state, this);
if (handler)
{
PushFunctionWrapper(state, 0, name, handler);
lua_rawsetp(state, -2, NULL);
}
this->state = state;
this->key = this;
}
/************************
* Main Open function *
************************/
@ -781,6 +1017,9 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
// Create the dfhack global
lua_newtable(state);
lua_pushboolean(state, IsCoreContext(state));
lua_setfield(state, -2, "is_core_context");
// Create the metatable for exceptions
lua_newtable(state);
lua_pushcfunction(state, dfhack_exception_tostring);
@ -789,6 +1028,15 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
lua_setfield(state, -2, "exception");
lua_newtable(state);
lua_pushcfunction(state, dfhack_event_call);
lua_setfield(state, -2, "__call");
lua_pushcfunction(state, Lua::NewEvent);
lua_setfield(state, -2, "new");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN);
lua_setfield(state, -2, "event");
// Initialize the dfhack global
luaL_setfuncs(state, dfhack_funcs, 0);
@ -796,9 +1044,40 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_setglobal(state, "dfhack");
// stash the loaded module table into our own registry key
lua_getglobal(state, "package");
assert(lua_istable(state, -1));
lua_getfield(state, -1, "loaded");
assert(lua_istable(state, -1));
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_LOADED_TOKEN);
lua_pop(state, 1);
// load dfhack.lua
Require(out, state, "dfhack");
lua_settop(state, 0);
if (!lua_checkstack(state, 64))
out.printerr("Could not extend initial lua stack size to 64 items.\n");
return state;
}
void DFHack::Lua::Core::Init(color_ostream &out)
{
if (State)
return;
State = luaL_newstate();
Lua::Open(out, State);
}
void DFHack::Lua::Core::Reset(color_ostream &out, const char *where)
{
int top = lua_gettop(State);
if (top != 0)
{
out.printerr("Common lua context stack top left at %d after %s.\n", top, where);
lua_settop(State, 0);
}
}

@ -1030,7 +1030,15 @@ static int meta_global_newindex(lua_State *state)
static int meta_call_function(lua_State *state)
{
auto id = (function_identity_base*)lua_touserdata(state, UPVAL_CONTAINER_ID);
if (lua_gettop(state) != id->getNumArgs())
return method_wrapper_core(state, id);
}
int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id)
{
if (id->adjustArgs())
lua_settop(state, id->getNumArgs());
else if (lua_gettop(state) != id->getNumArgs())
field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke");
try {
@ -1050,10 +1058,10 @@ static int meta_call_function(lua_State *state)
}
/**
* Create a closure invoking the given function, and add it to the field table.
* Push a closure invoking the given function.
*/
static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx,
const char *name, function_identity_base *fun)
void LuaWrapper::PushFunctionWrapper(lua_State *state, int meta_idx,
const char *name, function_identity_base *fun)
{
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN);
if (meta_idx)
@ -1063,7 +1071,15 @@ static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx,
lua_pushfstring(state, "%s()", name);
lua_pushlightuserdata(state, fun);
lua_pushcclosure(state, meta_call_function, 4);
}
/**
* Create a closure invoking the given function, and add it to the field table.
*/
static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx,
const char *name, function_identity_base *fun)
{
PushFunctionWrapper(state, meta_idx, name, fun);
lua_setfield(state, field_idx, name);
}

@ -14,7 +14,7 @@
#include <map>
#include "DFHack.h"
#include "Core.h"
#include "PluginManager.h"
#include "Hooks.h"
#include <iostream>

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

@ -32,6 +32,9 @@ distribution.
#include "DataDefs.h"
#include "MiscUtils.h"
#include "LuaWrapper.h"
#include "LuaTools.h"
using namespace DFHack;
#include <string>
@ -107,8 +110,8 @@ struct Plugin::RefLock
void lock_sub()
{
mut->lock();
refcount --;
wakeup->notify_one();
if (--refcount == 0)
wakeup->notify_one();
mut->unlock();
}
void wait()
@ -130,6 +133,13 @@ struct Plugin::RefAutolock
~RefAutolock(){ lock->unlock(); };
};
struct Plugin::RefAutoinc
{
RefLock * lock;
RefAutoinc(RefLock * lck):lock(lck){ lock->lock_add(); };
~RefAutoinc(){ lock->lock_sub(); };
};
Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * pm)
{
filename = filepath;
@ -210,6 +220,7 @@ bool Plugin::load(color_ostream &con)
plugin_shutdown = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_shutdown");
plugin_onstatechange = (command_result (*)(color_ostream &, state_change_event)) LookupPlugin(plug, "plugin_onstatechange");
plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect");
index_lua(plug);
this->name = *plug_name;
plugin_lib = plug;
commands.clear();
@ -222,6 +233,7 @@ bool Plugin::load(color_ostream &con)
else
{
con.printerr("Plugin %s has failed to initialize properly.\n", filename.c_str());
reset_lua();
ClosePlugin(plugin_lib);
state = PS_BROKEN;
return false;
@ -235,13 +247,22 @@ bool Plugin::unload(color_ostream &con)
// if we are actually loaded
if(state == PS_LOADED)
{
// notify the plugin about an attempt to shutdown
if (plugin_onstatechange &&
plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND)
{
con.printerr("Plugin %s has refused to be unloaded.\n", name.c_str());
access->unlock();
return false;
}
// wait for all calls to finish
access->wait();
// notify plugin about shutdown, if it has a shutdown function
command_result cr = CR_OK;
if(plugin_shutdown)
cr = plugin_shutdown(con);
// wait for all calls to finish
access->wait();
// cleanup...
reset_lua();
parent->unregisterCommands(this);
commands.clear();
if(cr == CR_OK)
@ -360,6 +381,7 @@ command_result Plugin::on_update(color_ostream &out)
if(state == PS_LOADED && plugin_onupdate)
{
cr = plugin_onupdate(out);
Lua::Core::Reset(out, "plugin_onupdate");
}
access->lock_sub();
return cr;
@ -372,6 +394,7 @@ command_result Plugin::on_state_change(color_ostream &out, state_change_event ev
if(state == PS_LOADED && plugin_onstatechange)
{
cr = plugin_onstatechange(out, event);
Lua::Core::Reset(out, "plugin_onstatechange");
}
access->lock_sub();
return cr;
@ -418,6 +441,125 @@ Plugin::plugin_state Plugin::getState() const
return state;
}
void Plugin::index_lua(DFLibrary *lib)
{
if (auto cmdlist = (CommandReg*)LookupPlugin(lib, "plugin_lua_commands"))
{
for (; cmdlist->name; ++cmdlist)
{
auto &cmd = lua_commands[cmdlist->name];
if (!cmd) cmd = new LuaCommand(this,cmdlist->name);
cmd->command = cmdlist->command;
}
}
if (auto funlist = (FunctionReg*)LookupPlugin(lib, "plugin_lua_functions"))
{
for (; funlist->name; ++funlist)
{
auto &cmd = lua_functions[funlist->name];
if (!cmd) cmd = new LuaFunction(this,funlist->name);
cmd->identity = funlist->identity;
}
}
if (auto evlist = (EventReg*)LookupPlugin(lib, "plugin_lua_events"))
{
for (; evlist->name; ++evlist)
{
auto &cmd = lua_events[evlist->name];
if (!cmd) cmd = new LuaEvent(this,evlist->name);
cmd->handler.identity = evlist->event->get_handler();
cmd->event = evlist->event;
if (cmd->active)
cmd->event->bind(Lua::Core::State, cmd);
}
}
}
void Plugin::reset_lua()
{
for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it)
it->second->command = NULL;
for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it)
it->second->identity = NULL;
for (auto it = lua_events.begin(); it != lua_events.end(); ++it)
{
it->second->handler.identity = NULL;
it->second->event = NULL;
}
}
int Plugin::lua_cmd_wrapper(lua_State *state)
{
auto cmd = (LuaCommand*)lua_touserdata(state, lua_upvalueindex(1));
RefAutoinc lock(cmd->owner->access);
if (!cmd->command)
luaL_error(state, "plugin command %s() has been unloaded",
(cmd->owner->name+"."+cmd->name).c_str());
return cmd->command(state);
}
int Plugin::lua_fun_wrapper(lua_State *state)
{
auto cmd = (LuaFunction*)lua_touserdata(state, UPVAL_CONTAINER_ID);
RefAutoinc lock(cmd->owner->access);
if (!cmd->identity)
luaL_error(state, "plugin function %s() has been unloaded",
(cmd->owner->name+"."+cmd->name).c_str());
return LuaWrapper::method_wrapper_core(state, cmd->identity);
}
void Plugin::open_lua(lua_State *state, int table)
{
table = lua_absindex(state, table);
RefAutolock lock(access);
for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it)
{
lua_pushlightuserdata(state, it->second);
lua_pushcclosure(state, lua_cmd_wrapper, 1);
lua_setfield(state, table, it->first.c_str());
}
for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it)
{
push_function(state, it->second);
lua_setfield(state, table, it->first.c_str());
}
if (Lua::IsCoreContext(state))
{
for (auto it = lua_events.begin(); it != lua_events.end(); ++it)
{
Lua::CreateEvent(state, it->second);
push_function(state, &it->second->handler);
lua_rawsetp(state, -2, NULL);
it->second->active = true;
if (it->second->event)
it->second->event->bind(state, it->second);
lua_setfield(state, table, it->first.c_str());
}
}
}
void Plugin::push_function(lua_State *state, LuaFunction *fn)
{
lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN);
lua_pushlightuserdata(state, NULL);
lua_pushfstring(state, "%s.%s()", name.c_str(), fn->name.c_str());
lua_pushlightuserdata(state, fn);
lua_pushcclosure(state, lua_fun_wrapper, 4);
}
PluginManager::PluginManager(Core * core)
{
#ifdef LINUX_BUILD

@ -61,6 +61,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "df/world.h"
#include "df/world_data.h"
#include "df/unit.h"
#include "df/unit_misc_trait.h"
#include "df/unit_soul.h"
#include "df/unit_skill.h"
#include "df/material.h"
@ -316,6 +317,19 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit,
}
}
if (mask && mask->misc_traits())
{
auto &vec = unit -> status.misc_traits;
for (size_t i = 0; i < vec.size(); i++)
{
auto trait = vec[i];
auto item = info->add_misc_traits();
item->set_id(trait->id);
item->set_value(trait->value);
}
}
if (unit->curse.add_tags1.whole ||
unit->curse.add_tags2.whole ||
unit->curse.rem_tags1.whole ||
@ -614,6 +628,20 @@ static command_result ListSquads(color_ostream &stream,
return CR_OK;
}
static command_result SetUnitLabors(color_ostream &stream, const SetUnitLaborsIn *in)
{
for (size_t i = 0; i < in->change_size(); i++)
{
auto change = in->change(i);
auto unit = df::unit::find(change.unit_id());
if (unit)
unit->status.labors[change.labor()] = change.value();
}
return CR_OK;
}
CoreService::CoreService() {
suspend_depth = 0;
@ -637,6 +665,8 @@ CoreService::CoreService() {
addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE);
addFunction("ListUnits", ListUnits);
addFunction("ListSquads", ListSquads);
addFunction("SetUnitLabors", SetUnitLabors);
}
CoreService::~CoreService()

@ -65,12 +65,6 @@ namespace DFHack
{
class df_window;
}
// anon type, pretty much
struct DFLibrary;
DFLibrary * OpenPlugin (const char * filename);
void * LookupPlugin (DFLibrary * plugin ,const char * function);
void ClosePlugin (DFLibrary * plugin);
// 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
@ -96,6 +90,8 @@ namespace DFHack
static Core instance;
return instance;
}
/// check if the activity lock is owned by this thread
bool isSuspended(void);
/// try to acquire the activity lock
void Suspend(void);
/// return activity lock
@ -139,6 +135,8 @@ namespace DFHack
static void print(const char *format, ...);
static void printerr(const char *format, ...);
PluginManager *getPluginManager() { return plug_mgr; }
private:
DFHack::Console con;

@ -32,10 +32,6 @@ distribution.
#include "DataIdentity.h"
#include "LuaWrapper.h"
#ifndef BUILD_DFHACK_LIB
#error Due to export issues this header is internal to the main library.
#endif
namespace df {
// A very simple and stupid implementation of some stuff from boost
template<class U, class V> struct is_same_type { static const bool value = false; };
@ -50,7 +46,7 @@ namespace df {
template<class T, bool isvoid = is_same_type<typename return_type<T>::type,void>::value>
struct function_wrapper {};
class cur_lua_ostream_argument {
class DFHACK_EXPORT cur_lua_ostream_argument {
DFHack::color_ostream *out;
public:
cur_lua_ostream_argument(lua_State *state);
@ -165,15 +161,15 @@ INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (vA1,vA2,vA3,vA4,vA5),
public:
typedef function_wrapper<T> wrapper;
function_identity(T ptr)
: function_identity_base(wrapper::num_args), ptr(ptr) {};
function_identity(T ptr, bool vararg)
: function_identity_base(wrapper::num_args, vararg), ptr(ptr) {};
virtual void invoke(lua_State *state, int base) { wrapper::execute(state, base, ptr); }
};
template<class T>
inline function_identity_base *wrap_function(T ptr) {
inline function_identity_base *wrap_function(T ptr, bool vararg = false) {
// bah, but didn't have any idea how to allocate statically
return new function_identity<T>(ptr);
return new function_identity<T>(ptr, vararg);
}
}

@ -39,13 +39,17 @@ namespace DFHack
{
class DFHACK_EXPORT function_identity_base : public type_identity {
int num_args;
bool vararg;
public:
function_identity_base(int num_args) : type_identity(0), num_args(num_args) {};
function_identity_base(int num_args, bool vararg = false)
: type_identity(0), num_args(num_args), vararg(vararg) {};
virtual identity_type type() { return IDTYPE_FUNCTION; }
int getNumArgs() { return num_args; }
bool adjustArgs() { return vararg; }
std::string getFullName() { return "function"; }
virtual void invoke(lua_State *state, int base) = 0;
@ -160,8 +164,6 @@ namespace DFHack
};
}
// Due to export issues, this stuff can only work in the main dll
#ifdef BUILD_DFHACK_LIB
namespace df
{
using DFHack::function_identity_base;
@ -171,7 +173,7 @@ namespace df
using DFHack::ptr_container_identity;
using DFHack::bit_container_identity;
class number_identity_base : public primitive_identity {
class DFHACK_EXPORT number_identity_base : public primitive_identity {
const char *name;
public:
@ -197,7 +199,7 @@ namespace df
virtual void write(void *ptr, double val) { *(T*)ptr = T(val); }
};
class bool_identity : public primitive_identity {
class DFHACK_EXPORT bool_identity : public primitive_identity {
public:
bool_identity() : primitive_identity(sizeof(bool)) {};
@ -207,7 +209,7 @@ namespace df
virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index);
};
class ptr_string_identity : public primitive_identity {
class DFHACK_EXPORT ptr_string_identity : public primitive_identity {
public:
ptr_string_identity() : primitive_identity(sizeof(char*)) {};
@ -217,7 +219,7 @@ namespace df
virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index);
};
class stl_string_identity : public DFHack::constructed_identity {
class DFHACK_EXPORT stl_string_identity : public DFHack::constructed_identity {
public:
stl_string_identity()
: constructed_identity(sizeof(std::string), &allocator_fn<std::string>)
@ -233,7 +235,7 @@ namespace df
virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index);
};
class stl_ptr_vector_identity : public ptr_container_identity {
class DFHACK_EXPORT stl_ptr_vector_identity : public ptr_container_identity {
public:
typedef std::vector<void*> container;
@ -276,6 +278,8 @@ namespace df
}
};
// Due to export issues, this stuff can only work in the main dll
#ifdef BUILD_DFHACK_LIB
class buffer_container_identity : public container_identity {
int size;
@ -370,8 +374,9 @@ namespace df
((container*)ptr)->set(idx, val);
}
};
#endif
class stl_bit_vector_identity : public bit_container_identity {
class DFHACK_EXPORT stl_bit_vector_identity : public bit_container_identity {
public:
typedef std::vector<bool> container;
@ -400,6 +405,7 @@ namespace df
}
};
#ifdef BUILD_DFHACK_LIB
template<class T>
class enum_list_attr_identity : public container_identity {
public:
@ -421,9 +427,10 @@ namespace df
return (void*)&((container*)ptr)->items[idx];
}
};
#endif
#define NUMBER_IDENTITY_TRAITS(type) \
template<> struct identity_traits<type> { \
template<> struct DFHACK_EXPORT identity_traits<type> { \
static number_identity<type> identity; \
static number_identity_base *get() { return &identity; } \
};
@ -439,37 +446,37 @@ namespace df
NUMBER_IDENTITY_TRAITS(uint64_t);
NUMBER_IDENTITY_TRAITS(float);
template<> struct identity_traits<bool> {
template<> struct DFHACK_EXPORT identity_traits<bool> {
static bool_identity identity;
static bool_identity *get() { return &identity; }
};
template<> struct identity_traits<std::string> {
template<> struct DFHACK_EXPORT identity_traits<std::string> {
static stl_string_identity identity;
static stl_string_identity *get() { return &identity; }
};
template<> struct identity_traits<char*> {
template<> struct DFHACK_EXPORT identity_traits<char*> {
static ptr_string_identity identity;
static ptr_string_identity *get() { return &identity; }
};
template<> struct identity_traits<const char*> {
template<> struct DFHACK_EXPORT identity_traits<const char*> {
static ptr_string_identity identity;
static ptr_string_identity *get() { return &identity; }
};
template<> struct identity_traits<void*> {
template<> struct DFHACK_EXPORT identity_traits<void*> {
static pointer_identity identity;
static pointer_identity *get() { return &identity; }
};
template<> struct identity_traits<std::vector<void*> > {
template<> struct DFHACK_EXPORT identity_traits<std::vector<void*> > {
static stl_ptr_vector_identity identity;
static stl_ptr_vector_identity *get() { return &identity; }
};
template<> struct identity_traits<std::vector<bool> > {
template<> struct DFHACK_EXPORT identity_traits<std::vector<bool> > {
static stl_bit_vector_identity identity;
static stl_bit_vector_identity *get() { return &identity; }
};
@ -478,14 +485,17 @@ namespace df
// Container declarations
#ifdef BUILD_DFHACK_LIB
template<class Enum, class FT> struct identity_traits<enum_field<Enum,FT> > {
static primitive_identity *get();
};
#endif
template<class T> struct identity_traits<T *> {
static pointer_identity *get();
};
#ifdef BUILD_DFHACK_LIB
template<class T, int sz> struct identity_traits<T [sz]> {
static container_identity *get();
};
@ -493,11 +503,13 @@ namespace df
template<class T> struct identity_traits<std::vector<T> > {
static container_identity *get();
};
#endif
template<class T> struct identity_traits<std::vector<T*> > {
static stl_ptr_vector_identity *get();
};
#ifdef BUILD_DFHACK_LIB
template<class T> struct identity_traits<std::deque<T> > {
static container_identity *get();
};
@ -518,13 +530,16 @@ namespace df
template<class T> struct identity_traits<enum_list_attr<T> > {
static container_identity *get();
};
#endif
// Container definitions
#ifdef BUILD_DFHACK_LIB
template<class Enum, class FT>
inline primitive_identity *identity_traits<enum_field<Enum,FT> >::get() {
return identity_traits<FT>::get();
}
#endif
template<class T>
inline pointer_identity *identity_traits<T *>::get() {
@ -532,6 +547,7 @@ namespace df
return &identity;
}
#ifdef BUILD_DFHACK_LIB
template<class T, int sz>
inline container_identity *identity_traits<T [sz]>::get() {
static buffer_container_identity identity(sz, identity_traits<T>::get());
@ -544,6 +560,7 @@ namespace df
static stl_container_identity<container> identity("vector", identity_traits<T>::get());
return &identity;
}
#endif
template<class T>
inline stl_ptr_vector_identity *identity_traits<std::vector<T*> >::get() {
@ -551,6 +568,7 @@ namespace df
return &identity;
}
#ifdef BUILD_DFHACK_LIB
template<class T>
inline container_identity *identity_traits<std::deque<T> >::get() {
typedef std::deque<T> container;
@ -576,5 +594,5 @@ namespace df
static enum_list_attr_identity<T> identity(identity_traits<T>::get());
return &identity;
}
}
#endif
}

@ -34,18 +34,33 @@ distribution.
#include <lua.h>
#include <lauxlib.h>
namespace DFHack { namespace Lua {
namespace DFHack {
class function_identity_base;
}
namespace DFHack {namespace Lua {
/**
* Create or initialize a lua interpreter with access to DFHack tools.
*/
DFHACK_EXPORT lua_State *Open(color_ostream &out, lua_State *state = NULL);
/**
* Load a module using require().
* Load a module using require(). Leaves the stack as is.
*/
DFHACK_EXPORT bool Require(color_ostream &out, lua_State *state,
const std::string &module, bool setglobal = false);
/**
* Push the module table, loading it using require() if necessary.
*/
DFHACK_EXPORT bool PushModule(color_ostream &out, lua_State *state, const char *module);
/**
* Push the public object name exported by the module. Uses PushModule.
*/
DFHACK_EXPORT bool PushModulePublic(color_ostream &out, lua_State *state,
const char *module, const char *name);
/**
* Check if the object at the given index is NIL or NULL.
*/
@ -79,6 +94,12 @@ namespace DFHack { namespace Lua {
*/
DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false);
/**
* Check that the value is a wrapped DF object of the given type, and if so return the pointer.
* Otherwise throw an argument type error.
*/
DFHACK_EXPORT void *CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false);
/**
* Assign the value at val_index to the target of given identity using df.assign().
* Return behavior is of SafeCall below.
@ -102,6 +123,14 @@ namespace DFHack { namespace Lua {
return (T*)GetDFObject(state, df::identity_traits<T>::get(), val_index, exact_type);
}
/**
* Check that the value is a wrapped DF object of the correct type, and if so return the pointer. Otherwise throw an argument type error.
*/
template<class T>
T *CheckDFObject(lua_State *state, int val_index, bool exact_type = false) {
return (T*)CheckDFObject(state, df::identity_traits<T>::get(), val_index, exact_type);
}
/**
* Assign the value at val_index to the target using df.assign().
*/
@ -134,5 +163,146 @@ namespace DFHack { namespace Lua {
*/
DFHACK_EXPORT bool InterpreterLoop(color_ostream &out, lua_State *state,
const char *prompt = NULL, int env = 0, const char *hfile = NULL);
/**
* Push utility functions
*/
#define NUMBER_PUSH(type) inline void Push(lua_State *state, type value) { lua_pushnumber(state, value); }
NUMBER_PUSH(char)
NUMBER_PUSH(int8_t) NUMBER_PUSH(uint8_t)
NUMBER_PUSH(int16_t) NUMBER_PUSH(uint16_t)
NUMBER_PUSH(int32_t) NUMBER_PUSH(uint32_t)
NUMBER_PUSH(int64_t) NUMBER_PUSH(uint64_t)
NUMBER_PUSH(float) NUMBER_PUSH(double)
#undef NUMBER_PUSH
inline void Push(lua_State *state, bool value) {
lua_pushboolean(state, value);
}
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); }
template<class T> inline void Push(lua_State *state, T *ptr) {
PushDFObject(state, ptr);
}
template<class T>
void PushVector(lua_State *state, const T &pvec)
{
lua_createtable(state,pvec.size(),0);
for (size_t i = 0; i < pvec.size(); i++)
{
Push(state, pvec[i]);
lua_rawseti(state, -2, i+1);
}
}
DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos);
DFHACK_EXPORT bool IsCoreContext(lua_State *state);
DFHACK_EXPORT int NewEvent(lua_State *state);
DFHACK_EXPORT void CreateEvent(lua_State *state, void *key);
DFHACK_EXPORT void InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args);
/**
* Namespace for the common lua interpreter state.
* All accesses must be done under CoreSuspender.
*/
namespace Core {
DFHACK_EXPORT extern lua_State *State;
// Not exported; for use by the Core class
void Init(color_ostream &out);
void Reset(color_ostream &out, const char *where);
template<class T> inline void Push(T &arg) { Lua::Push(State, arg); }
template<class T> inline void Push(const T &arg) { Lua::Push(State, arg); }
template<class T> inline void PushVector(const T &arg) { Lua::PushVector(State, arg); }
inline bool SafeCall(color_ostream &out, int nargs, int nres, bool perr = true) {
return Lua::SafeCall(out, State, nargs, nres, perr);
}
inline bool PushModule(color_ostream &out, const char *module) {
return Lua::PushModule(out, State, module);
}
inline bool PushModulePublic(color_ostream &out, const char *module, const char *name) {
return Lua::PushModulePublic(out, State, module, name);
}
}
class DFHACK_EXPORT Notification {
lua_State *state;
void *key;
function_identity_base *handler;
public:
Notification(function_identity_base *handler = NULL)
: state(NULL), key(NULL), handler(handler) {}
lua_State *get_state() { return state; }
function_identity_base *get_handler() { return handler; }
void invoke(color_ostream &out, int nargs);
void bind(lua_State *state, const char *name);
void bind(lua_State *state, void *key);
};
}}
#define DEFINE_LUA_EVENT_0(name, handler) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out) { \
handler(out); \
if (name##_event.get_state()) { \
name##_event.invoke(out, 0); \
} \
}
#define DEFINE_LUA_EVENT_1(name, handler, arg_type1) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out, arg_type1 arg1) { \
handler(out, arg1); \
if (auto state = name##_event.get_state()) { \
DFHack::Lua::Push(state, arg1); \
name##_event.invoke(out, 1); \
} \
}
#define DEFINE_LUA_EVENT_2(name, handler, arg_type1, arg_type2) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2) { \
handler(out, arg1, arg2); \
if (auto state = name##_event.get_state()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
name##_event.invoke(out, 2); \
} \
}
#define DEFINE_LUA_EVENT_3(name, handler, arg_type1, arg_type2, arg_type3) \
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) { \
handler(out, arg1, arg2, arg3); \
if (auto state = name##_event.get_state()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
DFHack::Lua::Push(state, arg3); \
name##_event.invoke(out, 3); \
} \
}
#define DEFINE_LUA_EVENT_4(name, handler, arg_type1, arg_type2, arg_type3, arg_type4) \
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) { \
handler(out, arg1, arg2, arg3, arg4); \
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); \
name##_event.invoke(out, 4); \
} \
}

@ -30,6 +30,7 @@ distribution.
#include <map>
#include "DataDefs.h"
#include "PluginManager.h"
#include <lua.h>
#include <lauxlib.h>
@ -156,7 +157,7 @@ namespace DFHack { namespace LuaWrapper {
* Verify that the object is a DF ref with UPVAL_METATABLE.
* If everything ok, extract the address.
*/
uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode);
DFHACK_EXPORT uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode);
bool is_type_compatible(lua_State *state, type_identity *type1, int meta1,
type_identity *type2, int meta2, bool exact_equal);
@ -221,16 +222,20 @@ namespace DFHack { namespace LuaWrapper {
*/
void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum);
struct FunctionReg {
const char *name;
function_identity_base *identity;
};
/**
* Push a closure invoking the given function.
*/
void PushFunctionWrapper(lua_State *state, int meta_idx,
const char *name, function_identity_base *fun);
/**
* Wrap functions and add them to the table on the top of the stack.
*/
using DFHack::FunctionReg;
void SetFunctionWrappers(lua_State *state, const FunctionReg *reg);
int method_wrapper_core(lua_State *state, function_identity_base *id);
void IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct);
void AttachDFGlobals(lua_State *state);

@ -33,7 +33,8 @@ distribution.
#include "RemoteClient.h"
struct DFLibrary;
typedef struct lua_State lua_State;
namespace tthread
{
class mutex;
@ -49,6 +50,20 @@ namespace DFHack
class PluginManager;
class virtual_identity;
class RPCService;
class function_identity_base;
namespace Lua {
class Notification;
}
// anon type, pretty much
struct DFLibrary;
// Open a plugin library
DFLibrary * OpenPlugin (const char * filename);
// find a symbol inside plugin
void * LookupPlugin (DFLibrary * plugin ,const char * function);
// Close a plugin library
void ClosePlugin (DFLibrary * plugin);
enum state_change_event
{
@ -56,7 +71,21 @@ namespace DFHack
SC_WORLD_UNLOADED,
SC_MAP_LOADED,
SC_MAP_UNLOADED,
SC_VIEWSCREEN_CHANGED
SC_VIEWSCREEN_CHANGED,
SC_CORE_INITIALIZED,
SC_BEGIN_UNLOAD
};
struct DFHACK_EXPORT CommandReg {
const char *name;
int (*command)(lua_State*);
};
struct DFHACK_EXPORT FunctionReg {
const char *name;
function_identity_base *identity;
};
struct DFHACK_EXPORT EventReg {
const char *name;
Lua::Notification *event;
};
struct DFHACK_EXPORT PluginCommand
{
@ -102,6 +131,7 @@ namespace DFHack
{
struct RefLock;
struct RefAutolock;
struct RefAutoinc;
enum plugin_state
{
PS_UNLOADED,
@ -138,6 +168,9 @@ namespace DFHack
{
return name;
}
void open_lua(lua_State *state, int table);
private:
RefLock * access;
std::vector <PluginCommand> commands;
@ -147,6 +180,37 @@ namespace DFHack
DFLibrary * plugin_lib;
PluginManager * parent;
plugin_state state;
struct LuaCommand {
Plugin *owner;
std::string name;
int (*command)(lua_State *state);
LuaCommand(Plugin *owner, std::string name) : owner(owner), name(name) {}
};
std::map<std::string, LuaCommand*> lua_commands;
static int lua_cmd_wrapper(lua_State *state);
struct LuaFunction {
Plugin *owner;
std::string name;
function_identity_base *identity;
LuaFunction(Plugin *owner, std::string name) : owner(owner), name(name) {}
};
std::map<std::string, LuaFunction*> lua_functions;
static int lua_fun_wrapper(lua_State *state);
void push_function(lua_State *state, LuaFunction *fn);
struct LuaEvent {
LuaFunction handler;
Lua::Notification *event;
bool active;
LuaEvent(Plugin *owner, std::string name) : handler(owner,name), active(false) {}
};
std::map<std::string, LuaEvent*> lua_events;
void index_lua(DFLibrary *lib);
void reset_lua();
command_result (*plugin_init)(color_ostream &, std::vector <PluginCommand> &);
command_result (*plugin_status)(color_ostream &, std::string &);
command_result (*plugin_shutdown)(color_ostream &);
@ -199,5 +263,18 @@ namespace DFHack
};
/// You have to have this in every plugin you write - just once. Ideally on top of the main file.
#define DFHACK_PLUGIN(plugin_name) DFhackDataExport const char * version = DFHACK_VERSION;\
DFhackDataExport const char * name = plugin_name;
#define DFHACK_PLUGIN(plugin_name) \
DFhackDataExport const char * version = DFHACK_VERSION;\
DFhackDataExport const char * name = plugin_name;
#define DFHACK_PLUGIN_LUA_COMMANDS \
DFhackCExport const DFHack::CommandReg plugin_lua_commands[] =
#define DFHACK_PLUGIN_LUA_FUNCTIONS \
DFhackCExport const DFHack::FunctionReg plugin_lua_functions[] =
#define DFHACK_PLUGIN_LUA_EVENTS \
DFhackCExport const DFHack::EventReg plugin_lua_events[] =
#define DFHACK_LUA_COMMAND(name) { #name, name }
#define DFHACK_LUA_FUNCTION(name) { #name, df::wrap_function(name,true) }
#define DFHACK_LUA_EVENT(name) { #name, &name##_event }
#define DFHACK_LUA_END { NULL, NULL }

@ -66,7 +66,7 @@ namespace DFHack
};
struct RPCMessageHeader {
static const int MAX_MESSAGE_SIZE = 8*1048756;
static const int MAX_MESSAGE_SIZE = 8*1048576;
int16_t id;
int32_t size;

@ -203,6 +203,18 @@ namespace DFHack
return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype));
}
inline
bool isWalkable(df::tiletype tiletype)
{
return ENUM_ATTR(tiletype_shape, walkable, tileShape(tiletype));
}
inline
bool isWalkableUp(df::tiletype tiletype)
{
return ENUM_ATTR(tiletype_shape, walkable_up, tileShape(tiletype));
}
inline
bool isWallTerrain(df::tiletype tiletype)
{

@ -16,4 +16,11 @@ inline void setassignment( int x, int y, bool bit )
tile_bitmask[y] |= (1 << x);
else
tile_bitmask[y] &= ~(1 << x);
}
}
bool has_assignments()
{
for (int i = 0; i < 16; i++)
if (tile_bitmask[i])
return true;
return false;
}

@ -16,4 +16,11 @@ inline void setassignment( int x, int y, bool bit )
tile_bitmask[y] |= (1 << x);
else
tile_bitmask[y] &= ~(1 << x);
}
}
bool has_assignments()
{
for (int i = 0; i < 16; i++)
if (tile_bitmask[i])
return true;
return false;
}

@ -262,12 +262,20 @@ extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, d
DFHACK_EXPORT df::burrow *findBurrowByName(std::string name);
void listBurrowBlocks(std::vector<df::map_block*> *pvec, df::burrow *burrow);
DFHACK_EXPORT void listBurrowBlocks(std::vector<df::map_block*> *pvec, df::burrow *burrow);
DFHACK_EXPORT void clearBurrowTiles(df::burrow *burrow);
df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false);
DFHACK_EXPORT df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false);
DFHACK_EXPORT bool deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block, df::block_burrow *mask);
bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile);
bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable);
inline bool deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block)
{
return deleteBlockBurrowMask(burrow, block, getBlockBurrowMask(burrow, block));
}
DFHACK_EXPORT bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile);
DFHACK_EXPORT bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable);
inline bool isBurrowTile(df::burrow *burrow, df::coord tile) {
return isBlockBurrowTile(burrow, getTileBlock(tile), tile);

@ -193,6 +193,11 @@ DFHACK_EXPORT int32_t GetDwarfCivId ( void );
DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target);
/// Returns the true position of the unit (non-trivial in case of caged).
DFHACK_EXPORT df::coord getPosition(df::unit *unit);
DFHACK_EXPORT df::item *getContainer(df::unit *unit);
DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick);
DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit);
@ -201,6 +206,10 @@ DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit);
DFHACK_EXPORT bool isDead(df::unit *unit);
DFHACK_EXPORT bool isAlive(df::unit *unit);
DFHACK_EXPORT bool isSane(df::unit *unit);
DFHACK_EXPORT bool isCitizen(df::unit *unit);
DFHACK_EXPORT bool isDwarf(df::unit *unit);
DFHACK_EXPORT void clearBurrowMembers(df::burrow *burrow);
DFHACK_EXPORT bool isInBurrow(df::unit *unit, df::burrow *burrow);
DFHACK_EXPORT void setInBurrow(df::unit *unit, df::burrow *burrow, bool enable);

@ -55,6 +55,10 @@ function mkmodule(module,env)
error("Not a table in package.loaded["..module.."]")
end
end
local plugname = string.match(module,'^plugins%.(%w+)$')
if plugname then
dfhack.open_plugin(pkg,plugname)
end
setmetatable(pkg, { __index = (env or _G) })
return pkg
end
@ -101,6 +105,10 @@ function xyz2pos(x,y,z)
end
end
function dfhack.event:__tostring()
return "<event>"
end
function dfhack.persistent:__tostring()
return "<persistent "..self.entry_id..":"..self.key.."=\""
..self.value.."\":"..table.concat(self.ints,",")..">"

@ -529,7 +529,7 @@ df::coord Items::getPosition(df::item *item)
case general_ref_type::UNIT_HOLDER:
if (auto unit = ref->getUnit())
return unit->pos;
return Units::getPosition(unit);
break;
case general_ref_type::BUILDING_HOLDER:
@ -579,7 +579,11 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
{
case general_ref_type::CONTAINED_IN_ITEM:
if (auto item2 = ref->getItem())
{
item2->flags.bits.weight_computed = false;
removeRef(item2->itemrefs, general_ref_type::CONTAINS_ITEM, item->id);
}
break;
case general_ref_type::UNIT_HOLDER:
@ -649,6 +653,8 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
item->flags.bits.in_inventory = true;
container->flags.bits.container = true;
container->flags.bits.weight_computed = false;
ref1->item_id = item->id;
container->itemrefs.push_back(ref1);
ref2->item_id = container->id;

@ -715,6 +715,42 @@ void Maps::listBurrowBlocks(std::vector<df::map_block*> *pvec, df::burrow *burro
}
}
static void destroyBurrowMask(df::block_burrow *mask)
{
if (!mask) return;
auto link = mask->link;
link->prev->next = link->next;
if (link->next)
link->next->prev = link->prev;
delete link;
delete mask;
}
void Maps::clearBurrowTiles(df::burrow *burrow)
{
CHECK_NULL_POINTER(burrow);
df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z);
for (size_t i = 0; i < burrow->block_x.size(); i++)
{
df::coord pos(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]);
auto block = getBlock(pos - base);
if (!block)
continue;
destroyBurrowMask(getBlockBurrowMask(burrow, block));
}
burrow->block_x.clear();
burrow->block_y.clear();
burrow->block_z.clear();
}
df::block_burrow *Maps::getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create)
{
CHECK_NULL_POINTER(burrow);
@ -754,6 +790,36 @@ df::block_burrow *Maps::getBlockBurrowMask(df::burrow *burrow, df::map_block *bl
return NULL;
}
bool Maps::deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block, df::block_burrow *mask)
{
CHECK_NULL_POINTER(burrow);
CHECK_NULL_POINTER(block);
if (!mask)
return false;
df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z);
df::coord pos = base + block->map_pos/16;
destroyBurrowMask(mask);
for (size_t i = 0; i < burrow->block_x.size(); i++)
{
df::coord cur(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]);
if (cur == pos)
{
vector_erase_at(burrow->block_x, i);
vector_erase_at(burrow->block_y, i);
vector_erase_at(burrow->block_z, i);
break;
}
}
return true;
}
bool Maps::isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile)
{
CHECK_NULL_POINTER(burrow);
@ -774,8 +840,13 @@ bool Maps::setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coor
auto mask = getBlockBurrowMask(burrow, block, enable);
if (mask)
{
mask->setassignment(tile & 15, enable);
if (!enable && !mask->has_assignments())
deleteBlockBurrowMask(burrow, block, mask);
}
return true;
}

@ -40,6 +40,7 @@ using namespace std;
// we connect to those
#include "modules/Units.h"
#include "modules/Items.h"
#include "modules/Materials.h"
#include "modules/Translation.h"
#include "ModuleFactory.h"
@ -500,6 +501,34 @@ void Units::CopyNameTo(df::unit * creature, df::language_name * target)
Translation::copyName(&creature->name, target);
}
df::coord Units::getPosition(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
if (unit->flags1.bits.caged)
{
auto cage = getContainer(unit);
if (cage)
return Items::getPosition(cage);
}
return unit->pos;
}
df::item *Units::getContainer(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
for (size_t i = 0; i < unit->refs.size(); i++)
{
df::general_ref *ref = unit->refs[i];
if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM)
return ref->getItem();
}
return NULL;
}
void Units::setNickname(df::unit *unit, std::string nick)
{
CHECK_NULL_POINTER(unit);
@ -624,6 +653,51 @@ bool DFHack::Units::isSane(df::unit *unit)
return true;
}
bool DFHack::Units::isCitizen(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
return unit->civ_id == ui->civ_id &&
!unit->flags1.bits.merchant &&
!unit->flags1.bits.diplomat &&
!unit->flags2.bits.resident &&
!unit->flags1.bits.dead &&
!unit->flags3.bits.ghostly;
}
bool DFHack::Units::isDwarf(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
return unit->race == ui->race_id;
}
void DFHack::Units::clearBurrowMembers(df::burrow *burrow)
{
CHECK_NULL_POINTER(burrow);
for (size_t i = 0; i < burrow->units.size(); i++)
{
auto unit = df::unit::find(burrow->units[i]);
if (unit)
erase_from_vector(unit->burrows, burrow->id);
}
burrow->units.clear();
// Sync ui if active
if (ui && ui->main.mode == ui_sidebar_mode::Burrows &&
ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id)
{
auto &sel = ui->burrows.sel_units;
for (size_t i = 0; i < sel.size(); i++)
sel[i] = false;
}
}
bool DFHack::Units::isInBurrow(df::unit *unit, df::burrow *burrow)
{
CHECK_NULL_POINTER(unit);

@ -130,6 +130,11 @@ message SkillInfo {
required int32 experience = 3;
};
message UnitMiscTrait {
required int32 id = 1;
required int32 value = 2;
};
message BasicUnitInfo {
required int32 unit_id = 1;
@ -166,6 +171,9 @@ message BasicUnitInfo {
// IF mask.skills:
repeated SkillInfo skills = 12;
// IF mask.misc_traits:
repeated UnitMiscTrait misc_traits = 24;
optional UnitCurseInfo curse = 16;
repeated int32 burrows = 21;
@ -175,6 +183,7 @@ message BasicUnitInfoMask {
optional bool labors = 1 [default = false];
optional bool skills = 2 [default = false];
optional bool profession = 3 [default = false];
optional bool misc_traits = 4 [default = false];
};
message BasicSquadInfo {
@ -188,3 +197,9 @@ message BasicSquadInfo {
// Member histfig ids:
repeated sint32 members = 4;
};
message UnitLaborState {
required int32 unit_id = 1;
required int32 labor = 2;
required bool value = 3;
};

@ -99,4 +99,9 @@ message ListUnitsOut {
message ListSquadsIn {}
message ListSquadsOut {
repeated BasicSquadInfo value = 1;
}
};
// RPC SetUnitLabors : SetUnitLaborsIn -> EmptyMessage
message SetUnitLaborsIn {
repeated UnitLaborState change = 1;
};

@ -1 +1 @@
Subproject commit 27fec9797d7d1215bd3f5530b44a55a9d394fe75
Subproject commit a545167050fee9eedd5e319b518d961f933154a3

@ -20,10 +20,11 @@ if(BUILD_DEV_PLUGINS)
add_subdirectory (devel)
endif()
OPTION(BUILD_DF2MC "Build DF2MC (needs a checkout first)." OFF)
if(BUILD_DF2MC)
add_subdirectory (df2mc)
endif()
#It's dead :<
#OPTION(BUILD_DF2MC "Build DF2MC (needs a checkout first)." OFF)
#if(BUILD_DF2MC)
# add_subdirectory (df2mc)
#endif()
OPTION(BUILD_MAPEXPORT "Build map exporter." ON)
if (BUILD_MAPEXPORT)
@ -35,6 +36,10 @@ if (BUILD_DWARFEXPORT)
add_subdirectory (dwarfexport)
endif()
install(DIRECTORY lua/
DESTINATION ${DFHACK_LUA_DESTINATION}/plugins
FILES_MATCHING PATTERN "*.lua")
# Protobuf
FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto)
@ -97,6 +102,8 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(feature feature.cpp)
DFHACK_PLUGIN(lair lair.cpp)
DFHACK_PLUGIN(zone zone.cpp)
# this one exports functions to lua
DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua)
# not yet. busy with other crud again...
#DFHACK_PLUGIN(versionosd versionosd.cpp)
endif()

@ -121,6 +121,13 @@ command_result lua_run_file (color_ostream &out, std::vector <std::string> &para
}
command_result lua_run (color_ostream &out, std::vector <std::string> &parameters)
{
if (!parameters.empty() && parameters[0] == "--core-context")
{
CoreSuspender suspend;
Lua::InterpreterLoop(out, Lua::Core::State);
return CR_OK;
}
mymutex->lock();
lua::state s=lua::glua::Get();

@ -0,0 +1,702 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "Error.h"
#include "DataFuncs.h"
#include "LuaTools.h"
#include "modules/Gui.h"
#include "modules/Job.h"
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "modules/World.h"
#include "modules/Units.h"
#include "TileTypes.h"
#include "DataDefs.h"
#include "df/ui.h"
#include "df/world.h"
#include "df/unit.h"
#include "df/burrow.h"
#include "df/map_block.h"
#include "df/block_burrow.h"
#include "df/job.h"
#include "df/job_list_link.h"
#include "MiscUtils.h"
#include <stdlib.h>
using std::vector;
using std::string;
using std::endl;
using namespace DFHack;
using namespace df::enums;
using namespace dfproto;
using df::global::ui;
using df::global::world;
/*
* Initialization.
*/
static command_result burrow(color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("burrows");
static void init_map(color_ostream &out);
static void deinit_map(color_ostream &out);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"burrow", "Miscellaneous burrow control.", burrow, false,
" burrow enable options...\n"
" burrow disable options...\n"
" Enable or disable features of the plugin.\n"
" See below for a list and explanation.\n"
" burrow clear-units burrow burrow...\n"
" burrow clear-tiles burrow burrow...\n"
" Removes all units or tiles from the burrows.\n"
" burrow set-units target-burrow src-burrow...\n"
" burrow add-units target-burrow src-burrow...\n"
" burrow remove-units target-burrow src-burrow...\n"
" Adds or removes units in source burrows to/from the target\n"
" burrow. Set is equivalent to clear and add.\n"
" burrow set-tiles target-burrow src-burrow...\n"
" burrow add-tiles target-burrow src-burrow...\n"
" burrow remove-tiles target-burrow src-burrow...\n"
" Adds or removes tiles in source burrows to/from the target\n"
" burrow. In place of a source burrow it is possible to use\n"
" one of the following keywords:\n"
" ABOVE_GROUND, SUBTERRANEAN, INSIDE, OUTSIDE,\n"
" LIGHT, DARK, HIDDEN, REVEALED\n"
"Implemented features:\n"
" auto-grow\n"
" When a wall inside a burrow with a name ending in '+' is dug\n"
" out, the burrow is extended to newly-revealed adjacent walls.\n"
" This final '+' may be omitted in burrow name args of commands above.\n"
" Note: Digging 1-wide corridors with the miner inside the burrow is SLOW.\n"
));
if (Core::getInstance().isMapLoaded())
init_map(out);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
deinit_map(out);
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
deinit_map(out);
if (df::global::game_mode &&
*df::global::game_mode == GAMEMODE_DWARF)
init_map(out);
break;
case SC_MAP_UNLOADED:
deinit_map(out);
break;
default:
break;
}
return CR_OK;
}
/*
* State change tracking.
*/
static int name_burrow_id = -1;
static void handle_burrow_rename(color_ostream &out, df::burrow *burrow);
DEFINE_LUA_EVENT_1(onBurrowRename, handle_burrow_rename, df::burrow*);
static void detect_burrow_renames(color_ostream &out)
{
if (ui->main.mode == ui_sidebar_mode::Burrows &&
ui->burrows.in_edit_name_mode &&
ui->burrows.sel_id >= 0)
{
name_burrow_id = ui->burrows.sel_id;
}
else if (name_burrow_id >= 0)
{
auto burrow = df::burrow::find(name_burrow_id);
name_burrow_id = -1;
if (burrow)
onBurrowRename(out, burrow);
}
}
struct DigJob {
int id;
df::job_type job;
df::coord pos;
df::tiletype old_tile;
};
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);
DEFINE_LUA_EVENT_4(onDigComplete, handle_dig_complete,
df::job_type, df::coord, df::tiletype, df::tiletype);
static void detect_digging(color_ostream &out)
{
for (auto it = diggers.begin(); it != diggers.end();)
{
auto worker = df::unit::find(it->first);
if (!worker || !worker->job.current_job ||
worker->job.current_job->id != it->second.id)
{
//out.print("Dig job %d expired.\n", it->second.id);
df::coord pos = it->second.pos;
if (auto block = Maps::getTileBlock(pos))
{
df::tiletype new_tile = block->tiletype[pos.x&15][pos.y&15];
//out.print("Tile %d -> %d\n", it->second.old_tile, new_tile);
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;
}
}
auto cur = it; ++it; diggers.erase(cur);
}
else
++it;
}
std::vector<df::job*> jvec;
if (Job::listNewlyCreated(&jvec, &next_job_id_save))
{
for (size_t i = 0; i < jvec.size(); i++)
{
auto job = jvec[i];
auto type = ENUM_ATTR(job_type, type, job->job_type);
if (type != job_type_class::Digging)
continue;
auto worker = Job::getWorker(job);
if (!worker)
continue;
df::coord pos = job->pos;
auto block = Maps::getTileBlock(pos);
if (!block)
continue;
auto &info = diggers[worker->id];
//out.print("New dig job %d.\n", job->id);
info.id = job->id;
info.job = job->job_type;
info.pos = pos;
info.old_tile = block->tiletype[pos.x&15][pos.y&15];
}
}
}
static bool active = false;
static bool auto_grow = false;
static std::vector<int> grow_burrows;
DFhackCExport command_result plugin_onupdate(color_ostream &out)
{
if (!active)
return CR_OK;
detect_burrow_renames(out);
if (auto_grow)
detect_digging(out);
return CR_OK;
}
/*
* Config and processing
*/
static std::map<std::string,int> name_lookup;
static void parse_names()
{
auto &list = ui->burrows.list;
grow_burrows.clear();
name_lookup.clear();
for (size_t i = 0; i < list.size(); i++)
{
auto burrow = list[i];
std::string name = burrow->name;
if (!name.empty())
{
name_lookup[name] = burrow->id;
if (name[name.size()-1] == '+')
{
grow_burrows.push_back(burrow->id);
name.resize(name.size()-1);
}
if (!name.empty())
name_lookup[name] = burrow->id;
}
}
}
static void reset_tracking()
{
diggers.clear();
next_job_id_save = 0;
}
static void init_map(color_ostream &out)
{
auto config = Core::getInstance().getWorld()->GetPersistentData("burrows/config");
if (config.isValid())
{
auto_grow = !!(config.ival(0) & 1);
}
parse_names();
name_burrow_id = -1;
reset_tracking();
active = true;
if (auto_grow && !grow_burrows.empty())
out.print("Auto-growing %d burrows.\n", grow_burrows.size());
}
static void deinit_map(color_ostream &out)
{
active = false;
auto_grow = false;
reset_tracking();
}
static PersistentDataItem create_config(color_ostream &out)
{
bool created;
auto rv = Core::getInstance().getWorld()->GetPersistentData("burrows/config", &created);
if (created && rv.isValid())
rv.ival(0) = 0;
if (!rv.isValid())
out.printerr("Could not write configuration.");
return rv;
}
static void enable_auto_grow(color_ostream &out, bool enable)
{
if (enable == auto_grow)
return;
auto config = create_config(out);
if (!config.isValid())
return;
if (enable)
config.ival(0) |= 1;
else
config.ival(0) &= ~1;
auto_grow = enable;
if (enable)
reset_tracking();
}
static void handle_burrow_rename(color_ostream &out, df::burrow *burrow)
{
parse_names();
}
static void add_to_burrows(std::vector<df::burrow*> &burrows, df::coord pos)
{
for (size_t i = 0; i < burrows.size(); i++)
Maps::setBurrowTile(burrows[i], pos, true);
}
static void add_walls_to_burrows(color_ostream &out, std::vector<df::burrow*> &burrows,
MapExtras::MapCache &mc, df::coord pos1, df::coord pos2)
{
for (int x = pos1.x; x <= pos2.x; x++)
{
for (int y = pos1.y; y <= pos2.y; y++)
{
for (int z = pos1.z; z <= pos2.z; z++)
{
df::coord pos(x,y,z);
auto tile = mc.tiletypeAt(pos);
if (isWallTerrain(tile))
add_to_burrows(burrows, pos);
}
}
}
}
static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos,
df::tiletype old_tile, df::tiletype new_tile)
{
if (!isWalkable(new_tile))
return;
std::vector<df::burrow*> to_grow;
for (size_t i = 0; i < grow_burrows.size(); i++)
{
auto b = df::burrow::find(grow_burrows[i]);
if (b && Maps::isBurrowTile(b, pos))
to_grow.push_back(b);
}
//out.print("%d to grow.\n", to_grow.size());
if (to_grow.empty())
return;
MapExtras::MapCache mc;
if (!isWalkable(old_tile))
{
add_walls_to_burrows(out, to_grow, mc, pos+df::coord(-1,-1,0), pos+df::coord(1,1,0));
if (isWalkableUp(new_tile))
add_to_burrows(to_grow, pos+df::coord(0,0,1));
if (tileShape(new_tile) == tiletype_shape::RAMP)
{
add_walls_to_burrows(out, to_grow, mc,
pos+df::coord(-1,-1,1), pos+df::coord(1,1,1));
}
}
if (LowPassable(new_tile) && !LowPassable(old_tile))
{
add_to_burrows(to_grow, pos-df::coord(0,0,1));
if (tileShape(new_tile) == tiletype_shape::RAMP_TOP)
{
add_walls_to_burrows(out, to_grow, mc,
pos+df::coord(-1,-1,-1), pos+df::coord(1,1,-1));
}
}
}
static void renameBurrow(color_ostream &out, df::burrow *burrow, std::string name)
{
CHECK_NULL_POINTER(burrow);
// The event makes this absolutely necessary
CoreSuspender suspend;
burrow->name = name;
onBurrowRename(out, burrow);
}
static df::burrow *findByName(color_ostream &out, std::string name, bool silent = false)
{
int id = -1;
if (name_lookup.count(name))
id = name_lookup[name];
auto rv = df::burrow::find(id);
if (!rv && !silent)
out.printerr("Burrow not found: '%s'\n", name.c_str());
return rv;
}
static void copyUnits(df::burrow *target, df::burrow *source, bool enable)
{
CHECK_NULL_POINTER(target);
CHECK_NULL_POINTER(source);
if (source == target)
{
if (!enable)
Units::clearBurrowMembers(target);
return;
}
for (size_t i = 0; i < source->units.size(); i++)
{
auto unit = df::unit::find(source->units[i]);
if (unit)
Units::setInBurrow(unit, target, enable);
}
}
static void copyTiles(df::burrow *target, df::burrow *source, bool enable)
{
CHECK_NULL_POINTER(target);
CHECK_NULL_POINTER(source);
if (source == target)
{
if (!enable)
Maps::clearBurrowTiles(target);
return;
}
std::vector<df::map_block*> pvec;
Maps::listBurrowBlocks(&pvec, source);
for (size_t i = 0; i < pvec.size(); i++)
{
auto block = pvec[i];
auto smask = Maps::getBlockBurrowMask(source, block);
if (!smask)
continue;
auto tmask = Maps::getBlockBurrowMask(target, block, enable);
if (!tmask)
continue;
if (enable)
{
for (int j = 0; j < 16; j++)
tmask->tile_bitmask[j] |= smask->tile_bitmask[j];
}
else
{
for (int j = 0; j < 16; j++)
tmask->tile_bitmask[j] &= ~smask->tile_bitmask[j];
if (!tmask->has_assignments())
Maps::deleteBlockBurrowMask(target, block, tmask);
}
}
}
static void setTilesByDesignation(df::burrow *target, df::tile_designation d_mask,
df::tile_designation d_value, bool enable)
{
CHECK_NULL_POINTER(target);
auto &blocks = world->map.map_blocks;
for (size_t i = 0; i < blocks.size(); i++)
{
auto block = blocks[i];
df::block_burrow *mask = NULL;
for (int x = 0; x < 16; x++)
{
for (int y = 0; y < 16; y++)
{
if ((block->designation[x][y].whole & d_mask.whole) != d_value.whole)
continue;
if (!mask)
mask = Maps::getBlockBurrowMask(target, block, enable);
if (!mask)
goto next_block;
mask->setassignment(x, y, enable);
}
}
if (mask && !enable && !mask->has_assignments())
Maps::deleteBlockBurrowMask(target, block, mask);
next_block:;
}
}
static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable)
{
CHECK_NULL_POINTER(target);
df::tile_designation mask(0);
df::tile_designation value(0);
if (name == "ABOVE_GROUND")
mask.bits.subterranean = true;
else if (name == "SUBTERRANEAN")
mask.bits.subterranean = value.bits.subterranean = true;
else if (name == "LIGHT")
mask.bits.light = value.bits.light = true;
else if (name == "DARK")
mask.bits.light = true;
else if (name == "OUTSIDE")
mask.bits.outside = value.bits.outside = true;
else if (name == "INSIDE")
mask.bits.outside = true;
else if (name == "HIDDEN")
mask.bits.hidden = value.bits.hidden = true;
else if (name == "REVEALED")
mask.bits.hidden = true;
else
return false;
setTilesByDesignation(target, mask, value, enable);
return true;
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(renameBurrow),
DFHACK_LUA_FUNCTION(findByName),
DFHACK_LUA_FUNCTION(copyUnits),
DFHACK_LUA_FUNCTION(copyTiles),
DFHACK_LUA_FUNCTION(setTilesByKeyword),
DFHACK_LUA_END
};
DFHACK_PLUGIN_LUA_EVENTS {
DFHACK_LUA_EVENT(onBurrowRename),
DFHACK_LUA_EVENT(onDigComplete),
DFHACK_LUA_END
};
static command_result burrow(color_ostream &out, vector <string> &parameters)
{
CoreSuspender suspend;
if (!active)
{
out.printerr("The plugin cannot be used without map.\n");
return CR_FAILURE;
}
string cmd;
if (!parameters.empty())
cmd = parameters[0];
if (cmd == "enable" || cmd == "disable")
{
if (parameters.size() < 2)
return CR_WRONG_USAGE;
bool state = (cmd == "enable");
for (int i = 1; i < parameters.size(); i++)
{
string &option = parameters[i];
if (option == "auto-grow")
enable_auto_grow(out, state);
else
return CR_WRONG_USAGE;
}
}
else if (cmd == "clear-units")
{
if (parameters.size() < 2)
return CR_WRONG_USAGE;
for (int i = 1; i < parameters.size(); i++)
{
auto target = findByName(out, parameters[i]);
if (!target)
return CR_WRONG_USAGE;
Units::clearBurrowMembers(target);
}
}
else if (cmd == "set-units" || cmd == "add-units" || cmd == "remove-units")
{
if (parameters.size() < 3)
return CR_WRONG_USAGE;
auto target = findByName(out, parameters[1]);
if (!target)
return CR_WRONG_USAGE;
if (cmd == "set-units")
Units::clearBurrowMembers(target);
bool enable = (cmd != "remove-units");
for (int i = 2; i < parameters.size(); i++)
{
auto source = findByName(out, parameters[i]);
if (!source)
return CR_WRONG_USAGE;
copyUnits(target, source, enable);
}
}
else if (cmd == "clear-tiles")
{
if (parameters.size() < 2)
return CR_WRONG_USAGE;
for (int i = 1; i < parameters.size(); i++)
{
auto target = findByName(out, parameters[i]);
if (!target)
return CR_WRONG_USAGE;
Maps::clearBurrowTiles(target);
}
}
else if (cmd == "set-tiles" || cmd == "add-tiles" || cmd == "remove-tiles")
{
if (parameters.size() < 3)
return CR_WRONG_USAGE;
auto target = findByName(out, parameters[1]);
if (!target)
return CR_WRONG_USAGE;
if (cmd == "set-tiles")
Maps::clearBurrowTiles(target);
bool enable = (cmd != "remove-tiles");
for (int i = 2; i < parameters.size(); i++)
{
if (setTilesByKeyword(target, parameters[i], enable))
continue;
auto source = findByName(out, parameters[i]);
if (!source)
return CR_WRONG_USAGE;
copyTiles(target, source, enable);
}
}
else
{
if (!parameters.empty() && cmd != "?")
out.printerr("Invalid command: %s\n", cmd.c_str());
return CR_WRONG_USAGE;
}
return CR_OK;
}

@ -0,0 +1,33 @@
local _ENV = mkmodule('plugins.burrows')
--[[
Native functions:
* findByName(name) -> burrow
* copyUnits(dest,src,enable)
* copyTiles(dest,src,enable)
* setTilesByKeyword(dest,kwd,enable) -> success
'enable' selects between add and remove modes
--]]
clearUnits = dfhack.units.clearBurrowMembers
function isBurrowUnit(burrow,unit)
return dfhack.units.isInBurrow(unit,burrow)
end
function setBurrowUnit(burrow,unit,enable)
return dfhack.units.setInBurrow(unit,burrow,enable)
end
clearTiles = dfhack.maps.clearBurrowTiles
listBlocks = dfhack.maps.listBurrowBlocks
isBurrowTile = dfhack.maps.isBurrowTile
setBurrowTile = dfhack.maps.setBurrowTile
isBlockBurrowTile = dfhack.maps.isBlockBurrowTile
setBlockBurrowTile = dfhack.maps.setBlockBurrowTile
return _ENV

@ -10,6 +10,8 @@
#include "modules/Units.h"
#include "modules/Items.h"
#include "MiscUtils.h"
#include "DataDefs.h"
#include "df/ui.h"
#include "df/world.h"
@ -53,14 +55,11 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" Intended to fix the case where you can't engrave memorials for ghosts.\n"
" Note that this is very dirty and possibly dangerous!\n"
" Most probably does not have the positive effect of a proper burial.\n"
" tweak clear-resident\n"
" Remove the resident flag from the selected unit.\n"
" Intended to fix bugged migrants who stay at the map edge.\n"
" Only works for dwarves of the own civilization.\n"
" tweak clear-merchant\n"
" Remove the merchant flag from the selected unit.\n"
" Assimilates bugged merchants who don't leave the map into your fort.\n"
" Only works for dwarves of the own civilization.\n"
" tweak fixmigrant\n"
" Forces the selected unit to become a member or your fortress.\n"
" Intended to fix bugged migrants and merchants who stay at the map edge.\n"
" Only works for units of your own race. Can be used for stealing caravan\n"
" traders and guards, but might result into weirdness during trading.\n"
));
return CR_OK;
}
@ -73,9 +72,10 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out)
static command_result lair(color_ostream &out, std::vector<std::string> & params);
// to be called by tweak-merchant and tweak-resident
// to be called by tweak-fixmigrant
// units forced into the fort by removing the flags do not own their clothes
// which has the result that they drop all their clothes and become unhappy because they are naked
// so we need to make them own their clothes and add them to their uniform
command_result fix_clothing_ownership(color_ostream &out, df::unit* unit)
{
// first, find one owned item to initialize the vtable
@ -101,6 +101,8 @@ command_result fix_clothing_ownership(color_ostream &out, df::unit* unit)
{
df::unit_inventory_item* inv_item = unit->inventory[j];
df::item* item = inv_item->item;
// unforbid items (for the case of kidnapping caravan escorts who have their stuff forbidden by default)
inv_item->item->flags.bits.forbid = 0;
if(inv_item->mode == df::unit_inventory_item::T_mode::Worn)
{
// ignore armor?
@ -111,12 +113,18 @@ command_result fix_clothing_ownership(color_ostream &out, df::unit* unit)
if(!Items::getOwner(item))
{
if(Items::setOwner(item, unit))
{
// add to uniform, so they know they should wear their clothes
insert_into_vector(unit->military.uniforms[0], item->id);
fixcount++;
}
else
out << "could not change ownership for item!" << endl;
}
}
}
// clear uniform_drop (without this they would drop their clothes and pick them up some time later)
unit->military.uniform_drop.clear();
out << "ownership for " << fixcount << " clothes fixed" << endl;
return CR_OK;
}
@ -166,49 +174,67 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
return CR_FAILURE;
}
}
else if (cmd == "clear-resident")
else if (cmd == "fixmigrant")
{
df::unit *unit = getSelectedUnit(out);
if (!unit)
return CR_FAILURE;
// must be own race and civ and a merchant
if ( unit->flags2.bits.resident
&& unit->race == df::global::ui->race_id
&& unit->civ_id == df::global::ui->civ_id)
if (!unit)
{
// remove resident flag
unit->flags2.bits.resident = 0;
return fix_clothing_ownership(out, unit);
out << "No unit selected!" << endl;
return CR_FAILURE;
}
else
if(unit->race != df::global::ui->race_id)
{
out.print("That's not a resident dwarf of your civilization!\n");
out << "Selected unit does not belong to your race!" << endl;
return CR_FAILURE;
}
// case #1: migrants who have the resident flag set
// see http://dffd.wimbli.com/file.php?id=6139 for a save
if (unit->flags2.bits.resident)
unit->flags2.bits.resident = 0;
// case #2: migrants who have the merchant flag
// happens on almost all maps after a few migrant waves
if(unit->flags1.bits.merchant)
unit->flags1.bits.merchant = 0;
// this one is a cheat, but bugged migrants usually have the same civ_id
// so it should not be triggered in most cases
// if it happens that the player has 'foreign' units of the same race
// (vanilla df: dwarves not from mountainhome) on his map, just grab them
if(unit->civ_id != df::global::ui->civ_id)
unit->civ_id = df::global::ui->civ_id;
return fix_clothing_ownership(out, unit);
}
else if (cmd == "clear-merchant")
else if (cmd == "makeown")
{
// force a unit into your fort, regardless of civ or race
// allows to "steal" caravan guards etc
df::unit *unit = getSelectedUnit(out);
if (!unit)
return CR_FAILURE;
// must be own race and civ and a merchant
if ( unit->flags1.bits.merchant
&& unit->race == df::global::ui->race_id
&& unit->civ_id == df::global::ui->civ_id)
{
// remove merchant flag
unit->flags1.bits.merchant = 0;
return fix_clothing_ownership(out, unit);
}
else
{
out.print("That's not a dwarf merchant of your civilization!\n");
out << "No unit selected!" << endl;
return CR_FAILURE;
}
if (unit->flags2.bits.resident)
unit->flags2.bits.resident = 0;
if(unit->flags1.bits.merchant)
unit->flags1.bits.merchant = 0;
if(unit->flags1.bits.forest)
unit->flags1.bits.forest = 0;
if(unit->civ_id != df::global::ui->civ_id)
unit->civ_id = df::global::ui->civ_id;
if(unit->profession == df::profession::MERCHANT)
unit->profession = df::profession::TRADER;
if(unit->profession2 == df::profession::MERCHANT)
unit->profession2 = df::profession::TRADER;
return fix_clothing_ownership(out, unit);
}
else return CR_WRONG_USAGE;
else
return CR_WRONG_USAGE;
return CR_OK;
}

@ -890,6 +890,8 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef()
return newref;
}
bool isInBuiltCage(df::unit* unit);
// check if assigned to pen, pit, (built) cage or chain
// note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence)
// animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead
@ -905,7 +907,7 @@ bool isAssigned(df::unit* unit)
if( rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED
|| rtype == df::general_ref_type::BUILDING_CAGED
|| rtype == df::general_ref_type::BUILDING_CHAIN
|| (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isBuiltCageAtPos(unit->pos))
|| (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isInBuiltCage(unit))
)
{
assigned = true;
@ -958,12 +960,11 @@ bool isInBuiltCage(df::unit* unit)
df::building* building = world->buildings.all[b];
if( building->getType() == building_type::Cage)
{
df::building_cagest* oldcage = (df::building_cagest*) building;
for(size_t oc=0; oc<oldcage->assigned_creature.size(); oc++)
df::building_cagest* cage = (df::building_cagest*) building;
for(size_t c=0; c<cage->assigned_creature.size(); c++)
{
if(oldcage->assigned_creature[oc] == unit->id)
if(cage->assigned_creature[c] == unit->id)
{
oldcage->assigned_creature.erase(oldcage->assigned_creature.begin() + oc);
caged = true;
break;
}
@ -2770,7 +2771,9 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
|| !isTame(unit)
|| isWar(unit) // ignore war dogs etc
|| isHunter(unit) // ignore hunting dogs etc
|| (isContainedInItem(unit) && hasValidMapPos(unit) && isBuiltCageAtPos(unit->pos))
// ignore creatures in built cages to leave zoos alone
// (TODO: allow some kind of slaughter cages which you can place near the butcher)
|| (isContainedInItem(unit) && isInBuiltCage(unit))
|| unit->name.has_name
)
continue;