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

develop
Warmist 2012-03-29 21:33:41 +03:00
commit 7d47208c65
37 changed files with 1140 additions and 351 deletions

@ -58,7 +58,7 @@ set(DF_VERSION_MINOR "34")
set(DF_VERSION_PATCH "06")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
set(DFHACK_RELEASE "1")
set(DFHACK_RELEASE "2")
set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-r${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")

@ -129,7 +129,7 @@ Examples:
``changelayer MARBLE all_biomes all_layers``
Convert all layers of all biomes which are not soil into marble.
.. Notes::
.. note::
* If you use changelayer and nothing happens, try to pause/unpause the game for a while and try to move the cursor to another tile. Then try again. If that doesn't help try temporarily changing some other layer, undo your changes and try again for the layer you want to change. Saving and reloading your map might also help.
* You should be fine if you only change single layers without the use of 'force'. Still it's advisable to save your game before messing with the map.
@ -144,6 +144,25 @@ Example:
``changevein NATIVE_PLATINUM``
Convert vein at cursor position into platinum ore.
changeitem
==========
Allows changing item material and base quality. By default the item currently selected in the UI will be changed (you can select items in the 'k' list or inside containers/inventory). By default change is only allowed if materials is of the same subtype (for example wood<->wood, stone<->stone etc). But since some transformations work pretty well and may be desired you can override this with 'force'. Note that some attributes will not be touched, possibly resulting in weirdness. To get an idea how the RAW id should look like, check some items with 'info'. Using 'force' might create items which are not touched by crafters/haulers.
Options
-------
:info: Don't change anything, print some info instead.
:here: Change all items at the cursor position. Requires in-game curser.
:material, m: Change material. Must be followed by valid material RAW id.
:quality, q: Change base quality. Must be followed by number (0-5).
:force: Ignore subtypes, force change to new material.
Examples:
---------
``changeitem m INORGANIC:GRANITE here``
Change material of all items under the cursor to granite.
``changeitem q 5``
Change currently selected item to masterpiece quality.
cursecheck
==========
Checks a single map tile or the whole map/world for cursed creatures (ghosts, vampires, necromancers, werebeasts, zombies).

File diff suppressed because it is too large Load Diff

@ -236,10 +236,17 @@ static int luaB_next (lua_State *L) {
static int luaB_pairs (lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */
lua_pushvalue(L, 1); /* state, */
lua_pushnil(L); /* and initial value */
luaL_checkany(L, 1);
if (luaL_getmetafield(L, 1, "__pairs")) {
lua_pushvalue(L, 1);
lua_call(L, 1, 3);
}
else {
luaL_checktype(L, 1, LUA_TTABLE);
lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */
lua_pushvalue(L, 1); /* state, */
lua_pushnil(L); /* and initial value */
}
return 3;
}
@ -255,10 +262,17 @@ static int ipairsaux (lua_State *L) {
static int luaB_ipairs (lua_State *L) {
luaL_checktype(L, 1, LUA_TTABLE);
lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */
lua_pushvalue(L, 1); /* state, */
lua_pushinteger(L, 0); /* and initial value */
luaL_checkany(L, 1);
if (luaL_getmetafield(L, 1, "__ipairs")) {
lua_pushvalue(L, 1);
lua_call(L, 1, 3);
}
else {
luaL_checktype(L, 1, LUA_TTABLE);
lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */
lua_pushvalue(L, 1); /* state, */
lua_pushinteger(L, 0); /* and initial value */
}
return 3;
}

@ -189,14 +189,14 @@ FILE(GLOB GENERATE_INPUT_SCRIPTS ${dfapi_SOURCE_DIR}/xml/*.pm ${dfapi_SOURCE_DIR
FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/*.xml)
ADD_CUSTOM_COMMAND(
OUTPUT ${dfapi_SOURCE_DIR}/include/df/static.inc
OUTPUT ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml
COMMAND ${PERL_EXECUTABLE} xml/codegen.pl xml include/df
WORKING_DIRECTORY ${dfapi_SOURCE_DIR}
MAIN_DEPENDENCY ${dfapi_SOURCE_DIR}/xml/codegen.pl
DEPENDS ${GENERATE_INPUT_XMLS} ${GENERATE_INPUT_SCRIPTS}
)
ADD_CUSTOM_TARGET(generate_headers DEPENDS ${dfapi_SOURCE_DIR}/include/df/static.inc)
ADD_CUSTOM_TARGET(generate_headers DEPENDS ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml)
IF(UNIX)
# Don't produce debug info for generated stubs

@ -215,12 +215,19 @@ static void autovivify_ptr(lua_State *state, int fname_idx, void **pptr,
lua_pop(state, 1);
}
static bool is_null(lua_State *state, int val_index)
{
return lua_isnil(state, val_index) ||
(lua_islightuserdata(state, val_index) &&
!lua_touserdata(state, val_index));
}
void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr,
type_identity *target, int val_index)
{
auto pptr = (void**)ptr;
if (lua_isnil(state, val_index))
if (is_null(state, val_index))
*pptr = NULL;
else if (lua_istable(state, val_index))
{
@ -389,6 +396,43 @@ static void *find_field(lua_State *state, int index, const char *mode)
return p;
}
static int cur_iter_index(lua_State *state, int len, int fidx, int first_idx = -1)
{
int rv;
if (lua_isnil(state, fidx))
rv = first_idx;
else
{
if (lua_isnumber(state, fidx))
rv = lua_tointeger(state, fidx);
else
{
lua_pushvalue(state, fidx);
lua_rawget(state, UPVAL_FIELDTABLE);
if (!lua_isnumber(state, -1))
field_error(state, fidx, "index not found", "iterate");
rv = lua_tointeger(state, -1);
lua_pop(state, 1);
}
if (rv < 0 || rv >= len)
field_error(state, fidx, "index out of bounds", "iterate");
}
return rv;
}
static void iter_idx_to_name(lua_State *state, int idx)
{
lua_pushvalue(state, idx);
lua_rawget(state, UPVAL_FIELDTABLE);
if (lua_isnil(state, -1))
lua_pop(state, 1);
else
lua_replace(state, idx);
}
static uint8_t *check_method_call(lua_State *state, int min_args, int max_args)
{
int argc = lua_gettop(state)-1;
@ -592,6 +636,24 @@ static int meta_struct_newindex(lua_State *state)
return 0;
}
/**
* Metamethod: iterator for structures.
*/
static int meta_struct_next(lua_State *state)
{
if (lua_gettop(state) < 2) lua_pushnil(state);
int len = lua_objlen(state, UPVAL_FIELDTABLE);
int idx = cur_iter_index(state, len+1, 2, 0);
if (idx == len)
return 0;
lua_rawgeti(state, UPVAL_FIELDTABLE, idx+1);
lua_dup(state);
lua_gettable(state, 1);
return 2;
}
/**
* Metamethod: __index for primitives, i.e. simple object references.
* Fields point to identity, or NULL for metafields.
@ -722,6 +784,39 @@ static int meta_container_newindex(lua_State *state)
return 0;
}
/**
* Metamethod: integer iterator for containers.
*/
static int meta_container_nexti(lua_State *state)
{
if (lua_gettop(state) < 2) lua_pushnil(state);
uint8_t *ptr = get_object_addr(state, 1, 2, "iterate");
auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID);
int len = id->lua_item_count(state, ptr, container_identity::COUNT_LEN);
int idx = cur_iter_index(state, len, 2);
if (++idx >= len)
return 0;
lua_pushinteger(state, idx);
id->lua_item_read(state, 2, ptr, idx);
return 2;
}
/**
* Metamethod: name iterator for containers.
*/
static int meta_container_next(lua_State *state)
{
if (!meta_container_nexti(state))
return 0;
iter_idx_to_name(state, lua_gettop(state)-1);
return 2;
}
/**
* Method: resize container
*/
@ -781,6 +876,17 @@ static int meta_bitfield_len(lua_State *state)
return 1;
}
static void read_bitfield(lua_State *state, uint8_t *ptr, bitfield_identity *id, int idx)
{
int size = id->getBits()[idx].size;
int value = getBitfieldField(ptr, idx, size);
if (size <= 1)
lua_pushboolean(state, value != 0);
else
lua_pushinteger(state, value);
}
/**
* Metamethod: __index for bitfields.
*/
@ -803,13 +909,7 @@ static int meta_bitfield_index(lua_State *state)
}
int idx = check_container_index(state, id->getNumBits(), 2, iidx, "read");
int size = id->getBits()[idx].size;
int value = getBitfieldField(ptr, idx, size);
if (size <= 1)
lua_pushboolean(state, value != 0);
else
lua_pushinteger(state, value);
read_bitfield(state, ptr, id, idx);
return 1;
}
@ -846,6 +946,44 @@ static int meta_bitfield_newindex(lua_State *state)
return 0;
}
/**
* Metamethod: integer iterator for bitfields.
*/
static int meta_bitfield_nexti(lua_State *state)
{
if (lua_gettop(state) < 2) lua_pushnil(state);
uint8_t *ptr = get_object_addr(state, 1, 2, "iterate");
auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID);
int len = id->getNumBits();
int idx = cur_iter_index(state, len, 2);
if (idx < 0)
idx = 0;
else
idx += std::max(1, (int)id->getBits()[idx].size);
if (idx >= len)
return 0;
lua_pushinteger(state, idx);
read_bitfield(state, ptr, id, idx);
return 2;
}
/**
* Metamethod: name iterator for bitfields.
*/
static int meta_bitfield_next(lua_State *state)
{
if (!meta_bitfield_nexti(state))
return 0;
iter_idx_to_name(state, lua_gettop(state)-1);
return 2;
}
/**
* Metamethod: __index for df.global
*/
@ -906,36 +1044,43 @@ static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx,
/**
* Add fields in the array to the UPVAL_FIELDTABLE candidates on the stack.
*/
static void IndexFields(lua_State *state, struct_identity *pstruct)
static void IndexFields(lua_State *state, int base, struct_identity *pstruct)
{
// stack: metatable fieldtable
if (pstruct->getParent())
IndexFields(state, base, pstruct->getParent());
auto fields = pstruct->getFields();
if (!fields)
return;
int base = lua_gettop(state) - 2;
int cnt = lua_objlen(state, base+3); // field iter table
for (struct_identity *p = pstruct; p; p = p->getParent())
for (int i = 0; fields[i].mode != struct_field_info::END; ++i)
{
auto fields = p->getFields();
if (!fields)
continue;
// Qualify conflicting field names with the type
std::string name = fields[i].name;
for (int i = 0; fields[i].mode != struct_field_info::END; ++i)
lua_getfield(state, base+2, name.c_str());
if (!lua_isnil(state, -1))
name = pstruct->getName() + ("." + name);
lua_pop(state, 1);
// Handle the field
switch (fields[i].mode)
{
switch (fields[i].mode)
{
case struct_field_info::OBJ_METHOD:
AddMethodWrapper(state, base+1, base+2, fields[i].name,
(function_identity_base*)fields[i].type);
break;
case struct_field_info::OBJ_METHOD:
AddMethodWrapper(state, base+1, base+2, name.c_str(),
(function_identity_base*)fields[i].type);
break;
case struct_field_info::CLASS_METHOD:
break;
case struct_field_info::CLASS_METHOD:
break;
default:
lua_pushstring(state,fields[i].name);
lua_pushlightuserdata(state,(void*)&fields[i]);
lua_rawset(state,base+2);
break;
}
default:
AssociateId(state, base+3, ++cnt, name.c_str());
lua_pushlightuserdata(state, (void*)&fields[i]);
lua_setfield(state, base+2, name.c_str());
break;
}
}
}
@ -976,8 +1121,20 @@ static void MakeFieldMetatable(lua_State *state, struct_identity *pstruct,
MakeMetatable(state, pstruct, "struct"); // meta, fields
IndexFields(state, pstruct);
// Index the fields
lua_newtable(state);
IndexFields(state, base, pstruct);
// Add the iteration metamethods
PushStructMethod(state, base+1, base+3, meta_struct_next);
SetPairsMethod(state, base+1, "__pairs");
lua_pushnil(state);
SetPairsMethod(state, base+1, "__ipairs");
lua_setfield(state, base+1, "_index_table");
// Add the indexing metamethods
SetStructMethod(state, base+1, base+2, reader, "__index");
SetStructMethod(state, base+1, base+2, writer, "__newindex");
@ -995,8 +1152,21 @@ static void MakePrimitiveMetatable(lua_State *state, type_identity *type)
SetPtrMethods(state, base+1, base+2);
// Index the fields
lua_newtable(state);
EnableMetaField(state, base+2, "value", type);
AssociateId(state, base+3, 1, "value");
// Add the iteration metamethods
PushStructMethod(state, base+1, base+3, meta_struct_next);
SetPairsMethod(state, base+1, "__pairs");
lua_pushnil(state);
SetPairsMethod(state, base+1, "__ipairs");
lua_setfield(state, base+1, "_index_table");
// Add the indexing metamethods
SetStructMethod(state, base+1, base+2, meta_primitive_index, "__index");
SetStructMethod(state, base+1, base+2, meta_primitive_newindex, "__newindex");
}
@ -1048,7 +1218,15 @@ static void MakeContainerMetatable(lua_State *state, container_identity *type,
AddContainerMethodFun(state, base+1, base+2, method_container_erase, "erase", type, item, count);
AddContainerMethodFun(state, base+1, base+2, method_container_insert, "insert", type, item, count);
// push the index table
AttachEnumKeys(state, base+1, base+2, ienum);
PushContainerMethod(state, base+1, base+3, meta_container_next, type, item, count);
SetPairsMethod(state, base+1, "__pairs");
PushContainerMethod(state, base+1, base+3, meta_container_nexti, type, item, count);
SetPairsMethod(state, base+1, "__ipairs");
lua_pop(state, 1);
}
/*
@ -1078,6 +1256,13 @@ void bitfield_identity::build_metatable(lua_State *state)
AttachEnumKeys(state, base+1, base+2, this);
PushContainerMethod(state, base+1, base+3, meta_bitfield_next, this, NULL, -1);
SetPairsMethod(state, base+1, "__pairs");
PushContainerMethod(state, base+1, base+3, meta_bitfield_nexti, this, NULL, -1);
SetPairsMethod(state, base+1, "__ipairs");
lua_pop(state, 1);
EnableMetaField(state, base+2, "whole", this);
}

@ -496,14 +496,7 @@ static int meta_sizeof(lua_State *state)
luaL_error(state, "Usage: object:sizeof() or df.sizeof(object)");
// Two special cases: nil and lightuserdata for NULL and void*
if (lua_isnil(state, 1))
{
lua_pushnil(state);
lua_pushinteger(state, 0);
return 2;
}
if (lua_islightuserdata(state, 1))
if (lua_isnil(state, 1) || lua_islightuserdata(state, 1))
{
lua_pushnil(state);
lua_pushnumber(state, (size_t)lua_touserdata(state, 1));
@ -853,6 +846,23 @@ static int meta_enum_attr_index(lua_State *state)
return 1;
}
static int meta_nodata(lua_State *state)
{
return 0;
}
/**
* Metamethod: __pairs, returning 1st upvalue as iterator
*/
static int meta_pairs(lua_State *state)
{
luaL_checkany(state, 1);
lua_pushvalue(state, lua_upvalueindex(1));
lua_pushvalue(state, 1);
lua_pushnil(state);
return 3;
}
/**
* Make a metatable with most common fields, and an empty table for UPVAL_FIELDTABLE.
*/
@ -879,7 +889,8 @@ void LuaWrapper::MakeMetatable(lua_State *state, type_identity *type, const char
lua_pushstring(state, kind);
lua_setfield(state, base+1, "_kind");
lua_newtable(state); // fieldtable
// Create the field table
lua_newtable(state);
}
/**
@ -930,25 +941,49 @@ void LuaWrapper::SetPtrMethods(lua_State *state, int meta_idx, int read_idx)
EnableMetaField(state, read_idx, "_displace");
}
/**
* Add a __pairs/__ipairs metamethod using iterator on the top of stack.
*/
void LuaWrapper::SetPairsMethod(lua_State *state, int meta_idx, const char *name)
{
if (lua_isnil(state, -1))
{
lua_pop(state, 1);
lua_pushcfunction(state, meta_nodata);
}
lua_pushcclosure(state, meta_pairs, 1);
lua_setfield(state, meta_idx, name);
}
/**
* Add a struct-style (3 upvalues) metamethod to the metatable.
*/
void LuaWrapper::SetStructMethod(lua_State *state, int meta_idx, int ftable_idx,
lua_CFunction function, const char *name)
void LuaWrapper::PushStructMethod(lua_State *state, int meta_idx, int ftable_idx,
lua_CFunction function)
{
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPETABLE_NAME);
lua_pushvalue(state, meta_idx);
lua_pushvalue(state, ftable_idx);
lua_pushcclosure(state, function, 3);
}
/**
* Add a struct-style (3 upvalues) metamethod to the metatable.
*/
void LuaWrapper::SetStructMethod(lua_State *state, int meta_idx, int ftable_idx,
lua_CFunction function, const char *name)
{
PushStructMethod(state, meta_idx, ftable_idx, function);
lua_setfield(state, meta_idx, name);
}
/**
* Add a 6 upvalue metamethod to the metatable.
*/
void LuaWrapper::SetContainerMethod(lua_State *state, int meta_idx, int ftable_idx,
lua_CFunction function, const char *name,
type_identity *container, type_identity *item, int count)
void LuaWrapper::PushContainerMethod(lua_State *state, int meta_idx, int ftable_idx,
lua_CFunction function,
type_identity *container, type_identity *item, int count)
{
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPETABLE_NAME);
lua_pushvalue(state, meta_idx);
@ -962,31 +997,48 @@ void LuaWrapper::SetContainerMethod(lua_State *state, int meta_idx, int ftable_i
lua_pushinteger(state, count);
lua_pushcclosure(state, function, 6);
}
/**
* Add a 6 upvalue metamethod to the metatable.
*/
void LuaWrapper::SetContainerMethod(lua_State *state, int meta_idx, int ftable_idx,
lua_CFunction function, const char *name,
type_identity *container, type_identity *item, int count)
{
PushContainerMethod(state, meta_idx, ftable_idx, function, container, item, count);
lua_setfield(state, meta_idx, name);
}
/**
* If ienum refers to a valid enum, attach its keys to UPVAL_FIELDTABLE,
* and the enum itself to the _enum metafield.
* and the enum itself to the _enum metafield. Pushes the key table on the stack
*/
void LuaWrapper::AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum)
{
EnableMetaField(state, ftable_idx, "_enum");
LookupInTable(state, ienum, DFHACK_TYPEID_TABLE_NAME);
lua_setfield(state, meta_idx, "_enum");
LookupInTable(state, ienum, DFHACK_ENUM_TABLE_NAME);
if (!lua_isnil(state, -1))
{
lua_dup(state);
lua_newtable(state);
lua_swap(state);
lua_setfield(state, -2, "__index");
lua_setmetatable(state, ftable_idx);
}
else
{
lua_pop(state, 1);
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_EMPTY_TABLE_NAME);
}
LookupInTable(state, ienum, DFHACK_TYPEID_TABLE_NAME);
lua_setfield(state, meta_idx, "_enum");
EnableMetaField(state, ftable_idx, "_enum");
lua_dup(state);
lua_setfield(state, meta_idx, "_index_table");
}
static void BuildTypeMetatable(lua_State *state, type_identity *type)
@ -1004,7 +1056,7 @@ static void BuildTypeMetatable(lua_State *state, type_identity *type)
static void RenderTypeChildren(lua_State *state, const std::vector<compound_identity*> &children);
static void AssociateId(lua_State *state, int table, int val, const char *name)
void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *name)
{
lua_pushinteger(state, val);
lua_pushstring(state, name);
@ -1157,6 +1209,8 @@ static void RenderType(lua_State *state, compound_identity *node)
lua_getfield(state, -1, "__newindex");
lua_setfield(state, base+2, "__newindex");
lua_getfield(state, -1, "__pairs");
lua_setfield(state, base+2, "__pairs");
lua_pop(state, 3);
return;
@ -1201,6 +1255,9 @@ static void DoAttach(lua_State *state)
lua_newtable(state);
lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_ENUM_TABLE_NAME);
lua_newtable(state);
lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_EMPTY_TABLE_NAME);
lua_pushcfunction(state, change_error);
lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_CHANGEERROR_NAME);
@ -1250,6 +1307,11 @@ static void DoAttach(lua_State *state)
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME);
lua_setfield(state, -2, "assign");
lua_pushlightuserdata(state, NULL);
lua_setfield(state, -2, "NULL");
lua_pushlightuserdata(state, NULL);
lua_setglobal(state, "NULL");
freeze_table(state, true, "df");
lua_remove(state, -2);
lua_setmetatable(state, -2);

