diff --git a/CMakeLists.txt b/CMakeLists.txt index d39d940e2..60b4ab656 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,7 @@ set(DF_VERSION_MINOR "34") set(DF_VERSION_PATCH "05") set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}") -set(DFHACK_RELEASE "1e") +set(DFHACK_RELEASE "1f") set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-r${DFHACK_RELEASE}") add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}") @@ -103,6 +103,7 @@ ENDIF() # use shared libraries for protobuf ADD_DEFINITIONS(-DPROTOBUF_USE_DLLS) +ADD_DEFINITIONS(-DLUA_BUILD_AS_DLL) if(UNIX) add_definitions(-D_LINUX) diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index b135f221d..23ea6a485 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -1,6 +1,9 @@ PROJECT ( lua C ) CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +# TODO: make this RelWithDebInfo only +ADD_DEFINITIONS(-DLUA_USE_APICHECK) + IF(WIN32) ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE ) ELSE() @@ -76,8 +79,13 @@ src/lzio.c ) LIST(APPEND SRC_LIBLUA ${HDR_LIBLUA}) -ADD_LIBRARY ( lua STATIC EXCLUDE_FROM_ALL ${SRC_LIBLUA} ) +ADD_LIBRARY ( lua SHARED EXCLUDE_FROM_ALL ${SRC_LIBLUA} ) TARGET_LINK_LIBRARIES ( lua ${LIBS}) + +install(TARGETS lua + LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} + RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) + IDE_FOLDER(lua "Depends") #SET ( SRC_LUA src/lua.c ) diff --git a/depends/lua/include/luaconf.h b/depends/lua/include/luaconf.h index 660793356..b202967b3 100644 --- a/depends/lua/include/luaconf.h +++ b/depends/lua/include/luaconf.h @@ -151,11 +151,20 @@ ** the libraries, you may want to use the following definition (define ** LUA_BUILD_AS_DLL to get it). */ +#ifdef __cplusplus + #define LUA_API_EXTERN extern "C" +#else + #define LUA_API_EXTERN extern +#endif #if defined(LUA_BUILD_AS_DLL) - #if defined(LUA_CORE) || defined(LUA_LIB) - #define LUA_API __declspec(dllexport) + #if defined(_MSC_VER) + #if defined(LUA_CORE) || defined(LUA_LIB) + #define LUA_API __declspec(dllexport) LUA_API_EXTERN + #else + #define LUA_API __declspec(dllimport) LUA_API_EXTERN + #endif #else - #define LUA_API __declspec(dllimport) + #define LUA_API LUA_API_EXTERN __attribute__ ((visibility("default"))) #endif #else #ifdef __cplusplus diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 63107456a..72bf91e62 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -55,8 +55,10 @@ SET(MAIN_SOURCES Core.cpp ColorText.cpp DataDefs.cpp +LuaWrapper.cpp DataStatics.cpp DataStaticsCtor.cpp +DataStaticsFields.cpp MiscUtils.cpp PluginManager.cpp TileTypes.cpp @@ -195,6 +197,14 @@ ADD_CUSTOM_COMMAND( ADD_CUSTOM_TARGET(generate_headers DEPENDS ${dfapi_SOURCE_DIR}/include/df/static.inc) +IF(UNIX) + # Don't produce debug info for generated stubs + SET_SOURCE_FILES_PROPERTIES(DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp + PROPERTIES COMPILE_FLAGS "-g0 -O1") +ELSE(WIN32) +ENDIF() + + # Compilation ADD_DEFINITIONS(-DBUILD_DFHACK_LIB) @@ -242,7 +252,7 @@ ENDIF() #effectively disables debug builds... SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) -TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket ${PROJECT_LIBS}) +TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua ${PROJECT_LIBS}) SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "") TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 30a4da271..6fcd1141a 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -34,28 +34,186 @@ distribution. #include "tinythread.h" // must be last due to MS stupidity #include "DataDefs.h" +#include "DataIdentity.h" #include "MiscUtils.h" using namespace DFHack; + +void *type_identity::do_allocate_pod() { + void *p = malloc(size); + memset(p, 0, size); + return p; +} + +void type_identity::do_copy_pod(void *tgt, const void *src) { + memmove(tgt, src, size); +}; + +void *type_identity::allocate() { + if (can_allocate()) + return do_allocate(); + else + return NULL; +} + +bool type_identity::copy(void *tgt, const void *src) { + if (can_allocate() && tgt && src) + do_copy(tgt, src); + else + return false; +} + +void *enum_identity::do_allocate() { + void *p = malloc(byte_size()); + memcpy(p, &first_item_value, std::min(byte_size(), sizeof(int64_t))); + return p; +} + /* The order of global object constructor calls is * undefined between compilation units. Therefore, * this list has to be plain data, so that it gets * initialized by the loader in the initial mmap. */ -virtual_identity *virtual_identity::list = NULL; +compound_identity *compound_identity::list = NULL; +std::vector compound_identity::top_scope; -virtual_identity::virtual_identity(const char *dfhack_name, const char *original_name, virtual_identity *parent) - : dfhack_name(dfhack_name), original_name(original_name), parent(parent), - prev(NULL), vtable_ptr(NULL), has_children(true) +compound_identity::compound_identity(size_t size, TAllocateFn alloc, + compound_identity *scope_parent, const char *dfhack_name) + : constructed_identity(size, alloc), scope_parent(scope_parent), dfhack_name(dfhack_name) { - // Link into the static list. Nothing else can be safely done at this point. - next = list; list = this; + next = list; list = this; +} + +void compound_identity::doInit(Core *) +{ + if (scope_parent) + scope_parent->scope_children.push_back(this); + else + top_scope.push_back(this); +} + +std::string compound_identity::getFullName() +{ + if (scope_parent) + return scope_parent->getFullName() + "." + getName(); + else + return getName(); } -/* Vtable to identity lookup. */ static tthread::mutex *known_mutex = NULL; + +void compound_identity::Init(Core *core) +{ + if (!known_mutex) + known_mutex = new tthread::mutex(); + + // This cannot be done in the constructors, because + // they are called in an undefined order. + for (compound_identity *p = list; p; p = p->next) + p->doInit(core); + + //FIXME: ... nuked. the group was empty... +/* + // Read pre-filled vtable ptrs + OffsetGroup *ptr_table = core->vinfo->getGroup("vtable"); + for (virtual_identity *p = list; p; p = p->next) { + void * tmp; + if (ptr_table->getSafeAddress(p->getName(),tmp)) + p->vtable_ptr = tmp; + } + */ +} + +bitfield_identity::bitfield_identity(size_t size, + compound_identity *scope_parent, const char *dfhack_name, + int num_bits, const bitfield_item_info *bits) + : compound_identity(size, NULL, scope_parent, dfhack_name), bits(bits), num_bits(num_bits) +{ +} + +enum_identity::enum_identity(size_t size, + compound_identity *scope_parent, const char *dfhack_name, + type_identity *base_type, + int64_t first_item_value, int64_t last_item_value, + const char *const *keys) + : compound_identity(size, NULL, scope_parent, dfhack_name), + first_item_value(first_item_value), last_item_value(last_item_value), + keys(keys), base_type(base_type) +{ +} + +struct_identity::struct_identity(size_t size, TAllocateFn alloc, + compound_identity *scope_parent, const char *dfhack_name, + struct_identity *parent, const struct_field_info *fields) + : compound_identity(size, alloc, scope_parent, dfhack_name), + parent(parent), has_children(false), fields(fields) +{ +} + +void struct_identity::doInit(Core *core) +{ + compound_identity::doInit(core); + + if (parent) { + parent->children.push_back(this); + parent->has_children = true; + } +} + +bool struct_identity::is_subclass(struct_identity *actual) +{ + for (; actual; actual = actual->getParent()) + if (actual == this) return true; + + return false; +} + +std::string pointer_identity::getFullName() +{ + return (target ? target->getFullName() : std::string("void")) + "*"; +} + +std::string container_identity::getFullName(type_identity *item) +{ + return "<" + (item ? item->getFullName() : std::string("void")) + ">"; +} + +std::string ptr_container_identity::getFullName(type_identity *item) +{ + return "<" + (item ? item->getFullName() : std::string("void")) + "*>"; +} + +std::string bit_container_identity::getFullName(type_identity *) +{ + return ""; +} + +std::string df::buffer_container_identity::getFullName(type_identity *item) +{ + return (item ? item->getFullName() : std::string("void")) + + (size > 0 ? stl_sprintf("[%d]", size) : std::string("[]")); +} + +virtual_identity::virtual_identity(size_t size, TAllocateFn alloc, + const char *dfhack_name, const char *original_name, + virtual_identity *parent, const struct_field_info *fields) + : struct_identity(size, alloc, NULL, dfhack_name, parent, fields), original_name(original_name), + vtable_ptr(NULL) +{ +} + +static std::map name_lookup; + +void virtual_identity::doInit(Core *core) +{ + struct_identity::doInit(core); + + name_lookup[getOriginalName()] = this; +} + +/* Vtable to identity lookup. */ std::map virtual_identity::known; virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) @@ -78,8 +236,9 @@ virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) virtual_identity *actual = NULL; - for (virtual_identity *p = list; p; p = p->next) { - if (strcmp(name.c_str(), p->getOriginalName()) != 0) continue; + auto name_it = name_lookup.find(name); + if (name_it != name_lookup.end()) { + virtual_identity *p = name_it->second; if (p->vtable_ptr && p->vtable_ptr != vtable) { std::cerr << "Conflicting vtable ptr for class '" << p->getName() @@ -103,14 +262,6 @@ virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) return NULL; } -bool virtual_identity::is_subclass(virtual_identity *actual) -{ - for (; actual; actual = actual->parent) - if (actual == this) return true; - - return false; -} - void virtual_identity::adjust_vtable(virtual_ptr obj, virtual_identity *main) { if (vtable_ptr) { @@ -135,35 +286,6 @@ virtual_ptr virtual_identity::clone(virtual_ptr obj) return copy; } -void virtual_identity::Init(Core *core) -{ - if (!known_mutex) - known_mutex = new tthread::mutex(); - - // This cannot be done in the constructors, because - // they are called in an undefined order. - for (virtual_identity *p = list; p; p = p->next) { - p->has_children = false; - p->children.clear(); - } - for (virtual_identity *p = list; p; p = p->next) { - if (p->parent) { - p->parent->children.push_back(p); - p->parent->has_children = true; - } - } - //FIXME: ... nuked. the group was empty... -/* - // Read pre-filled vtable ptrs - OffsetGroup *ptr_table = core->vinfo->getGroup("vtable"); - for (virtual_identity *p = list; p; p = p->next) { - void * tmp; - if (ptr_table->getSafeAddress(p->getName(),tmp)) - p->vtable_ptr = tmp; - } - */ -} - bool DFHack::findBitfieldField(unsigned *idx, const std::string &name, unsigned size, const bitfield_item_info *items) { diff --git a/library/DataStatics.cpp b/library/DataStatics.cpp index 5ccabb7e1..1e4b21be9 100644 --- a/library/DataStatics.cpp +++ b/library/DataStatics.cpp @@ -7,6 +7,8 @@ #include "df/world_data.h" #include "df/ui.h" +#include "DataIdentity.h" + namespace { template inline T &_toref(T &r) { return r; } @@ -21,6 +23,8 @@ namespace { #define INIT_GLOBAL_FUNCTION_ITEM(type,name) \ if (global_table_->getAddress(#name,tmp_)) name = (type*)tmp_; +#define TID(type) (&identity_traits< type >::identity) + // Instantiate all the static objects #include "df/static.inc" #include "df/static.enums.inc" diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp new file mode 100644 index 000000000..89736369f --- /dev/null +++ b/library/DataStaticsFields.cpp @@ -0,0 +1,49 @@ +#include "Internal.h" +#include "DataDefs.h" +#include "MiscUtils.h" +#include "VersionInfo.h" + +#include "df/world.h" +#include "df/world_data.h" +#include "df/ui.h" + +#include "DataIdentity.h" + +#include + +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +namespace df { +#define NUMBER_IDENTITY_TRAITS(type) \ + number_identity identity_traits::identity(#type); + + NUMBER_IDENTITY_TRAITS(char); + NUMBER_IDENTITY_TRAITS(int8_t); + NUMBER_IDENTITY_TRAITS(uint8_t); + NUMBER_IDENTITY_TRAITS(int16_t); + NUMBER_IDENTITY_TRAITS(uint16_t); + NUMBER_IDENTITY_TRAITS(int32_t); + NUMBER_IDENTITY_TRAITS(uint32_t); + NUMBER_IDENTITY_TRAITS(int64_t); + NUMBER_IDENTITY_TRAITS(uint64_t); + NUMBER_IDENTITY_TRAITS(float); + + bool_identity identity_traits::identity; + stl_string_identity identity_traits::identity; + pointer_identity identity_traits::identity; + stl_ptr_vector_identity identity_traits >::identity; + stl_bit_vector_identity identity_traits >::identity; + + buffer_container_identity buffer_container_identity::base_instance; + +#undef NUMBER_IDENTITY_TRAITS +} + +#define TID(type) (&identity_traits< type >::identity) + +#define FLD(mode, name) struct_field_info::mode, #name, offsetof(CUR_STRUCT, name) +#define GFLD(mode, name) struct_field_info::mode, #name, (size_t)&df::global::name +#define FLD_END struct_field_info::END + +// Field definitions +#include "df/static.fields.inc" diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp new file mode 100644 index 000000000..d6edef4e2 --- /dev/null +++ b/library/LuaWrapper.cpp @@ -0,0 +1,1722 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "Internal.h" + +#include +#include +#include + +#include "MemAccess.h" +#include "Core.h" +#include "VersionInfo.h" +#include "tinythread.h" +// must be last due to MS stupidity +#include "DataDefs.h" +#include "DataIdentity.h" + +#include "MiscUtils.h" + +#include +#include + +using namespace DFHack; + +static luaL_Reg no_functions[] = { { NULL, NULL } }; + +inline void lua_dup(lua_State *state) { lua_pushvalue(state, -1); } +inline void lua_swap(lua_State *state) { lua_insert(state, -2); } + +/* + * Registry name: hash of type metatables <-> type identities. + */ +#define DFHACK_TYPETABLE_NAME "DFHack::DFTypes" + +/* + * Registry name: hash of type identity -> node in df.etc... + */ +#define DFHACK_TYPEID_TABLE_NAME "DFHack::DFTypeIds" + +/* + * Registry name: hash of enum/bitfield identity -> index lookup table + */ +#define DFHACK_ENUM_TABLE_NAME "DFHack::DFEnums" + +/* + * Registry name: hash of pointer target identity <-> adhoc pointer identity userdata. + */ +#define DFHACK_PTR_IDTABLE_NAME "DFHack::PtrDFTypes" + +// Function registry names +#define DFHACK_CHANGEERROR_NAME "DFHack::ChangeError" +#define DFHACK_COMPARE_NAME "DFHack::ComparePtrs" +#define DFHACK_TYPE_TOSTRING_NAME "DFHack::TypeToString" +#define DFHACK_SIZEOF_NAME "DFHack::Sizeof" +#define DFHACK_DISPLACE_NAME "DFHack::Displace" + +/* + * Upvalue: contents of DFHACK_TYPETABLE_NAME + */ +#define UPVAL_TYPETABLE lua_upvalueindex(1) + +/* + * Expected metatable of the current object. + */ +#define UPVAL_METATABLE lua_upvalueindex(2) + +/* + * Table mapping field names to indices or data structure pointers. + * Enum index table is linked into here via getmetatable($).__index. + * Fields that are actually in UPVAL_METATABLE are marked with NULL light udata. + */ +#define UPVAL_FIELDTABLE lua_upvalueindex(3) + +/* + * Only for containers: light udata with container identity. + */ +#define UPVAL_CONTAINER_ID lua_upvalueindex(4) + +/* + * Only for containers: light udata with item identity. + */ +#define UPVAL_ITEM_ID lua_upvalueindex(5) + +/* + * Only for containers: if not nil, overrides the item count. + */ +#define UPVAL_ITEM_COUNT lua_upvalueindex(6) + +namespace { + /** + * Object references are represented as userdata instances + * with an appropriate metatable; the payload of userdata is + * this structure: + */ + struct DFRefHeader { + void *ptr; + }; + + /* + * The system might be extended to carry some simple + * objects inline inside the reference buffer. + */ + inline bool is_self_contained(DFRefHeader *ptr) { + void **pp = &ptr->ptr; + return **(void****)pp == (pp + 1); + } +} + +/** + * Report an error while accessing a field (index = field name). + */ +static void field_error(lua_State *state, int index, const char *err, const char *mode) +{ + lua_getfield(state, UPVAL_METATABLE, "__metatable"); + const char *cname = lua_tostring(state, -1); + const char *fname = index ? lua_tostring(state, index) : "*"; + luaL_error(state, "Cannot %s field %s.%s: %s.", + mode, (cname ? cname : "?"), (fname ? fname : "?"), err); +} + +/* + * If is_method is true, these use UPVAL_TYPETABLE to save a hash lookup. + */ +static void push_object_internal(lua_State *state, type_identity *type, void *ptr, bool in_method = true); +static void *get_object_internal(lua_State *state, type_identity *type, int val_index, bool in_method = true); + +void DFHack::PushDFObject(lua_State *state, type_identity *type, void *ptr) +{ + push_object_internal(state, type, ptr, false); +} + +void *DFHack::GetDFObject(lua_State *state, type_identity *type, int val_index) +{ + return get_object_internal(state, type, val_index, false); +} + +static void push_adhoc_pointer(lua_State *state, void *ptr, type_identity *target); + +/************************************** + * Identity object read/write methods * + **************************************/ + +void constructed_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + push_object_internal(state, this, ptr); +} + +void constructed_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + field_error(state, fname_idx, "complex object", "write"); +} + +void enum_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + base_type->lua_read(state, fname_idx, ptr); +} + +void enum_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + base_type->lua_write(state, fname_idx, ptr, val_index); +} + +void df::number_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_pushnumber(state, read(ptr)); +} + +void df::number_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + if (!lua_isnumber(state, val_index)) + field_error(state, fname_idx, "number expected", "write"); + + write(ptr, lua_tonumber(state, val_index)); +} + +void df::bool_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_pushboolean(state, *(bool*)ptr); +} + +void df::bool_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + char *pb = (char*)ptr; + + if (lua_isboolean(state, val_index) || lua_isnil(state, val_index)) + *pb = lua_toboolean(state, val_index); + else if (lua_isnumber(state, val_index)) + *pb = lua_tointeger(state, val_index); + else + field_error(state, fname_idx, "boolean or number expected", "write"); +} + +void df::stl_string_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + auto pstr = (std::string*)ptr; + lua_pushlstring(state, pstr->data(), pstr->size()); +} + +void df::stl_string_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + size_t size; + const char *bytes = lua_tolstring(state, val_index, &size); + if (!bytes) + field_error(state, fname_idx, "string expected", "write"); + + *(std::string*)ptr = std::string(bytes, size); +} + +void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr, type_identity *target) +{ + push_object_internal(state, target, *(void**)ptr); +} + +void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_read(state, fname_idx, ptr, target); +} + +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)) + *pptr = NULL; + else + { + void *nval = get_object_internal(state, target, val_index); + if (nval) + *pptr = nval; + else + field_error(state, fname_idx, "incompatible pointer type", "write"); + } +} + +void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + lua_write(state, fname_idx, ptr, target, val_index); +} + +int container_identity::lua_item_count(lua_State *state, void *ptr) +{ + if (lua_isnumber(state, UPVAL_ITEM_COUNT)) + return lua_tointeger(state, UPVAL_ITEM_COUNT); + else + return item_count(ptr); +} + +void container_identity::lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + push_object_internal(state, id, pitem); +} + +void container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + id->lua_read(state, fname_idx, pitem); +} + +void container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + id->lua_write(state, fname_idx, pitem, val_index); +} + +void ptr_container_identity::lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + push_adhoc_pointer(state, pitem, id); +} + +void ptr_container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(&df::identity_traits::identity, ptr, idx); + df::pointer_identity::lua_read(state, fname_idx, pitem, id); +} + +void ptr_container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(&df::identity_traits::identity, ptr, idx); + df::pointer_identity::lua_write(state, fname_idx, pitem, id, val_index); +} + +void bit_container_identity::lua_item_reference(lua_State *state, int, void *, int) +{ + lua_pushnil(state); +} + +void bit_container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) +{ + lua_pushboolean(state, get_item(ptr, idx)); +} + +void bit_container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +{ + if (lua_isboolean(state, val_index) || lua_isnil(state, val_index)) + set_item(ptr, idx, lua_toboolean(state, val_index)); + else if (lua_isnumber(state, val_index)) + set_item(ptr, idx, lua_tointeger(state, val_index) != 0); + else + field_error(state, fname_idx, "boolean or number expected", "write"); +} + +/* */ + +static int change_error(lua_State *state) +{ + luaL_error(state, "Attempt to change a read-only table.\n"); + return 0; +} + +/** + * Wrap a table so that it can't be modified. + */ +static void freeze_table(lua_State *state, bool leave_metatable = false, const char *name = NULL) +{ + // rv = {}; setmetatable(rv, { __index = in, __newindex = change_error, __metatable = name }) + int base = lua_gettop(state); + lua_newtable(state); + lua_swap(state); + lua_setfield(state, base, "__index"); + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_CHANGEERROR_NAME); + lua_setfield(state, base, "__newindex"); + lua_newtable(state); + lua_swap(state); + lua_dup(state); + lua_setmetatable(state, base); + if (name) + { + lua_pushstring(state, name); + lua_setfield(state, -2, "__metatable"); + } + // result: [frozen table] [metatable] + if (!leave_metatable) + lua_pop(state, 1); +} + +static void LookupInTable(lua_State *state, const char *tname) +{ + lua_getfield(state, LUA_REGISTRYINDEX, tname); + lua_swap(state); + lua_rawget(state, -2); + lua_remove(state, -2); +} + +/** + * Look up the key on the stack in DFHACK_TYPETABLE; + * if found, put result on the stack and return true. + */ +static bool LookupTypeInfo(lua_State *state, bool in_method) +{ + // stack: [lookup key] + + if (in_method) + { + lua_rawget(state, UPVAL_TYPETABLE); + } + else + { + LookupInTable(state, DFHACK_TYPETABLE_NAME); + } + + // stack: [info] + + if (lua_isnil(state, -1)) + { + lua_pop(state, 1); + return false; + } + else + return true; +} + +static void LookupInTable(lua_State *state, void *id, const char *tname) +{ + lua_getfield(state, LUA_REGISTRYINDEX, tname); + lua_pushlightuserdata(state, id); + lua_rawget(state, -2); + lua_remove(state, -2); +} + +static void SaveInTable(lua_State *state, void *node, const char *tname) +{ + // stack: [info] + lua_getfield(state, LUA_REGISTRYINDEX, tname); + + lua_pushlightuserdata(state, node); + lua_pushvalue(state, -3); + lua_rawset(state, -3); + + lua_pushvalue(state, -2); + lua_pushlightuserdata(state, node); + lua_rawset(state, -3); + + lua_pop(state, 1); + // stack: [info] +} + +static void SaveTypeInfo(lua_State *state, void *node) +{ + SaveInTable(state, node, DFHACK_TYPETABLE_NAME); +} + +static void BuildTypeMetatable(lua_State *state, type_identity *type); + +/** + * Push the pointer as DF object ref using metatable on the stack. + */ +static void push_object_ref(lua_State *state, void *ptr) +{ + // stack: [metatable] + auto ref = (DFRefHeader*)lua_newuserdata(state, sizeof(DFRefHeader)); + ref->ptr = ptr; + + lua_swap(state); + lua_setmetatable(state, -2); + // stack: [userdata] +} + +static void *get_object_ref(lua_State *state, int val_index) +{ + assert(!lua_islightuserdata(state, val_index)); + + auto ref = (DFRefHeader*)lua_touserdata(state, val_index); + return ref->ptr; +} + +/** + * Push the pointer using given identity. + */ +static void push_object_internal(lua_State *state, type_identity *type, void *ptr, bool in_method) +{ + /* + * If NULL pointer or no type, push something simple + */ + + if (!ptr || !type) + { + if (!ptr) + lua_pushnil(state); + else + lua_pushlightuserdata(state, ptr); + + return; + } + + /* + * Resolve actual class using vtable + */ + + if (type->type() == IDTYPE_CLASS) + { + virtual_identity *class_vid = virtual_identity::get(virtual_ptr(ptr)); + if (class_vid) + type = class_vid; + } + + /* + * Resolve metatable by identity, and push the object + */ + + lua_pushlightuserdata(state, type); // () -> type + + if (!LookupTypeInfo(state, in_method)) // type -> metatable? + BuildTypeMetatable(state, type); // () -> metatable + + push_object_ref(state, ptr); // metatable -> userdata +} + +/** + * Verify that the value matches the identity, and return ptr if so. + */ +static void *get_object_internal(lua_State *state, type_identity *type, int val_index, bool in_method) +{ + /* + * Non-userdata results in NULL; nil for NULL gets handled here too. + */ + if (!lua_isuserdata(state, val_index)) + return NULL; + + /* + * Light user data is allowed with null type; otherwise bail out. + */ + if (!lua_getmetatable(state, val_index)) // () -> metatable? + { + if (!type && lua_islightuserdata(state, val_index)) + return lua_touserdata(state, val_index); + + return NULL; + } + + /* + * Verify that the metatable is known, and refers to the correct type. + * Here doing reverse lookup of identity by metatable. + */ + if (!LookupTypeInfo(state, in_method)) // metatable -> type? + return NULL; + + bool ok = lua_islightuserdata(state, -1) && + (!type || lua_touserdata(state, -1) == type); + + lua_pop(state, 1); // type -> () + + if (!ok) + return NULL; + + /* + * Finally decode the reference. + */ + return get_object_ref(state, val_index); +} + +/** + * Given a DF object reference or type, safely retrieve its identity pointer. + */ +static type_identity *get_object_identity(lua_State *state, int objidx, + const char *ctx, bool allow_type = false) +{ + if (!lua_getmetatable(state, objidx)) + luaL_error(state, "Invalid object in %s", ctx); + + // Verify object type validity + if (lua_isuserdata(state, objidx)) + { + lua_dup(state); + LookupInTable(state, DFHACK_TYPETABLE_NAME); + } + else + { + if (!allow_type) + luaL_error(state, "Object expected in %s", ctx); + + lua_pushvalue(state, objidx); + LookupInTable(state, DFHACK_TYPEID_TABLE_NAME); + } + + if (lua_isnil(state, -1)) + luaL_error(state, "Invalid object metatable in %s", ctx); + lua_pop(state, 1); + + // Extract identity from metatable + lua_getfield(state, -1, "_identity"); + + type_identity *id = (type_identity*)lua_touserdata(state, -1); + if (!id) + luaL_error(state, "Invalid object identity in %s", ctx); + + lua_pop(state, 2); + return id; +} + +/** + * Metamethod: compare two DF object references. + * + * Equal if same pointer and same metatable. + */ +static int meta_ptr_compare(lua_State *state) +{ + if (!lua_isuserdata(state, 1) || !lua_isuserdata(state, 2) || + !lua_getmetatable(state, 1) || !lua_getmetatable(state, 2)) + { + lua_pushboolean(state, false); + return 1; + } + + if (get_object_ref(state, 1) != get_object_ref(state, 2)) + { + lua_pushboolean(state, false); + return 1; + } + + if (!lua_rawequal(state, -1, -2)) + { + // todo: nonidentical type comparison + lua_pushboolean(state, false); + return 1; + } + + lua_pushboolean(state, true); + return 1; +} + +/** + * Method: sizeof for DF object references. + * + * Returns: size[, address] + */ +static int meta_sizeof(lua_State *state) +{ + int argc = lua_gettop(state); + + if (argc != 1) + 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)) + { + lua_pushnil(state); + lua_pushnumber(state, (size_t)lua_touserdata(state, 1)); + return 2; + } + + type_identity *id = get_object_identity(state, 1, "df.sizeof()", true); + + lua_pushinteger(state, id->byte_size()); + + if (lua_isuserdata(state, 1)) + { + lua_pushnumber(state, (size_t)get_object_ref(state, 1)); + return 2; + } + else + return 1; +} + +/** + * Method: displace for DF object references. + * + * Returns: a reference with the same type, but modified address + */ +static int meta_displace(lua_State *state) +{ + int argc = lua_gettop(state); + + bool has_step = (argc >= 3); + if ((argc < 2 || argc > 3) || + !lua_isnumber(state, 2) || + (has_step && !lua_isnumber(state, 3))) + { + luaL_error(state, "Usage: object:_displace(index[,step]) or df._displace(object,...)"); + } + + int index = lua_tointeger(state, 2); + int step = has_step ? lua_tointeger(state, 3) : 1; + + // Two special cases: nil and lightuserdata for NULL and void* + if (lua_isnil(state, 1)) + { + lua_pushnil(state); + return 1; + } + + if (lua_islightuserdata(state, 1)) + { + if (!has_step) + luaL_error(state, "Step is mandatory in _displace of void*"); + + auto ptr = (uint8_t*)lua_touserdata(state, 1); + lua_pushlightuserdata(state, ptr + index*step); + return 1; + } + + type_identity *id = get_object_identity(state, 1, "df._displace()"); + + if (!has_step) + step = id->byte_size(); + + if (index == 0 || step == 0) + { + lua_pushvalue(state, 1); + } + else + { + auto ptr = (uint8_t*)get_object_ref(state, 1); + lua_getmetatable(state, 1); + push_object_ref(state, ptr + index*step); + } + + return 1; +} + +/** + * Resolve the field name in UPVAL_FIELDTABLE, die if not found. + */ +static void lookup_field(lua_State *state, int index, const char *mode) +{ + lua_pushvalue(state, index); + lua_gettable(state, UPVAL_FIELDTABLE); // uses metatable with enum keys + + if (lua_isnil(state, -1)) + field_error(state, index, "not found", mode); +} + +static void *find_field(lua_State *state, int index, const char *mode) +{ + lookup_field(state, index, mode); + + void *p = lua_touserdata(state, -1); + lua_pop(state, 1); + return p; +} + +/** + * Verify that the object is a DF ref with UPVAL_METATABLE. + * If everything ok, extract the address. + */ +static uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode) +{ + if (!lua_isuserdata(state, obj) || + !lua_getmetatable(state, obj)) + field_error(state, field, "invalid object", mode); + + if (!lua_rawequal(state, -1, UPVAL_METATABLE)) + field_error(state, field, "invalid object metatable", mode); + + lua_pop(state, 1); + + return (uint8_t*)get_object_ref(state, obj); +} + +static void GetAdHocMetatable(lua_State *state, const struct_field_info *field); + +static void read_field(lua_State *state, const struct_field_info *field, void *ptr) +{ + switch (field->mode) + { + case struct_field_info::STATIC_STRING: + { + int len = strnlen((char*)ptr, field->count); + lua_pushlstring(state, (char*)ptr, len); + return; + } + + case struct_field_info::PRIMITIVE: + case struct_field_info::SUBSTRUCT: + field->type->lua_read(state, 2, ptr); + return; + + case struct_field_info::POINTER: + df::pointer_identity::lua_read(state, 2, ptr, field->type); + return; + + case struct_field_info::CONTAINER: + if (!field->eid || !field->type->isContainer() || + field->eid == ((container_identity*)field->type)->getIndexEnumType()) + { + field->type->lua_read(state, 2, ptr); + return; + } + // fallthrough + + case struct_field_info::STATIC_ARRAY: + case struct_field_info::STL_VECTOR_PTR: + GetAdHocMetatable(state, field); + push_object_ref(state, ptr); + return; + + case struct_field_info::END: + break; + } + + lua_pushnil(state); +} + +static void field_reference(lua_State *state, const struct_field_info *field, void *ptr) +{ + switch (field->mode) + { + case struct_field_info::PRIMITIVE: + case struct_field_info::SUBSTRUCT: + push_object_internal(state, field->type, ptr); + return; + + case struct_field_info::POINTER: + push_adhoc_pointer(state, ptr, field->type); + return; + + case struct_field_info::CONTAINER: + read_field(state, field, ptr); + return; + + case struct_field_info::STATIC_STRING: + case struct_field_info::STATIC_ARRAY: + case struct_field_info::STL_VECTOR_PTR: + GetAdHocMetatable(state, field); + push_object_ref(state, ptr); + return; + + case struct_field_info::END: + break; + } + + lua_pushnil(state); +} + +static void write_field(lua_State *state, const struct_field_info *field, void *ptr, int value_idx) +{ + switch (field->mode) + { + case struct_field_info::STATIC_STRING: + { + size_t size; + const char *str = lua_tolstring(state, value_idx, &size); + if (!str) + field_error(state, 2, "string expected", "write"); + memcpy(ptr, str, std::min(size+1, size_t(field->count))); + return; + } + + case struct_field_info::PRIMITIVE: + case struct_field_info::SUBSTRUCT: + case struct_field_info::CONTAINER: + field->type->lua_write(state, 2, ptr, value_idx); + return; + + case struct_field_info::POINTER: + df::pointer_identity::lua_write(state, 2, ptr, field->type, value_idx); + + case struct_field_info::STATIC_ARRAY: + case struct_field_info::STL_VECTOR_PTR: + field_error(state, 2, "complex object", "write"); + + case struct_field_info::END: + return; + } +} + +/** + * Metamethod: represent a type node as string. + */ +static int meta_type_tostring(lua_State *state) +{ + if (!lua_getmetatable(state, 1)) + return 0; + + lua_getfield(state, -1, "__metatable"); + const char *cname = lua_tostring(state, -1); + + lua_pushstring(state, stl_sprintf("", cname).c_str()); + return 1; +} + +/** + * Metamethod: represent a DF object reference as string. + */ +static int meta_ptr_tostring(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 0, "access"); + + lua_getfield(state, UPVAL_METATABLE, "__metatable"); + const char *cname = lua_tostring(state, -1); + + lua_pushstring(state, stl_sprintf("<%s: 0x%08x>", cname, (unsigned)ptr).c_str()); + return 1; +} + +// Resolve the field in the metatable and return +static int get_metafield(lua_State *state) +{ + lua_rawget(state, UPVAL_METATABLE); + return 1; +} + +/** + * Metamethod: __index for structures. + */ +static int meta_struct_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + auto field = (struct_field_info*)find_field(state, 2, "read"); + if (!field) + return get_metafield(state); + read_field(state, field, ptr + field->offset); + return 1; +} + +/** + * Method: _field for structures. + */ +static int meta_struct_field_reference(lua_State *state) +{ + if (lua_gettop(state) != 2) + luaL_error(state, "Usage: object._field(name)"); + uint8_t *ptr = get_object_addr(state, 1, 2, "reference"); + auto field = (struct_field_info*)find_field(state, 2, "reference"); + if (!field) + field_error(state, 2, "builtin property", "reference"); + field_reference(state, field, ptr + field->offset); + return 1; +} + +/** + * Metamethod: __newindex for structures. + */ +static int meta_struct_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + auto field = (struct_field_info*)find_field(state, 2, "write"); + if (!field) + field_error(state, 2, "builtin property", "write"); + write_field(state, field, ptr + field->offset, 3); + return 0; +} + +/** + * Metamethod: __index for primitives, i.e. simple object references. + * Fields point to identity, or NULL for metafields. + */ +static int meta_primitive_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + auto type = (type_identity*)find_field(state, 2, "read"); + if (!type) + return get_metafield(state); + type->lua_read(state, 2, ptr); + return 1; +} + +/** + * Metamethod: __newindex for primitives. + */ +static int meta_primitive_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + auto type = (type_identity*)find_field(state, 2, "write"); + if (!type) + field_error(state, 2, "builtin property", "write"); + type->lua_write(state, 2, ptr, 3); + return 0; +} + +/** + * Metamethod: __len for containers. + */ +static int meta_container_len(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 0, "get length"); + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + lua_pushinteger(state, len); + return 1; +} + +/** + * Field lookup for containers: + * + * - Numbers are indices and handled directly. + * - NULL userdata are metafields; push and exit; + */ +static int lookup_container_field(lua_State *state, int field, const char *mode = NULL) +{ + if (lua_type(state, field) == LUA_TNUMBER) + return field; + + lookup_field(state, field, mode ? mode : "read"); + + if (lua_isuserdata(state, -1) && !lua_touserdata(state, -1)) + { + if (mode) + field_error(state, field, "builtin property", mode); + + lua_pop(state, 1); + get_metafield(state); + return 0; + } + + return -1; +} + +/** + * Index verification: number and in range. + */ +static int check_container_index(lua_State *state, int len, + int fidx, int iidx, const char *mode) +{ + if (!lua_isnumber(state, iidx)) + field_error(state, fidx, "invalid index", mode); + + int idx = lua_tointeger(state, iidx); + if (idx < 0 || idx >= len) + field_error(state, fidx, "index out of bounds", mode); + + return idx; +} + +/** + * Metamethod: __index for containers. + */ +static int meta_container_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + int iidx = lookup_container_field(state, 2); + if (!iidx) + return 1; + + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + int idx = check_container_index(state, len, 2, iidx, "read"); + id->lua_item_read(state, 2, ptr, idx); + return 1; +} + +/** + * Method: _field for containers. + */ +static int meta_container_field_reference(lua_State *state) +{ + if (lua_gettop(state) != 2) + luaL_error(state, "Usage: object._field(index)"); + uint8_t *ptr = get_object_addr(state, 1, 2, "reference"); + int iidx = lookup_container_field(state, 2, "reference"); + + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + int idx = check_container_index(state, len, 2, iidx, "reference"); + id->lua_item_reference(state, 2, ptr, idx); + return 1; +} + +/** + * Metamethod: __index for containers. + */ +static int meta_container_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + int iidx = lookup_container_field(state, 2, "write"); + + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + int idx = check_container_index(state, len, 2, iidx, "write"); + id->lua_item_write(state, 2, ptr, idx, 3); + return 0; +} + +/** + * Metamethod: __len for bitfields. + */ +static int meta_bitfield_len(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 0, "get size"); + auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + lua_pushinteger(state, id->getNumBits()); + return 1; +} + +/** + * Metamethod: __index for bitfields. + */ +static int meta_bitfield_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + int iidx = lookup_container_field(state, 2); + if (!iidx) + return 1; + + auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + // whole + if (lua_isuserdata(state, iidx) && lua_touserdata(state, iidx) == id) + { + size_t intv = 0; + memcpy(&intv, ptr, std::min(sizeof(intv), size_t(id->byte_size()))); + lua_pushnumber(state, intv); + return 1; + } + + 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); + return 1; +} + +/** + * Metamethod: __newindex for bitfields. + */ +static int meta_bitfield_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + int iidx = lookup_container_field(state, 2, "write"); + + auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + // whole + if (lua_isuserdata(state, iidx) && lua_touserdata(state, iidx) == id) + { + if (!lua_isnumber(state, 3)) + field_error(state, 2, "number expected", "write"); + + size_t intv = (size_t)lua_tonumber(state, 3); + memcpy(ptr, &intv, std::min(sizeof(intv), size_t(id->byte_size()))); + return 0; + } + + int idx = check_container_index(state, id->getNumBits(), 2, iidx, "write"); + int size = id->getBits()[idx].size; + + if (lua_isboolean(state, 3) || lua_isnil(state, 3)) + setBitfieldField(ptr, idx, size, lua_toboolean(state, 3)); + else if (lua_isnumber(state, 3)) + setBitfieldField(ptr, idx, size, lua_tointeger(state, 3)); + else + field_error(state, 2, "number or boolean expected", "write"); + return 0; +} + +/** + * Metamethod: __index for df.global + */ +static int meta_global_index(lua_State *state) +{ + auto field = (struct_field_info*)find_field(state, 2, "read"); + if (!field) + return get_metafield(state); + void *ptr = *(void**)field->offset; + if (!ptr) + field_error(state, 2, "global address not known", "read"); + read_field(state, field, ptr); + return 1; +} + +/** + * Metamethod: __newindex for df.global + */ +static int meta_global_newindex(lua_State *state) +{ + auto field = (struct_field_info*)find_field(state, 2, "write"); + if (!field) + field_error(state, 2, "builtin property", "write"); + void *ptr = *(void**)field->offset; + if (!ptr) + field_error(state, 2, "global address not known", "write"); + write_field(state, field, ptr, 3); + return 0; +} + +/** + * Add fields in the array to the UPVAL_FIELDTABLE candidates on the stack. + */ +static void IndexFields(lua_State *state, struct_identity *pstruct) +{ + // stack: fieldtable + + int base = lua_gettop(state); + + for (struct_identity *p = pstruct; p; p = p->getParent()) + { + auto fields = p->getFields(); + + for (; fields; ++fields) + { + if (fields->mode == struct_field_info::END) + break; + + lua_pushstring(state,fields->name); + lua_pushlightuserdata(state,(void*)fields); + lua_rawset(state,base); + } + } +} + +/** + * Enable a metafield by injecting an entry into a UPVAL_FIELDTABLE. + */ +static void EnableMetaField(lua_State *state, int ftable_idx, const char *name, void *id = NULL) +{ + lua_pushlightuserdata(state, id); + lua_setfield(state, ftable_idx, name); +} + +/** + * Set metatable properties common to all actual DF object references. + */ +static void SetPtrMethods(lua_State *state, int meta_idx, int read_idx) +{ + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_COMPARE_NAME); + lua_setfield(state, meta_idx, "__eq"); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPETABLE_NAME); + lua_pushvalue(state, meta_idx); + lua_pushcclosure(state, meta_ptr_tostring, 2); + lua_setfield(state, meta_idx, "__tostring"); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); + lua_setfield(state, meta_idx, "sizeof"); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_DISPLACE_NAME); + lua_setfield(state, meta_idx, "_displace"); + + EnableMetaField(state, read_idx, "_type"); + EnableMetaField(state, read_idx, "_kind"); + + EnableMetaField(state, read_idx, "_field"); + + EnableMetaField(state, read_idx, "sizeof"); + EnableMetaField(state, read_idx, "_displace"); +} + +/** + * Add a struct-style (3 upvalues) metamethod to the metatable. + */ +static void SetStructMethod(lua_State *state, int meta_idx, int ftable_idx, + lua_CFunction function, const char *name) +{ + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPETABLE_NAME); + lua_pushvalue(state, meta_idx); + lua_pushvalue(state, ftable_idx); + lua_pushcclosure(state, function, 3); + lua_setfield(state, meta_idx, name); +} + +/** + * Make a metatable with most common fields, and an empty table for UPVAL_FIELDTABLE. + */ +static void MakeMetatable(lua_State *state, type_identity *type, const char *kind) +{ + int base = lua_gettop(state); + lua_newtable(state); // metatable + + lua_pushstring(state, type->getFullName().c_str()); + lua_setfield(state, base+1, "__metatable"); + + lua_pushlightuserdata(state, type); + lua_setfield(state, base+1, "_identity"); + + LookupInTable(state, type, DFHACK_TYPEID_TABLE_NAME); + if (lua_isnil(state, -1)) + { + // Copy the string from __metatable if no real type + lua_pop(state, 1); + lua_getfield(state, base+1, "__metatable"); + } + lua_setfield(state, base+1, "_type"); + + lua_pushstring(state, kind); + lua_setfield(state, base+1, "_kind"); + + lua_newtable(state); // fieldtable +} + +/** + * Make a struct-style object metatable. + */ +static void MakeFieldMetatable(lua_State *state, struct_identity *pstruct, + lua_CFunction reader, lua_CFunction writer) +{ + int base = lua_gettop(state); + + MakeMetatable(state, pstruct, "struct"); // meta, fields + + IndexFields(state, pstruct); + + SetStructMethod(state, base+1, base+2, reader, "__index"); + SetStructMethod(state, base+1, base+2, writer, "__newindex"); + + // returns: [metatable readfields writefields]; +} + +/** + * Make a primitive-style metatable + */ +static void MakePrimitiveMetatable(lua_State *state, type_identity *type) +{ + int base = lua_gettop(state); + + MakeMetatable(state, type, "primitive"); + + SetPtrMethods(state, base+1, base+2); + + EnableMetaField(state, base+2, "value", type); + + SetStructMethod(state, base+1, base+2, meta_primitive_index, "__index"); + SetStructMethod(state, base+1, base+2, meta_primitive_newindex, "__newindex"); +} + +/** + * Add a 6 upvalue metamethod to the metatable. + */ +static void SetContainerMethod(lua_State *state, int meta_idx, int ftable_idx, + lua_CFunction function, const char *name, + type_identity *container, type_identity *item, int count) +{ + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPETABLE_NAME); + lua_pushvalue(state, meta_idx); + lua_pushvalue(state, ftable_idx); + + lua_pushlightuserdata(state, container); + lua_pushlightuserdata(state, item); + if (count < 0) + lua_pushnil(state); + else + lua_pushinteger(state, count); + + lua_pushcclosure(state, function, 6); + 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. + */ +static void AttachEnumKeys(lua_State *state, int base, type_identity *ienum) +{ + LookupInTable(state, ienum, DFHACK_ENUM_TABLE_NAME); + + if (!lua_isnil(state, -1)) + { + lua_newtable(state); + lua_swap(state); + lua_setfield(state, -2, "__index"); + lua_setmetatable(state, base+2); + } + else + lua_pop(state, 1); + + LookupInTable(state, ienum, DFHACK_TYPEID_TABLE_NAME); + lua_setfield(state, base+1, "_enum"); + + EnableMetaField(state, base+2, "_enum"); +} + +/** + * Make a container-style object metatable. + */ +static void MakeContainerMetatable(lua_State *state, container_identity *type, + type_identity *item, int count, type_identity *ienum) +{ + int base = lua_gettop(state); + + MakeMetatable(state, type, "container"); + SetPtrMethods(state, base+1, base+2); + + // Update the type name using full info + lua_pushstring(state, type->getFullName(item).c_str()); + lua_dup(state); + lua_setfield(state, base+1, "__metatable"); + lua_setfield(state, base+1, "_type"); + + lua_pushlightuserdata(state, item); + lua_setfield(state, base+1, "_field_identity"); + + if (count >= 0) + { + lua_pushinteger(state, count); + lua_setfield(state, base+1, "_count"); + } + + SetContainerMethod(state, base+1, base+2, meta_container_len, "__len", type, item, count); + SetContainerMethod(state, base+1, base+2, meta_container_index, "__index", type, item, count); + SetContainerMethod(state, base+1, base+2, meta_container_newindex, "__newindex", type, item, count); + + SetContainerMethod(state, base+1, base+2, meta_container_field_reference, "_field", type, item, count); + + AttachEnumKeys(state, base, ienum); +} + +/* + * Metatable construction identity methods. + */ +void type_identity::build_metatable(lua_State *state) +{ + MakePrimitiveMetatable(state, this); +} + +void container_identity::build_metatable(lua_State *state) +{ + MakeContainerMetatable(state, this, getItemType(), -1, getIndexEnumType()); +} + +void bitfield_identity::build_metatable(lua_State *state) +{ + int base = lua_gettop(state); + + MakeMetatable(state, this, "bitfield"); + + SetPtrMethods(state, base+1, base+2); + + SetContainerMethod(state, base+1, base+2, meta_bitfield_len, "__len", this, NULL, -1); + SetContainerMethod(state, base+1, base+2, meta_bitfield_index, "__index", this, NULL, -1); + SetContainerMethod(state, base+1, base+2, meta_bitfield_newindex, "__newindex", this, NULL, -1); + + AttachEnumKeys(state, base, this); + + EnableMetaField(state, base+2, "whole", this); +} + +void struct_identity::build_metatable(lua_State *state) +{ + int base = lua_gettop(state); + MakeFieldMetatable(state, this, meta_struct_index, meta_struct_newindex); + SetStructMethod(state, base+1, base+2, meta_struct_field_reference, "_field"); + SetPtrMethods(state, base+1, base+2); +} + +void global_identity::build_metatable(lua_State *state) +{ + MakeFieldMetatable(state, this, meta_global_index, meta_global_newindex); +} + +static void BuildTypeMetatable(lua_State *state, type_identity *type) +{ + type->build_metatable(state); + + lua_pop(state, 1); + + SaveTypeInfo(state, type); +} + +/** + * Construct a metatable for an object type folded into the field descriptor. + * This is done to reduce compile-time symbol table bloat due to templates. + */ +static void GetAdHocMetatable(lua_State *state, const struct_field_info *field) +{ + lua_pushlightuserdata(state, (void*)field); + + if (!LookupTypeInfo(state, true)) + { + switch (field->mode) + { + case struct_field_info::CONTAINER: + { + auto ctype = (container_identity*)field->type; + MakeContainerMetatable(state, ctype, ctype->getItemType(), -1, field->eid); + break; + } + + case struct_field_info::STATIC_STRING: + MakeContainerMetatable(state, &df::buffer_container_identity::base_instance, + &df::identity_traits::identity, field->count, NULL); + break; + + case struct_field_info::STATIC_ARRAY: + MakeContainerMetatable(state, &df::buffer_container_identity::base_instance, + field->type, field->count, field->eid); + break; + + case struct_field_info::STL_VECTOR_PTR: + MakeContainerMetatable(state, &df::identity_traits >::identity, + field->type, -1, field->eid); + break; + + default: + luaL_error(state, "Invalid ad-hoc field: %d", field->mode); + } + + lua_pop(state, 1); + + SaveTypeInfo(state, (void*)field); + } +} + +static void push_adhoc_pointer(lua_State *state, void *ptr, type_identity *target) +{ + if (!target) + { + push_object_internal(state, &df::identity_traits::identity, ptr); + return; + } + + LookupInTable(state, target, DFHACK_PTR_IDTABLE_NAME); + + type_identity *id = (type_identity*)lua_touserdata(state, -1); + lua_pop(state, 1); + + if (!id) + { + /* + * HACK: relies on + * 1) pointer_identity destructor being no-op + * 2) lua gc never moving objects in memory + */ + + void *newobj = lua_newuserdata(state, sizeof(pointer_identity)); + id = new (newobj) pointer_identity(target); + + SaveInTable(state, target, DFHACK_PTR_IDTABLE_NAME); + lua_pop(state, 1); + } + + push_object_internal(state, id, ptr); +} + +/* + * Recursive walk of scopes to construct the df... tree. + */ + +static void RenderTypeChildren(lua_State *state, const std::vector &children); + +static void AssociateId(lua_State *state, int table, int val, const char *name) +{ + lua_pushinteger(state, val); + lua_pushstring(state, name); + lua_dup(state); + lua_pushinteger(state, val); + + lua_rawset(state, table); + lua_rawset(state, table); +} + +static void RenderType(lua_State *state, compound_identity *node) +{ + assert(node->getName()); + std::string name = node->getFullName(); + + lua_newtable(state); + if (!lua_checkstack(state, 20)) + return; + + int base = lua_gettop(state); + + switch (node->type()) + { + case IDTYPE_STRUCT: + lua_pushstring(state, "struct-type"); + lua_setfield(state, base, "_kind"); + break; + + case IDTYPE_CLASS: + lua_pushstring(state, "class-type"); + lua_setfield(state, base, "_kind"); + break; + + case IDTYPE_ENUM: + { + lua_pushstring(state, "enum-type"); + lua_setfield(state, base, "_kind"); + + enum_identity *eid = (enum_identity*)node; + const char *const *keys = eid->getKeys(); + + // For enums, set mapping between keys and values + for (int64_t i = eid->getFirstItem(), j = 0; i <= eid->getLastItem(); i++, j++) + { + if (keys[j]) + AssociateId(state, base, i, keys[j]); + } + + if (eid->getFirstItem() <= eid->getLastItem()) + { + lua_pushinteger(state, eid->getFirstItem()); + lua_setfield(state, base, "_first_item"); + + lua_pushinteger(state, eid->getLastItem()); + lua_setfield(state, base, "_last_item"); + } + + SaveInTable(state, node, DFHACK_ENUM_TABLE_NAME); + } + break; + + case IDTYPE_BITFIELD: + { + lua_pushstring(state, "bitfield-type"); + lua_setfield(state, base, "_kind"); + + bitfield_identity *eid = (bitfield_identity*)node; + auto bits = eid->getBits(); + + for (int i = 0; i < eid->getNumBits(); i++) + { + if (bits[i].name) + AssociateId(state, base, i, bits[i].name); + if (bits[i].size > 1) + i += bits[i].size-1; + } + + lua_pushinteger(state, 0); + lua_setfield(state, base, "_first_item"); + + lua_pushinteger(state, eid->getNumBits()-1); + lua_setfield(state, base, "_last_item"); + + SaveInTable(state, node, DFHACK_ENUM_TABLE_NAME); + } + break; + + default: + break; + } + + RenderTypeChildren(state, node->getScopeChildren()); + + assert(base == lua_gettop(state)); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); + lua_setfield(state, base, "sizeof"); + + if (node->type() == IDTYPE_GLOBAL) + { + BuildTypeMetatable(state, node); + + // Set metatable for the inner table + lua_dup(state); + lua_setmetatable(state, base); + lua_swap(state); // -> meta curtable + + freeze_table(state, true, "global"); + + // Copy __newindex to the outer metatable + lua_getfield(state, base, "__newindex"); + lua_setfield(state, -2, "__newindex"); + + lua_remove(state, base); + } + else + { + freeze_table(state, true, name.c_str()); + } + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPE_TOSTRING_NAME); + lua_setfield(state, -2, "__tostring"); + + lua_pushlightuserdata(state, node); + lua_setfield(state, -2, "_identity"); + + lua_pop(state, 1); + + SaveInTable(state, node, DFHACK_TYPEID_TABLE_NAME); +} + +static void RenderTypeChildren(lua_State *state, const std::vector &children) +{ + for (size_t i = 0; i < children.size(); i++) + { + RenderType(state, children[i]); + lua_pushstring(state, children[i]->getName()); + lua_swap(state); + lua_rawset(state, -3); + } +} + +static void DoAttach(lua_State *state) +{ + int base = lua_gettop(state); + + lua_newtable(state); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_PTR_IDTABLE_NAME); + + lua_newtable(state); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_TYPEID_TABLE_NAME); + + lua_newtable(state); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_ENUM_TABLE_NAME); + + lua_pushcfunction(state, change_error); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_CHANGEERROR_NAME); + + lua_pushcfunction(state, meta_ptr_compare); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_COMPARE_NAME); + + lua_pushcfunction(state, meta_type_tostring); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_TYPE_TOSTRING_NAME); + + lua_pushcfunction(state, meta_sizeof); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); + + lua_pushcfunction(state, meta_displace); + lua_setfield(state, LUA_REGISTRYINDEX, DFHACK_DISPLACE_NAME); + + luaL_register(state, "df", no_functions); + + { + // Assign df a metatable with read-only contents + lua_newtable(state); + + // Render the type structure + RenderTypeChildren(state, compound_identity::getTopScope()); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); + lua_setfield(state, -2, "sizeof"); + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_DISPLACE_NAME); + lua_setfield(state, -2, "_displace"); + + freeze_table(state, true, "df"); + lua_remove(state, -2); + lua_setmetatable(state, -2); + } + + lua_pop(state, 1); +} + +/** + * Initialize access to DF objects from the interpreter + * context, unless it has already been done. + */ +void DFHack::AttachDFGlobals(lua_State *state) +{ + if (luaL_newmetatable(state, DFHACK_TYPETABLE_NAME)) + DoAttach(state); + + lua_pop(state, 1); +} diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 99694d949..17751d90d 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -37,6 +37,8 @@ distribution. #undef interface #endif +typedef struct lua_State lua_State; + /* * Definitions of DFHack namespace structs used by generated headers. */ @@ -45,47 +47,252 @@ namespace DFHack { class virtual_class {}; + enum identity_type { + IDTYPE_GLOBAL, + IDTYPE_PRIMITIVE, + IDTYPE_POINTER, + IDTYPE_CONTAINER, + IDTYPE_PTR_CONTAINER, + IDTYPE_BIT_CONTAINER, + IDTYPE_BITFIELD, + IDTYPE_ENUM, + IDTYPE_STRUCT, + IDTYPE_CLASS, + IDTYPE_STL_PTR_VECTOR + }; + + typedef void *(*TAllocateFn)(void*,const void*); + + class DFHACK_EXPORT type_identity { + size_t size; + + protected: + type_identity(size_t size) : size(size) {}; + + void *do_allocate_pod(); + void do_copy_pod(void *tgt, const void *src); + + virtual bool can_allocate() { return true; } + virtual void *do_allocate() { return do_allocate_pod(); } + virtual void do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); } + + public: + virtual ~type_identity() {} + + size_t byte_size() { return size; } + + virtual identity_type type() = 0; + + virtual std::string getFullName() = 0; + + // For internal use in the lua wrapper + virtual void lua_read(lua_State *state, int fname_idx, void *ptr) = 0; + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) = 0; + virtual void build_metatable(lua_State *state); + + virtual bool isContainer() { return false; } + + void *allocate(); + bool copy(void *tgt, const void *src); + }; + + class DFHACK_EXPORT constructed_identity : public type_identity { + TAllocateFn allocator; + + protected: + constructed_identity(size_t size, TAllocateFn alloc) + : type_identity(size), allocator(alloc) {}; + + virtual bool can_allocate() { return (allocator != NULL); } + virtual void *do_allocate() { return allocator(NULL,NULL); } + virtual void do_copy(void *tgt, const void *src) { allocator(tgt,src); } + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + }; + + class DFHACK_EXPORT compound_identity : public constructed_identity { + static compound_identity *list; + compound_identity *next; + + const char *dfhack_name; + compound_identity *scope_parent; + std::vector scope_children; + static std::vector top_scope; + + protected: + compound_identity(size_t size, TAllocateFn alloc, + compound_identity *scope_parent, const char *dfhack_name); + + virtual void doInit(Core *core); + + public: + const char *getName() { return dfhack_name; } + + virtual std::string getFullName(); + + compound_identity *getScopeParent() { return scope_parent; } + const std::vector &getScopeChildren() { return scope_children; } + static const std::vector &getTopScope() { return top_scope; } + + static void Init(Core *core); + }; + + // Bitfields + struct bitfield_item_info { + const char *name; + int size; + }; + + class DFHACK_EXPORT bitfield_identity : public compound_identity { + const bitfield_item_info *bits; + int num_bits; + + protected: + virtual bool can_allocate() { return true; } + virtual void *do_allocate() { return do_allocate_pod(); } + virtual void do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); } + + public: + bitfield_identity(size_t size, + compound_identity *scope_parent, const char *dfhack_name, + int num_bits, const bitfield_item_info *bits); + + virtual identity_type type() { return IDTYPE_BITFIELD; } + + int getNumBits() { return num_bits; } + const bitfield_item_info *getBits() { return bits; } + + virtual void build_metatable(lua_State *state); + }; + + class DFHACK_EXPORT enum_identity : public compound_identity { + const char *const *keys; + int64_t first_item_value; + int64_t last_item_value; + + type_identity *base_type; + + protected: + virtual bool can_allocate() { return true; } + virtual void *do_allocate(); + virtual void do_copy(void *tgt, const void *src) { do_copy_pod(tgt, src); } + + public: + enum_identity(size_t size, + compound_identity *scope_parent, const char *dfhack_name, + type_identity *base_type, + int64_t first_item_value, int64_t last_item_value, + const char *const *keys); + + virtual identity_type type() { return IDTYPE_ENUM; } + + int64_t getFirstItem() { return first_item_value; } + int64_t getLastItem() { return last_item_value; } + int getCount() { return int(last_item_value-first_item_value+1); } + const char *const *getKeys() { return keys; } + + type_identity *getBaseType() { return base_type; } + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + }; + + struct struct_field_info { + enum Mode { + END, + PRIMITIVE, + STATIC_STRING, + POINTER, + STATIC_ARRAY, + SUBSTRUCT, + CONTAINER, + STL_VECTOR_PTR + }; + Mode mode; + const char *name; + size_t offset; + type_identity *type; + size_t count; + enum_identity *eid; + }; + + class DFHACK_EXPORT struct_identity : public compound_identity { + struct_identity *parent; + std::vector children; + bool has_children; + + const struct_field_info *fields; + + protected: + virtual void doInit(Core *core); + + public: + struct_identity(size_t size, TAllocateFn alloc, + compound_identity *scope_parent, const char *dfhack_name, + struct_identity *parent, const struct_field_info *fields); + + virtual identity_type type() { return IDTYPE_STRUCT; } + + struct_identity *getParent() { return parent; } + const std::vector &getChildren() { return children; } + bool hasChildren() { return has_children; } + + const struct_field_info *getFields() { return fields; } + + bool is_subclass(struct_identity *subtype); + + virtual void build_metatable(lua_State *state); + }; + + class DFHACK_EXPORT global_identity : public struct_identity { + public: + global_identity(const struct_field_info *fields) + : struct_identity(0,NULL,NULL,"global",NULL,fields) {} + + virtual identity_type type() { return IDTYPE_GLOBAL; } + + virtual void build_metatable(lua_State *state); + }; + #ifdef _MSC_VER typedef void *virtual_ptr; #else typedef virtual_class *virtual_ptr; #endif - class DFHACK_EXPORT virtual_identity { - static virtual_identity *list; + class DFHACK_EXPORT virtual_identity : public struct_identity { static std::map known; - - virtual_identity *prev, *next; - const char *dfhack_name; + const char *original_name; - virtual_identity *parent; - std::vector children; - + void *vtable_ptr; - bool has_children; protected: - virtual_identity(const char *dfhack_name, const char *original_name, virtual_identity *parent); + virtual void doInit(Core *core); static void *get_vtable(virtual_ptr instance_ptr) { return *(void**)instance_ptr; } + bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); } + public: - const char *getName() { return dfhack_name; } - const char *getOriginalName() { return original_name ? original_name : dfhack_name; } + virtual_identity(size_t size, TAllocateFn alloc, + const char *dfhack_name, const char *original_name, + virtual_identity *parent, const struct_field_info *fields); - virtual_identity *getParent() { return parent; } - const std::vector &getChildren() { return children; } + virtual identity_type type() { return IDTYPE_CLASS; } + + const char *getOriginalName() { return original_name ? original_name : getName(); } public: static virtual_identity *get(virtual_ptr instance_ptr); - - bool is_subclass(virtual_identity *subtype); + bool is_instance(virtual_ptr instance_ptr) { if (!instance_ptr) return false; if (vtable_ptr) { void *vtable = get_vtable(instance_ptr); if (vtable == vtable_ptr) return true; - if (!has_children) return false; + if (!hasChildren()) return false; } return is_subclass(get(instance_ptr)); } @@ -97,16 +304,11 @@ namespace DFHack } public: - bool can_instantiate() { return (vtable_ptr != NULL); } - virtual_ptr instantiate() { return can_instantiate() ? do_instantiate() : NULL; } + bool can_instantiate() { return can_allocate(); } + virtual_ptr instantiate() { return can_instantiate() ? (virtual_ptr)do_allocate() : NULL; } static virtual_ptr clone(virtual_ptr obj); - protected: - virtual virtual_ptr do_instantiate() = 0; - virtual void do_copy(virtual_ptr tgt, virtual_ptr src) = 0; public: - static void Init(Core *core); - // Strictly for use in virtual class constructors void adjust_vtable(virtual_ptr obj, virtual_identity *main); }; @@ -127,6 +329,23 @@ namespace DFHack void InitDataDefGlobals(Core *core); + // LUA wrapper + + /** + * Make DF objects available to the given interpreter. + */ + DFHACK_EXPORT void AttachDFGlobals(lua_State *state); + + /** + * Push the pointer onto the stack as a wrapped DF object of the given type. + */ + DFHACK_EXPORT void PushDFObject(lua_State *state, type_identity *type, void *ptr); + + /** + * Check that the value is a wrapped DF object of the given type, and if so return the pointer. + */ + DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index); + template T *ifnull(T *a, T *b) { return a ? a : b; } @@ -135,12 +354,6 @@ namespace DFHack size_t size; const T *items; }; - - // Bitfields - struct bitfield_item_info { - const char *name; - int size; - }; } template @@ -164,33 +377,37 @@ inline int linear_index(const DFHack::enum_list_attr &lst, const st namespace df { + using DFHack::type_identity; + using DFHack::compound_identity; using DFHack::virtual_ptr; using DFHack::virtual_identity; using DFHack::virtual_class; + using DFHack::global_identity; + using DFHack::struct_identity; + using DFHack::struct_field_info; using DFHack::bitfield_item_info; + using DFHack::bitfield_identity; + using DFHack::enum_identity; using DFHack::enum_list_attr; using DFHack::BitArray; using DFHack::DfArray; template - struct enum_traits {}; + void *allocator_fn(void *out, const void *in) { + if (out) { *(T*)out = *(const T*)in; return out; } + else return new T(); + } template - struct bitfield_traits {}; + struct identity_traits { + static compound_identity *get() { return &T::_identity; } + }; template - class class_virtual_identity : public virtual_identity { - public: - class_virtual_identity(const char *dfhack_name, const char *original_name, virtual_identity *parent) - : virtual_identity(dfhack_name, original_name, parent) {}; - - T *instantiate() { return static_cast(virtual_identity::instantiate()); } - T *clone(T* obj) { return static_cast(virtual_identity::clone(obj)); } + struct enum_traits {}; - protected: - virtual virtual_ptr do_instantiate() { return new T(); } - virtual void do_copy(virtual_ptr tgt, virtual_ptr src) { *static_cast(tgt) = *static_cast(src); } - }; + template + struct bitfield_traits {}; template struct enum_field { @@ -427,6 +644,24 @@ namespace DFHack { flagarray_to_string(&tmp, val); return join_strings(sep, tmp); } + + // LUA wrapper + + /** + * Push the pointer onto the stack as a wrapped DF object of a specific type. + */ + template + void PushDFObject(lua_State *state, T *ptr) { + PushDFObject(state, df::identity_traits::get(), ptr); + } + + /** + * Check that the value is a wrapped DF object of the correct type, and if so return the pointer. + */ + template + T *GetDFObject(lua_State *state, int val_index) { + return GetDFObject(state, df::identity_traits::get(), val_index); + } } #define ENUM_ATTR(enum,attr,val) (df::enum_traits::attrs(val).attr) diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h new file mode 100644 index 000000000..808634798 --- /dev/null +++ b/library/include/DataIdentity.h @@ -0,0 +1,438 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#pragma once + +#include +#include +#include +#include + +#include "DataDefs.h" + +/* + * Definitions of DFHack namespace structs used by generated headers. + */ + +namespace DFHack +{ + class DFHACK_EXPORT primitive_identity : public type_identity { + public: + primitive_identity(size_t size) : type_identity(size) {}; + + virtual identity_type type() { return IDTYPE_PRIMITIVE; } + }; + + class DFHACK_EXPORT pointer_identity : public primitive_identity { + type_identity *target; + + public: + pointer_identity(type_identity *target = NULL) + : primitive_identity(sizeof(void*)), target(target) {}; + + virtual identity_type type() { return IDTYPE_POINTER; } + + type_identity *getTarget() { return target; } + + std::string getFullName(); + + static void lua_read(lua_State *state, int fname_idx, void *ptr, type_identity *target); + static void lua_write(lua_State *state, int fname_idx, void *ptr, type_identity *target, int val_index); + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + }; + + class DFHACK_EXPORT container_identity : public constructed_identity { + type_identity *item; + enum_identity *ienum; + + public: + container_identity(size_t size, TAllocateFn alloc, type_identity *item, enum_identity *ienum = NULL) + : constructed_identity(size, alloc), item(item), ienum(ienum) {}; + + virtual identity_type type() { return IDTYPE_CONTAINER; } + + std::string getFullName() { return getFullName(item); } + + virtual void build_metatable(lua_State *state); + virtual bool isContainer() { return true; } + + type_identity *getItemType() { return item; } + type_identity *getIndexEnumType() { return ienum; } + + virtual std::string getFullName(type_identity *item); + + int lua_item_count(lua_State *state, void *ptr); + + virtual void lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + + protected: + virtual int item_count(void *ptr) = 0; + virtual void *item_pointer(type_identity *item, void *ptr, int idx) = 0; + }; + + class DFHACK_EXPORT ptr_container_identity : public container_identity { + public: + ptr_container_identity(size_t size, TAllocateFn alloc, + type_identity *item, enum_identity *ienum = NULL) + : container_identity(size, alloc, item, ienum) {}; + + virtual identity_type type() { return IDTYPE_PTR_CONTAINER; } + + std::string getFullName(type_identity *item); + + virtual void lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + }; + + class DFHACK_EXPORT bit_container_identity : public container_identity { + public: + bit_container_identity(size_t size, TAllocateFn alloc, enum_identity *ienum = NULL) + : container_identity(size, alloc, NULL, ienum) {}; + + virtual identity_type type() { return IDTYPE_BIT_CONTAINER; } + + std::string getFullName(type_identity *item); + + virtual void lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx); + virtual void lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + + protected: + virtual void *item_pointer(type_identity *, void *, int) { return NULL; } + + virtual bool get_item(void *ptr, int idx) = 0; + virtual void set_item(void *ptr, int idx, bool val) = 0; + }; +} + +namespace df +{ + using DFHack::primitive_identity; + using DFHack::pointer_identity; + using DFHack::container_identity; + using DFHack::ptr_container_identity; + using DFHack::bit_container_identity; + + class number_identity_base : public primitive_identity { + const char *name; + + public: + number_identity_base(size_t size, const char *name) + : primitive_identity(size), name(name) {}; + + std::string getFullName() { return name; } + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + + protected: + virtual double read(void *ptr) = 0; + virtual void write(void *ptr, double val) = 0; + }; + + template + class number_identity : public number_identity_base { + public: + number_identity(const char *name) : number_identity_base(sizeof(T), name) {} + protected: + virtual double read(void *ptr) { return double(*(T*)ptr); } + virtual void write(void *ptr, double val) { *(T*)ptr = T(val); } + }; + + class bool_identity : public primitive_identity { + public: + bool_identity() : primitive_identity(sizeof(bool)) {}; + + std::string getFullName() { return "bool"; } + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + }; + + class stl_string_identity : public primitive_identity { + public: + stl_string_identity() : primitive_identity(sizeof(std::string)) {}; + + std::string getFullName() { return "string"; } + + virtual void lua_read(lua_State *state, int fname_idx, void *ptr); + virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); + }; + + class stl_ptr_vector_identity : public ptr_container_identity { + public: + /* + * This class assumes that std::vector is equivalent + * in layout and behavior to std::vector for any T. + */ + + stl_ptr_vector_identity(type_identity *item = NULL, enum_identity *ienum = NULL) + : ptr_container_identity(sizeof(std::vector),allocator_fn >,item, ienum) + {}; + + std::string getFullName(type_identity *item) { + return "vector" + ptr_container_identity::getFullName(item); + } + + virtual DFHack::identity_type type() { return DFHack::IDTYPE_STL_PTR_VECTOR; } + + protected: + virtual int item_count(void *ptr) { + return ((std::vector*)ptr)->size(); + }; + virtual void *item_pointer(type_identity *, void *ptr, int idx) { + return &(*(std::vector*)ptr)[idx]; + } + }; + + class buffer_container_identity : public container_identity { + int size; + + public: + buffer_container_identity() + : container_identity(0, NULL, NULL, NULL), size(0) + {} + + buffer_container_identity(int size, type_identity *item, enum_identity *ienum = NULL) + : container_identity(item->byte_size()*size, NULL, item, ienum), size(size) + {} + + std::string getFullName(type_identity *item); + + static buffer_container_identity base_instance; + + protected: + virtual int item_count(void *ptr) { return size; } + virtual void *item_pointer(type_identity *item, void *ptr, int idx) { + return ((uint8_t*)ptr) + idx * item->byte_size(); + } + }; + + template + class stl_container_identity : public container_identity { + const char *name; + + public: + stl_container_identity(const char *name, type_identity *item, enum_identity *ienum = NULL) + : container_identity(sizeof(T), &allocator_fn, item, ienum), name(name) + {} + + std::string getFullName(type_identity *item) { + return name + container_identity::getFullName(item); + } + + protected: + virtual int item_count(void *ptr) { return ((T*)ptr)->size(); } + virtual void *item_pointer(type_identity *item, void *ptr, int idx) { + return &(*(T*)ptr)[idx]; + } + }; + + class bit_array_identity : public bit_container_identity { + public: + /* + * This class assumes that BitArray is equivalent + * in layout and behavior to BitArray for any T. + */ + + typedef BitArray container; + + bit_array_identity(enum_identity *ienum = NULL) + : bit_container_identity(sizeof(container), &allocator_fn, ienum) + {} + + std::string getFullName(type_identity *item) { + return "BitArray<>"; + } + + protected: + virtual int item_count(void *ptr) { return ((container*)ptr)->size * 8; } + virtual bool get_item(void *ptr, int idx) { + return ((container*)ptr)->is_set(idx); + } + virtual void set_item(void *ptr, int idx, bool val) { + ((container*)ptr)->set(idx, val); + } + }; + + class stl_bit_vector_identity : public bit_container_identity { + public: + typedef std::vector container; + + stl_bit_vector_identity(enum_identity *ienum = NULL) + : bit_container_identity(sizeof(container), &allocator_fn, ienum) + {} + + std::string getFullName(type_identity *item) { + return "vector" + bit_container_identity::getFullName(item); + } + + protected: + virtual int item_count(void *ptr) { return ((container*)ptr)->size(); } + virtual bool get_item(void *ptr, int idx) { + return (*(container*)ptr)[idx]; + } + virtual void set_item(void *ptr, int idx, bool val) { + (*(container*)ptr)[idx] = val; + } + }; + +#define NUMBER_IDENTITY_TRAITS(type) \ + template<> struct identity_traits { \ + static number_identity identity; \ + static number_identity_base *get() { return &identity; } \ + }; + + NUMBER_IDENTITY_TRAITS(char); + NUMBER_IDENTITY_TRAITS(int8_t); + NUMBER_IDENTITY_TRAITS(uint8_t); + NUMBER_IDENTITY_TRAITS(int16_t); + NUMBER_IDENTITY_TRAITS(uint16_t); + NUMBER_IDENTITY_TRAITS(int32_t); + NUMBER_IDENTITY_TRAITS(uint32_t); + NUMBER_IDENTITY_TRAITS(int64_t); + NUMBER_IDENTITY_TRAITS(uint64_t); + NUMBER_IDENTITY_TRAITS(float); + + template<> struct identity_traits { + static bool_identity identity; + static bool_identity *get() { return &identity; } + }; + + template<> struct identity_traits { + static stl_string_identity identity; + static stl_string_identity *get() { return &identity; } + }; + + template<> struct identity_traits { + static pointer_identity identity; + static pointer_identity *get() { return &identity; } + }; + + template<> struct identity_traits > { + static stl_ptr_vector_identity identity; + static stl_ptr_vector_identity *get() { return &identity; } + }; + + template<> struct identity_traits > { + static stl_bit_vector_identity identity; + static stl_bit_vector_identity *get() { return &identity; } + }; + +#undef NUMBER_IDENTITY_TRAITS + + // Container declarations + + template struct identity_traits > { + static primitive_identity *get(); + }; + + template struct identity_traits { + static pointer_identity *get(); + }; + + template struct identity_traits { + static container_identity *get(); + }; + + template struct identity_traits > { + static container_identity *get(); + }; + + template struct identity_traits > { + static stl_ptr_vector_identity *get(); + }; + + template struct identity_traits > { + static container_identity *get(); + }; + + template struct identity_traits > { + static bit_container_identity *get(); + }; + + template struct identity_traits > { + static container_identity *get(); + }; + + // Container definitions + + template + primitive_identity *identity_traits >::get() { + return identity_traits::get(); + } + + template + pointer_identity *identity_traits::get() { + static pointer_identity identity(identity_traits::get()); + return &identity; + } + + template + container_identity *identity_traits::get() { + static buffer_container_identity identity(sz, identity_traits::get()); + return &identity; + } + + template + container_identity *identity_traits >::get() { + typedef std::vector container; + static stl_container_identity identity("vector", identity_traits::get()); + return &identity; + } + + template + stl_ptr_vector_identity *identity_traits >::get() { + static stl_ptr_vector_identity identity(identity_traits::get()); + return &identity; + } + + template + container_identity *identity_traits >::get() { + typedef std::deque container; + static stl_container_identity identity("deque", identity_traits::get()); + return &identity; + } + + template + bit_container_identity *identity_traits >::get() { + static type_identity *eid = identity_traits::get(); + static enum_identity *reid = eid->type() == DFHack::IDTYPE_ENUM ? (enum_identity*)eid : NULL; + static bit_array_identity identity(reid); + return &identity; + } + + template + container_identity *identity_traits >::get() { + typedef DfArray container; + static stl_container_identity identity("DfArray", identity_traits::get()); + return &identity; + } +} + diff --git a/library/proto/BasicApi.proto b/library/proto/BasicApi.proto index 7b105ee88..137f25726 100644 --- a/library/proto/BasicApi.proto +++ b/library/proto/BasicApi.proto @@ -4,6 +4,10 @@ option optimize_for = LITE_RUNTIME; import "Basic.proto"; +// RPC GetVersion : EmptyMessage -> StringMessage +// RPC GetDFVersion : EmptyMessage -> StringMessage + +// RPC GetWorldInfo : EmptyMessage -> GetWorldInfoOut message GetWorldInfoOut { enum Mode { MODE_DWARF = 1; @@ -27,6 +31,7 @@ message GetWorldInfoOut { repeated int32 companion_histfig_ids = 10; }; +// RPC ListEnums : EmptyMessage -> ListEnumsOut message ListEnumsOut { repeated EnumItemName material_flags = 1; repeated EnumItemName inorganic_flags = 2; @@ -46,6 +51,7 @@ message ListEnumsOut { repeated EnumItemName profession = 11; }; +// RPC ListMaterials : ListMaterialsIn -> ListMaterialsOut message ListMaterialsIn { optional BasicMaterialInfoMask mask = 1; @@ -62,6 +68,7 @@ message ListMaterialsOut { repeated BasicMaterialInfo value = 1; }; +// RPC ListUnits : ListUnitsIn -> ListUnitsOut message ListUnitsIn { optional BasicUnitInfoMask mask = 1; @@ -81,6 +88,7 @@ message ListUnitsOut { repeated BasicUnitInfo value = 1; }; +// RPC ListSquads : ListSquadsIn -> ListSquadsOut message ListSquadsIn {} message ListSquadsOut { repeated BasicSquadInfo value = 1; diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto index 6f3b84510..92d7c48d9 100644 --- a/library/proto/CoreProtocol.proto +++ b/library/proto/CoreProtocol.proto @@ -62,18 +62,22 @@ message StringListMessage { repeated string value = 1; } +// RPC BindMethod : CoreBindRequest -> CoreBindReply message CoreBindRequest { required string method = 1; required string input_msg = 2; required string output_msg = 3; optional string plugin = 4; } - message CoreBindReply { required int32 assigned_id = 1; } +// RPC RunCommand : CoreRunCommandRequest -> EmptyMessage message CoreRunCommandRequest { required string command = 1; repeated string arguments = 2; } + +// RPC CoreSuspend : EmptyMessage -> IntMessage +// RPC CoreResume : EmptyMessage -> IntMessage diff --git a/library/xml b/library/xml index 08e1f71e8..1a6d5acf0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 08e1f71e89c1af6b3bef940914ed7f3d8fed89b0 +Subproject commit 1a6d5acf09ac4c11da62c5ed11a567e480d9fb59 diff --git a/plugins/Brushes.h b/plugins/Brushes.h new file mode 100644 index 000000000..73c3a05a9 --- /dev/null +++ b/plugins/Brushes.h @@ -0,0 +1,178 @@ +#pragma once + +typedef vector coord_vec; +class Brush +{ +public: + virtual ~Brush(){}; + virtual coord_vec points(MapExtras::MapCache & mc,DFHack::DFCoord start) = 0; +}; +/** + * generic 3D rectangle brush. you can specify the dimensions of + * the rectangle and optionally which tile is its 'center' + */ +class RectangleBrush : public Brush +{ +public: + RectangleBrush(int x, int y, int z = 1, int centerx = -1, int centery = -1, int centerz = -1) + { + if(centerx == -1) + cx_ = x/2; + else + cx_ = centerx; + if(centery == -1) + cy_ = y/2; + else + cy_ = centery; + if(centerz == -1) + cz_ = z/2; + else + cz_ = centerz; + x_ = x; + y_ = y; + z_ = z; + }; + coord_vec points(MapExtras::MapCache & mc, DFHack::DFCoord start) + { + coord_vec v; + DFHack::DFCoord iterstart(start.x - cx_, start.y - cy_, start.z - cz_); + DFHack::DFCoord iter = iterstart; + for(int xi = 0; xi < x_; xi++) + { + for(int yi = 0; yi < y_; yi++) + { + for(int zi = 0; zi < z_; zi++) + { + if(mc.testCoord(iter)) + v.push_back(iter); + iter.z++; + } + iter.z = iterstart.z; + iter.y++; + } + iter.y = iterstart.y; + iter.x ++; + } + return v; + }; + ~RectangleBrush(){}; +private: + int x_, y_, z_; + int cx_, cy_, cz_; +}; + +/** + * stupid block brush, legacy. use when you want to apply something to a whole DF map block. + */ +class BlockBrush : public Brush +{ +public: + BlockBrush(){}; + ~BlockBrush(){}; + coord_vec points(MapExtras::MapCache & mc, DFHack::DFCoord start) + { + coord_vec v; + DFHack::DFCoord blockc = start / 16; + DFHack::DFCoord iterc = blockc * 16; + if( !mc.testCoord(start) ) + return v; + auto starty = iterc.y; + for(int xi = 0; xi < 16; xi++) + { + for(int yi = 0; yi < 16; yi++) + { + v.push_back(iterc); + iterc.y++; + } + iterc.y = starty; + iterc.x ++; + } + return v; + }; +}; + +/** + * Column from a position through open space tiles + * example: create a column of magma + */ +class ColumnBrush : public Brush +{ +public: + ColumnBrush(){}; + ~ColumnBrush(){}; + coord_vec points(MapExtras::MapCache & mc, DFHack::DFCoord start) + { + coord_vec v; + bool juststarted = true; + while (mc.testCoord(start)) + { + df::tiletype tt = mc.tiletypeAt(start); + if(DFHack::LowPassable(tt) || juststarted && DFHack::HighPassable(tt)) + { + v.push_back(start); + juststarted = false; + start.z++; + } + else break; + } + return v; + }; +}; + +/** + * Flood-fill water tiles from cursor (for wclean) + * example: remove salt flag from a river + */ +class FloodBrush : public Brush +{ +public: + FloodBrush(Core *c){c_ = c;}; + ~FloodBrush(){}; + coord_vec points(MapExtras::MapCache & mc, DFHack::DFCoord start) + { + coord_vec v; + + std::stack to_flood; + to_flood.push(start); + + std::set seen; + + while (!to_flood.empty()) { + DFCoord xy = to_flood.top(); + to_flood.pop(); + + df::tile_designation des = mc.designationAt(xy); + + if (seen.find(xy) == seen.end() + && des.bits.flow_size + && des.bits.liquid_type == tile_liquid::Water) { + v.push_back(xy); + seen.insert(xy); + + maybeFlood(DFCoord(xy.x - 1, xy.y, xy.z), to_flood, mc); + maybeFlood(DFCoord(xy.x + 1, xy.y, xy.z), to_flood, mc); + maybeFlood(DFCoord(xy.x, xy.y - 1, xy.z), to_flood, mc); + maybeFlood(DFCoord(xy.x, xy.y + 1, xy.z), to_flood, mc); + + df::tiletype tt = mc.tiletypeAt(xy); + if (LowPassable(tt)) + { + maybeFlood(DFCoord(xy.x, xy.y, xy.z - 1), to_flood, mc); + } + if (HighPassable(tt)) + { + maybeFlood(DFCoord(xy.x, xy.y, xy.z + 1), to_flood, mc); + } + } + } + + return v; + } +private: + void maybeFlood(DFCoord c, std::stack &to_flood, MapExtras::MapCache &mc) { + if (mc.testCoord(c)) { + to_flood.push(c); + } + } + Core *c_; +}; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 36efb82e1..ff03de3e6 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -65,6 +65,10 @@ OPTION(BUILD_SUPPORTED "Build the supported plugins (reveal, probe, etc.)." ON) if (BUILD_SUPPORTED) DFHACK_PLUGIN(reveal reveal.cpp) DFHACK_PLUGIN(probe probe.cpp) + # this is a plugin which helps detect cursed creatures (vampires, necromancers, werebeasts, ...) + DFHACK_PLUGIN(cursecheck cursecheck.cpp) + # automatically assign labors to dwarves! + DFHACK_PLUGIN(autolabor autolabor.cpp) DFHACK_PLUGIN(drybuckets drybuckets.cpp) DFHACK_PLUGIN(getplants getplants.cpp) DFHACK_PLUGIN(plants plants.cpp) @@ -109,14 +113,3 @@ if(BUILD_SKELETON) add_subdirectory(skeleton) endif() -# this is a plugin which helps detect cursed creatures (vampires, necromancers, werebeasts, ...) -OPTION(BUILD_CURSECHECK "Build the cursecheck plugin." ON) -if(BUILD_CURSECHECK) - add_subdirectory(cursecheck) -endif() - -# alternative version of liquids which can be used non-interactively after configuring it -OPTION(BUILD_LIQUIDSGO "Build the liquidsgo plugin." ON) -if(BUILD_LIQUIDSGO) - add_subdirectory(liquidsgo) -endif() diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index bf41d7103..ecb5b5023 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -476,28 +476,25 @@ function getSelectedUnit() end end function getxyz() -- this will return pointers x,y and z coordinates. - local off=VersionInfo.getGroup("Position"):getAddress("cursor_xyz") -- lets find where in memory its being held - -- now lets read them (they are double words (or unsigned longs or 4 bits each) and go in sucesion - local x=engine.peekd(off) - local y=engine.peekd(off+4) --next is 4 from start - local z=engine.peekd(off+8) --next is 8 from start - --print("Pointer @:"..x..","..y..","..z) + local x=df.cursor.x + local y=df.cursor.y + local z=df.cursor.z return x,y,z -- return the coords end -function GetCreatureAtPos(x,y,z) -- gets the creature index @ x,y,z coord +function getCreatureAtPos(x,y,z) -- gets the creature index @ x,y,z coord --local x,y,z=getxyz() --get 'X' coords - local vector=engine.peek(VersionInfo.getGroup("Creatures"):getAddress("vector"),ptr_vector) -- load all creatures - for i = 0, vector:size()-1 do -- look into all creatures offsets - local curoff=vector:getval(i) -- get i-th creatures offset - local cx=engine.peek(curoff,ptr_Creature.x) --get its coordinates - local cy=engine.peek(curoff,ptr_Creature.y) - local cz=engine.peek(curoff,ptr_Creature.z) + local vector=df.world.units.all -- load all creatures + for i = 0, vector.size-1 do -- look into all creatures offsets + local curpos=vector[i]:deref().pos --get its coordinates + local cx=curpos.x + local cy=curpos.y + local cz=curpos.z if cx==x and cy==y and cz==z then --compare them - return i --return index + return vector[i]:deref() --return index end end print("Creature not found!") - return -1 + return nil end function Allocate(size) diff --git a/plugins/Dfusion/luafiles/init.lua b/plugins/Dfusion/luafiles/init.lua index ccb51f572..97766a978 100644 --- a/plugins/Dfusion/luafiles/init.lua +++ b/plugins/Dfusion/luafiles/init.lua @@ -72,7 +72,8 @@ table.insert(plugins,{"adv_tools","some tools for (mainly) advneturer hacking"}) table.insert(plugins,{"triggers","a function calling plug (discontinued...)"}) table.insert(plugins,{"migrants","multi race imigrations"}) -table.insert(plugins,{"onfunction","run lua on some df function"})--]=] +--]=] +table.insert(plugins,{"onfunction","run lua on some df function"}) table.insert(plugins,{"editor","edit internals of df",EditDF}) table.insert(plugins,{"saves","run current worlds's init.lua",RunSaved}) loadall(plugins) diff --git a/plugins/Dfusion/luafiles/onfunction/locations.lua b/plugins/Dfusion/luafiles/onfunction/locations.lua index 57043b7a3..7849fc45d 100644 --- a/plugins/Dfusion/luafiles/onfunction/locations.lua +++ b/plugins/Dfusion/luafiles/onfunction/locations.lua @@ -1,5 +1,5 @@ if WINDOWS then --windows function defintions - onfunction.AddFunction(0x55499D+offsets.base(),"Move") --on creature move found with "watch mem=xcoord" + --[=[onfunction.AddFunction(0x55499D+offsets.base(),"Move") --on creature move found with "watch mem=xcoord" onfunction.AddFunction(0x275933+offsets.base(),"Die",{creature="edi"}) --on creature death? found by watching dead flag then stepping until new function onfunction.AddFunction(0x2c1834+offsets.base(),"CreateCreature",{protocreature="eax"}) --arena onfunction.AddFunction(0x349640+offsets.base(),"AddItem",{item="esp"}) --or esp @@ -7,8 +7,9 @@ if WINDOWS then --windows function defintions onfunction.AddFunction(0x3d4301+offsets.base(),"Make_Item",{item_type="esp"}) onfunction.AddFunction(0x5af826+offsets.base(),"Hurt",{target="esi",attacker={off=0x74,rtype=DWORD,reg="esp"}}) onfunction.AddFunction(0x3D5886+offsets.base(),"Flip",{building="esi"}) - onfunction.AddFunction(0x35E340+offsets.base(),"ItemCreate") + onfunction.AddFunction(0x35E340+offsets.base(),"ItemCreate")--]=] + onfunction.AddFunction(4B34B6+offsets.base(),"ReactionFinish") --esp item. Ecx creature, edx? else --linux - onfunction.AddFunction(0x899befe+offsets.base(),"Move") -- found out by attaching watch... - onfunction.AddFunction(0x850eecd+offsets.base(),"Die",{creature="ebx"}) -- same + --[=[onfunction.AddFunction(0x899befe+offsets.base(),"Move") -- found out by attaching watch... + onfunction.AddFunction(0x850eecd+offsets.base(),"Die",{creature="ebx"}) -- same--]=] end diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index a12a01481..43747aba6 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -264,6 +264,11 @@ function tools.empregnate(unit) if unit==nil then unit=getSelectedUnit() end + + if unit==nil then + unit=getCreatureAtPos(getxyz()) + end + if unit==nil then error("Failed to empregnate. Unit not selected/valide") end diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp new file mode 100644 index 000000000..3ef0f9e5c --- /dev/null +++ b/plugins/autolabor.cpp @@ -0,0 +1,874 @@ +// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D + +// some headers required for a plugin. Nothing special, just the basics. +#include "Core.h" +#include +#include +#include + +#include +#include + +// DF data structure definition headers +#include "DataDefs.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace DFHack; +using namespace df::enums; +using df::global::ui; +using df::global::world; + +#define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) + +static int enable_autolabor; + + +// Here go all the command declarations... +// mostly to allow having the mandatory stuff on top of the file and commands on the bottom +command_result autolabor (color_ostream &out, std::vector & parameters); + +// A plugin must be able to return its name and version. +// The name string provided must correspond to the filename - autolabor.plug.so or autolabor.plug.dll in this case +DFHACK_PLUGIN("autolabor"); + +enum labor_mode { + FIXED, + AUTOMATIC, + EVERYONE, + HAULERS, +}; + +enum dwarf_state { + // Ready for a new task + IDLE, + + // Busy with a useful task + BUSY, + + // In the military, can't work + MILITARY, + + // Child or noble, can't work + CHILD, + + // Doing something that precludes working, may be busy for a while + OTHER +}; + +static const dwarf_state dwarf_states[] = { + BUSY /* CarveFortification */, + BUSY /* DetailWall */, + BUSY /* DetailFloor */, + BUSY /* Dig */, + BUSY /* CarveUpwardStaircase */, + BUSY /* CarveDownwardStaircase */, + BUSY /* CarveUpDownStaircase */, + BUSY /* CarveRamp */, + BUSY /* DigChannel */, + BUSY /* FellTree */, + BUSY /* GatherPlants */, + BUSY /* RemoveConstruction */, + BUSY /* CollectWebs */, + BUSY /* BringItemToDepot */, + BUSY /* BringItemToShop */, + OTHER /* Eat */, + OTHER /* GetProvisions */, + OTHER /* Drink */, + OTHER /* Drink2 */, + OTHER /* FillWaterskin */, + OTHER /* FillWaterskin2 */, + OTHER /* Sleep */, + BUSY /* CollectSand */, + BUSY /* Fish */, + BUSY /* Hunt */, + OTHER /* HuntVermin */, + BUSY /* Kidnap */, + BUSY /* BeatCriminal */, + BUSY /* StartingFistFight */, + BUSY /* CollectTaxes */, + BUSY /* GuardTaxCollector */, + BUSY /* CatchLiveLandAnimal */, + BUSY /* CatchLiveFish */, + BUSY /* ReturnKill */, + BUSY /* CheckChest */, + BUSY /* StoreOwnedItem */, + BUSY /* PlaceItemInTomb */, + BUSY /* StoreItemInStockpile */, + BUSY /* StoreItemInBag */, + BUSY /* StoreItemInHospital */, + BUSY /* StoreItemInChest */, + BUSY /* StoreItemInCabinet */, + BUSY /* StoreWeapon */, + BUSY /* StoreArmor */, + BUSY /* StoreItemInBarrel */, + BUSY /* StoreItemInBin */, + BUSY /* SeekArtifact */, + BUSY /* SeekInfant */, + OTHER /* AttendParty */, + OTHER /* GoShopping */, + OTHER /* GoShopping2 */, + BUSY /* Clean */, + OTHER /* Rest */, + BUSY /* PickupEquipment */, + BUSY /* DumpItem */, + OTHER /* StrangeMoodCrafter */, + OTHER /* StrangeMoodJeweller */, + OTHER /* StrangeMoodForge */, + OTHER /* StrangeMoodMagmaForge */, + OTHER /* StrangeMoodBrooding */, + OTHER /* StrangeMoodFell */, + OTHER /* StrangeMoodCarpenter */, + OTHER /* StrangeMoodMason */, + OTHER /* StrangeMoodBowyer */, + OTHER /* StrangeMoodTanner */, + OTHER /* StrangeMoodWeaver */, + OTHER /* StrangeMoodGlassmaker */, + OTHER /* StrangeMoodMechanics */, + BUSY /* ConstructBuilding */, + BUSY /* ConstructDoor */, + BUSY /* ConstructFloodgate */, + BUSY /* ConstructBed */, + BUSY /* ConstructThrone */, + BUSY /* ConstructCoffin */, + BUSY /* ConstructTable */, + BUSY /* ConstructChest */, + BUSY /* ConstructBin */, + BUSY /* ConstructArmorStand */, + BUSY /* ConstructWeaponRack */, + BUSY /* ConstructCabinet */, + BUSY /* ConstructStatue */, + BUSY /* ConstructBlocks */, + BUSY /* MakeRawGlass */, + BUSY /* MakeCrafts */, + BUSY /* MintCoins */, + BUSY /* CutGems */, + BUSY /* CutGlass */, + BUSY /* EncrustWithGems */, + BUSY /* EncrustWithGlass */, + BUSY /* DestroyBuilding */, + BUSY /* SmeltOre */, + BUSY /* MeltMetalObject */, + BUSY /* ExtractMetalStrands */, + BUSY /* PlantSeeds */, + BUSY /* HarvestPlants */, + BUSY /* TrainHuntingAnimal */, + BUSY /* TrainWarAnimal */, + BUSY /* MakeWeapon */, + BUSY /* ForgeAnvil */, + BUSY /* ConstructCatapultParts */, + BUSY /* ConstructBallistaParts */, + BUSY /* MakeArmor */, + BUSY /* MakeHelm */, + BUSY /* MakePants */, + BUSY /* StudWith */, + BUSY /* ButcherAnimal */, + BUSY /* PrepareRawFish */, + BUSY /* MillPlants */, + BUSY /* BaitTrap */, + BUSY /* MilkCreature */, + BUSY /* MakeCheese */, + BUSY /* ProcessPlants */, + BUSY /* ProcessPlantsBag */, + BUSY /* ProcessPlantsVial */, + BUSY /* ProcessPlantsBarrel */, + BUSY /* PrepareMeal */, + BUSY /* WeaveCloth */, + BUSY /* MakeGloves */, + BUSY /* MakeShoes */, + BUSY /* MakeShield */, + BUSY /* MakeCage */, + BUSY /* MakeChain */, + BUSY /* MakeFlask */, + BUSY /* MakeGoblet */, + BUSY /* MakeInstrument */, + BUSY /* MakeToy */, + BUSY /* MakeAnimalTrap */, + BUSY /* MakeBarrel */, + BUSY /* MakeBucket */, + BUSY /* MakeWindow */, + BUSY /* MakeTotem */, + BUSY /* MakeAmmo */, + BUSY /* DecorateWith */, + BUSY /* MakeBackpack */, + BUSY /* MakeQuiver */, + BUSY /* MakeBallistaArrowHead */, + BUSY /* AssembleSiegeAmmo */, + BUSY /* LoadCatapult */, + BUSY /* LoadBallista */, + BUSY /* FireCatapult */, + BUSY /* FireBallista */, + BUSY /* ConstructMechanisms */, + BUSY /* MakeTrapComponent */, + BUSY /* LoadCageTrap */, + BUSY /* LoadStoneTrap */, + BUSY /* LoadWeaponTrap */, + BUSY /* CleanTrap */, + BUSY /* CastSpell */, + BUSY /* LinkBuildingToTrigger */, + BUSY /* PullLever */, + BUSY /* BrewDrink */, + BUSY /* ExtractFromPlants */, + BUSY /* ExtractFromRawFish */, + BUSY /* ExtractFromLandAnimal */, + BUSY /* TameVermin */, + BUSY /* TameAnimal */, + BUSY /* ChainAnimal */, + BUSY /* UnchainAnimal */, + BUSY /* UnchainPet */, + BUSY /* ReleaseLargeCreature */, + BUSY /* ReleasePet */, + BUSY /* ReleaseSmallCreature */, + BUSY /* HandleSmallCreature */, + BUSY /* HandleLargeCreature */, + BUSY /* CageLargeCreature */, + BUSY /* CageSmallCreature */, + BUSY /* RecoverWounded */, + BUSY /* DiagnosePatient */, + BUSY /* ImmobilizeBreak */, + BUSY /* DressWound */, + BUSY /* CleanPatient */, + BUSY /* Surgery */, + BUSY /* Suture */, + BUSY /* SetBone */, + BUSY /* PlaceInTraction */, + BUSY /* DrainAquarium */, + BUSY /* FillAquarium */, + BUSY /* FillPond */, + BUSY /* GiveWater */, + BUSY /* GiveFood */, + BUSY /* GiveWater2 */, + BUSY /* GiveFood2 */, + BUSY /* RecoverPet */, + BUSY /* PitLargeAnimal */, + BUSY /* PitSmallAnimal */, + BUSY /* SlaughterAnimal */, + BUSY /* MakeCharcoal */, + BUSY /* MakeAsh */, + BUSY /* MakeLye */, + BUSY /* MakePotashFromLye */, + BUSY /* FertilizeField */, + BUSY /* MakePotashFromAsh */, + BUSY /* DyeThread */, + BUSY /* DyeCloth */, + BUSY /* SewImage */, + BUSY /* MakePipeSection */, + BUSY /* OperatePump */, + OTHER /* ManageWorkOrders */, + OTHER /* UpdateStockpileRecords */, + OTHER /* TradeAtDepot */, + BUSY /* ConstructHatchCover */, + BUSY /* ConstructGrate */, + BUSY /* RemoveStairs */, + BUSY /* ConstructQuern */, + BUSY /* ConstructMillstone */, + BUSY /* ConstructSplint */, + BUSY /* ConstructCrutch */, + BUSY /* ConstructTractionBench */, + BUSY /* CleanSelf */, + BUSY /* BringCrutch */, + BUSY /* ApplyCast */, + BUSY /* CustomReaction */, + BUSY /* ConstructSlab */, + BUSY /* EngraveSlab */, + BUSY /* ShearCreature */, + BUSY /* SpinThread */, + BUSY /* PenLargeAnimal */, + BUSY /* PenSmallAnimal */, + BUSY /* MakeTool */, + BUSY /* CollectClay */, + BUSY /* InstallColonyInHive */, + BUSY /* CollectHiveProducts */, + OTHER /* CauseTrouble */, + OTHER /* DrinkBlood */, + OTHER /* ReportCrime */, + OTHER /* ExecuteCriminal */ +}; + +struct labor_info +{ + labor_mode mode; + bool is_exclusive; + int minimum_dwarfs; +}; + +static const struct labor_info labor_infos[] = { + /* MINE */ {AUTOMATIC, true, 2}, + /* HAUL_STONE */ {HAULERS, false, 1}, + /* HAUL_WOOD */ {HAULERS, false, 1}, + /* HAUL_BODY */ {HAULERS, false, 1}, + /* HAUL_FOOD */ {HAULERS, false, 1}, + /* HAUL_REFUSE */ {HAULERS, false, 1}, + /* HAUL_ITEM */ {HAULERS, false, 1}, + /* HAUL_FURNITURE */ {HAULERS, false, 1}, + /* HAUL_ANIMAL */ {HAULERS, false, 1}, + /* CLEAN */ {HAULERS, false, 1}, + /* CUTWOOD */ {AUTOMATIC, true, 1}, + /* CARPENTER */ {AUTOMATIC, false, 1}, + /* DETAIL */ {AUTOMATIC, false, 1}, + /* MASON */ {AUTOMATIC, false, 1}, + /* ARCHITECT */ {AUTOMATIC, false, 1}, + /* ANIMALTRAIN */ {AUTOMATIC, false, 1}, + /* ANIMALCARE */ {AUTOMATIC, false, 1}, + /* DIAGNOSE */ {AUTOMATIC, false, 1}, + /* SURGERY */ {AUTOMATIC, false, 1}, + /* BONE_SETTING */ {AUTOMATIC, false, 1}, + /* SUTURING */ {AUTOMATIC, false, 1}, + /* DRESSING_WOUNDS */ {AUTOMATIC, false, 1}, + /* FEED_WATER_CIVILIANS */ {EVERYONE, false, 1}, + /* RECOVER_WOUNDED */ {HAULERS, false, 1}, + /* BUTCHER */ {AUTOMATIC, false, 1}, + /* TRAPPER */ {AUTOMATIC, false, 1}, + /* DISSECT_VERMIN */ {AUTOMATIC, false, 1}, + /* LEATHER */ {AUTOMATIC, false, 1}, + /* TANNER */ {AUTOMATIC, false, 1}, + /* BREWER */ {AUTOMATIC, false, 1}, + /* ALCHEMIST */ {AUTOMATIC, false, 1}, + /* SOAP_MAKER */ {AUTOMATIC, false, 1}, + /* WEAVER */ {AUTOMATIC, false, 1}, + /* CLOTHESMAKER */ {AUTOMATIC, false, 1}, + /* MILLER */ {AUTOMATIC, false, 1}, + /* PROCESS_PLANT */ {AUTOMATIC, false, 1}, + /* MAKE_CHEESE */ {AUTOMATIC, false, 1}, + /* MILK */ {AUTOMATIC, false, 1}, + /* COOK */ {AUTOMATIC, false, 1}, + /* PLANT */ {AUTOMATIC, false, 1}, + /* HERBALIST */ {AUTOMATIC, false, 1}, + /* FISH */ {FIXED, false, 1}, + /* CLEAN_FISH */ {AUTOMATIC, false, 1}, + /* DISSECT_FISH */ {AUTOMATIC, false, 1}, + /* HUNT */ {FIXED, true, 1}, + /* SMELT */ {AUTOMATIC, false, 1}, + /* FORGE_WEAPON */ {AUTOMATIC, false, 1}, + /* FORGE_ARMOR */ {AUTOMATIC, false, 1}, + /* FORGE_FURNITURE */ {AUTOMATIC, false, 1}, + /* METAL_CRAFT */ {AUTOMATIC, false, 1}, + /* CUT_GEM */ {AUTOMATIC, false, 1}, + /* ENCRUST_GEM */ {AUTOMATIC, false, 1}, + /* WOOD_CRAFT */ {AUTOMATIC, false, 1}, + /* STONE_CRAFT */ {AUTOMATIC, false, 1}, + /* BONE_CARVE */ {AUTOMATIC, false, 1}, + /* GLASSMAKER */ {AUTOMATIC, false, 1}, + /* EXTRACT_STRAND */ {AUTOMATIC, false, 1}, + /* SIEGECRAFT */ {AUTOMATIC, false, 1}, + /* SIEGEOPERATE */ {AUTOMATIC, false, 1}, + /* BOWYER */ {AUTOMATIC, false, 1}, + /* MECHANIC */ {AUTOMATIC, false, 1}, + /* POTASH_MAKING */ {AUTOMATIC, false, 1}, + /* LYE_MAKING */ {AUTOMATIC, false, 1}, + /* DYER */ {AUTOMATIC, false, 1}, + /* BURN_WOOD */ {AUTOMATIC, false, 1}, + /* OPERATE_PUMP */ {AUTOMATIC, false, 1}, + /* SHEARER */ {AUTOMATIC, false, 1}, + /* SPINNER */ {AUTOMATIC, false, 1}, + /* POTTERY */ {AUTOMATIC, false, 1}, + /* GLAZING */ {AUTOMATIC, false, 1}, + /* PRESSING */ {AUTOMATIC, false, 1}, + /* BEEKEEPING */ {AUTOMATIC, false, 1}, + /* WAX_WORKING */ {AUTOMATIC, false, 1}, +}; + +static const df::job_skill noble_skills[] = { + df::enums::job_skill::APPRAISAL, + df::enums::job_skill::ORGANIZATION, + df::enums::job_skill::RECORD_KEEPING, +}; + +struct dwarf_info +{ + int highest_skill; + int total_skill; + bool is_best_noble; + int mastery_penalty; + int assigned_jobs; + dwarf_state state; + bool has_exclusive_labor; +}; + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + assert(ARRAY_COUNT(labor_infos) > ENUM_LAST_ITEM(unit_labor)); + + // Fill the command list with your commands. + commands.clear(); + commands.push_back(PluginCommand( + "autolabor", "Automatically manage dwarf labors.", + autolabor, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " autolabor enable\n" + " autolabor disable\n" + " Enables or disables the plugin.\n" + "Function:\n" + " When enabled, autolabor periodically checks your dwarves and enables or\n" + " disables labors. It tries to keep as many dwarves as possible busy but\n" + " also tries to have dwarves specialize in specific skills.\n" + " Warning: autolabor will override any manual changes you make to labors\n" + " while it is enabled.\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + static int step_count = 0; + + if (!enable_autolabor) + return CR_OK; + + if (++step_count < 60) + return CR_OK; + step_count = 0; + + uint32_t race = ui->race_id; + uint32_t civ = ui->civ_id; + + std::vector dwarfs; + + bool has_butchers = false; + bool has_fishery = false; + + for (int i = 0; i < world->buildings.all.size(); ++i) + { + df::building *build = world->buildings.all[i]; + auto type = build->getType(); + if (df::enums::building_type::Workshop == type) + { + auto subType = build->getSubtype(); + if (df::enums::workshop_type::Butchers == subType) + has_butchers = true; + if (df::enums::workshop_type::Fishery == subType) + has_fishery = true; + } + } + + for (int i = 0; i < world->units.all.size(); ++i) + { + df::unit* cre = world->units.all[i]; + if (cre->race == race && cre->civ_id == civ && !cre->flags1.bits.marauder && !cre->flags1.bits.diplomat && !cre->flags1.bits.merchant && !cre->flags1.bits.dead) { + dwarfs.push_back(cre); + } + } + + int n_dwarfs = dwarfs.size(); + + if (n_dwarfs == 0) + return CR_OK; + + std::vector dwarf_info(n_dwarfs); + + std::vector best_noble(ARRAY_COUNT(noble_skills)); + std::vector highest_noble_skill(ARRAY_COUNT(noble_skills)); + std::vector highest_noble_experience(ARRAY_COUNT(noble_skills)); + + // Find total skill and highest skill for each dwarf. More skilled dwarves shouldn't be used for minor tasks. + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + assert(dwarfs[dwarf]->status.souls.size() > 0); + + for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s != dwarfs[dwarf]->status.souls[0]->skills.end(); s++) + { + df::job_skill skill = (*s)->id; + + df::job_skill_class skill_class = ENUM_ATTR(job_skill, type, skill); + + int skill_level = (*s)->rating; + int skill_experience = (*s)->experience; + + // Track the dwarfs with the best Appraisal, Organization, and Record Keeping skills. + // They are likely to have appointed noble positions, so should be kept free where possible. + + int noble_skill_id = -1; + for (int i = 0; i < ARRAY_COUNT(noble_skills); i++) + { + if (skill == noble_skills[i]) + noble_skill_id = i; + } + + if (noble_skill_id >= 0) + { + assert(noble_skill_id < ARRAY_COUNT(noble_skills)); + + if (highest_noble_skill[noble_skill_id] < skill_level || + (highest_noble_skill[noble_skill_id] == skill_level && + highest_noble_experience[noble_skill_id] < skill_experience)) + { + highest_noble_skill[noble_skill_id] = skill_level; + highest_noble_experience[noble_skill_id] = skill_experience; + best_noble[noble_skill_id] = dwarf; + } + } + + // Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.) + + if (skill_class != df::enums::job_skill_class::Normal && skill_class != df::enums::job_skill_class::Medical) + continue; + + if (dwarf_info[dwarf].highest_skill < skill_level) + dwarf_info[dwarf].highest_skill = skill_level; + dwarf_info[dwarf].total_skill += skill_level; + } + } + + // Mark the best nobles, so we try to keep them non-busy. (It would be better to find the actual assigned nobles.) + + for (int i = 0; i < ARRAY_COUNT(noble_skills); i++) + { + assert(best_noble[i] >= 0); + assert(best_noble[i] < n_dwarfs); + + dwarf_info[best_noble[i]].is_best_noble = true; + } + + // Calculate a base penalty for using each dwarf for a task he isn't good at. + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + dwarf_info[dwarf].mastery_penalty -= 40 * dwarf_info[dwarf].highest_skill; + dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill; + if (dwarf_info[dwarf].is_best_noble) + dwarf_info[dwarf].mastery_penalty -= 250; + + for (int labor = ENUM_FIRST_ITEM(unit_labor); labor <= ENUM_LAST_ITEM(unit_labor); labor++) + { + 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; + } + } + + // Find the activity state for each dwarf. It's important to get this right - a dwarf who we think is IDLE but + // can't work will gum everything up. In the future I might add code to auto-detect slacker dwarves. + + std::vector state_count(5); + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + bool is_on_break = false; + + for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) + { + // 7 / 0x7 = Newly arrived migrant, will not work yet + // 17 / 0x11 = On break + if ((*p)->id == 0x07 || (*p)->id == 0x11) + is_on_break = true; + } + + if (dwarfs[dwarf]->profession == df::enums::profession::BABY || + dwarfs[dwarf]->profession == df::enums::profession::CHILD || + dwarfs[dwarf]->profession == df::enums::profession::DRUNK) + { + dwarf_info[dwarf].state = CHILD; + } + else if (dwarfs[dwarf]->job.current_job == NULL) + { + if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession)) + dwarf_info[dwarf].state = MILITARY; + else if (is_on_break) + dwarf_info[dwarf].state = OTHER; + else if (dwarfs[dwarf]->meetings.size() > 0) + dwarf_info[dwarf].state = OTHER; + else + dwarf_info[dwarf].state = IDLE; + } + else + { + 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]; + } + + state_count[dwarf_info[dwarf].state]++; + } + + // Generate labor -> skill mapping + + df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; + for (int i = 0; i <= ENUM_LAST_ITEM(unit_labor); i++) + labor_to_skill[i] = df::enums::job_skill::NONE; + + FOR_ENUM_ITEMS(job_skill, skill) + { + 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; + } + } + + std::vector labors; + + FOR_ENUM_ITEMS(unit_labor, labor) + { + 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; }); + + // Handle all skills except those marked HAULERS + + for (auto lp = labors.begin(); lp != labors.end(); ++lp) + { + auto labor = *lp; + + assert(labor >= 0); + assert(labor < ARRAY_COUNT(labor_infos)); + + df::job_skill skill = labor_to_skill[labor]; + + if (labor_infos[labor].mode == HAULERS) + continue; + + int best_dwarf = 0; + int best_value = -10000; + + std::vector values(n_dwarfs); + std::vector candidates; + std::vector backup_candidates; + std::map dwarf_skill; + + auto mode = labor_infos[labor].mode; + if (AUTOMATIC == mode && state_count[IDLE] == 0) + mode = FIXED; + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + if (dwarf_info[dwarf].state != IDLE && dwarf_info[dwarf].state != BUSY && mode != EVERYONE) + continue; + + 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; + + if (skill != df::enums::job_skill::NONE) + { + int skill_level = 0; + int skill_experience = 0; + + for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s < dwarfs[dwarf]->status.souls[0]->skills.end(); s++) + { + if ((*s)->id == skill) + { + skill_level = (*s)->rating; + skill_experience = (*s)->experience; + break; + } + } + + dwarf_skill[dwarf] = skill_level; + + value += skill_level * 100; + value += skill_experience / 20; + if (skill_level > 0 || skill_experience > 0) + value += 200; + if (skill_level >= 15) + value += 1000 * (skill_level - 14); + } + else + { + dwarf_skill[dwarf] = 0; + } + + if (dwarfs[dwarf]->status.labors[labor]) + { + value += 5; + if (labor_infos[labor].is_exclusive) + value += 350; + } + + values[dwarf] = value; + + if (mode == AUTOMATIC && dwarf_info[dwarf].state != IDLE) + backup_candidates.push_back(dwarf); + else + candidates.push_back(dwarf); + + } + + if (candidates.size() == 0) + { + candidates = backup_candidates; + mode = FIXED; + } + + if (labor_infos[labor].mode != EVERYONE) + std::sort(candidates.begin(), candidates.end(), [&values] (int i, int j) { return values[i] > values[j]; }); + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + bool allow_labor = false; + + if (dwarf_info[dwarf].state == BUSY && + mode == AUTOMATIC && + (labor_infos[labor].is_exclusive || dwarf_skill[dwarf] > 0)) + { + allow_labor = true; + } + + if (dwarf_info[dwarf].state == OTHER && + mode == AUTOMATIC && + dwarf_skill[dwarf] > 0 && + !dwarf_info[dwarf].is_best_noble) + { + allow_labor = true; + } + + if (dwarfs[dwarf]->status.labors[labor] && + allow_labor && + !(labor_infos[labor].is_exclusive && dwarf_info[dwarf].has_exclusive_labor)) + { + if (labor_infos[labor].is_exclusive) + dwarf_info[dwarf].has_exclusive_labor = true; + + dwarf_info[dwarf].assigned_jobs++; + } + else + { + dwarfs[dwarf]->status.labors[labor] = false; + } + } + + int minimum_dwarfs = labor_infos[labor].minimum_dwarfs; + + if (labor_infos[labor].mode == EVERYONE) + minimum_dwarfs = n_dwarfs; + + // Special - don't assign hunt without a butchers, or fish without a fishery + if (df::enums::unit_labor::HUNT == labor && !has_butchers) + minimum_dwarfs = 0; + if (df::enums::unit_labor::FISH == labor && !has_fishery) + minimum_dwarfs = 0; + + for (int i = 0; i < candidates.size() && i < minimum_dwarfs; i++) + { + int dwarf = candidates[i]; + + assert(dwarf >= 0); + assert(dwarf < n_dwarfs); + + if (!dwarfs[dwarf]->status.labors[labor]) + dwarf_info[dwarf].assigned_jobs++; + + dwarfs[dwarf]->status.labors[labor] = true; + + if (labor_infos[labor].is_exclusive) + dwarf_info[dwarf].has_exclusive_labor = true; + } + } + + // Set about 1/3 of the dwarfs as haulers. The haulers have all HAULER labors enabled. Having a lot of haulers helps + // make sure that hauling jobs are handled quickly rather than building up. + + int num_haulers = state_count[IDLE] + state_count[BUSY] / 3; + if (num_haulers < 1) + num_haulers = 1; + + std::vector hauler_ids; + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) + hauler_ids.push_back(dwarf); + } + + // 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; + }); + + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == df::enums::unit_labor::NONE) + continue; + + assert(labor >= 0); + assert(labor < ARRAY_COUNT(labor_infos)); + + if (labor_infos[labor].mode != HAULERS) + continue; + + for (int i = 0; i < num_haulers; i++) + { + assert(i < hauler_ids.size()); + + int dwarf = hauler_ids[i]; + + assert(dwarf >= 0); + assert(dwarf < n_dwarfs); + + dwarfs[dwarf]->status.labors[labor] = true; + dwarf_info[dwarf].assigned_jobs++; + } + + for (int i = num_haulers; i < hauler_ids.size(); i++) + { + assert(i < hauler_ids.size()); + + int dwarf = hauler_ids[i]; + + assert(dwarf >= 0); + assert(dwarf < n_dwarfs); + + dwarfs[dwarf]->status.labors[labor] = false; + } + } + + return CR_OK; +} + +// A command! It sits around and looks pretty. And it's nice and friendly. +command_result autolabor (color_ostream &out, std::vector & parameters) +{ + if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "1")) + { + if (parameters[0] == "0") + enable_autolabor = 0; + else + enable_autolabor = 1; + out.print("autolabor %sactivated.\n", (enable_autolabor ? "" : "de")); + } + else + { + out.print("Automatically assigns labors to dwarves.\n" + "Activate with 'autolabor 1', deactivate with 'autolabor 0'.\n" + "Current state: %d.\n", enable_autolabor); + } + + return CR_OK; +} diff --git a/plugins/cursecheck/cursecheck.cpp b/plugins/cursecheck.cpp similarity index 100% rename from plugins/cursecheck/cursecheck.cpp rename to plugins/cursecheck.cpp diff --git a/plugins/cursecheck/CMakeLists.txt b/plugins/cursecheck/CMakeLists.txt deleted file mode 100644 index 0c94976db..000000000 --- a/plugins/cursecheck/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -PROJECT (cursecheck) -# A list of source files -SET(PROJECT_SRCS - cursecheck.cpp -) -# A list of headers -SET(PROJECT_HDRS - cursecheck.h -) -SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) - -# mash them together (headers are marked as headers and nothing will try to compile them) -LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) - -# option to use a thread for no particular reason -#OPTION(SKELETON_THREAD "Use threads in the skeleton plugin." ON) -#linux -IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - ) -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) -# this makes sure all the stuff is put in proper places and linked to dfhack -DFHACK_PLUGIN(cursecheck ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/cursecheck/cursecheck.h b/plugins/cursecheck/cursecheck.h deleted file mode 100644 index 7b9637ef9..000000000 --- a/plugins/cursecheck/cursecheck.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once \ No newline at end of file diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 3bf67f4d2..5f4d5b4fe 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -1,3 +1,25 @@ +// plugin liquids +// +// This is a rewrite of the liquids module which can also be used non-interactively (hotkey). +// First the user sets the mode and other parameters with the interactive command liqiudsgo +// just like in the original liquids module. +// They are stored in statics to allow being used after the interactive session was closed. +// After defining an action the non-interactive command liquids-here can be used to call the +// execute method without the necessity to go back to the console. This allows convenient painting +// of liquids and obsidian using the ingame cursor and a hotkey. +// +// Commands: +// liquids - basically the original liquids with the map changing stuff moved to an execute method +// liquids-here - runs the execute method with the last settings from liquids +// (intended to be mapped to a hotkey) +// Options: +// ?, help - print some help +// +// TODO: +// - maybe allow all parameters be passed as command line options? tedious parsing but might be useful +// - grab the code from digcircle to get a circle brush - could be nice when painting with obsidian +// - maybe store the last parameters in a file to make them persistent after dfhack is closed? + #include #include #include @@ -19,14 +41,16 @@ using std::set; #include "modules/Gui.h" #include "TileTypes.h" #include "modules/MapCache.h" +#include "Brushes.h" using namespace MapExtras; using namespace DFHack; using namespace df::enums; -typedef vector coord_vec; CommandHistory liquids_hist; command_result df_liquids (color_ostream &out, vector & parameters); +command_result df_liquids_here (color_ostream &out, vector & parameters); +command_result df_liquids_execute (color_ostream &out); DFHACK_PLUGIN("liquids"); @@ -34,7 +58,14 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector to_flood; - to_flood.push(start); - - std::set seen; - - while (!to_flood.empty()) { - DFCoord xy = to_flood.top(); - to_flood.pop(); - - df::tile_designation des = mc.designationAt(xy); - - if (seen.find(xy) == seen.end() - && des.bits.flow_size - && des.bits.liquid_type == tile_liquid::Water) { - v.push_back(xy); - seen.insert(xy); - - maybeFlood(DFCoord(xy.x - 1, xy.y, xy.z), to_flood, mc); - maybeFlood(DFCoord(xy.x + 1, xy.y, xy.z), to_flood, mc); - maybeFlood(DFCoord(xy.x, xy.y - 1, xy.z), to_flood, mc); - maybeFlood(DFCoord(xy.x, xy.y + 1, xy.z), to_flood, mc); - - df::tiletype tt = mc.tiletypeAt(xy); - if (LowPassable(tt)) - { - maybeFlood(DFCoord(xy.x, xy.y, xy.z - 1), to_flood, mc); - } - if (HighPassable(tt)) - { - maybeFlood(DFCoord(xy.x, xy.y, xy.z + 1), to_flood, mc); - } - } - } - - return v; - } -private: - void maybeFlood(DFCoord c, std::stack &to_flood, MapCache &mc) { - if (mc.testCoord(c)) { - to_flood.push(c); - } - } - Core *c_; -}; +// static stuff to be remembered between sessions +static string brushname = "point"; +static string mode="magma"; +static string flowmode="f+"; +static string setmode ="s."; +static unsigned int amount = 7; +static int width = 1, height = 1, z_levels = 1; command_result df_liquids (color_ostream &out_, vector & parameters) { - int32_t x,y,z; - - assert(out_.is_console()); + if(!out_.is_console()) + return CR_FAILURE; Console &out = static_cast(out_); for(size_t i = 0; i < parameters.size();i++) { if(parameters[i] == "help" || parameters[i] == "?") { - out.print("This tool allows placing magma, water and other similar things.\n" - "It is interactive and further help is available when you run it.\n" - ); + out.print( "This tool allows placing magma, water and other similar things.\n" + "It is interactive and further help is available when you run it.\n" + "The settings will be remembered until dfhack is closed and you can call\n" + "'liquids-here' (mapped to a hotkey) to paint liquids at the cursor position\n" + "without the need to go back to the dfhack console.\n"); return CR_OK; } } @@ -244,23 +108,22 @@ command_result df_liquids (color_ostream &out_, vector & parameters) return CR_FAILURE; } - Brush * brush = new RectangleBrush(1,1); - string brushname = "point"; bool end = false; + out << "Welcome to the liquid spawner.\nType 'help' or '?' for a list of available commands, 'q' to quit.\nPress return after a command to confirm." << std::endl; - string mode="magma"; - string flowmode="f+"; - string setmode ="s."; - unsigned int amount = 7; - int width = 1, height = 1, z_levels = 1; while(!end) { string command = ""; + std::stringstream str; - str <<"[" << mode << ":" << brushname << ":" << amount << ":" << flowmode << ":" << setmode << "]#"; + str <<"[" << mode << ":" << brushname; + if (brushname == "range") + str << "(w" << width << ":h" << height << ":z" << z_levels << ")"; + str << ":" << amount << ":" << flowmode << ":" << setmode << "]#"; if(out.lineedit(str.str(),command,liquids_hist) == -1) return CR_FAILURE; + if(command=="help" || command == "?") { out << "Modes:" << endl @@ -296,6 +159,7 @@ command_result df_liquids (color_ostream &out_, vector & parameters) << endl << "Usage: point the DF cursor at a tile you want to modify" << endl << "and use the commands available :)" << endl; + out << endl << "Settings will be remembered until you quit DF. You can call liquids-here to execute the last configured action. Useful in combination with keybindings." << endl; } else if(command == "m") { @@ -327,9 +191,7 @@ command_result df_liquids (color_ostream &out_, vector & parameters) } else if(command == "point" || command == "p") { - delete brush; brushname = "point"; - brush = new RectangleBrush(1,1); } else if(command == "range" || command == "r") { @@ -354,35 +216,27 @@ command_result df_liquids (color_ostream &out_, vector & parameters) range_hist.add(command); z_levels = command == "" ? z_levels : atoi (command.c_str()); if(z_levels < 1) z_levels = 1; - delete brush; if(width == 1 && height == 1 && z_levels == 1) { - brushname="point"; + brushname = "point"; } else { brushname = "range"; } - brush = new RectangleBrush(width,height,z_levels,0,0,0); } else if(command == "block") { - delete brush; brushname = "block"; - brush = new BlockBrush(); } else if(command == "column") { - delete brush; brushname = "column"; - brush = new ColumnBrush(); } - else if(command == "flood") - { - delete brush; - brushname = "flood"; - brush = new FloodBrush(&Core::getInstance()); - } + else if(command == "flood") + { + brushname = "flood"; + } else if(command == "q") { end = true; @@ -430,184 +284,248 @@ command_result df_liquids (color_ostream &out_, vector & parameters) amount = 7; else if(command.empty()) { - CoreSuspender suspend; + df_liquids_execute(out); + } + else + { + out << command << " : unknown command." << endl; + } + } + return CR_OK; +} + +command_result df_liquids_here (color_ostream &out, vector & parameters) +{ + for(size_t i = 0; i < parameters.size();i++) + { + if(parameters[i] == "help" || parameters[i] == "?") + { + out << "This command is supposed to be mapped to a hotkey." << endl; + out << "It will use the current/last parameters set in liquids." << endl; + return CR_OK; + } + } - do + out.print("Run liquids-here with these parameters: "); + out << "[" << mode << ":" << brushname; + if (brushname == "range") + out << "(w" << width << ":h" << height << ":z" << z_levels << ")"; + out << ":" << amount << ":" << flowmode << ":" << setmode << "]\n"; + + return df_liquids_execute(out); +} + +command_result df_liquids_execute(color_ostream &out) +{ + // create brush type depending on old parameters + Brush * brush; + + if (brushname == "point") + { + brush = new RectangleBrush(1,1,1,0,0,0); + //width = 1; + //height = 1; + //z_levels = 1; + } + else if (brushname == "range") + { + brush = new RectangleBrush(width,height,z_levels,0,0,0); + } + else if(brushname == "block") + { + brush = new BlockBrush(); + } + else if(brushname == "column") + { + brush = new ColumnBrush(); + } + else if(brushname == "flood") + { + brush = new FloodBrush(&Core::getInstance()); + } + else + { + // this should never happen! + out << "Old brushtype is invalid! Resetting to point brush.\n"; + brushname = "point"; + width = 1; + height = 1; + z_levels = 1; + brush = new RectangleBrush(width,height,z_levels,0,0,0); + } + + CoreSuspender suspend; + + do + { + if (!Maps::IsValid()) + { + out << "Can't see any DF map loaded." << endl; + break;; + } + int32_t x,y,z; + if(!Gui::getCursorCoords(x,y,z)) + { + out << "Can't get cursor coords! Make sure you have a cursor active in DF." << endl; + break; + } + out << "cursor coords: " << x << "/" << y << "/" << z << endl; + MapCache mcache; + DFHack::DFCoord cursor(x,y,z); + coord_vec all_tiles = brush->points(mcache,cursor); + out << "working..." << endl; + if(mode == "obsidian") + { + coord_vec::iterator iter = all_tiles.begin(); + while (iter != all_tiles.end()) + { + mcache.setTiletypeAt(*iter, tiletype::LavaWall); + mcache.setTemp1At(*iter,10015); + mcache.setTemp2At(*iter,10015); + df::tile_designation des = mcache.designationAt(*iter); + des.bits.flow_size = 0; + mcache.setDesignationAt(*iter, des); + iter ++; + } + } + if(mode == "obsidian_floor") + { + coord_vec::iterator iter = all_tiles.begin(); + while (iter != all_tiles.end()) + { + mcache.setTiletypeAt(*iter, findRandomVariant(tiletype::LavaFloor1)); + iter ++; + } + } + else if(mode == "riversource") + { + coord_vec::iterator iter = all_tiles.begin(); + while (iter != all_tiles.end()) + { + mcache.setTiletypeAt(*iter, tiletype::RiverSource); + + df::tile_designation a = mcache.designationAt(*iter); + a.bits.liquid_type = tile_liquid::Water; + a.bits.liquid_static = false; + a.bits.flow_size = 7; + mcache.setTemp1At(*iter,10015); + mcache.setTemp2At(*iter,10015); + mcache.setDesignationAt(*iter,a); + + Block * b = mcache.BlockAt((*iter)/16); + DFHack::t_blockflags bf = b->BlockFlags(); + bf.bits.liquid_1 = true; + bf.bits.liquid_2 = true; + b->setBlockFlags(bf); + + iter++; + } + } + else if(mode=="wclean") + { + coord_vec::iterator iter = all_tiles.begin(); + while (iter != all_tiles.end()) { - if (!Maps::IsValid()) + DFHack::DFCoord current = *iter; + df::tile_designation des = mcache.designationAt(current); + des.bits.water_salt = false; + des.bits.water_stagnant = false; + mcache.setDesignationAt(current,des); + iter++; + } + } + else if(mode== "magma" || mode== "water" || mode == "flowbits") + { + set seen_blocks; + coord_vec::iterator iter = all_tiles.begin(); + while (iter != all_tiles.end()) + { + DFHack::DFCoord current = *iter; // current tile coord + DFHack::DFCoord curblock = current /16; // current block coord + // check if the block is actually there + if(!mcache.BlockAt(curblock)) { - out << "Can't see any DF map loaded." << endl; - break;; + iter ++; + continue; } - if(!Gui::getCursorCoords(x,y,z)) + df::tile_designation des = mcache.designationAt(current); + df::tiletype tt = mcache.tiletypeAt(current); + // don't put liquids into places where they don't belong... + if(!DFHack::FlowPassable(tt)) { - out << "Can't get cursor coords! Make sure you have a cursor active in DF." << endl; - break; + iter++; + continue; } - out << "cursor coords: " << x << "/" << y << "/" << z << endl; - MapCache mcache; - DFHack::DFCoord cursor(x,y,z); - coord_vec all_tiles = brush->points(mcache,cursor); - out << "working..." << endl; - if(mode == "obsidian") + if(mode != "flowbits") { - coord_vec::iterator iter = all_tiles.begin(); - while (iter != all_tiles.end()) + if(setmode == "s.") { - mcache.setTiletypeAt(*iter, tiletype::LavaWall); - mcache.setTemp1At(*iter,10015); - mcache.setTemp2At(*iter,10015); - df::tile_designation des = mcache.designationAt(*iter); - des.bits.flow_size = 0; - mcache.setDesignationAt(*iter, des); - iter ++; + des.bits.flow_size = amount; } - } - if(mode == "obsidian_floor") - { - coord_vec::iterator iter = all_tiles.begin(); - while (iter != all_tiles.end()) + else if(setmode == "s+") { - mcache.setTiletypeAt(*iter, findRandomVariant(tiletype::LavaFloor1)); - iter ++; + if(des.bits.flow_size < amount) + des.bits.flow_size = amount; } - } - else if(mode == "riversource") - { - coord_vec::iterator iter = all_tiles.begin(); - while (iter != all_tiles.end()) + else if(setmode == "s-") { - mcache.setTiletypeAt(*iter, tiletype::RiverSource); - - df::tile_designation a = mcache.designationAt(*iter); - a.bits.liquid_type = tile_liquid::Water; - a.bits.liquid_static = false; - a.bits.flow_size = 7; - mcache.setTemp1At(*iter,10015); - mcache.setTemp2At(*iter,10015); - mcache.setDesignationAt(*iter,a); - - Block * b = mcache.BlockAt((*iter)/16); - DFHack::t_blockflags bf = b->BlockFlags(); - bf.bits.liquid_1 = true; - bf.bits.liquid_2 = true; - b->setBlockFlags(bf); - - iter++; + if (des.bits.flow_size > amount) + des.bits.flow_size = amount; } - } - else if(mode=="wclean") - { - coord_vec::iterator iter = all_tiles.begin(); - while (iter != all_tiles.end()) + if(amount != 0 && mode == "magma") { - DFHack::DFCoord current = *iter; - df::tile_designation des = mcache.designationAt(current); - des.bits.water_salt = false; - des.bits.water_stagnant = false; - mcache.setDesignationAt(current,des); - iter++; + des.bits.liquid_type = tile_liquid::Magma; + mcache.setTemp1At(current,12000); + mcache.setTemp2At(current,12000); } - } - else if(mode== "magma" || mode== "water" || mode == "flowbits") - { - set seen_blocks; - coord_vec::iterator iter = all_tiles.begin(); - while (iter != all_tiles.end()) + else if(amount != 0 && mode == "water") { - DFHack::DFCoord current = *iter; // current tile coord - DFHack::DFCoord curblock = current /16; // current block coord - // check if the block is actually there - if(!mcache.BlockAt(curblock)) - { - iter ++; - continue; - } - df::tile_designation des = mcache.designationAt(current); - df::tiletype tt = mcache.tiletypeAt(current); - // don't put liquids into places where they don't belong... - if(!DFHack::FlowPassable(tt)) - { - iter++; - continue; - } - if(mode != "flowbits") - { - if(setmode == "s.") - { - des.bits.flow_size = amount; - } - else if(setmode == "s+") - { - if(des.bits.flow_size < amount) - des.bits.flow_size = amount; - } - else if(setmode == "s-") - { - if (des.bits.flow_size > amount) - des.bits.flow_size = amount; - } - if(amount != 0 && mode == "magma") - { - des.bits.liquid_type = tile_liquid::Magma; - mcache.setTemp1At(current,12000); - mcache.setTemp2At(current,12000); - } - else if(amount != 0 && mode == "water") - { - des.bits.liquid_type = tile_liquid::Water; - mcache.setTemp1At(current,10015); - mcache.setTemp2At(current,10015); - } - else if(amount == 0 && (mode == "water" || mode == "magma")) - { - // reset temperature to sane default - mcache.setTemp1At(current,10015); - mcache.setTemp2At(current,10015); - } - mcache.setDesignationAt(current,des); - } - seen_blocks.insert(mcache.BlockAt(current / 16)); - iter++; + des.bits.liquid_type = tile_liquid::Water; + mcache.setTemp1At(current,10015); + mcache.setTemp2At(current,10015); } - set ::iterator biter = seen_blocks.begin(); - while (biter != seen_blocks.end()) + else if(amount == 0 && (mode == "water" || mode == "magma")) { - DFHack::t_blockflags bflags = (*biter)->BlockFlags(); - if(flowmode == "f+") - { - bflags.bits.liquid_1 = true; - bflags.bits.liquid_2 = true; - (*biter)->setBlockFlags(bflags); - } - else if(flowmode == "f-") - { - bflags.bits.liquid_1 = false; - bflags.bits.liquid_2 = false; - (*biter)->setBlockFlags(bflags); - } - else - { - out << "flow bit 1 = " << bflags.bits.liquid_1 << endl; - out << "flow bit 2 = " << bflags.bits.liquid_2 << endl; - } - biter ++; + // reset temperature to sane default + mcache.setTemp1At(current,10015); + mcache.setTemp2At(current,10015); } + mcache.setDesignationAt(current,des); + } + seen_blocks.insert(mcache.BlockAt(current / 16)); + iter++; + } + set ::iterator biter = seen_blocks.begin(); + while (biter != seen_blocks.end()) + { + DFHack::t_blockflags bflags = (*biter)->BlockFlags(); + if(flowmode == "f+") + { + bflags.bits.liquid_1 = true; + bflags.bits.liquid_2 = true; + (*biter)->setBlockFlags(bflags); + } + else if(flowmode == "f-") + { + bflags.bits.liquid_1 = false; + bflags.bits.liquid_2 = false; + (*biter)->setBlockFlags(bflags); } - if(mcache.WriteAll()) - out << "OK" << endl; else - out << "Something failed horribly! RUN!" << endl; - } while (0); + { + out << "flow bit 1 = " << bflags.bits.liquid_1 << endl; + out << "flow bit 2 = " << bflags.bits.liquid_2 << endl; + } + biter ++; + } } + if(mcache.WriteAll()) + out << "OK" << endl; else - { - out << command << " : unknown command." << endl; - } - } - - //cleanup - delete brush; + out << "Something failed horribly! RUN!" << endl; + } while (0); + // cleanup + delete brush; return CR_OK; } diff --git a/plugins/liquidsgo/CMakeLists.txt b/plugins/liquidsgo/CMakeLists.txt deleted file mode 100644 index cdae8efd7..000000000 --- a/plugins/liquidsgo/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -PROJECT (liquidsgo) -# A list of source files -SET(PROJECT_SRCS - liquidsgo.cpp -) -# A list of headers -SET(PROJECT_HDRS - liquidsgo.h -) -SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) - -# mash them together (headers are marked as headers and nothing will try to compile them) -LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) - -# option to use a thread for no particular reason -#OPTION(SKELETON_THREAD "Use threads in the skeleton plugin." ON) -#linux -IF(UNIX) - add_definitions(-DLINUX_BUILD) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - ) -# windows -ELSE(UNIX) - SET(PROJECT_LIBS - # add any extra linux libs here - ${PROJECT_LIBS} - $(NOINHERIT) - ) -ENDIF(UNIX) -# this makes sure all the stuff is put in proper places and linked to dfhack -DFHACK_PLUGIN(liquidsgo ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/liquidsgo/liquidsgo.cpp b/plugins/liquidsgo/liquidsgo.cpp deleted file mode 100644 index edae580a6..000000000 --- a/plugins/liquidsgo/liquidsgo.cpp +++ /dev/null @@ -1,709 +0,0 @@ -// plugin liquidsgo -// -// This is a rewrite of the liquids module which can also be used non-interactively (hotkey). -// First the user sets the mode and other parameters with the interactive command liqiudsgo -// just like in the original liquids module. -// They are stored in statics to allow being used after the interactive session was closed. -// After defining an action the non-interactive command liquidsgo-here can be used to call the -// execute method without the necessity to go back to the console. This allows convenient painting -// of liquids and obsidian using the ingame cursor and a hotkey. -// -// Commands: -// liquidsgo - basically the original liquids with the map changing stuff moved to an execute method -// liquidsgo-here - runs the execute method with the last settings from liquidsgo -// (intended to be mapped to a hotkey) -// Options: -// ?, help - print some help -// -// TODO: -// - maybe allow all parameters be passed as command line options? tedious parsing but might be useful -// - grab the code from digcircle to get a circle brush - could be nice when painting with obsidian -// - maybe store the last parameters in a file to make them persistent after dfhack is closed? - -#include -#include -#include -#include -#include -#include -#include -using std::vector; -using std::string; -using std::endl; -using std::set; - -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "modules/Vegetation.h" -#include "modules/Maps.h" -#include "modules/Gui.h" -#include "TileTypes.h" -#include "modules/MapCache.h" -using namespace MapExtras; -using namespace DFHack; -using namespace df::enums; -typedef vector coord_vec; - -CommandHistory liquidsgo_hist; - -command_result df_liquidsgo (color_ostream &out, vector & parameters); -command_result df_liquidsgo_here (color_ostream &out, vector & parameters); -command_result df_liquidsgo_execute (color_ostream &out); - -DFHACK_PLUGIN("liquidsgo"); - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - liquidsgo_hist.load("liquidsgo.history"); - commands.clear(); - commands.push_back(PluginCommand( - "liquidsgo", "Place magma, water or obsidian.", - df_liquidsgo, true)); // interactive, needs console for prompt - commands.push_back(PluginCommand( - "liquidsgo-here", "Use settings from liquidsgo at cursor position.", - df_liquidsgo_here, Gui::cursor_hotkey, // non-interactive, needs ingame cursor - " Identical to pressing enter in liquidsgo, intended for use as keybinding.\n" - " Can (but doesn't need to) be called while liquidsgo is running in the console.")); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - liquidsgo_hist.save("liquidsgo.history"); - return CR_OK; -} - -class Brush -{ -public: - virtual ~Brush(){}; - virtual coord_vec points(MapCache & mc,DFHack::DFCoord start) = 0; -}; -/** - * generic 3D rectangle brush. you can specify the dimensions of - * the rectangle and optionally which tile is its 'center' - */ -class RectangleBrush : public Brush -{ -public: - RectangleBrush(int x, int y, int z = 1, int centerx = -1, int centery = -1, int centerz = -1) - { - if(centerx == -1) - cx_ = x/2; - else - cx_ = centerx; - if(centery == -1) - cy_ = y/2; - else - cy_ = centery; - if(centerz == -1) - cz_ = z/2; - else - cz_ = centerz; - x_ = x; - y_ = y; - z_ = z; - }; - coord_vec points(MapCache & mc, DFHack::DFCoord start) - { - coord_vec v; - DFHack::DFCoord iterstart(start.x - cx_, start.y - cy_, start.z - cz_); - DFHack::DFCoord iter = iterstart; - for(int xi = 0; xi < x_; xi++) - { - for(int yi = 0; yi < y_; yi++) - { - for(int zi = 0; zi < z_; zi++) - { - if(mc.testCoord(iter)) - v.push_back(iter); - iter.z++; - } - iter.z = iterstart.z; - iter.y++; - } - iter.y = iterstart.y; - iter.x ++; - } - return v; - }; - ~RectangleBrush(){}; -private: - int x_, y_, z_; - int cx_, cy_, cz_; -}; - -/** - * stupid block brush, legacy. use when you want to apply something to a whole DF map block. - */ -class BlockBrush : public Brush -{ -public: - BlockBrush(){}; - ~BlockBrush(){}; - coord_vec points(MapCache & mc, DFHack::DFCoord start) - { - coord_vec v; - DFHack::DFCoord blockc = start / 16; - DFHack::DFCoord iterc = blockc * 16; - if( !mc.testCoord(start) ) - return v; - auto starty = iterc.y; - for(int xi = 0; xi < 16; xi++) - { - for(int yi = 0; yi < 16; yi++) - { - v.push_back(iterc); - iterc.y++; - } - iterc.y = starty; - iterc.x ++; - } - return v; - }; -}; - -/** - * Column from a position through open space tiles - * example: create a column of magma - */ -class ColumnBrush : public Brush -{ -public: - ColumnBrush(){}; - ~ColumnBrush(){}; - coord_vec points(MapCache & mc, DFHack::DFCoord start) - { - coord_vec v; - bool juststarted = true; - while (mc.testCoord(start)) - { - df::tiletype tt = mc.tiletypeAt(start); - if(DFHack::LowPassable(tt) || juststarted && DFHack::HighPassable(tt)) - { - v.push_back(start); - juststarted = false; - start.z++; - } - else break; - } - return v; - }; -}; - -/** - * Flood-fill water tiles from cursor (for wclean) - * example: remove salt flag from a river - */ -class FloodBrush : public Brush -{ -public: - FloodBrush(Core *c){c_ = c;}; - ~FloodBrush(){}; - coord_vec points(MapCache & mc, DFHack::DFCoord start) - { - coord_vec v; - - std::stack to_flood; - to_flood.push(start); - - std::set seen; - - while (!to_flood.empty()) { - DFCoord xy = to_flood.top(); - to_flood.pop(); - - df::tile_designation des = mc.designationAt(xy); - - if (seen.find(xy) == seen.end() - && des.bits.flow_size - && des.bits.liquid_type == tile_liquid::Water) { - v.push_back(xy); - seen.insert(xy); - - maybeFlood(DFCoord(xy.x - 1, xy.y, xy.z), to_flood, mc); - maybeFlood(DFCoord(xy.x + 1, xy.y, xy.z), to_flood, mc); - maybeFlood(DFCoord(xy.x, xy.y - 1, xy.z), to_flood, mc); - maybeFlood(DFCoord(xy.x, xy.y + 1, xy.z), to_flood, mc); - - df::tiletype tt = mc.tiletypeAt(xy); - if (LowPassable(tt)) - { - maybeFlood(DFCoord(xy.x, xy.y, xy.z - 1), to_flood, mc); - } - if (HighPassable(tt)) - { - maybeFlood(DFCoord(xy.x, xy.y, xy.z + 1), to_flood, mc); - } - } - } - - return v; - } -private: - void maybeFlood(DFCoord c, std::stack &to_flood, MapCache &mc) { - if (mc.testCoord(c)) { - to_flood.push(c); - } - } - Core *c_; -}; - - -// static stuff to be remembered between sessions -static string brushname = "point"; -static string mode="magma"; -static string flowmode="f+"; -static string setmode ="s."; -static unsigned int amount = 7; -static int width = 1, height = 1, z_levels = 1; - -command_result df_liquidsgo (color_ostream &out_, vector & parameters) -{ - assert(out_.is_console()); - Console &out = static_cast(out_); - - for(size_t i = 0; i < parameters.size();i++) - { - if(parameters[i] == "help" || parameters[i] == "?") - { - out.print( "This tool allows placing magma, water and other similar things.\n" - "It is interactive and further help is available when you run it.\n" - "The settings will be remembered until dfhack is closed and you can call\n" - "'liquidsgo-here' (mapped to a hotkey) to paint liquids at the cursor position\n" - "without the need to go back to the dfhack console.\n"); - return CR_OK; - } - } - - if (!Maps::IsValid()) - { - out.printerr("Map is not available!\n"); - return CR_FAILURE; - } - - bool end = false; - - out << "Welcome to the liquid spawner.\nType 'help' or '?' for a list of available commands, 'q' to quit.\nPress return after a command to confirm." << std::endl; - - while(!end) - { - string command = ""; - - std::stringstream str; - str <<"[" << mode << ":" << brushname; - if (brushname == "range") - str << "(w" << width << ":h" << height << ":z" << z_levels << ")"; - str << ":" << amount << ":" << flowmode << ":" << setmode << "]#"; - if(out.lineedit(str.str(),command,liquidsgo_hist) == -1) - return CR_FAILURE; - - if(command=="help" || command == "?") - { - out << "Modes:" << endl - << "m - switch to magma" << endl - << "w - switch to water" << endl - << "o - make obsidian wall instead" << endl - << "of - make obsidian floors" << endl - << "rs - make a river source" << endl - << "f - flow bits only" << endl - << "wclean - remove salt and stagnant flags from tiles" << endl - << "Set-Modes (only for magma/water):" << endl - << "s+ - only add" << endl - << "s. - set" << endl - << "s- - only remove" << endl - << "Properties (only for magma/water):" << endl - << "f+ - make the spawned liquid flow" << endl - << "f. - don't change flow state (read state in flow mode)" << endl - << "f- - make the spawned liquid static" << endl - << "0-7 - set liquid amount" << endl - << "Brush:" << endl - << "point - single tile [p]" << endl - << "range - block with cursor at bottom north-west [r]" << endl - << " (any place, any size)" << endl - << "block - DF map block with cursor in it" << endl - << " (regular spaced 16x16x1 blocks)" << endl - << "column - Column from cursor, up through free space" << endl - << "flood - Flood-fill water tiles from cursor" << endl - << " (only makes sense with wclean)" << endl - << "Other:" << endl - << "q - quit" << endl - << "help or ? - print this list of commands" << endl - << "empty line - put liquid" << endl - << endl - << "Usage: point the DF cursor at a tile you want to modify" << endl - << "and use the commands available :)" << endl; - out << endl << "Settings will be remembered until you quit DF. You can call liquidsgo-here to execute the last configured action. Useful in combination with keybindings." << endl; - } - else if(command == "m") - { - mode = "magma"; - } - else if(command == "o") - { - mode = "obsidian"; - } - else if(command == "of") - { - mode = "obsidian_floor"; - } - else if(command == "w") - { - mode = "water"; - } - else if(command == "f") - { - mode = "flowbits"; - } - else if(command == "rs") - { - mode = "riversource"; - } - else if(command == "wclean") - { - mode = "wclean"; - } - else if(command == "point" || command == "p") - { - brushname = "point"; - } - else if(command == "range" || command == "r") - { - std::stringstream str; - CommandHistory range_hist; - str << " :set range width<" << width << "># "; - out.lineedit(str.str(),command,range_hist); - range_hist.add(command); - width = command == "" ? width : atoi (command.c_str()); - if(width < 1) width = 1; - - str.str(""); - str << " :set range height<" << height << "># "; - out.lineedit(str.str(),command,range_hist); - range_hist.add(command); - height = command == "" ? height : atoi (command.c_str()); - if(height < 1) height = 1; - - str.str(""); - str << " :set range z-levels<" << z_levels << "># "; - out.lineedit(str.str(),command,range_hist); - range_hist.add(command); - z_levels = command == "" ? z_levels : atoi (command.c_str()); - if(z_levels < 1) z_levels = 1; - if(width == 1 && height == 1 && z_levels == 1) - { - brushname = "point"; - } - else - { - brushname = "range"; - } - } - else if(command == "block") - { - brushname = "block"; - } - else if(command == "column") - { - brushname = "column"; - } - else if(command == "flood") - { - brushname = "flood"; - } - else if(command == "q") - { - end = true; - } - else if(command == "f+") - { - flowmode = "f+"; - } - else if(command == "f-") - { - flowmode = "f-"; - } - else if(command == "f.") - { - flowmode = "f."; - } - else if(command == "s+") - { - setmode = "s+"; - } - else if(command == "s-") - { - setmode = "s-"; - } - else if(command == "s.") - { - setmode = "s."; - } - // blah blah, bad code, bite me. - else if(command == "0") - amount = 0; - else if(command == "1") - amount = 1; - else if(command == "2") - amount = 2; - else if(command == "3") - amount = 3; - else if(command == "4") - amount = 4; - else if(command == "5") - amount = 5; - else if(command == "6") - amount = 6; - else if(command == "7") - amount = 7; - else if(command.empty()) - { - df_liquidsgo_execute(out); - } - else - { - out << command << " : unknown command." << endl; - } - } - - return CR_OK; -} - -command_result df_liquidsgo_here (color_ostream &out, vector & parameters) -{ - for(size_t i = 0; i < parameters.size();i++) - { - if(parameters[i] == "help" || parameters[i] == "?") - { - out << "This command is supposed to be mapped to a hotkey." << endl; - out << "It will use the current/last parameters set in liquidsgo." << endl; - return CR_OK; - } - } - - out.print("Run liquidsgo-here with these parameters: "); - out << "[" << mode << ":" << brushname; - if (brushname == "range") - out << "(w" << width << ":h" << height << ":z" << z_levels << ")"; - out << ":" << amount << ":" << flowmode << ":" << setmode << "]\n"; - - return df_liquidsgo_execute(out); -} - -command_result df_liquidsgo_execute(color_ostream &out) -{ - // create brush type depending on old parameters - Brush * brush; - - if (brushname == "point") - { - brush = new RectangleBrush(1,1,1,0,0,0); - //width = 1; - //height = 1; - //z_levels = 1; - } - else if (brushname == "range") - { - brush = new RectangleBrush(width,height,z_levels,0,0,0); - } - else if(brushname == "block") - { - brush = new BlockBrush(); - } - else if(brushname == "column") - { - brush = new ColumnBrush(); - } - else if(brushname == "flood") - { - brush = new FloodBrush(&Core::getInstance()); - } - else - { - // this should never happen! - out << "Old brushtype is invalid! Resetting to point brush.\n"; - brushname = "point"; - width = 1; - height = 1; - z_levels = 1; - brush = new RectangleBrush(width,height,z_levels,0,0,0); - } - - CoreSuspender suspend; - - do - { - if (!Maps::IsValid()) - { - out << "Can't see any DF map loaded." << endl; - break;; - } - int32_t x,y,z; - if(!Gui::getCursorCoords(x,y,z)) - { - out << "Can't get cursor coords! Make sure you have a cursor active in DF." << endl; - break; - } - out << "cursor coords: " << x << "/" << y << "/" << z << endl; - MapCache mcache; - DFHack::DFCoord cursor(x,y,z); - coord_vec all_tiles = brush->points(mcache,cursor); - out << "working..." << endl; - if(mode == "obsidian") - { - coord_vec::iterator iter = all_tiles.begin(); - while (iter != all_tiles.end()) - { - mcache.setTiletypeAt(*iter, tiletype::LavaWall); - mcache.setTemp1At(*iter,10015); - mcache.setTemp2At(*iter,10015); - df::tile_designation des = mcache.designationAt(*iter); - des.bits.flow_size = 0; - mcache.setDesignationAt(*iter, des); - iter ++; - } - } - if(mode == "obsidian_floor") - { - coord_vec::iterator iter = all_tiles.begin(); - while (iter != all_tiles.end()) - { - mcache.setTiletypeAt(*iter, findRandomVariant(tiletype::LavaFloor1)); - iter ++; - } - } - else if(mode == "riversource") - { - coord_vec::iterator iter = all_tiles.begin(); - while (iter != all_tiles.end()) - { - mcache.setTiletypeAt(*iter, tiletype::RiverSource); - - df::tile_designation a = mcache.designationAt(*iter); - a.bits.liquid_type = tile_liquid::Water; - a.bits.liquid_static = false; - a.bits.flow_size = 7; - mcache.setTemp1At(*iter,10015); - mcache.setTemp2At(*iter,10015); - mcache.setDesignationAt(*iter,a); - - Block * b = mcache.BlockAt((*iter)/16); - DFHack::t_blockflags bf = b->BlockFlags(); - bf.bits.liquid_1 = true; - bf.bits.liquid_2 = true; - b->setBlockFlags(bf); - - iter++; - } - } - else if(mode=="wclean") - { - coord_vec::iterator iter = all_tiles.begin(); - while (iter != all_tiles.end()) - { - DFHack::DFCoord current = *iter; - df::tile_designation des = mcache.designationAt(current); - des.bits.water_salt = false; - des.bits.water_stagnant = false; - mcache.setDesignationAt(current,des); - iter++; - } - } - else if(mode== "magma" || mode== "water" || mode == "flowbits") - { - set seen_blocks; - coord_vec::iterator iter = all_tiles.begin(); - while (iter != all_tiles.end()) - { - DFHack::DFCoord current = *iter; // current tile coord - DFHack::DFCoord curblock = current /16; // current block coord - // check if the block is actually there - if(!mcache.BlockAt(curblock)) - { - iter ++; - continue; - } - df::tile_designation des = mcache.designationAt(current); - df::tiletype tt = mcache.tiletypeAt(current); - // don't put liquids into places where they don't belong... - if(!DFHack::FlowPassable(tt)) - { - iter++; - continue; - } - if(mode != "flowbits") - { - if(setmode == "s.") - { - des.bits.flow_size = amount; - } - else if(setmode == "s+") - { - if(des.bits.flow_size < amount) - des.bits.flow_size = amount; - } - else if(setmode == "s-") - { - if (des.bits.flow_size > amount) - des.bits.flow_size = amount; - } - if(amount != 0 && mode == "magma") - { - des.bits.liquid_type = tile_liquid::Magma; - mcache.setTemp1At(current,12000); - mcache.setTemp2At(current,12000); - } - else if(amount != 0 && mode == "water") - { - des.bits.liquid_type = tile_liquid::Water; - mcache.setTemp1At(current,10015); - mcache.setTemp2At(current,10015); - } - else if(amount == 0 && (mode == "water" || mode == "magma")) - { - // reset temperature to sane default - mcache.setTemp1At(current,10015); - mcache.setTemp2At(current,10015); - } - mcache.setDesignationAt(current,des); - } - seen_blocks.insert(mcache.BlockAt(current / 16)); - iter++; - } - set ::iterator biter = seen_blocks.begin(); - while (biter != seen_blocks.end()) - { - DFHack::t_blockflags bflags = (*biter)->BlockFlags(); - if(flowmode == "f+") - { - bflags.bits.liquid_1 = true; - bflags.bits.liquid_2 = true; - (*biter)->setBlockFlags(bflags); - } - else if(flowmode == "f-") - { - bflags.bits.liquid_1 = false; - bflags.bits.liquid_2 = false; - (*biter)->setBlockFlags(bflags); - } - else - { - out << "flow bit 1 = " << bflags.bits.liquid_1 << endl; - out << "flow bit 2 = " << bflags.bits.liquid_2 << endl; - } - biter ++; - } - } - if(mcache.WriteAll()) - out << "OK" << endl; - else - out << "Something failed horribly! RUN!" << endl; - } while (0); - - // cleanup - delete brush; - - return CR_OK; -} diff --git a/plugins/liquidsgo/liquidsgo.h b/plugins/liquidsgo/liquidsgo.h deleted file mode 100644 index 7b9637ef9..000000000 --- a/plugins/liquidsgo/liquidsgo.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once \ No newline at end of file diff --git a/plugins/proto/rename.proto b/plugins/proto/rename.proto index a6c3b055a..aa1e95f48 100644 --- a/plugins/proto/rename.proto +++ b/plugins/proto/rename.proto @@ -2,6 +2,7 @@ package dfproto; option optimize_for = LITE_RUNTIME; +// RPC RenameSquad : RenameSquadIn -> EmptyMessage message RenameSquadIn { required int32 squad_id = 1; @@ -9,6 +10,7 @@ message RenameSquadIn { optional string alias = 3; } +// RPC RenameUnit : RenameUnitIn -> EmptyMessage message RenameUnitIn { required int32 unit_id = 1; diff --git a/plugins/stonesense b/plugins/stonesense index 719dbc048..7525c0030 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 719dbc048a55ba1def2ce21e9cd29e33dbe833ce +Subproject commit 7525c003089367823183eaf5093a90271a5eb9b4