@ -69,6 +69,7 @@ namespace DFHack { namespace LuaWrapper {
#define DFHACK_NEW_NAME "DFHack::New"
#define DFHACK_ASSIGN_NAME "DFHack::Assign"
#define DFHACK_DELETE_NAME "DFHack::Delete"
#define DFHACK_EMPTY_TABLE_NAME "DFHack::EmptyTable"
/*
* Upvalue: contents of DFHACK_TYPETABLE_NAME
@ -160,6 +161,8 @@ namespace DFHack { namespace LuaWrapper {
void SaveInTable(lua_State *state, void *node, const char *tname);
void SaveTypeInfo(lua_State *state, void *node);
void AssociateId(lua_State *state, int table, int val, const char *name);
/**
* Look up the key on the stack in DFHACK_TYPETABLE;
* if found, put result on the stack and return true.
@ -178,11 +181,26 @@ namespace DFHack { namespace LuaWrapper {
* Set metatable properties common to all actual DF object references.
*/
void SetPtrMethods(lua_State *state, int meta_idx, int read_idx);
/**
* Add a __pairs/__ipairs metamethod using iterator on the top of stack.
*/
void SetPairsMethod(lua_State *state, int meta_idx, const char *name);
/**
* Add a struct-style (3 upvalues) metamethod to the stack.
*/
void PushStructMethod(lua_State *state, int meta_idx, int ftable_idx,
lua_CFunction function);
/**
* Add a struct-style (3 upvalues) metamethod to the metatable.
*/
void SetStructMethod(lua_State *state, int meta_idx, int ftable_idx,
lua_CFunction function, const char *name);
/**
* Add a 6 upvalue metamethod to the stack.
*/
void PushContainerMethod(lua_State *state, int meta_idx, int ftable_idx,
lua_CFunction function,
type_identity *container, type_identity *item, int count);
/**
* Add a 6 upvalue metamethod to the metatable.
*/
@ -191,7 +209,7 @@ namespace DFHack { namespace LuaWrapper {
type_identity *container, type_identity *item, int count);
/**
* If ienum refers to a valid enum, attach its keys to UPVAL_FIELDTABLE,
* and the enum itself to the _enum metafield.
* and the enum itself to the _enum metafield. Pushes the key table on the stack.
*/
void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum);

@ -1 +1 @@
Subproject commit 6d11abbbae7e5408e739563266f3300261a5c726
Subproject commit 3e1c728640d8f5a9501908064a2d5385a156058c

@ -91,6 +91,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(follow follow.cpp)
DFHACK_PLUGIN(changevein changevein.cpp)
DFHACK_PLUGIN(changelayer changelayer.cpp)
DFHACK_PLUGIN(changeitem changeitem.cpp)
DFHACK_PLUGIN(advtools advtools.cpp)
DFHACK_PLUGIN(tweak tweak.cpp)
DFHACK_PLUGIN(feature feature.cpp)

@ -55,8 +55,6 @@ DFHACK_PLUGIN("advtools");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
if (!ui_advmode)
return CR_OK;

@ -384,7 +384,7 @@ static const df::job_skill noble_skills[] = {
df::enums::job_skill::RECORD_KEEPING,
};
struct dwarf_info
struct dwarf_info_t
{
int highest_skill;
int total_skill;
@ -432,6 +432,38 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
// sorting objects
struct dwarfinfo_sorter
{
dwarfinfo_sorter(std::vector <dwarf_info_t> & info):dwarf_info(info){};
bool operator() (int i,int j)
{
if (dwarf_info[i].state == IDLE && dwarf_info[j].state != IDLE)
return true;
if (dwarf_info[i].state != IDLE && dwarf_info[j].state == IDLE)
return false;
return dwarf_info[i].mastery_penalty > dwarf_info[j].mastery_penalty;
};
std::vector <dwarf_info_t> & dwarf_info;
};
struct laborinfo_sorter
{
bool operator() (int i,int j)
{
return labor_infos[i].mode < labor_infos[j].mode;
};
};
struct values_sorter
{
values_sorter(std::vector <int> & values):values(values){};
bool operator() (int i,int j)
{
return values[i] > values[j];
};
std::vector<int> & values;
};
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
static int step_count = 0;
@ -478,7 +510,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (n_dwarfs == 0)
return CR_OK;
std::vector<dwarf_info> dwarf_info(n_dwarfs);
std::vector<dwarf_info_t> dwarf_info(n_dwarfs);
std::vector<int> best_noble(ARRAY_COUNT(noble_skills));
std::vector<int> highest_noble_skill(ARRAY_COUNT(noble_skills));
@ -558,8 +590,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (labor == df::enums::unit_labor::NONE)
continue;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
if (labor_infos[labor].is_exclusive && dwarfs[dwarf]->status.labors[labor])
dwarf_info[dwarf].mastery_penalty -= 100;
@ -604,8 +638,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
int job = dwarfs[dwarf]->job.current_job->job_type;
/*
assert(job >= 0);
assert(job < ARRAY_COUNT(dwarf_states));
*/
dwarf_info[dwarf].state = dwarf_states[job];
}
@ -624,8 +660,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
int labor = ENUM_ATTR(job_skill, labor, skill);
if (labor != df::enums::unit_labor::NONE)
{
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_to_skill));
*/
labor_to_skill[labor] = skill;
}
@ -638,13 +676,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (labor == df::enums::unit_labor::NONE)
continue;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
labors.push_back(labor);
}
std::sort(labors.begin(), labors.end(), [] (int i, int j) { return labor_infos[i].mode < labor_infos[j].mode; });
laborinfo_sorter lasorter;
std::sort(labors.begin(), labors.end(), lasorter);
// Handle all skills except those marked HAULERS
@ -652,8 +692,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
auto labor = *lp;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
df::job_skill skill = labor_to_skill[labor];
@ -680,7 +722,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (labor_infos[labor].is_exclusive && dwarf_info[dwarf].has_exclusive_labor)
continue;
int value = dwarf_info[dwarf].mastery_penalty - dwarf_info[dwarf].assigned_jobs;
int value = dwarf_info[dwarf].mastery_penalty - dwarf_info[dwarf].assigned_jobs * 50;
if (skill != df::enums::job_skill::NONE)
{
@ -734,7 +776,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
}
if (labor_infos[labor].mode != EVERYONE)
std::sort(candidates.begin(), candidates.end(), [&values] (int i, int j) { return values[i] > values[j]; });
{
values_sorter ivs(values);
std::sort(candidates.begin(), candidates.end(), ivs);
}
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{
@ -793,8 +838,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarfs[dwarf]->status.labors[labor] = true;
if (labor_infos[labor].is_exclusive)
if (labor_infos[labor].is_exclusive)
{
dwarf_info[dwarf].has_exclusive_labor = true;
// all the exclusive labors require equipment so this should force the dorf to reequip if needed
dwarfs[dwarf]->military.pickup_flags.bits.update = 1;
}
}
}
@ -811,16 +860,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY)
hauler_ids.push_back(dwarf);
}
dwarfinfo_sorter sorter(dwarf_info);
// Idle dwarves come first, then we sort from least-skilled to most-skilled.
std::sort(hauler_ids.begin(), hauler_ids.end(), [&dwarf_info] (int i, int j) -> bool
{
if (dwarf_info[i].state == IDLE && dwarf_info[j].state != IDLE)
return true;
if (dwarf_info[i].state != IDLE && dwarf_info[j].state == IDLE)
return false;
return dwarf_info[i].mastery_penalty > dwarf_info[j].mastery_penalty;
});
std::sort(hauler_ids.begin(), hauler_ids.end(), sorter);
// don't set any haulers if everyone is off drinking or something
if (hauler_ids.size() == 0) {
@ -832,8 +874,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (labor == df::enums::unit_labor::NONE)
continue;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
if (labor_infos[labor].mode != HAULERS)
continue;

@ -0,0 +1,321 @@
// changeitem plugin
// allows to change the material type and quality of selected items
#include <iostream>
#include <iomanip>
#include <sstream>
#include <climits>
#include <vector>
#include <string>
#include <algorithm>
#include <set>
using namespace std;
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Maps.h"
#include "modules/Gui.h"
#include "modules/Items.h"
#include "modules/Materials.h"
#include "modules/MapCache.h"
#include "DataDefs.h"
#include "df/item.h"
#include "df/world.h"
#include "df/general_ref.h"
using namespace DFHack;
using namespace df::enums;
using MapExtras::Block;
using MapExtras::MapCache;
using df::global::world;
DFHACK_PLUGIN("changeitem");
command_result df_changeitem(color_ostream &out, vector <string> & parameters);
const string changeitem_help =
"Changeitem allows to change some item attributes.\n"
"By default the item currently selected in the UI will be changed\n"
"(you can select items in the 'k' list or inside containers/inventory).\n"
"By default change is only allowed if materials is of the same subtype\n"
"(for example wood<->wood, stone<->stone etc). But since some transformations\n"
"work pretty well and may be desired you can override this with 'force'.\n"
"Note that some attributes will not be touched, possibly resulting in weirdness.\n"
"To get an idea how the RAW id should look like, check some items with 'info'.\n"
"Using 'force' might create items which are not touched by crafters/haulers.\n"
"Options:\n"
" info - don't change anything, print some item info instead\n"
" here - change all items at cursor position\n"
" material, m - change material. must be followed by material RAW id\n"
" quality, q - change base quality. must be followed by number (0-5)\n"
" force - ignore subtypes, force change to new material.\n"
"Example:\n"
" changeitem m INORGANIC:GRANITE here\n"
" change material of all items under the cursor to granite\n"
" changeitem q 5\n"
" change currently selected item to masterpiece quality\n";
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"changeitem", "Change item attributes (material, quality).",
df_changeitem, false,
changeitem_help.c_str()
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
// probably there is some method in the library which does the same
// todo: look for it :)
string describeQuality(int q)
{
switch(q)
{
case 0:
return "Basic";
case 1:
return "-Well-crafted-";
case 2:
return "+Finely-crafted+";
case 3:
return "*Superior quality*";
case 4:
return "#Exceptional#";
case 5:
return "$Masterful$";
default:
return "!INVALID!";
}
}
command_result changeitem_execute(
color_ostream &out, df::item * item,
bool info, bool force,
bool change_material, string new_material,
bool change_quality, int new_quality);
command_result df_changeitem(color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
bool here = false;
bool info = false;
bool force = false;
bool change_material = false;
string new_material = "none";
bool change_quality = false;
int new_quality = 0;
for (size_t i = 0; i < parameters.size(); i++)
{
string & p = parameters[i];
if (p == "help" || p == "?")
{
out << changeitem_help << endl;
return CR_OK;
}
else if (p == "here")
{
here = true;
}
else if (p == "info")
{
info = true;
}
else if (p == "force")
{
force = true;
}
else if (p == "material" || p == "m" )
{
// must be followed by material RAW id
// (string like 'INORGANIC:GRANITE', 'PLANT:MAPLE:WOOD', ...)
if(i == parameters.size()-1)
{
out.printerr("no material specified!\n");
}
change_material = true;
new_material = parameters[i+1];
i++;
}
else if (p == "quality" || p == "q")
{
// must be followed by numeric quality (allowed: 0-5)
if(i == parameters.size()-1)
{
out.printerr("no quality specified!\n");
}
string & q = parameters[i+1];
// meh. should use a stringstream instead. but it's only 6 numbers
if(q == "0")
new_quality = 0;
else if(q == "1")
new_quality = 1;
else if(q == "2")
new_quality = 2;
else if(q == "3")
new_quality = 3;
else if(q == "4")
new_quality = 4;
else if(q == "5")
new_quality = 5;
else
{
out << "Invalid quality specified!" << endl;
return CR_WRONG_USAGE;
}
out << "change to quality: " << describeQuality(new_quality) << endl;
change_quality = true;
i++;
}
else
{
out << p << ": Unknown command!" << endl;
return CR_WRONG_USAGE;
}
}
if (!Maps::IsValid())
{
out.printerr("Map is not available!\n");
return CR_FAILURE;
}
MaterialInfo mat_new;
if (change_material && !mat_new.find(new_material))
{
out.printerr("No such material!\n");
return CR_FAILURE;
}
if (here)
{
int processed_total = 0;
int cx, cy, cz;
DFCoord pos_cursor;
// needs a cursor
if (!Gui::getCursorCoords(cx,cy,cz))
{
out.printerr("Cursor position not found. Please enable the cursor.\n");
return CR_FAILURE;
}
pos_cursor = DFCoord(cx,cy,cz);
// uh. is this check necessary?
// changeitem doesn't do stuff with map blocks...
{
MapCache MC;
Block * b = MC.BlockAt(pos_cursor / 16);
if(!b)
{
out.printerr("Cursor is in an invalid/uninitialized area. Place it over a floor.\n");
return CR_FAILURE;
}
// when only changing material it doesn't matter if cursor is over a tile
//df::tiletype ttype = MC.tiletypeAt(pos_cursor);
//if(!DFHack::isFloorTerrain(ttype))
//{
// out.printerr("Cursor should be placed over a floor.\n");
// return CR_FAILURE;
//}
}
// iterate over all items, process those where pos = pos_cursor
size_t numItems = world->items.all.size();
for(size_t i=0; i< numItems; i++)
{
df::item * item = world->items.all[i];
DFCoord pos_item(item->pos.x, item->pos.y, item->pos.z);
if (pos_item != pos_cursor)
continue;
changeitem_execute(out, item, info, force, change_material, new_material, change_quality, new_quality);
processed_total++;
}
out.print("Done. %d items processed.\n", processed_total);
}
else
{
// needs a selected item
df::item *item = Gui::getSelectedItem(out);
if (!item)
{
out.printerr("No item selected.\n");
return CR_FAILURE;
}
changeitem_execute(out, item, info, force, change_material, new_material, change_quality, new_quality);
}
return CR_OK;
}
command_result changeitem_execute(
color_ostream &out, df::item * item,
bool info, bool force,
bool change_material, string new_material,
bool change_quality, int new_quality )
{
MaterialInfo mat_new;
MaterialInfo mat_old;
if(change_material)
mat_new.find(new_material);
if(change_material || info)
mat_old.decode(item);
// print some info, don't change stuff
if(info)
{
out << "Item info: " << endl;
out << " quality: " << describeQuality(item->getQuality()) << endl;
//if(item->isImproved())
// out << " imp.quality: " << describeQuality(item->getImprovementQuality()) << endl;
out << " material: " << mat_old.getToken() << endl;
return CR_OK;
}
if(change_quality)
{
item->setQuality(new_quality);
// it would be nice to be able to change the improved quality, too
// (only allowed if the item is already improved)
// but there is no method in item.h which supports that
// ok: hints from _Q/angavrilov: improvent is a vector, an item can have more than one improvement
// -> virtual_cast to item_constructedst
}
if(change_material)
{
// subtype and mode should match to avoid doing dumb stuff like changing boulders into meat whatever
// changing a stone cabinet to wood is fine, though. as well as lots of other combinations.
// still, it's better to make the user activate 'force' if he really wants to.
if(force||(mat_old.subtype == mat_new.subtype && mat_old.mode==mat_new.mode))
{
item->setMaterial(mat_new.type);
item->setMaterialIndex(mat_new.index);
}
else
{
out.printerr("change denied: subtype doesn't match. use 'force' to override.\n");
}
item->flags.bits.temps_computed = 0; // recalc temperatures next time touched
item->flags.bits.weight_computed = 0; // recalc weight next time touched
}
return CR_OK;
}

@ -84,11 +84,8 @@ command_result changelayer (color_ostream &out, std::vector <std::string> & para
DFHACK_PLUGIN("changelayer");
// Mandatory init function. If you have some global state, create it here.
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
// Fill the command list with your commands.
commands.clear();
commands.push_back(PluginCommand(
"changelayer", "Change a whole geology layer.",
changelayer, false, /* true means that the command can't be used from non-interactive user interface */
@ -98,12 +95,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
return CR_OK;
}
// This is called right before the plugin library is removed from memory.
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
// You *MUST* kill all threads you created before this returns.
// If everything fails, just return CR_FAILURE. Your plugin will be
// in a zombie state, but things won't crash.
return CR_OK;
}

@ -57,7 +57,6 @@ DFHACK_PLUGIN("cursecheck");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("cursecheck",
"Checks for cursed creatures (vampires, necromancers, zombies, ...).",
cursecheck, false ));

@ -56,7 +56,6 @@ DFHACK_PLUGIN("regrass");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector<PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("regrass", "Regrows all surface grass, restoring outdoor plant growth for pre-0.31.19 worlds.", df_regrass));
return CR_OK;
}

@ -96,7 +96,6 @@ DFHACK_PLUGIN("feature");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand(
"feature", "List or manage map features.", feature, false,
" feature list\n"

@ -57,7 +57,6 @@ DFHACK_PLUGIN("liquids");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
liquids_hist.load("liquids.history");
commands.clear();
commands.push_back(PluginCommand(
"liquids", "Place magma, water or obsidian.",
df_liquids, true)); // interactive, needs console for prompt

@ -30,7 +30,6 @@ DFHACK_PLUGIN("mapexport");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
commands.clear();
commands.push_back(PluginCommand("mapexport", "Exports the current map to a file.", mapexport, true));
return CR_OK;
}

@ -18,7 +18,6 @@ DFHACK_PLUGIN("mode");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand(
"mode","View, change and track game mode.",
mode, true,
@ -96,7 +95,8 @@ void printCurrentModes(t_gamemodes gm, Console & con)
command_result mode (color_ostream &out_, vector <string> & parameters)
{
assert(out_.is_console());
if(!out_.is_console())
return CR_FAILURE;
Console &out = static_cast<Console&>(out_);
string command = "";

@ -28,7 +28,6 @@ DFHACK_PLUGIN("plants");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("grow", "Grows saplings into trees (with active cursor, only the targetted one).", df_grow));
commands.push_back(PluginCommand("immolate", "Set plants on fire (under cursor, 'shrubs', 'trees' or 'all').", df_immolate));
commands.push_back(PluginCommand("extirpate", "Kill plants (same mechanics as immolate).", df_extirpate));

@ -41,7 +41,6 @@ DFHACK_PLUGIN("probe");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("probe",
"A tile probe",
df_probe));

@ -195,7 +195,6 @@ DFHACK_PLUGIN("prospector");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand(
"prospect", "Show stats of available raw resources.",
prospector, false,

@ -38,7 +38,6 @@ DFHACK_PLUGIN("rename");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
if (world && ui) {
commands.push_back(PluginCommand(
"rename", "Rename various things.", rename, false,

@ -76,7 +76,6 @@ DFHACK_PLUGIN("reveal");
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("reveal","Reveal the map. 'reveal hell' will also reveal hell. 'reveal demon' won't pause.",reveal));
commands.push_back(PluginCommand("unreveal","Revert the map to its previous state.",unreveal));
commands.push_back(PluginCommand("revtoggle","Reveal/unreveal depending on state.",revtoggle));

@ -243,7 +243,6 @@ DFHACK_PLUGIN("seedwatch");
DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginCommand>& commands)
{
commands.clear();
commands.push_back(PluginCommand("seedwatch", "Switches cookery based on quantity of seeds, to keep reserves", df_seedwatch));
// fill in the abbreviations map, with abbreviations for the standard plants
abbreviations["bs"] = "SLIVER_BARB";

@ -272,7 +272,6 @@ DFHACK_PLUGIN("showmood");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector<PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("showmood", "Shows items needed for current strange mood.", df_showmood));
return CR_OK;
}

@ -29,7 +29,6 @@ DFHACK_PLUGIN("skeleton");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
// Fill the command list with your commands.
commands.clear();
commands.push_back(PluginCommand(
"skeleton", "Do nothing, look pretty.",
skeleton, false, /* true means that the command can't be used from non-interactive user interface */

@ -29,7 +29,6 @@ DFHACK_PLUGIN("stockpiles");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
if (world && ui) {
commands.push_back(
PluginCommand(

@ -1 +1 @@
Subproject commit 07ddc8c13e0bb38cde77937803e9320e2b26f2d9
Subproject commit 906d6439d4cb694cf287d63badabb675a8b89329

@ -497,7 +497,6 @@ DFHACK_PLUGIN("tiletypes");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
tiletypes_hist.load("tiletypes.history");
commands.clear();
commands.push_back(PluginCommand("tiletypes", "Paint map tiles freely, similar to liquids.", df_tiletypes, true));
return CR_OK;
}
@ -525,7 +524,8 @@ command_result df_tiletypes (color_ostream &out, vector <string> & parameters)
}
}
assert(out.is_console());
if(!out.is_console())
return CR_FAILURE;
Console &con = static_cast<Console&>(out);
TileType filter, paint;

@ -23,7 +23,6 @@ DFHACK_PLUGIN("tubefill");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("tubefill","Fill in all the adamantine tubes again.",tubefill));
return CR_OK;
}

@ -41,7 +41,6 @@ DFHACK_PLUGIN("tweak");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand(
"tweak", "Various tweaks for minor bugs.", tweak, false,
" tweak clear-missing\n"

@ -27,7 +27,6 @@ DFHACK_PLUGIN("vdig");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand(
"vdig","Dig a whole vein.",vdig,Gui::cursor_hotkey,
" Designates a whole vein under the cursor for digging.\n"

@ -46,7 +46,6 @@ DFTileSurface* createTile(int x, int y)
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("versionosd",
"Toggles displaying version in DF window",
df_versionosd));

@ -19,7 +19,6 @@ DFHACK_PLUGIN("weather");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand(
"weather", "Print the weather map or change weather.",
weather, false,

@ -59,7 +59,6 @@ DFHACK_PLUGIN("workflow");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
if (!world || !ui)
return CR_FAILURE;