From e8ee876ddc230d55d38979ea2fa3136ecc9ef2fa Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 29 Mar 2012 21:18:57 -0500 Subject: [PATCH 001/108] Track. --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 3e1c72864..b41c666c6 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3e1c728640d8f5a9501908064a2d5385a156058c +Subproject commit b41c666c6be6fe18906a98dababdc4ff681b8382 diff --git a/plugins/stonesense b/plugins/stonesense index 906d6439d..07ddc8c13 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 906d6439d4cb694cf287d63badabb675a8b89329 +Subproject commit 07ddc8c13e0bb38cde77937803e9320e2b26f2d9 From 564150bb192d1598a5ac277fb449551615eb5cd7 Mon Sep 17 00:00:00 2001 From: RossM Date: Wed, 4 Apr 2012 23:33:26 -0700 Subject: [PATCH 002/108] Persist autolabor config in the savefile. --- plugins/autolabor.cpp | 262 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 227 insertions(+), 35 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index fedde8f33..bcf725559 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -9,6 +9,8 @@ #include #include +#include "modules/World.h" + // DF data structure definition headers #include "DataDefs.h" #include @@ -62,6 +64,13 @@ static bool print_debug = 0; static std::vector state_count(5); +static PersistentDataItem config; + +enum ConfigFlags { + CF_ENABLED = 1, +}; + + // 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); @@ -333,6 +342,27 @@ static const dwarf_state dwarf_states[] = { }; struct labor_info +{ + PersistentDataItem config; + + bool is_exclusive; + int active_dwarfs; + + labor_mode mode() { return (labor_mode) config.ival(1); } + void set_mode(labor_mode mode) { config.ival(1) = mode; } + + int minimum_dwarfs() { return config.ival(2); } + void set_minimum_dwarfs(int minimum_dwarfs) { config.ival(2) = minimum_dwarfs; } + + int maximum_dwarfs() { return config.ival(3); } + void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(3) = maximum_dwarfs; } + + df::enums::unit_labor::unit_labor labor() { return (df::enums::unit_labor::unit_labor) config.ival(0); } + void set_labor(df::enums::unit_labor::unit_labor labor) { config.ival(0) = labor; } + +}; + +struct labor_default { labor_mode mode; bool is_exclusive; @@ -341,9 +371,9 @@ struct labor_info int active_dwarfs; }; -static struct labor_info* labor_infos; +static std::vector labor_infos; -static const struct labor_info default_labor_infos[] = { +static const struct labor_default default_labor_infos[] = { /* MINE */ {AUTOMATIC, true, 2, 200, 0}, /* HAUL_STONE */ {HAULERS, false, 1, 200, 0}, /* HAUL_WOOD */ {HAULERS, false, 1, 200, 0}, @@ -436,15 +466,102 @@ struct dwarf_info_t bool has_exclusive_labor; }; +static bool isOptionEnabled(unsigned flag) +{ + return config.isValid() && (config.ival(0) & flag) != 0; +} + +static void setOptionEnabled(ConfigFlags flag, bool on) +{ + if (!config.isValid()) + return; + + if (on) + config.ival(0) |= flag; + else + config.ival(0) &= ~flag; +} + +static void cleanup_state() +{ + labor_infos.clear(); +} + +static void reset_labor(df::enums::unit_labor::unit_labor labor) +{ + labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs); + labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); + labor_infos[labor].set_mode(default_labor_infos[labor].mode); + labor_infos[labor].set_labor(labor); +} + +static void init_state() +{ + auto pworld = Core::getInstance().getWorld(); + + config = pworld->GetPersistentData("autolabor/config"); + if (config.isValid() && config.ival(0) == -1) + config.ival(0) = 0; + + enable_autolabor = isOptionEnabled(CF_ENABLED); + + if (!enable_autolabor) + return; + + // Load labors from save + labor_infos.resize(ARRAY_COUNT(default_labor_infos)); + + std::vector items; + pworld->GetPersistentData(&items, "autolabor/labors"); + + for (auto p = items.begin(); p != items.end(); p++) + { + df::enums::unit_labor::unit_labor labor = (df::enums::unit_labor::unit_labor) p->ival(0); + if (labor >= 0 && labor <= labor_infos.size()) + { + labor_infos[labor].config = *p; + labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive; + labor_infos[labor].active_dwarfs = 0; + } + } + + // Add default labors for those not in save + for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { + if (labor_infos[i].config.isValid()) + continue; + + labor_infos[i].config = pworld->AddPersistentData("autolabor/labors"); + + labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive; + labor_infos[i].active_dwarfs = 0; + reset_labor((df::enums::unit_labor::unit_labor) i); + } +} + +static void enable_plugin(color_ostream &out) +{ + auto pworld = Core::getInstance().getWorld(); + + if (!config.isValid()) + { + config = pworld->AddPersistentData("autolabor/config"); + config.ival(0) = 0; + } + + setOptionEnabled(CF_ENABLED, true); + enable_autolabor = true; + out << "Enabling the plugin." << endl; + + cleanup_state(); + init_state(); +} + DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { // initialize labor infos table from default table if(ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1) return CR_FAILURE; - labor_infos = new struct labor_info[ARRAY_COUNT(default_labor_infos)]; - for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { - labor_infos[i] = default_labor_infos[i]; - } + // Fill the command list with your commands. commands.push_back(PluginCommand( "autolabor", "Automatically manage dwarf labors.", @@ -459,8 +576,14 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector disable\n" " Turn off autolabor for a specific labor.\n" + " autolabor reset\n" + " Return a labor to the default handling.\n" + " autolabor reset-all\n" + " Return all labors to the default handling.\n" " autolabor list\n" " List current status of all labors.\n" + " autolabor status\n" + " Show basic status information.\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" @@ -477,13 +600,15 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & values; }; +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + cleanup_state(); + init_state(); + break; + case SC_MAP_UNLOADED: + cleanup_state(); + break; + default: + break; + } + + return CR_OK; +} + DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { static int step_count = 0; @@ -757,7 +899,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { auto labor = *lp; - if (labor_infos[labor].mode != DISABLE) + if (labor_infos[labor].mode() != DISABLE) continue; for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) @@ -785,7 +927,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) df::job_skill skill = labor_to_skill[labor]; - if (labor_infos[labor].mode != AUTOMATIC) + if (labor_infos[labor].mode() != AUTOMATIC) continue; int best_dwarf = 0; @@ -796,7 +938,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) std::map dwarf_skill; std::vector previously_enabled(n_dwarfs); - auto mode = labor_infos[labor].mode; + auto mode = labor_infos[labor].mode(); // Find candidate dwarfs, and calculate a preference value for each dwarf for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) @@ -867,8 +1009,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) dwarfs[dwarf]->status.labors[labor] = false; } - int min_dwarfs = labor_infos[labor].minimum_dwarfs; - int max_dwarfs = labor_infos[labor].maximum_dwarfs; + int min_dwarfs = labor_infos[labor].minimum_dwarfs(); + int max_dwarfs = labor_infos[labor].maximum_dwarfs(); // Special - don't assign hunt without a butchers, or fish without a fishery if (df::enums::unit_labor::HUNT == labor && !has_butchers) @@ -964,7 +1106,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) assert(labor < ARRAY_COUNT(labor_infos)); */ - if (labor_infos[labor].mode != HAULERS) + if (labor_infos[labor].mode() != HAULERS) continue; for (int i = 0; i < num_haulers; i++) @@ -1009,31 +1151,58 @@ void print_labor (df::enums::unit_labor::unit_labor labor, color_ostream &out) out << labor_name << ": "; for (int i = 0; i < 20 - (int)labor_name.length(); i++) out << ' '; - if (labor_infos[labor].mode == DISABLE) + if (labor_infos[labor].mode() == DISABLE) out << "disabled" << endl; else { - if (labor_infos[labor].mode == HAULERS) + if (labor_infos[labor].mode() == HAULERS) out << "haulers"; else - out << "minimum " << labor_infos[labor].minimum_dwarfs << ", maximum " << labor_infos[labor].maximum_dwarfs; + out << "minimum " << labor_infos[labor].minimum_dwarfs() << ", maximum " << labor_infos[labor].maximum_dwarfs(); out << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl; } } command_result autolabor (color_ostream &out, std::vector & parameters) { + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("World is not loaded: please load a game first.\n"); + return CR_FAILURE; + } + if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "enable" || parameters[0] == "1" || parameters[0] == "disable")) { - if (parameters[0] == "0" || parameters[0] == "disable") - enable_autolabor = 0; + bool enable = (parameters[0] == "1" || parameters[0] == "enable"); + if (enable && !enable_autolabor) + { + enable_plugin(out); + } else - enable_autolabor = 1; - out.print("autolabor %sactivated.\n", (enable_autolabor ? "" : "de")); + { + if (enable_autolabor) + { + enable_autolabor = false; + setOptionEnabled(CF_ENABLED, false); + } + + out << "The plugin is disabled." << endl; + return CR_OK; + } + + return CR_OK; } - else if (parameters.size() == 2 || parameters.size() == 3) { + + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + if (parameters.size() == 2 || parameters.size() == 3) { df::enums::unit_labor::unit_labor labor = df::enums::unit_labor::NONE; FOR_ENUM_ITEMS(unit_labor, test_labor) @@ -1050,13 +1219,19 @@ command_result autolabor (color_ostream &out, std::vector & parame if (parameters[1] == "haulers") { - labor_infos[labor].mode = HAULERS; + labor_infos[labor].set_mode(HAULERS); print_labor(labor, out); return CR_OK; } if (parameters[1] == "disable") { - labor_infos[labor].mode = DISABLE; + labor_infos[labor].set_mode(DISABLE); + print_labor(labor, out); + return CR_OK; + } + if (parameters[1] == "reset") + { + reset_labor(labor); print_labor(labor, out); return CR_OK; } @@ -1072,12 +1247,22 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_WRONG_USAGE; } - labor_infos[labor].minimum_dwarfs = minimum; - labor_infos[labor].maximum_dwarfs = maximum; - labor_infos[labor].mode = AUTOMATIC; + labor_infos[labor].set_minimum_dwarfs(minimum); + labor_infos[labor].set_maximum_dwarfs(maximum); + labor_infos[labor].set_mode(AUTOMATIC); print_labor(labor, out); + + return CR_OK; } - else if (parameters.size() == 1 && parameters[0] == "list") { + else if (parameters.size() == 1 && parameters[0] == "reset-all") { + for (int i = 0; i < labor_infos.size(); i++) + { + reset_labor((df::enums::unit_labor::unit_labor) i); + } + out << "All labors reset." << endl; + return CR_OK; + } + else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") { if (!enable_autolabor) { out << "autolabor not activated." << endl; @@ -1096,23 +1281,30 @@ command_result autolabor (color_ostream &out, std::vector & parame } out << endl; - FOR_ENUM_ITEMS(unit_labor, labor) + if (parameters[0] == "list") { - if (labor == df::enums::unit_labor::NONE) - continue; + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == df::enums::unit_labor::NONE) + continue; - print_labor(labor, out); + print_labor(labor, out); + } } + + return CR_OK; } else if (parameters.size() == 1 && parameters[0] == "debug") { print_debug = 1; + + return CR_OK; } 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; + return CR_OK; + } } From d1b27418a6a39aa15d51465e61975c5230dcc9d6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 5 Apr 2012 11:32:23 +0400 Subject: [PATCH 003/108] Add a World::GetPersistentData version that auto-adds if not found. --- library/include/DataFuncs.h | 4 ++++ library/include/DataIdentity.h | 4 +++- library/include/modules/World.h | 8 ++++++++ library/modules/World.cpp | 15 +++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 822dcd45d..109b2c176 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -32,6 +32,10 @@ distribution. #include "DataIdentity.h" #include "LuaWrapper.h" +#ifndef BUILD_DFHACK_LIB +#error Due to export issues this header is internal to the main library. +#endif + namespace df { // A very simple and stupid implementation of some stuff from boost template struct is_same_type { static const bool value = false; }; diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index c83b2092a..bab87e1bb 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -160,6 +160,8 @@ namespace DFHack }; } +// Due to export issues, this stuff can only work in the main dll +#ifdef BUILD_DFHACK_LIB namespace df { using DFHack::function_identity_base; @@ -575,4 +577,4 @@ namespace df return &identity; } } - +#endif diff --git a/library/include/modules/World.h b/library/include/modules/World.h index 9ed6a3ed9..e7b17ce41 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -139,8 +139,16 @@ namespace DFHack PersistentDataItem AddPersistentData(const std::string &key); PersistentDataItem GetPersistentData(const std::string &key); PersistentDataItem GetPersistentData(int entry_id); + // Calls GetPersistentData(key); if not found, adds and sets added to true. + // The result can still be not isValid() e.g. if the world is not loaded. + PersistentDataItem GetPersistentData(const std::string &key, bool *added); + // Lists all items with the given key. + // If prefix is true, search for keys starting with key+"/". + // GetPersistentData(&vec,"",true) returns all items. + // Items have alphabetic order by key; same key ordering is undefined. void GetPersistentData(std::vector *vec, const std::string &key, bool prefix = false); + // Deletes the item; returns true if success. bool DeletePersistentData(const PersistentDataItem &item); void ClearPersistentCache(); diff --git a/library/modules/World.cpp b/library/modules/World.cpp index d570abaec..ca5d0aee0 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -300,6 +300,21 @@ PersistentDataItem World::GetPersistentData(int entry_id) return PersistentDataItem(); } +PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) +{ + *added = false; + + PersistentDataItem rv = GetPersistentData(key); + + if (!rv.isValid()) + { + *added = true; + rv = AddPersistentData(key); + } + + return rv; +} + void World::GetPersistentData(std::vector *vec, const std::string &key, bool prefix) { if (!BuildPersistentCache()) From 3afed43cdb435c788964d983a358d97ee291c2fc Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 5 Apr 2012 11:59:39 +0400 Subject: [PATCH 004/108] Experimental: try wrapping a dfhack api function. --- library/LuaTools.cpp | 6 ++++++ library/LuaTypes.cpp | 11 ++++++++--- library/LuaWrapper.cpp | 5 ++++- library/include/DataFuncs.h | 22 ++++++++++++++++++++-- library/include/LuaWrapper.h | 5 +++++ 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 5a50d0dd2..16c60457a 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -35,14 +35,18 @@ distribution. // must be last due to MS stupidity #include "DataDefs.h" #include "DataIdentity.h" +#include "DataFuncs.h" #include "modules/World.h" +#include "modules/Gui.h" #include "LuaWrapper.h" #include "LuaTools.h" #include "MiscUtils.h" +#include "df/job.h" + #include #include #include @@ -868,6 +872,8 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) OpenPersistent(state); + LuaWrapper::AddMethodWrapper(state, 0, -1, "getSelectedJob", df::wrap_function(&Gui::getSelectedJob)); + lua_setglobal(state, "dfhack"); // load dfhack.lua diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 6d5961f8d..2cc929ee9 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1029,11 +1029,16 @@ static int meta_call_function(lua_State *state) /** * Create a closure invoking the given function, and add it to the field table. */ -static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, - const char *name, function_identity_base *fun) +void LuaWrapper::AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, + const char *name, function_identity_base *fun) { + field_idx = lua_absindex(state, field_idx); + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN); - lua_pushvalue(state, meta_idx); + if (meta_idx) + lua_pushvalue(state, meta_idx); + else + lua_pushlightuserdata(state, NULL); // can't be a metatable lua_pushfstring(state, "%s()", name); lua_pushlightuserdata(state, fun); lua_pushcclosure(state, meta_call_function, 4); diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 818e6b9e1..2f3958efe 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -53,7 +53,10 @@ static luaL_Reg no_functions[] = { { NULL, NULL } }; */ void LuaWrapper::field_error(lua_State *state, int index, const char *err, const char *mode) { - lua_getfield(state, UPVAL_METATABLE, "__metatable"); + if (lua_islightuserdata(state, UPVAL_METATABLE)) + lua_pushstring(state, "(global)"); + else + 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.", diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 109b2c176..3529d8059 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -67,10 +67,15 @@ namespace df { CT *self = (CT*)DFHack::LuaWrapper::get_object_addr(state, base++, UPVAL_METHOD_NAME, "invoke"); #define LOAD_ARG(type) \ type v##type; df::identity_traits::get()->lua_write(state, UPVAL_METHOD_NAME, &v##type, base++); +#define OSTREAM_ARG DFHack::color_ostream& +#define LOAD_OSTREAM(name) \ + DFHack::color_ostream_proxy name(DFHack::Core::getInstance().getConsole()); -#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \ +#define INSTANTIATE_RETURN_TYPE(FArgs) \ template struct return_type { typedef RT type; }; \ - template struct return_type { typedef RT type; }; \ + template struct return_type { typedef RT type; }; + +#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \ template struct function_wrapper { \ static const bool is_method = false; \ static const int num_args = Count; \ @@ -96,24 +101,35 @@ namespace df { #define FW_TARGSC #define FW_TARGS +INSTANTIATE_RETURN_TYPE(()) INSTANTIATE_WRAPPERS(0, (), (), ;) +INSTANTIATE_WRAPPERS(0, (OSTREAM_ARG), (out), LOAD_OSTREAM(out);) #undef FW_TARGS #undef FW_TARGSC #define FW_TARGSC FW_TARGS, #define FW_TARGS class A1 +INSTANTIATE_RETURN_TYPE((A1)) INSTANTIATE_WRAPPERS(1, (A1), (vA1), LOAD_ARG(A1);) +INSTANTIATE_WRAPPERS(1, (OSTREAM_ARG,A1), (out,vA1), LOAD_OSTREAM(out); LOAD_ARG(A1);) #undef FW_TARGS #define FW_TARGS class A1, class A2 +INSTANTIATE_RETURN_TYPE((A1,A2)) INSTANTIATE_WRAPPERS(2, (A1,A2), (vA1,vA2), LOAD_ARG(A1); LOAD_ARG(A2);) +INSTANTIATE_WRAPPERS(2, (OSTREAM_ARG,A1,A2), (out,vA1,vA2), + LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3 +INSTANTIATE_RETURN_TYPE((A1,A2,A3)) INSTANTIATE_WRAPPERS(3, (A1,A2,A3), (vA1,vA2,vA3), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);) +INSTANTIATE_WRAPPERS(3, (OSTREAM_ARG,A1,A2,A3), (out,vA1,vA2,vA3), + LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3, class A4 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4)) INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (vA1,vA2,vA3,vA4), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);) #undef FW_TARGS @@ -124,6 +140,8 @@ INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (vA1,vA2,vA3,vA4), #undef INVOKE_RV #undef LOAD_CLASS #undef LOAD_ARG +#undef OSTREAM_ARG +#undef LOAD_OSTREAM template class function_identity : public function_identity_base { diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 72aa0e12e..66e77bb1f 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -220,6 +220,11 @@ namespace DFHack { namespace LuaWrapper { * and the enum itself to the _enum metafield. Pushes the key table on the stack. */ void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); + /** + * Create a closure invoking the given function, and add it to the field table. + */ + void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, + const char *name, function_identity_base *fun); void IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct); From 59f411e4016405ebef0a928c002098586be77ebb Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 5 Apr 2012 12:32:10 +0400 Subject: [PATCH 005/108] Oops, forgot return statement. --- library/modules/World.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/modules/World.cpp b/library/modules/World.cpp index ca5d0aee0..6f6a69a59 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -254,6 +254,8 @@ bool World::BuildPersistentCache() d->persistent_index.insert(T_persistent_item(hfvec[i]->name.first_name, -hfvec[i]->id)); } + + return true; } PersistentDataItem World::AddPersistentData(const std::string &key) From 33ceee8310f6dba6e7932e74dc74d7963ccc2417 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Thu, 5 Apr 2012 11:40:27 +0200 Subject: [PATCH 006/108] zone tool: some cleanup, added 'slaughter' command --- plugins/zone.cpp | 196 ++++++++++++++++++++++++++++++----------------- 1 file changed, 124 insertions(+), 72 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 5e712510c..d73bc8145 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -119,7 +119,7 @@ const string zone_help_examples = " (dfhack) 'zone set' to use this zone for future assignments\n" " (dfhack) map 'zone assign' to a hotkey of your choice\n" " (ingame) select unit with 'v', 'k' or from unit list or inside a cage\n" - " (ingame) press hotkey to assign unit to it's new home (or pit)\n" + " (ingame) press hotkey to assign unit to it's new home (or pit)\n" "Examples for assigning with filters:\n" " (this assumes you have already set up a target zone)\n" " zone assign all own grazer maxage 10\n" @@ -185,8 +185,8 @@ bool isBuiltCageAtPos(df::coord pos); int32_t getUnitAge(df::unit* unit) { - // If the birthday this year has not yet passed, subtract one year. - // ASSUMPTION: birth_time is on the same scale as cur_year_tick + // If the birthday this year has not yet passed, subtract one year. + // ASSUMPTION: birth_time is on the same scale as cur_year_tick int32_t yearDifference = *df::global::cur_year - unit->relations.birth_year; if (unit->relations.birth_time >= *df::global::cur_year_tick) yearDifference--; @@ -195,10 +195,26 @@ int32_t getUnitAge(df::unit* unit) bool isDead(df::unit* unit) { - if(unit->flags1.bits.dead) - return true; - else - return false; + if(unit->flags1.bits.dead) + return true; + else + return false; +} + + +// marked for slaughter? +bool isMarkedForSlaughter(df::unit* unit) +{ + if(unit->flags2.bits.slaughter) + return true; + else + return false; +} + +// mark for slaughter +void doMarkForSlaughter(df::unit* unit) +{ + unit->flags2.bits.slaughter = 1; } bool isTame(df::unit* creature) @@ -272,6 +288,7 @@ bool isHunter(df::unit* creature) return false; } + // check if creature belongs to the player's civilization // (don't try to pasture/slaughter random untame animals) bool isOwnCiv(df::unit* creature) @@ -294,13 +311,29 @@ bool isOwnRace(df::unit* creature) string getRaceName(df::unit* unit) { - df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; return raw->creature_id; } +bool isBaby(df::unit* unit) +{ + if(unit->profession != df::profession::BABY) + return true; + else + return false; +} + +bool isChild(df::unit* unit) +{ + if(unit->profession != df::profession::CHILD) + return true; + else + return false; +} + bool isEggLayer(df::unit* unit) { - df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; size_t sizecas = raw->caste.size(); for (size_t j = 0; j < sizecas;j++) { @@ -314,7 +347,7 @@ bool isEggLayer(df::unit* unit) bool isGrazer(df::unit* unit) { - df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; size_t sizecas = raw->caste.size(); for (size_t j = 0; j < sizecas;j++) { @@ -327,7 +360,7 @@ bool isGrazer(df::unit* unit) bool isMilkable(df::unit* unit) { - df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; size_t sizecas = raw->caste.size(); for (size_t j = 0; j < sizecas;j++) { @@ -356,23 +389,23 @@ bool isFemale(df::unit* unit) // dump some unit info void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) { - if(isDead(unit)) - return; + if(isDead(unit)) + return; out.print("Unit %d ", unit->id); //race %d, civ %d,", creature->race, creature->civ_id if(unit->name.has_name) - { - // units given a nick with the rename tool might not have a first name (animals etc) - string firstname = unit->name.first_name; - if(firstname.size() > 0) - { - firstname[0] = toupper(firstname[0]); - out << "Name: " << firstname; - } - if(unit->name.nickname.size() > 0) - out << " '" << unit->name.nickname << "'"; + { + // units given a nick with the rename tool might not have a first name (animals etc) + string firstname = unit->name.first_name; + if(firstname.size() > 0) + { + firstname[0] = toupper(firstname[0]); + out << "Name: " << firstname; + } + if(unit->name.nickname.size() > 0) + out << " '" << unit->name.nickname << "'"; out << ", "; - } + } out << getRaceName(unit) << " ("; switch(unit->sex) { @@ -485,16 +518,16 @@ bool isCage(df::building * building) { if(building->getType() == building_type::Cage) return true; - else - return false; + else + return false; } bool isChain(df::building * building) { if(building->getType() == building_type::Chain) return true; - else - return false; + else + return false; } bool isActive(df::building * building) @@ -648,14 +681,14 @@ bool isAssigned(df::unit* unit) for (size_t r=0; r < unit->refs.size(); r++) { df::general_ref * ref = unit->refs[r]; - auto rtype = ref->getType(); + auto rtype = ref->getType(); if( rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED - || rtype == df::general_ref_type::BUILDING_CAGED - || rtype == df::general_ref_type::BUILDING_CHAIN + || rtype == df::general_ref_type::BUILDING_CAGED + || rtype == df::general_ref_type::BUILDING_CHAIN || (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isBuiltCageAtPos(unit->pos)) - ) + ) { - assigned = true; + assigned = true; break; } } @@ -864,7 +897,7 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building out << "no old zone info found."; } - ref->building_id = building->id; + ref->building_id = building->id; unit->refs.push_back(ref); df::building_civzonest * civz = (df::building_civzonest *) building; @@ -946,11 +979,11 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose = false) return; out << endl; out << "x1:" <x1 - << " x2:" <x2 - << " y1:" <y1 - << " y2:" <y2 - << " z:" <z - << endl; + << " x2:" <x2 + << " y1:" <y1 + << " y2:" <y2 + << " z:" <z + << endl; int32_t creaturecount = civ->assigned_creature.size(); out << "Creatures in this zone: " << creaturecount << endl; @@ -985,10 +1018,10 @@ void cageInfo(color_ostream & out, df::building* building, bool verbose = false) building->getType()); out.print("\n"); - out << "x:" << building->x1 - << " y:" << building->y1 - << " z:" << building->z - << endl; + out << "x:" << building->x1 + << " y:" << building->y1 + << " z:" << building->z + << endl; df::building_cagest * cage = (df::building_cagest*) building; int32_t creaturecount = cage->assigned_creature.size(); @@ -1025,16 +1058,16 @@ void chainInfo(color_ostream & out, df::building* building, bool list_refs = fal out.print("\n"); df::building_chainst* chain = (df::building_chainst*) building; - if(chain->assigned) - { - out << "assigned: "; - unitInfo(out, chain->assigned, true); - } - if(chain->chained) - { - out << "chained: "; - unitInfo(out, chain->chained, true); - } + if(chain->assigned) + { + out << "assigned: "; + unitInfo(out, chain->assigned, true); + } + if(chain->chained) + { + out << "chained: "; + unitInfo(out, chain->chained, true); + } } command_result df_zone (color_ostream &out, vector & parameters) @@ -1046,16 +1079,16 @@ command_result df_zone (color_ostream &out, vector & parameters) bool zone_info = false; //bool cage_info = false; //bool chain_info = false; - - bool find_unassigned = false; - bool find_caged = false; - bool find_uncaged = false; - bool find_foreign = false; - bool find_untrained = false; - //bool find_trained = false; - bool find_war = false; - bool find_own = false; - bool find_tame = false; + + bool find_unassigned = false; + bool find_caged = false; + bool find_uncaged = false; + bool find_foreign = false; + bool find_untrained = false; + //bool find_trained = false; + bool find_war = false; + bool find_own = false; + bool find_tame = false; bool find_male = false; bool find_female = false; bool find_egglayer = false; @@ -1071,7 +1104,7 @@ command_result df_zone (color_ostream &out, vector & parameters) size_t target_count = 0; bool find_race = false; - string target_race = ""; + string target_race = ""; bool zone_assign = false; bool zone_unassign = false; @@ -1079,6 +1112,7 @@ command_result df_zone (color_ostream &out, vector & parameters) bool verbose = false; bool all = false; bool auto_nestbox = false; + bool unit_slaughter = false; static int target_zone = -1; for (size_t i = 0; i < parameters.size(); i++) @@ -1151,7 +1185,7 @@ command_result df_zone (color_ostream &out, vector & parameters) } else { - target_race = parameters[i+1]; + target_race = parameters[i+1]; i++; out << "Filter by race " << target_race << endl; find_race = true; @@ -1202,6 +1236,11 @@ command_result df_zone (color_ostream &out, vector & parameters) out << "Auto-assign female tame owned egg-layers to free nestboxes." << endl; auto_nestbox = true; } + else if(p == "slaughter") + { + out << "Assign animals for slaughter." << endl; + unit_slaughter = true; + } else if(p == "count") { if(i == parameters.size()-1) @@ -1303,10 +1342,10 @@ command_result df_zone (color_ostream &out, vector & parameters) all = true; } else - { - out << "Unknown command: " << p << endl; + { + out << "Unknown command: " << p << endl; return CR_WRONG_USAGE; - } + } } if((zone_info && !all) || zone_set) @@ -1405,12 +1444,12 @@ command_result df_zone (color_ostream &out, vector & parameters) } // assign to pen or pit - if(zone_assign || unit_info) + if(zone_assign || unit_info || unit_slaughter) { df::building * building; if(zone_assign) { - // try to get building index from the id + // try to get building index from the id int32_t index = findBuildingIndexById(target_zone); if(index == -1) { @@ -1463,12 +1502,17 @@ command_result df_zone (color_ostream &out, vector & parameters) out << "invalid unit pos" << endl; unitInfo(out, unit, verbose); } - else + else if(zone_assign) { command_result result = assignUnitToBuilding(out, unit, building, verbose); if(result != CR_OK) return result; } + else if(unit_slaughter) + { + doMarkForSlaughter(unit); + } + count++; if(find_count && count >= target_count) break; @@ -1490,8 +1534,16 @@ command_result df_zone (color_ostream &out, vector & parameters) unitInfo(out, unit, verbose); return CR_OK; } + else if(zone_assign) + { + return assignUnitToBuilding(out, unit, building, verbose); + } + else if(unit_slaughter) + { + doMarkForSlaughter(unit); + return CR_OK; + } - return assignUnitToBuilding(out, unit, building, verbose); } } From 28a741082f8b0981806b8a63589279627bd8e39e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 5 Apr 2012 18:10:16 +0400 Subject: [PATCH 007/108] Encode & decode names in utf-8 for transfer in remote messages. That's the encoding required by the protobuf spec. --- LICENSE | 24 ++++++ library/MiscUtils.cpp | 161 +++++++++++++++++++++++++++++++++++- library/RemoteTools.cpp | 14 ++-- library/include/MiscUtils.h | 4 + plugins/rename.cpp | 10 ++- 5 files changed, 201 insertions(+), 12 deletions(-) diff --git a/LICENSE b/LICENSE index 5ea59addf..96ab022d9 100644 --- a/LICENSE +++ b/LICENSE @@ -113,3 +113,27 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------- + +See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +Copyright (c) 2008-2010 Bjoern Hoehrmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 8247cd002..9b26e2a61 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -37,6 +37,7 @@ distribution. #include #include +#include std::string stl_sprintf(const char *fmt, ...) { va_list lst; @@ -149,4 +150,162 @@ uint64_t GetTimeMs64() return ret; } -#endif \ No newline at end of file +#endif + +/* Character decoding */ + +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 + +static const uint8_t utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; + +static inline uint32_t +decode(uint32_t* state, uint32_t* codep, uint8_t byte) { + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +/* Character encoding */ + +static inline int encode(uint8_t *out, uint16_t c) { + if (c <= 0x7F) + { + out[0] = c; + return 1; + } + else if (c <= 0x7FF) + { + out[0] = (0xC0 | (c >> 6)); + out[1] = (0x80 | (c & 0x3F)); + return 2; + } + else /*if (c <= 0xFFFF)*/ + { + out[0] = (0xE0 | (c >> 12)); + out[1] = (0x80 | ((c >> 6) & 0x3F)); + out[2] = (0x80 | (c & 0x3F)); + return 3; + } +} + +/* CP437 */ + +static uint16_t character_table[256] = { + 0, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, // + 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C, + 0x25BA, 0x25C4, 0x2195, 0x203C, 0xB6, 0xA7, 0x25AC, 0x21A8, // + 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x2302, + 0xC7, 0xFC, 0xE9, 0xE2, 0xE4, 0xE0, 0xE5, 0xE7, // + 0xEA, 0xEB, 0xE8, 0xEF, 0xEE, 0xEC, 0xC4, 0xC5, + 0xC9, 0xE6, 0xC6, 0xF4, 0xF6, 0xF2, 0xFB, 0xF9, // + 0xFF, 0xD6, 0xDC, 0xA2, 0xA3, 0xA5, 0x20A7, 0x192, + 0xE1, 0xED, 0xF3, 0xFA, 0xF1, 0xD1, 0xAA, 0xBA, // + 0xBF, 0x2310, 0xAC, 0xBD, 0xBC, 0xA1, 0xAB, 0xBB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, // + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, // + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, // + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x3B1, 0xDF, 0x393, 0x3C0, 0x3A3, 0x3C3, 0xB5, 0x3C4, // + 0x3A6, 0x398, 0x3A9, 0x3B4, 0x221E, 0x3C6, 0x3B5, 0x2229, + 0x2261, 0xB1, 0x2265, 0x2264, 0x2320, 0x2321, 0xF7, 0x2248, // + 0xB0, 0x2219, 0xB7, 0x221A, 0x207F, 0xB2, 0x25A0, 0xA0 +}; + +std::string DF2UTF(const std::string &in) +{ + std::string out; + out.reserve(in.size()); + + uint8_t buf[4]; + for (size_t i = 0; i < in.size(); i++) + { + int cnt = encode(buf, character_table[(uint8_t)in[i]]); + out.append(&buf[0], &buf[cnt]); + } + + return out; +} + +std::string UTF2DF(const std::string &in) +{ + // Unicode to normal lookup table + static std::map ctable; + + if (ctable.empty()) + { + for (uint16_t i = 0; i < 256; i++) + if (character_table[i] != i) + ctable[character_table[i]] = char(i); + } + + // Actual conversion loop + size_t size = in.size(); + std::string out(size, char(0)); + + uint32_t codepoint = 0; + uint32_t state = UTF8_ACCEPT, prev = UTF8_ACCEPT; + uint32_t pos = 0; + + for (unsigned i = 0; i < size; prev = state, i++) { + switch (decode(&state, &codepoint, uint8_t(in[i]))) { + case UTF8_ACCEPT: + if (codepoint < 256 && character_table[codepoint] == codepoint) { + out[pos++] = char(codepoint); + } else { + char v = ctable[codepoint]; + out[pos++] = v ? v : '?'; + } + break; + + case UTF8_REJECT: + out[pos++] = '?'; + if (prev != UTF8_ACCEPT) --i; + state = UTF8_ACCEPT; + break; + } + } + + if (pos != size) + out.resize(pos); + return out; +} diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 229e5a70c..01c110643 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -221,30 +221,30 @@ void DFHack::describeMaterial(BasicMaterialInfo *info, const MaterialInfo &mat, void DFHack::describeName(NameInfo *info, df::language_name *name) { if (!name->first_name.empty()) - info->set_first_name(name->first_name); + info->set_first_name(DF2UTF(name->first_name)); if (!name->nickname.empty()) - info->set_nickname(name->nickname); + info->set_nickname(DF2UTF(name->nickname)); if (name->language >= 0) info->set_language_id(name->language); std::string lname = Translation::TranslateName(name, false, true); if (!lname.empty()) - info->set_last_name(lname); + info->set_last_name(DF2UTF(lname)); lname = Translation::TranslateName(name, true, true); if (!lname.empty()) - info->set_english_name(lname); + info->set_english_name(DF2UTF(lname)); } void DFHack::describeNameTriple(NameTriple *info, const std::string &name, const std::string &plural, const std::string &adj) { - info->set_normal(name); + info->set_normal(DF2UTF(name)); if (!plural.empty() && plural != name) - info->set_plural(plural); + info->set_plural(DF2UTF(plural)); if (!adj.empty() && adj != name) - info->set_adjective(adj); + info->set_adjective(DF2UTF(adj)); } void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index e5ecb25f4..c2a153eb1 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -279,3 +279,7 @@ DFHACK_EXPORT uint64_t GetTimeMs64(); DFHACK_EXPORT std::string stl_sprintf(const char *fmt, ...); DFHACK_EXPORT std::string stl_vsprintf(const char *fmt, va_list args); + +// Conversion between CP437 and UTF-8 +DFHACK_EXPORT std::string UTF2DF(const std::string &in); +DFHACK_EXPORT std::string DF2UTF(const std::string &in); diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 8dacf62a9..d983d0962 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -20,6 +20,8 @@ #include "RemoteServer.h" #include "rename.pb.h" +#include "MiscUtils.h" + #include using std::vector; @@ -128,9 +130,9 @@ static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in return CR_NOT_FOUND; if (in->has_nickname()) - set_nickname(&squad->name, in->nickname()); + set_nickname(&squad->name, UTF2DF(in->nickname())); if (in->has_alias()) - squad->alias = in->alias(); + squad->alias = UTF2DF(in->alias()); return CR_OK; } @@ -142,9 +144,9 @@ static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) return CR_NOT_FOUND; if (in->has_nickname()) - setUnitNickname(unit, in->nickname()); + setUnitNickname(unit, UTF2DF(in->nickname())); if (in->has_profession()) - unit->custom_profession = in->profession(); + unit->custom_profession = UTF2DF(in->profession()); return CR_OK; } From 9eed9f0d246f44a51266a05e0107ea22fea54e73 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 5 Apr 2012 19:55:59 +0400 Subject: [PATCH 008/108] Wrap a few utility functions defined on the c++ side for lua. --- LUA_API.rst | 61 ++++++++++++++++++++++++++++++++++++ Lua API.html | 58 ++++++++++++++++++++++++++++++++++ library/LuaTools.cpp | 48 +++++++++++++++++++++++++++- library/LuaTypes.cpp | 33 ++++++++++++++++--- library/MiscUtils.cpp | 5 +++ library/include/DataFuncs.h | 9 +++++- library/include/Error.h | 12 +++++++ library/include/LuaWrapper.h | 11 +++++-- library/lua/dfhack.lua | 26 +++++++++++++++ library/modules/Job.cpp | 15 +++++++++ 10 files changed, 268 insertions(+), 10 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index bf6c81ec0..dab299741 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -497,3 +497,64 @@ Since the data is hidden in data structures owned by the DF world, and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead. + +C++ function wrappers +===================== + +Thin wrappers around C++ functions, similar to the ones for virtual methods. + +Gui module +---------- + +* ``dfhack.gui.getSelectedWorkshopJob(silent)`` + + When a job is selected in *'q'* mode, returns the job, else + prints error unless silent and returns *nil*. + +* ``dfhack.gui.getSelectedJob(silent)`` + + Returns the job selected in a workshop or unit/jobs screen. + +* ``dfhack.gui.getSelectedUnit(silent)`` + + Returns the unit selected via *'v'*, *'k'*, unit/jobs, or + a full-screen item view of a cage or suchlike. + +* ``dfhack.gui.getSelectedItem(silent)`` + + Returns the item selected via *'v'* ->inventory, *'k'*, *'t'*, or + a full-screen item view of a container. Note that in the + last case, the highlighted *contained item* is returned, not + the container itself. + +* ``dfhack.gui.showAnnouncement(text,color,is_bright)`` + + Adds a regular announcement with given text, color, and brightness. + The is_bright boolean actually seems to invert the brightness. + +* ``dfhack.gui.showPopupAnnouncement(text,color,is_bright)`` + + Pops up a titan-style modal announcement window. + +Job module +---------- + +* ``dfhack.job.cloneJobStruct(job)`` + + Creates a deep copy of the given job. + +* ``dfhack.job.printJobDetails(job)`` + + Prints info about the job. + +* ``dfhack.job.getJobHolder(job)`` + + Returns the building holding the job. + +* ``dfhack.job.is_equal(job1,job2)`` + + Compares important fields in the job and nested item structures. + +* ``dfhack.job.is_item_equal(job_item1,job_item2)`` + + Compares important fields in the job item structures. diff --git a/Lua API.html b/Lua API.html index 25b6f06d6..877bdbd88 100644 --- a/Lua API.html +++ b/Lua API.html @@ -335,6 +335,11 @@ ul.auto-toc {
  • DFHack utilities
  • @@ -753,6 +758,59 @@ and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead.

    +
    +

    C++ function wrappers

    +

    Thin wrappers around C++ functions, similar to the ones for virtual methods.

    +
    +

    Gui module

    +
      +
    • dfhack.gui.getSelectedWorkshopJob(silent)

      +

      When a job is selected in 'q' mode, returns the job, else +prints error unless silent and returns nil.

      +
    • +
    • dfhack.gui.getSelectedJob(silent)

      +

      Returns the job selected in a workshop or unit/jobs screen.

      +
    • +
    • dfhack.gui.getSelectedUnit(silent)

      +

      Returns the unit selected via 'v', 'k', unit/jobs, or +a full-screen item view of a cage or suchlike.

      +
    • +
    • dfhack.gui.getSelectedItem(silent)

      +

      Returns the item selected via 'v' ->inventory, 'k', 't', or +a full-screen item view of a container. Note that in the +last case, the highlighted contained item is returned, not +the container itself.

      +
    • +
    • dfhack.gui.showAnnouncement(text,color,is_bright)

      +

      Adds a regular announcement with given text, color, and brightness. +The is_bright boolean actually seems to invert the brightness.

      +
    • +
    • dfhack.gui.showPopupAnnouncement(text,color,is_bright)

      +

      Pops up a titan-style modal announcement window.

      +
    • +
    +
    +
    +

    Job module

    +
      +
    • dfhack.job.cloneJobStruct(job)

      +

      Creates a deep copy of the given job.

      +
    • +
    • dfhack.job.printJobDetails(job)

      +

      Prints info about the job.

      +
    • +
    • dfhack.job.getJobHolder(job)

      +

      Returns the building holding the job.

      +
    • +
    • dfhack.job.is_equal(job1,job2)

      +

      Compares important fields in the job and nested item structures.

      +
    • +
    • dfhack.job.is_item_equal(job_item1,job_item2)

      +

      Compares important fields in the job item structures.

      +
    • +
    +
    +
    diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 16c60457a..5afd1156b 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -39,6 +39,7 @@ distribution. #include "modules/World.h" #include "modules/Gui.h" +#include "modules/Job.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -46,6 +47,10 @@ distribution. #include "MiscUtils.h" #include "df/job.h" +#include "df/job_item.h" +#include "df/building.h" +#include "df/unit.h" +#include "df/item.h" #include #include @@ -74,6 +79,13 @@ color_ostream *DFHack::Lua::GetOutput(lua_State *L) return rv; } +df::cur_lua_ostream_argument::cur_lua_ostream_argument(lua_State *state) +{ + out = DFHack::Lua::GetOutput(state); + if (!out) + LuaWrapper::field_error(state, UPVAL_METHOD_NAME, "no output stream", "invoke"); +} + static void set_dfhack_output(lua_State *L, color_ostream *p) { lua_pushlightuserdata(L, p); @@ -844,6 +856,39 @@ static void OpenPersistent(lua_State *state) lua_pop(state, 1); } +static void OpenModule(lua_State *state, const char *mname, const LuaWrapper::FunctionReg *reg) +{ + luaL_getsubtable(state, lua_gettop(state), mname); + LuaWrapper::SetFunctionWrappers(state, reg); + lua_pop(state, 1); +} + +#define WRAPM(module, function) { #function, df::wrap_function(&module::function) } +#define WRAP(function) { #function, df::wrap_function(&function) } +#define WRAPN(name, function) { #name, df::wrap_function(&function) } + +static const LuaWrapper::FunctionReg dfhack_gui_module[] = { + WRAPM(Gui, getSelectedWorkshopJob), + WRAPM(Gui, getSelectedJob), + WRAPM(Gui, getSelectedUnit), + WRAPM(Gui, getSelectedItem), + WRAPM(Gui, showAnnouncement), + WRAPM(Gui, showPopupAnnouncement), + { NULL, NULL } +}; + +static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } +static bool jobItemEqual(df::job_item *job1, df::job_item *job2) { return *job1 == *job2; } + +static const LuaWrapper::FunctionReg dfhack_job_module[] = { + WRAP(cloneJobStruct), + WRAP(printJobDetails), + WRAP(getJobHolder), + WRAPN(is_equal, jobEqual), + WRAPN(is_item_equal, jobItemEqual), + { NULL, NULL } +}; + lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) { if (!state) @@ -872,7 +917,8 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) OpenPersistent(state); - LuaWrapper::AddMethodWrapper(state, 0, -1, "getSelectedJob", df::wrap_function(&Gui::getSelectedJob)); + OpenModule(state, "gui", dfhack_gui_module); + OpenModule(state, "job", dfhack_job_module); lua_setglobal(state, "dfhack"); diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 2cc929ee9..4213499eb 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -30,6 +30,7 @@ distribution. #include "MemAccess.h" #include "Core.h" +#include "Error.h" #include "VersionInfo.h" #include "tinythread.h" // must be last due to MS stupidity @@ -1022,18 +1023,29 @@ static int meta_call_function(lua_State *state) auto id = (function_identity_base*)lua_touserdata(state, UPVAL_CONTAINER_ID); if (lua_gettop(state) != id->getNumArgs()) field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke"); - id->invoke(state, 1); + + try { + id->invoke(state, 1); + } + catch (Error::NullPointer &e) { + const char *vn = e.varname(); + std::string tmp = stl_sprintf("NULL pointer: %s", vn ? vn : "?"); + field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); + } + catch (std::exception &e) { + std::string tmp = stl_sprintf("C++ exception: %s", e.what()); + field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); + } + return 1; } /** * Create a closure invoking the given function, and add it to the field table. */ -void LuaWrapper::AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, - const char *name, function_identity_base *fun) +static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, + const char *name, function_identity_base *fun) { - field_idx = lua_absindex(state, field_idx); - lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN); if (meta_idx) lua_pushvalue(state, meta_idx); @@ -1046,6 +1058,17 @@ void LuaWrapper::AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, lua_setfield(state, field_idx, name); } +/** + * Wrap functions and add them to the table on the top of the stack. + */ +void LuaWrapper::SetFunctionWrappers(lua_State *state, const FunctionReg *reg) +{ + int base = lua_gettop(state); + + for (; reg && reg->name; ++reg) + AddMethodWrapper(state, 0, base, reg->name, reg->identity); +} + /** * Add fields in the array to the UPVAL_FIELDTABLE candidates on the stack. */ diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 9b26e2a61..b9ff35cfc 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -25,6 +25,7 @@ distribution. #include "Internal.h" #include "Export.h" #include "MiscUtils.h" +#include "Error.h" #ifndef LINUX_BUILD #include @@ -39,6 +40,10 @@ distribution. #include #include +const char *DFHack::Error::NullPointer::what() const throw() { + return "NULL pointer access"; +} + std::string stl_sprintf(const char *fmt, ...) { va_list lst; va_start(lst, fmt); diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 3529d8059..6cd7d42f4 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -50,6 +50,13 @@ namespace df { template::type,void>::value> struct function_wrapper {}; + class cur_lua_ostream_argument { + DFHack::color_ostream *out; + public: + cur_lua_ostream_argument(lua_State *state); + operator DFHack::color_ostream& () { return *out; } + }; + /* * Since templates can't match variable arg count, * a separate specialization is needed for every @@ -69,7 +76,7 @@ namespace df { type v##type; df::identity_traits::get()->lua_write(state, UPVAL_METHOD_NAME, &v##type, base++); #define OSTREAM_ARG DFHack::color_ostream& #define LOAD_OSTREAM(name) \ - DFHack::color_ostream_proxy name(DFHack::Core::getInstance().getConsole()); + cur_lua_ostream_argument name(state); #define INSTANTIATE_RETURN_TYPE(FArgs) \ template struct return_type { typedef RT type; }; \ diff --git a/library/include/Error.h b/library/include/Error.h index 159a84713..0fbc6ba78 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -39,6 +39,18 @@ namespace DFHack * the whole array of DFHack exceptions from the rest */ class DFHACK_EXPORT All : public std::exception{}; + + class DFHACK_EXPORT NullPointer : public All { + const char *varname_; + public: + NullPointer(const char *varname_ = NULL) : varname_(varname_) {} + const char *varname() const { return varname_; } + virtual const char *what() const throw(); + }; + +#define CHECK_NULL_POINTER(var) \ + { if (var == NULL) throw DFHack::Error::NullPointer(#var); } + class DFHACK_EXPORT AllSymbols : public All{}; // Syntax errors and whatnot, the xml can't be read class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 66e77bb1f..479ca0d67 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -220,11 +220,16 @@ namespace DFHack { namespace LuaWrapper { * and the enum itself to the _enum metafield. Pushes the key table on the stack. */ void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); + + struct FunctionReg { + const char *name; + function_identity_base *identity; + }; + /** - * Create a closure invoking the given function, and add it to the field table. + * Wrap functions and add them to the table on the top of the stack. */ - void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, - const char *name, function_identity_base *fun); + void SetFunctionWrappers(lua_State *state, const FunctionReg *reg); void IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 67652ff15..1101367fc 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -1,12 +1,36 @@ -- Common startup file for all dfhack plugins with lua support -- The global dfhack table is already created by C++ init code. +-- Console color constants + +COLOR_RESET = -1 +COLOR_BLACK = 0 +COLOR_BLUE = 1 +COLOR_GREEN = 2 +COLOR_CYAN = 3 +COLOR_RED = 4 +COLOR_MAGENTA = 5 +COLOR_BROWN = 6 +COLOR_GREY = 7 +COLOR_DARKGREY = 8 +COLOR_LIGHTBLUE = 9 +COLOR_LIGHTGREEN = 10 +COLOR_LIGHTCYAN = 11 +COLOR_LIGHTRED = 12 +COLOR_LIGHTMAGENTA = 13 +COLOR_YELLOW = 14 +COLOR_WHITE = 15 + +-- Error handling + safecall = dfhack.safecall function dfhack.pcall(f, ...) return xpcall(f, dfhack.onerror, ...) end +-- Module loading + function mkmodule(module,env) local pkg = package.loaded[module] if pkg == nil then @@ -31,6 +55,8 @@ function reload(module) dofile(path) end +-- Misc functions + function printall(table) if table == nil then return end for k,v in pairs(table) do diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 2c9a5d1d0..679ecc0e7 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -32,6 +32,7 @@ distribution. using namespace std; #include "Core.h" +#include "Error.h" #include "PluginManager.h" #include "MiscUtils.h" @@ -54,6 +55,8 @@ using namespace df::enums; df::job *DFHack::cloneJobStruct(df::job *job) { + CHECK_NULL_POINTER(job); + df::job *pnew = new df::job(*job); // Clean out transient fields @@ -105,6 +108,9 @@ void DFHack::deleteJobStruct(df::job *job) bool DFHack::operator== (const df::job_item &a, const df::job_item &b) { + CHECK_NULL_POINTER(&a); + CHECK_NULL_POINTER(&b); + if (!(CMP(item_type) && CMP(item_subtype) && CMP(mat_type) && CMP(mat_index) && CMP(flags1.whole) && CMP(quantity) && CMP(vector_id) && @@ -125,6 +131,9 @@ bool DFHack::operator== (const df::job_item &a, const df::job_item &b) bool DFHack::operator== (const df::job &a, const df::job &b) { + CHECK_NULL_POINTER(&a); + CHECK_NULL_POINTER(&b); + if (!(CMP(job_type) && CMP(unk2) && CMP(mat_type) && CMP(mat_index) && CMP(item_subtype) && CMP(item_category.whole) && @@ -141,6 +150,8 @@ bool DFHack::operator== (const df::job &a, const df::job &b) static void print_job_item_details(color_ostream &out, df::job *job, unsigned idx, df::job_item *item) { + CHECK_NULL_POINTER(item); + ItemTypeInfo info(item); out << " Input Item " << (idx+1) << ": " << info.toString(); @@ -175,6 +186,8 @@ static void print_job_item_details(color_ostream &out, df::job *job, unsigned id void DFHack::printJobDetails(color_ostream &out, df::job *job) { + CHECK_NULL_POINTER(job); + out.color(job->flags.bits.suspend ? Console::COLOR_DARKGREY : Console::COLOR_GREY); out << "Job " << job->id << ": " << ENUM_KEY_STR(job_type,job->job_type); if (job->flags.whole) @@ -216,6 +229,8 @@ void DFHack::printJobDetails(color_ostream &out, df::job *job) df::building *DFHack::getJobHolder(df::job *job) { + CHECK_NULL_POINTER(job); + for (size_t i = 0; i < job->references.size(); i++) { VIRTUAL_CAST_VAR(ref, df::general_ref_building_holderst, job->references[i]); From 577e333ac9e277da88ca28b8cdecdfeeacd77978 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Thu, 5 Apr 2012 20:32:44 +0200 Subject: [PATCH 009/108] some cleanup in zone tool, added slaughter option, autonestbox is now an own command which can be set to run every X ticks --- README.rst | 11 +- plugins/zone.cpp | 421 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 327 insertions(+), 105 deletions(-) diff --git a/README.rst b/README.rst index 4ede446bb..60cdc330f 100644 --- a/README.rst +++ b/README.rst @@ -808,7 +808,6 @@ Options: :set: Set zone under cursor as default for future assigns. :assign: Assign unit(s) to the pen or pit marked with the 'set' command. If no filters are set a unit must be selected in the in-game ui. Can also be followed by a valid zone id which will be set instead. :unassign: Unassign selected creature from it's zone. -:autonestbox: Assign all (unless count is specified) unpastured female egg-layers to empty pens which contain a nestbox. If the pen is bigger than 1x1 the nestbox must be placed at the top left corner to be recognized. Only 1 unit will be assigned per pen. :uinfo: Print info about unit(s). If no filters are set a unit must be selected in the in-game ui. :zinfo: Print info about zone(s). If no filters are set zones under the cursor are listed. :verbose: Print some more info. @@ -853,3 +852,13 @@ All filters can be used together with the 'assign' command. The only restriction Assign up to 5 own female milkable creatures to the selected pasture. ``zone assign all own race DWARF maxage 2`` Throw all useless kids into a pit :) + +autonestbox +=========== +Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless of the size. The age of the units is currently not checked, most birds grow up quite fast. When called without options autonestbox will instantly run once. + +Options: +-------- +:start: Start running every X frames (df simulation ticks). Default: X=6000, which would be every 60 seconds at 100fps. +:stop: Stop running automatically. +:sleep: Must be followed by number X. Changes the timer to sleep X frames between runs. diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 6b9204b5e..9aa8547c9 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -65,9 +65,9 @@ using df::global::ui; using namespace DFHack::Gui; -struct genref : df::general_ref_building_civzone_assignedst {}; - command_result df_zone (color_ostream &out, vector & parameters); +command_result df_autonestbox (color_ostream &out, vector & parameters); +//command_result df_autoslaughter (color_ostream &out, vector & parameters); DFHACK_PLUGIN("zone"); @@ -78,10 +78,10 @@ const string zone_help = " assign - assign creature(s) to a pen or pit\n" " if no filters are used, a single unit must be selected.\n" " can be followed by valid zone id which will then be set.\n" + " slaughter - mark creature(s) for slaughter\n" + " if no filters are used, a single unit must be selected.\n" + " with filters named units are ignored unless specified.\n" " unassign - unassign selected creature from it's zone\n" - " autonestbox - assign unpastured female egg-layers to nestbox zones\n" - " requires you to create 1-tile pastures above nestboxes\n" - " filters (except count) will be ignored currently\n" " uinfo - print info about selected unit\n" " zinfo - print info about zone(s) under cursor\n" " verbose - print some more info, mostly useless debug stuff\n" @@ -103,6 +103,8 @@ const string zone_help_filters = " own - from own civilization (fortress)\n" " war - trained war creature\n" " tamed - tamed\n" + " named - has name or nickname\n" + " can be used to mark named units for slaughter\n" " trained - obvious\n" " untrained - obvious\n" " male - obvious\n" @@ -137,6 +139,20 @@ const string zone_help_examples = " unless you have a mod with egg-laying male elves who give milk.\n"; +const string autonestbox_help = + "Assigns unpastured female egg-layers to nestbox zones.\n" + "Requires that you create pen/pasture zones above nestboxes.\n" + "If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n" + "Only 1 unit will be assigned per pen, regardless of the size.\n" + "The age of the units is currently not checked, most birds grow up quite fast.\n" + "When called without options autonestbox will instantly run once.\n" + "Options:\n" + " start - run every X frames (df simulation ticks)\n" + " default: X=6000 (~60 seconds at 100fps)\n" + " stop - stop running automatically\n" + " sleep X - change timer to sleep X frames between runs.\n"; + + DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( @@ -144,6 +160,16 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector = sleep_autonestbox) + { + ticks_autonestbox = 0; + autoNestbox(out, false); + } + } + + if(enable_autoslaughter) + { + if(++ticks_autoslaughter >= sleep_autoslaughter) + { + ticks_autoslaughter = 0; + autoSlaughter(out, false); + } + } + + return CR_OK; +} + /////////////// // Various small tool functions @@ -164,6 +247,7 @@ bool isTrained(df::unit* creature); bool isWar(df::unit* creature); bool isHunter(df::unit* creature); bool isOwnCiv(df::unit* creature); + bool isActivityZone(df::building * building); bool isPenPasture(df::building * building); bool isPit(df::building * building); @@ -177,10 +261,10 @@ int32_t findChainAtCursor(); df::general_ref_building_civzone_assignedst * createCivzoneRef(); bool unassignUnitFromZone(df::unit* unit); command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose); -void unitInfo(color_ostream & out, df::unit* creature, bool list_refs); -void zoneInfo(color_ostream & out, df::building* building, bool list_refs); -void cageInfo(color_ostream & out, df::building* building, bool list_refs); -void chainInfo(color_ostream & out, df::building* building, bool list_refs); +void unitInfo(color_ostream & out, df::unit* creature, bool verbose); +void zoneInfo(color_ostream & out, df::building* building, bool verbose); +void cageInfo(color_ostream & out, df::building* building, bool verbose); +void chainInfo(color_ostream & out, df::building* building, bool verbose); bool isBuiltCageAtPos(df::coord pos); int32_t getUnitAge(df::unit* unit) @@ -195,23 +279,14 @@ int32_t getUnitAge(df::unit* unit) bool isDead(df::unit* unit) { - if(unit->flags1.bits.dead) - return true; - else - return false; + return unit->flags1.bits.dead; } - -// marked for slaughter? bool isMarkedForSlaughter(df::unit* unit) { - if(unit->flags2.bits.slaughter) - return true; - else - return false; + return unit->flags2.bits.slaughter; } -// mark for slaughter void doMarkForSlaughter(df::unit* unit) { unit->flags2.bits.slaughter = 1; @@ -293,20 +368,14 @@ bool isHunter(df::unit* creature) // (don't try to pasture/slaughter random untame animals) bool isOwnCiv(df::unit* creature) { - if(creature->civ_id == ui->civ_id) - return true; - else - return false; + return creature->civ_id == ui->civ_id; } // check if creature belongs to the player's race // (in combination with check for civ helps to filter out own dwarves) bool isOwnRace(df::unit* creature) { - if(creature->race == ui->race_id) - return true; - else - return false; + return creature->race == ui->race_id; } string getRaceName(df::unit* unit) @@ -314,21 +383,30 @@ string getRaceName(df::unit* unit) df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; return raw->creature_id; } +string getRaceBabyName(df::unit* unit) +{ + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + return raw->general_baby_name[0]; +} +string getRaceChildName(df::unit* unit) +{ + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + return raw->general_child_name[0]; +} bool isBaby(df::unit* unit) { - if(unit->profession != df::profession::BABY) - return true; - else - return false; + return unit->profession == df::profession::BABY; } bool isChild(df::unit* unit) { - if(unit->profession != df::profession::CHILD) - return true; - else - return false; + return unit->profession == df::profession::CHILD; +} + +bool isAdult(df::unit* unit) +{ + return !isBaby(unit) && !isChild(unit); } bool isEggLayer(df::unit* unit) @@ -373,17 +451,12 @@ bool isMilkable(df::unit* unit) bool isMale(df::unit* unit) { - if(unit->sex==1) - return true; - else - return false; + return unit->sex == 1; } + bool isFemale(df::unit* unit) { - if(unit->sex==0) - return true; - else - return false; + return unit->sex == 0; } // dump some unit info @@ -406,6 +479,19 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << " '" << unit->name.nickname << "'"; out << ", "; } + + if(isAdult(unit)) + out << "adult"; + else if(isBaby(unit)) + out << "baby"; + else if(isChild(unit)) + out << "child"; + out << " "; + // sometimes empty even in vanilla RAWS, sometimes contains full race name (e.g. baby alpaca) + // all animals I looked at don't have babies anyways, their offspring starts as CHILD + //out << getRaceBabyName(unit); + //out << getRaceChildName(unit); + out << getRaceName(unit) << " ("; switch(unit->sex) { @@ -516,18 +602,12 @@ bool isPit(df::building * building) bool isCage(df::building * building) { - if(building->getType() == building_type::Cage) - return true; - else - return false; + return building->getType() == building_type::Cage; } bool isChain(df::building * building) { - if(building->getType() == building_type::Chain) - return true; - else - return false; + return building->getType() == building_type::Chain; } bool isActive(df::building * building) @@ -695,7 +775,6 @@ bool isAssigned(df::unit* unit) return assigned; } - // check if assigned to a chain or built cage // (need to check if the ref needs to be removed, until then touching them is forbidden) bool isChained(df::unit* unit) @@ -714,8 +793,6 @@ bool isChained(df::unit* unit) return contained; } - - // check if contained in item (e.g. animals in cages) bool isContainedInItem(df::unit* unit) { @@ -787,14 +864,12 @@ bool isEmptyPasture(df::building* building) df::building* findFreeNestboxZone() { df::building * free_building = NULL; - - //df::unit * free_unit = findFreeEgglayer(); - bool cage = false; for (size_t b=0; b < world->buildings.all.size(); b++) { df::building* building = world->buildings.all[b]; if( isEmptyPasture(building) && + isActive(building) && isNestboxAtPos(building->x1, building->y1, building->z)) { free_building = building; @@ -804,18 +879,27 @@ df::building* findFreeNestboxZone() return free_building; } +bool isFreeEgglayer(df::unit * unit) +{ + if( !isDead(unit) + && isFemale(unit) + && isTame(unit) + && isOwnCiv(unit) + && isEggLayer(unit) + && !isAssigned(unit) + ) + return true; + else + return false; +} + df::unit * findFreeEgglayer() { df::unit* free_unit = NULL; for (size_t i=0; i < world->units.all.size(); i++) { df::unit* unit = world->units.all[i]; - if( isFemale(unit) - && isTame(unit) - && isOwnCiv(unit) - && isEggLayer(unit) - && !isAssigned(unit) - ) + if(isFreeEgglayer(unit)) { free_unit = unit; break; @@ -824,6 +908,17 @@ df::unit * findFreeEgglayer() return free_unit; } +size_t countFreeEgglayers() +{ + size_t count = 0; + for (size_t i=0; i < world->units.all.size(); i++) + { + df::unit* unit = world->units.all[i]; + if(isFreeEgglayer(unit)) + count ++; + } + return count; +} // check if unit is already assigned to a zone, remove that ref from unit and old zone // returns false if no pasture information was found @@ -1094,6 +1189,7 @@ command_result df_zone (color_ostream &out, vector & parameters) bool find_egglayer = false; bool find_grazer = false; bool find_milkable = false; + bool find_named = false; bool find_agemin = false; bool find_agemax = false; @@ -1111,7 +1207,6 @@ command_result df_zone (color_ostream &out, vector & parameters) bool zone_set = false; bool verbose = false; bool all = false; - bool auto_nestbox = false; bool unit_slaughter = false; static int target_zone = -1; @@ -1231,10 +1326,10 @@ command_result df_zone (color_ostream &out, vector & parameters) out << "Filter by 'tame'." << endl; find_tame = true; } - else if(p == "autonestbox") + else if(p == "named") { - out << "Auto-assign female tame owned egg-layers to free nestboxes." << endl; - auto_nestbox = true; + out << "Filter by 'has name or nickname'." << endl; + find_named = true; } else if(p == "slaughter") { @@ -1348,6 +1443,12 @@ command_result df_zone (color_ostream &out, vector & parameters) } } + if (!Maps::IsValid()) + { + out.printerr("Map is not available!\n"); + return CR_FAILURE; + } + if((zone_info && !all) || zone_set) need_cursor = true; @@ -1365,7 +1466,7 @@ command_result df_zone (color_ostream &out, vector & parameters) } // try to cope with user dumbness - if(target_agemin > target_agemax || target_agemax < target_agemin) + if(target_agemin > target_agemax) { out << "Invalid values for minage/maxage specified! I'll swap them." << endl; int oldmin = target_agemin; @@ -1373,41 +1474,6 @@ command_result df_zone (color_ostream &out, vector & parameters) target_agemax = oldmin; } - // auto-assign to empty nestboxes. this requires an empty 1x1 pen/pasture zone placed over a nestbox - // currently it will not be checked if the nestbox is already claimed by another egglayer - if(auto_nestbox) - { - bool stop = false; - size_t processed = 0; - do - { - df::building * free_building = findFreeNestboxZone(); - df::unit * free_unit = findFreeEgglayer(); - if(free_building && free_unit) - { - command_result result = assignUnitToBuilding(out, free_unit, free_building, verbose); - if(result != CR_OK) - return result; - processed ++; - if(find_count && processed >= target_count) - stop = true; - } - else - { - stop = true; - if(free_unit && !free_building) - { - out << "Not enough free nestbox zones found!" << endl; - out << "You can check how many more you need with:" << endl; - out << "'zone uinfo all unassigned own female egglayer'" << endl; - out << "Or simply build some more and use 'zone autonestbox' again." << endl; - } - } - } while (!stop); - out << processed << " units assigned to their new nestboxes." << endl; - return CR_OK; - } - // give info on zone(s), chain or cage under cursor // (doesn't use the findXyzAtCursor() methods because zones might overlap and contain a cage or chain) if(zone_info) // || chain_info || cage_info) @@ -1493,6 +1559,7 @@ command_result df_zone (color_ostream &out, vector & parameters) || (find_milkable && !isMilkable(unit)) || (find_male && !isMale(unit)) || (find_female && !isFemale(unit)) + || (find_named && !unit->name.has_name) ) continue; @@ -1510,6 +1577,9 @@ command_result df_zone (color_ostream &out, vector & parameters) } else if(unit_slaughter) { + // don't slaughter named creatures unless told to do so + if(!find_named && unit->name.has_name) + continue; doMarkForSlaughter(unit); } @@ -1540,6 +1610,13 @@ command_result df_zone (color_ostream &out, vector & parameters) } else if(unit_slaughter) { + // by default behave like the game: only allow slaughtering of named war/hunting pets + //if(unit->name.has_name && !find_named && !(isWar(unit)||isHunter(unit))) + //{ + // out << "Slaughter of named unit denied. Use the filter 'named' to override this check." << endl; + // return CR_OK; + //} + doMarkForSlaughter(unit); return CR_OK; } @@ -1571,3 +1648,139 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } + +command_result df_autonestbox(color_ostream &out, vector & parameters) +{ + CoreSuspender suspend; + + bool verbose = false; + + for (size_t i = 0; i < parameters.size(); i++) + { + string & p = parameters[i]; + + if (p == "help" || p == "?") + { + out << autonestbox_help << endl; + return CR_OK; + } + if (p == "start") + { + enable_autonestbox = true; + autonestbox_did_complain = false; + out << "Autonestbox started."; + return autoNestbox(out, verbose); + //return CR_OK; + } + if (p == "stop") + { + enable_autonestbox = false; + out << "Autonestbox stopped."; + return CR_OK; + } + else if(p == "verbose") + { + verbose = true; + } + else if(p == "sleep") + { + if(i == parameters.size()-1) + { + out.printerr("No duration specified!"); + return CR_WRONG_USAGE; + } + else + { + size_t ticks = 0; + stringstream ss(parameters[i+1]); + i++; + ss >> ticks; + if(ticks <= 0) + { + out.printerr("Invalid duration specified (must be > 0)!"); + return CR_WRONG_USAGE; + } + sleep_autonestbox = ticks; + out << "New sleep timer for autonestbox: " << ticks << " ticks." << endl; + return CR_OK; + } + } + else + { + out << "Unknown command: " << p << endl; + return CR_WRONG_USAGE; + } + } + return autoNestbox(out, verbose); + //return CR_OK; +} + +command_result autoNestbox( color_ostream &out, bool verbose = false ) +{ + bool stop = false; + size_t processed = 0; + + if (!Maps::IsValid()) + { + out.printerr("Map is not available!\n"); + enable_autonestbox = false; + return CR_FAILURE; + } + + do + { + df::building * free_building = findFreeNestboxZone(); + df::unit * free_unit = findFreeEgglayer(); + if(free_building && free_unit) + { + command_result result = assignUnitToBuilding(out, free_unit, free_building, verbose); + if(result != CR_OK) + return result; + processed ++; + //if(find_count && processed >= target_count) + // stop = true; + } + else + { + stop = true; + if(free_unit && !free_building) + { + static size_t old_count = 0; + size_t freeEgglayers = countFreeEgglayers(); + // avoid spamming the same message + if(old_count != freeEgglayers) + autonestbox_did_complain = false; + old_count = freeEgglayers; + if(!autonestbox_did_complain) + { + stringstream ss; + ss << freeEgglayers; + string announce = "Not enough free nestbox zones found! You need " + ss.str() + " more."; + Gui::showAnnouncement(announce, 6, true); + out << announce << endl; + autonestbox_did_complain = true; + } + } + } + } while (!stop); + if(processed > 0) + { + stringstream ss; + ss << processed; + string announce; + announce = ss.str() + " nestboxes were assigned."; + Gui::showAnnouncement(announce, 2, false); + out << announce << endl; + // can complain again + // (might lead to spamming the same message twice, but catches the case + // where for example 2 new egglayers hatched right after 2 zones were created and assigned) + autonestbox_did_complain = false; + } + return CR_OK; +} + +command_result autoSlaughter( color_ostream &out, bool verbose = false ) +{ + out << "Autoslaughter would run now." << endl; + return CR_OK; +} \ No newline at end of file From 284ada943286c211b0466bc4e40b5dccba9de4ed Mon Sep 17 00:00:00 2001 From: RossM Date: Thu, 5 Apr 2012 20:34:06 -0700 Subject: [PATCH 010/108] Better config persistence. --- plugins/autolabor.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index bcf725559..cfd9419db 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -348,17 +348,14 @@ struct labor_info bool is_exclusive; int active_dwarfs; - labor_mode mode() { return (labor_mode) config.ival(1); } - void set_mode(labor_mode mode) { config.ival(1) = mode; } + labor_mode mode() { return (labor_mode) config.ival(0); } + void set_mode(labor_mode mode) { config.ival(0) = mode; } - int minimum_dwarfs() { return config.ival(2); } - void set_minimum_dwarfs(int minimum_dwarfs) { config.ival(2) = minimum_dwarfs; } + int minimum_dwarfs() { return config.ival(1); } + void set_minimum_dwarfs(int minimum_dwarfs) { config.ival(1) = minimum_dwarfs; } - int maximum_dwarfs() { return config.ival(3); } - void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(3) = maximum_dwarfs; } - - df::enums::unit_labor::unit_labor labor() { return (df::enums::unit_labor::unit_labor) config.ival(0); } - void set_labor(df::enums::unit_labor::unit_labor labor) { config.ival(0) = labor; } + int maximum_dwarfs() { return config.ival(2); } + void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; } }; @@ -492,7 +489,6 @@ static void reset_labor(df::enums::unit_labor::unit_labor labor) labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs); labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); labor_infos[labor].set_mode(default_labor_infos[labor].mode); - labor_infos[labor].set_labor(labor); } static void init_state() @@ -512,11 +508,12 @@ static void init_state() labor_infos.resize(ARRAY_COUNT(default_labor_infos)); std::vector items; - pworld->GetPersistentData(&items, "autolabor/labors"); + pworld->GetPersistentData(&items, "autolabor/labors/", true); for (auto p = items.begin(); p != items.end(); p++) { - df::enums::unit_labor::unit_labor labor = (df::enums::unit_labor::unit_labor) p->ival(0); + string key = p->key(); + df::enums::unit_labor::unit_labor labor = (df::enums::unit_labor::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); if (labor >= 0 && labor <= labor_infos.size()) { labor_infos[labor].config = *p; @@ -530,7 +527,10 @@ static void init_state() if (labor_infos[i].config.isValid()) continue; - labor_infos[i].config = pworld->AddPersistentData("autolabor/labors"); + std::stringstream name; + name << "autolabor/labors/" << i; + + labor_infos[i].config = pworld->AddPersistentData(name.str()); labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive; labor_infos[i].active_dwarfs = 0; From 903e9ee716e4f84f692eadcc60190223ace250d8 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 6 Apr 2012 11:21:28 +0400 Subject: [PATCH 011/108] Export a few more functions to lua. --- LUA_API.rst | 23 ++++++++++++++++++++++ Lua API.html | 23 ++++++++++++++++++++++ library/LuaTools.cpp | 35 ++++++++++++++++++++++++++++++--- library/RemoteTools.cpp | 2 +- library/include/modules/Units.h | 2 +- library/lua/dfhack.lua | 7 ++++--- library/modules/Translation.cpp | 3 +++ library/modules/Units.cpp | 27 ++++++++++++++----------- 8 files changed, 103 insertions(+), 19 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index dab299741..422ba6b49 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -503,6 +503,10 @@ C++ function wrappers Thin wrappers around C++ functions, similar to the ones for virtual methods. +* ``dfhack.TranslateName(name,in_english,only_last_name)`` + + Convert a language_name or only the last name part to string. + Gui module ---------- @@ -558,3 +562,22 @@ Job module * ``dfhack.job.is_item_equal(job_item1,job_item2)`` Compares important fields in the job item structures. + +Units module +------------ + +* ``dfhack.units.getVisibleName(unit)`` + + Returns the name visible in game, accounting for false identities. + +* ``dfhack.units.isDead(unit)`` + + The unit is completely dead and passive. + +* ``dfhack.units.isAlive(unit)`` + + The unit isn't dead or undead. + +* ``dfhack.units.isSane(unit)`` + + The unit is capable of rational action, i.e. not dead, insane or zombie. diff --git a/Lua API.html b/Lua API.html index 877bdbd88..ab64eea9a 100644 --- a/Lua API.html +++ b/Lua API.html @@ -338,6 +338,7 @@ ul.auto-toc {
  • C++ function wrappers
  • @@ -761,6 +762,11 @@ However, currently every entry has a 180+-byte dead-weight overhead.

    C++ function wrappers

    Thin wrappers around C++ functions, similar to the ones for virtual methods.

    +
      +
    • dfhack.TranslateName(name,in_english,only_last_name)

      +

      Convert a language_name or only the last name part to string.

      +
    • +

    Gui module

      @@ -810,6 +816,23 @@ The is_bright boolean actually seems to invert the brightness.

    +
    +

    Units module

    +
      +
    • dfhack.units.getVisibleName(unit)

      +

      Returns the name visible in game, accounting for false identities.

      +
    • +
    • dfhack.units.isDead(unit)

      +

      The unit is completely dead and passive.

      +
    • +
    • dfhack.units.isAlive(unit)

      +

      The unit isn't dead or undead.

      +
    • +
    • dfhack.units.isSane(unit)

      +

      The unit is capable of rational action, i.e. not dead, insane or zombie.

      +
    • +
    +
    diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 5afd1156b..5a87c931d 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -40,6 +40,8 @@ distribution. #include "modules/World.h" #include "modules/Gui.h" #include "modules/Job.h" +#include "modules/Translation.h" +#include "modules/Units.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -641,9 +643,9 @@ static const luaL_Reg dfhack_funcs[] = { { NULL, NULL } }; -/* - * Per-world persistent configuration storage. - */ +/************************************************** + * Per-world persistent configuration storage API * + **************************************************/ static PersistentDataItem persistent_by_struct(lua_State *state, int idx) { @@ -783,6 +785,7 @@ static int dfhack_persistent_save(lua_State *state) lua_settop(state, 1); + // Retrieve existing or create a new entry PersistentDataItem ref; bool added = false; @@ -804,6 +807,7 @@ static int dfhack_persistent_save(lua_State *state) ref = Core::getInstance().getWorld()->GetPersistentData(str); } + // Auto-add if not found if (!ref.isValid()) { ref = Core::getInstance().getWorld()->AddPersistentData(str); @@ -812,6 +816,7 @@ static int dfhack_persistent_save(lua_State *state) added = true; } + // Copy data from lua to C++ memory lua_getfield(state, 1, "value"); if (const char *str = lua_tostring(state, -1)) ref.val() = str; @@ -830,6 +835,7 @@ static int dfhack_persistent_save(lua_State *state) } lua_pop(state, 1); + // Reinitialize lua from C++ and return read_persistent(state, ref, false); lua_pushboolean(state, added); return 2; @@ -856,6 +862,10 @@ static void OpenPersistent(lua_State *state) lua_pop(state, 1); } +/************************ + * Wrappers for C++ API * + ************************/ + static void OpenModule(lua_State *state, const char *mname, const LuaWrapper::FunctionReg *reg) { luaL_getsubtable(state, lua_gettop(state), mname); @@ -867,6 +877,11 @@ static void OpenModule(lua_State *state, const char *mname, const LuaWrapper::Fu #define WRAP(function) { #function, df::wrap_function(&function) } #define WRAPN(name, function) { #name, df::wrap_function(&function) } +static const LuaWrapper::FunctionReg dfhack_module[] = { + WRAPM(Translation, TranslateName), + { NULL, NULL } +}; + static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getSelectedWorkshopJob), WRAPM(Gui, getSelectedJob), @@ -889,6 +904,18 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { { NULL, NULL } }; +static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, getVisibleName), + WRAPM(Units, isDead), + WRAPM(Units, isAlive), + WRAPM(Units, isSane), + { NULL, NULL } +}; + +/************************ + * Main Open function * + ************************/ + lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) { if (!state) @@ -917,8 +944,10 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) OpenPersistent(state); + LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "job", dfhack_job_module); + OpenModule(state, "units", dfhack_units_module); lua_setglobal(state, "dfhack"); diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 01c110643..00344d6a2 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -256,7 +256,7 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, info->set_pos_y(unit->pos.y); info->set_pos_z(unit->pos.z); - auto name = Units::GetVisibleName(unit); + auto name = Units::getVisibleName(unit); if (name->has_name) describeName(info->mutable_name(), name); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 0fd75bf1f..b966a39be 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -193,7 +193,7 @@ DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); DFHACK_EXPORT bool RemoveOwnedItemByIdx(const uint32_t index, int32_t id); DFHACK_EXPORT bool RemoveOwnedItemByPtr(df::unit * unit, int32_t id); -DFHACK_EXPORT df::language_name *GetVisibleName(df::unit *unit); +DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 1101367fc..9e2cdf257 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -58,9 +58,10 @@ end -- Misc functions function printall(table) - if table == nil then return end - for k,v in pairs(table) do - print(k," = "..tostring(v)) + if type(table) == 'table' or df.isvalid(table) == 'ref' then + for k,v in pairs(table) do + print(string.format("%-23s\t = %s",tostring(k),tostring(v))) + end end end diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index 7b5fa654c..bc0724707 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -35,6 +35,7 @@ using namespace std; #include "Types.h" #include "ModuleFactory.h" #include "Core.h" +#include "Error.h" using namespace DFHack; using namespace df::enums; @@ -93,6 +94,8 @@ void addNameWord (string &out, const string &word) string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart) { + CHECK_NULL_POINTER(name); + string out; string word; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index f6259bd3b..3dc5d1b04 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -527,8 +527,10 @@ void Units::CopyNameTo(df::unit * creature, df::language_name * target) Translation::copyName(&creature->name, target); } -df::language_name *Units::GetVisibleName(df::unit *unit) +df::language_name *Units::getVisibleName(df::unit *unit) { + CHECK_NULL_POINTER(unit); + df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); if (figure) @@ -555,11 +557,15 @@ df::language_name *Units::GetVisibleName(df::unit *unit) bool DFHack::Units::isDead(df::unit *unit) { + CHECK_NULL_POINTER(unit); + return unit->flags1.bits.dead; } bool DFHack::Units::isAlive(df::unit *unit) { + CHECK_NULL_POINTER(unit); + return !unit->flags1.bits.dead && !unit->flags3.bits.ghostly && !unit->curse.add_tags1.bits.NOT_LIVING; @@ -567,23 +573,22 @@ bool DFHack::Units::isAlive(df::unit *unit) bool DFHack::Units::isSane(df::unit *unit) { + CHECK_NULL_POINTER(unit); + if (unit->flags1.bits.dead || unit->flags3.bits.ghostly || unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || unit->curse.add_tags1.bits.CRAZED) return false; - if (unit->flags1.bits.has_mood) + switch (unit->mood) { - switch (unit->mood) - { - case mood_type::Melancholy: - case mood_type::Raving: - case mood_type::Berserk: - return false; - default: - break; - } + case mood_type::Melancholy: + case mood_type::Raving: + case mood_type::Berserk: + return false; + default: + break; } return true; From 2640addf49d48092104b1ba14518895a5765359d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 6 Apr 2012 18:00:54 +0400 Subject: [PATCH 012/108] Split LuaTools.cpp to separate core utils from general dfhack api. --- library/CMakeLists.txt | 1 + library/LuaApi.cpp | 345 +++++++++++++++++++++++++++++++++++++++++ library/LuaTools.cpp | 278 +-------------------------------- 3 files changed, 349 insertions(+), 275 deletions(-) create mode 100644 library/LuaApi.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index d0410850c..b4ea751e7 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -59,6 +59,7 @@ DataDefs.cpp LuaWrapper.cpp LuaTypes.cpp LuaTools.cpp +LuaApi.cpp DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp new file mode 100644 index 000000000..45680f8ef --- /dev/null +++ b/library/LuaApi.cpp @@ -0,0 +1,345 @@ +/* +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 "DataFuncs.h" + +#include "modules/World.h" +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/Translation.h" +#include "modules/Units.h" + +#include "LuaWrapper.h" +#include "LuaTools.h" + +#include "MiscUtils.h" + +#include "df/job.h" +#include "df/job_item.h" +#include "df/building.h" +#include "df/unit.h" +#include "df/item.h" + +#include +#include +#include + +using namespace DFHack; +using namespace DFHack::LuaWrapper; + +/************************************************** + * Per-world persistent configuration storage API * + **************************************************/ + +static PersistentDataItem persistent_by_struct(lua_State *state, int idx) +{ + lua_getfield(state, idx, "entry_id"); + int id = lua_tointeger(state, -1); + lua_pop(state, 1); + + PersistentDataItem ref = Core::getInstance().getWorld()->GetPersistentData(id); + + if (ref.isValid()) + { + lua_getfield(state, idx, "key"); + const char *str = lua_tostring(state, -1); + if (!str || str != ref.key()) + luaL_argerror(state, idx, "inconsistent id and key"); + lua_pop(state, 1); + } + + return ref; +} + +static int read_persistent(lua_State *state, PersistentDataItem ref, bool create) +{ + if (!ref.isValid()) + { + lua_pushnil(state); + lua_pushstring(state, "entry not found"); + return 2; + } + + if (create) + lua_createtable(state, 0, 4); + + lua_pushvalue(state, lua_upvalueindex(1)); + lua_setmetatable(state, -2); + + lua_pushinteger(state, ref.entry_id()); + lua_setfield(state, -2, "entry_id"); + lua_pushstring(state, ref.key().c_str()); + lua_setfield(state, -2, "key"); + lua_pushstring(state, ref.val().c_str()); + lua_setfield(state, -2, "value"); + + lua_createtable(state, PersistentDataItem::NumInts, 0); + for (int i = 0; i < PersistentDataItem::NumInts; i++) + { + lua_pushinteger(state, ref.ival(i)); + lua_rawseti(state, -2, i+1); + } + lua_setfield(state, -2, "ints"); + + return 1; +} + +static PersistentDataItem get_persistent(lua_State *state) +{ + luaL_checkany(state, 1); + + if (lua_istable(state, 1)) + { + if (!lua_getmetatable(state, 1) || + !lua_rawequal(state, -1, lua_upvalueindex(1))) + luaL_argerror(state, 1, "invalid table type"); + + lua_settop(state, 1); + + return persistent_by_struct(state, 1); + } + else + { + const char *str = luaL_checkstring(state, 1); + + return Core::getInstance().getWorld()->GetPersistentData(str); + } +} + +static int dfhack_persistent_get(lua_State *state) +{ + CoreSuspender suspend; + + auto ref = get_persistent(state); + + return read_persistent(state, ref, !lua_istable(state, 1)); +} + +static int dfhack_persistent_delete(lua_State *state) +{ + CoreSuspender suspend; + + auto ref = get_persistent(state); + + bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); + + lua_pushboolean(state, ok); + return 1; +} + +static int dfhack_persistent_get_all(lua_State *state) +{ + CoreSuspender suspend; + + const char *str = luaL_checkstring(state, 1); + bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); + + std::vector data; + Core::getInstance().getWorld()->GetPersistentData(&data, str, prefix); + + if (data.empty()) + { + lua_pushnil(state); + } + else + { + lua_createtable(state, data.size(), 0); + for (size_t i = 0; i < data.size(); ++i) + { + read_persistent(state, data[i], true); + lua_rawseti(state, -2, i+1); + } + } + + return 1; +} + +static int dfhack_persistent_save(lua_State *state) +{ + CoreSuspender suspend; + + lua_settop(state, 2); + luaL_checktype(state, 1, LUA_TTABLE); + bool add = lua_toboolean(state, 2); + + lua_getfield(state, 1, "key"); + const char *str = lua_tostring(state, -1); + if (!str) + luaL_argerror(state, 1, "no key field"); + + lua_settop(state, 1); + + // Retrieve existing or create a new entry + PersistentDataItem ref; + bool added = false; + + if (add) + { + ref = Core::getInstance().getWorld()->AddPersistentData(str); + added = true; + } + else if (lua_getmetatable(state, 1)) + { + if (!lua_rawequal(state, -1, lua_upvalueindex(1))) + return luaL_argerror(state, 1, "invalid table type"); + lua_pop(state, 1); + + ref = persistent_by_struct(state, 1); + } + else + { + ref = Core::getInstance().getWorld()->GetPersistentData(str); + } + + // Auto-add if not found + if (!ref.isValid()) + { + ref = Core::getInstance().getWorld()->AddPersistentData(str); + if (!ref.isValid()) + luaL_error(state, "cannot create persistent entry"); + added = true; + } + + // Copy data from lua to C++ memory + lua_getfield(state, 1, "value"); + if (const char *str = lua_tostring(state, -1)) + ref.val() = str; + lua_pop(state, 1); + + lua_getfield(state, 1, "ints"); + if (lua_istable(state, -1)) + { + for (int i = 0; i < PersistentDataItem::NumInts; i++) + { + lua_rawgeti(state, -1, i+1); + if (lua_isnumber(state, -1)) + ref.ival(i) = lua_tointeger(state, -1); + lua_pop(state, 1); + } + } + lua_pop(state, 1); + + // Reinitialize lua from C++ and return + read_persistent(state, ref, false); + lua_pushboolean(state, added); + return 2; +} + +static const luaL_Reg dfhack_persistent_funcs[] = { + { "get", dfhack_persistent_get }, + { "delete", dfhack_persistent_delete }, + { "get_all", dfhack_persistent_get_all }, + { "save", dfhack_persistent_save }, + { NULL, NULL } +}; + +static void OpenPersistent(lua_State *state) +{ + luaL_getsubtable(state, lua_gettop(state), "persistent"); + + lua_dup(state); + luaL_setfuncs(state, dfhack_persistent_funcs, 1); + + lua_dup(state); + lua_setfield(state, -2, "__index"); + + lua_pop(state, 1); +} + +/************************ + * Wrappers for C++ API * + ************************/ + +static void OpenModule(lua_State *state, const char *mname, const LuaWrapper::FunctionReg *reg) +{ + luaL_getsubtable(state, lua_gettop(state), mname); + LuaWrapper::SetFunctionWrappers(state, reg); + lua_pop(state, 1); +} + +#define WRAPM(module, function) { #function, df::wrap_function(&module::function) } +#define WRAP(function) { #function, df::wrap_function(&function) } +#define WRAPN(name, function) { #name, df::wrap_function(&function) } + +static const LuaWrapper::FunctionReg dfhack_module[] = { + WRAPM(Translation, TranslateName), + { NULL, NULL } +}; + +static const LuaWrapper::FunctionReg dfhack_gui_module[] = { + WRAPM(Gui, getSelectedWorkshopJob), + WRAPM(Gui, getSelectedJob), + WRAPM(Gui, getSelectedUnit), + WRAPM(Gui, getSelectedItem), + WRAPM(Gui, showAnnouncement), + WRAPM(Gui, showPopupAnnouncement), + { NULL, NULL } +}; + +static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } +static bool jobItemEqual(df::job_item *job1, df::job_item *job2) { return *job1 == *job2; } + +static const LuaWrapper::FunctionReg dfhack_job_module[] = { + WRAP(cloneJobStruct), + WRAP(printJobDetails), + WRAP(getJobHolder), + WRAPN(is_equal, jobEqual), + WRAPN(is_item_equal, jobItemEqual), + { NULL, NULL } +}; + +static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, getVisibleName), + WRAPM(Units, isDead), + WRAPM(Units, isAlive), + WRAPM(Units, isSane), + { NULL, NULL } +}; + +/************************ + * Main Open function * + ************************/ + +void OpenDFHackApi(lua_State *state) +{ + OpenPersistent(state); + + LuaWrapper::SetFunctionWrappers(state, dfhack_module); + OpenModule(state, "gui", dfhack_gui_module); + OpenModule(state, "job", dfhack_job_module); + OpenModule(state, "units", dfhack_units_module); +} diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 5a87c931d..1e7a83b30 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -643,279 +643,12 @@ static const luaL_Reg dfhack_funcs[] = { { NULL, NULL } }; -/************************************************** - * Per-world persistent configuration storage API * - **************************************************/ - -static PersistentDataItem persistent_by_struct(lua_State *state, int idx) -{ - lua_getfield(state, idx, "entry_id"); - int id = lua_tointeger(state, -1); - lua_pop(state, 1); - - PersistentDataItem ref = Core::getInstance().getWorld()->GetPersistentData(id); - - if (ref.isValid()) - { - lua_getfield(state, idx, "key"); - const char *str = lua_tostring(state, -1); - if (!str || str != ref.key()) - luaL_argerror(state, idx, "inconsistent id and key"); - lua_pop(state, 1); - } - - return ref; -} - -static int read_persistent(lua_State *state, PersistentDataItem ref, bool create) -{ - if (!ref.isValid()) - { - lua_pushnil(state); - lua_pushstring(state, "entry not found"); - return 2; - } - - if (create) - lua_createtable(state, 0, 4); - - lua_pushvalue(state, lua_upvalueindex(1)); - lua_setmetatable(state, -2); - - lua_pushinteger(state, ref.entry_id()); - lua_setfield(state, -2, "entry_id"); - lua_pushstring(state, ref.key().c_str()); - lua_setfield(state, -2, "key"); - lua_pushstring(state, ref.val().c_str()); - lua_setfield(state, -2, "value"); - - lua_createtable(state, PersistentDataItem::NumInts, 0); - for (int i = 0; i < PersistentDataItem::NumInts; i++) - { - lua_pushinteger(state, ref.ival(i)); - lua_rawseti(state, -2, i+1); - } - lua_setfield(state, -2, "ints"); - - return 1; -} - -static PersistentDataItem get_persistent(lua_State *state) -{ - luaL_checkany(state, 1); - - if (lua_istable(state, 1)) - { - if (!lua_getmetatable(state, 1) || - !lua_rawequal(state, -1, lua_upvalueindex(1))) - luaL_argerror(state, 1, "invalid table type"); - - lua_settop(state, 1); - - return persistent_by_struct(state, 1); - } - else - { - const char *str = luaL_checkstring(state, 1); - - return Core::getInstance().getWorld()->GetPersistentData(str); - } -} - -static int dfhack_persistent_get(lua_State *state) -{ - CoreSuspender suspend; - - auto ref = get_persistent(state); - - return read_persistent(state, ref, !lua_istable(state, 1)); -} - -static int dfhack_persistent_delete(lua_State *state) -{ - CoreSuspender suspend; - - auto ref = get_persistent(state); - - bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); - - lua_pushboolean(state, ok); - return 1; -} - -static int dfhack_persistent_get_all(lua_State *state) -{ - CoreSuspender suspend; - - const char *str = luaL_checkstring(state, 1); - bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); - - std::vector data; - Core::getInstance().getWorld()->GetPersistentData(&data, str, prefix); - - if (data.empty()) - { - lua_pushnil(state); - } - else - { - lua_createtable(state, data.size(), 0); - for (size_t i = 0; i < data.size(); ++i) - { - read_persistent(state, data[i], true); - lua_rawseti(state, -2, i+1); - } - } - - return 1; -} - -static int dfhack_persistent_save(lua_State *state) -{ - CoreSuspender suspend; - - lua_settop(state, 2); - luaL_checktype(state, 1, LUA_TTABLE); - bool add = lua_toboolean(state, 2); - - lua_getfield(state, 1, "key"); - const char *str = lua_tostring(state, -1); - if (!str) - luaL_argerror(state, 1, "no key field"); - - lua_settop(state, 1); - - // Retrieve existing or create a new entry - PersistentDataItem ref; - bool added = false; - - if (add) - { - ref = Core::getInstance().getWorld()->AddPersistentData(str); - added = true; - } - else if (lua_getmetatable(state, 1)) - { - if (!lua_rawequal(state, -1, lua_upvalueindex(1))) - return luaL_argerror(state, 1, "invalid table type"); - lua_pop(state, 1); - - ref = persistent_by_struct(state, 1); - } - else - { - ref = Core::getInstance().getWorld()->GetPersistentData(str); - } - - // Auto-add if not found - if (!ref.isValid()) - { - ref = Core::getInstance().getWorld()->AddPersistentData(str); - if (!ref.isValid()) - luaL_error(state, "cannot create persistent entry"); - added = true; - } - - // Copy data from lua to C++ memory - lua_getfield(state, 1, "value"); - if (const char *str = lua_tostring(state, -1)) - ref.val() = str; - lua_pop(state, 1); - - lua_getfield(state, 1, "ints"); - if (lua_istable(state, -1)) - { - for (int i = 0; i < PersistentDataItem::NumInts; i++) - { - lua_rawgeti(state, -1, i+1); - if (lua_isnumber(state, -1)) - ref.ival(i) = lua_tointeger(state, -1); - lua_pop(state, 1); - } - } - lua_pop(state, 1); - - // Reinitialize lua from C++ and return - read_persistent(state, ref, false); - lua_pushboolean(state, added); - return 2; -} - -static const luaL_Reg dfhack_persistent_funcs[] = { - { "get", dfhack_persistent_get }, - { "delete", dfhack_persistent_delete }, - { "get_all", dfhack_persistent_get_all }, - { "save", dfhack_persistent_save }, - { NULL, NULL } -}; - -static void OpenPersistent(lua_State *state) -{ - luaL_getsubtable(state, lua_gettop(state), "persistent"); - - lua_dup(state); - luaL_setfuncs(state, dfhack_persistent_funcs, 1); - - lua_dup(state); - lua_setfield(state, -2, "__index"); - - lua_pop(state, 1); -} - -/************************ - * Wrappers for C++ API * - ************************/ - -static void OpenModule(lua_State *state, const char *mname, const LuaWrapper::FunctionReg *reg) -{ - luaL_getsubtable(state, lua_gettop(state), mname); - LuaWrapper::SetFunctionWrappers(state, reg); - lua_pop(state, 1); -} - -#define WRAPM(module, function) { #function, df::wrap_function(&module::function) } -#define WRAP(function) { #function, df::wrap_function(&function) } -#define WRAPN(name, function) { #name, df::wrap_function(&function) } - -static const LuaWrapper::FunctionReg dfhack_module[] = { - WRAPM(Translation, TranslateName), - { NULL, NULL } -}; - -static const LuaWrapper::FunctionReg dfhack_gui_module[] = { - WRAPM(Gui, getSelectedWorkshopJob), - WRAPM(Gui, getSelectedJob), - WRAPM(Gui, getSelectedUnit), - WRAPM(Gui, getSelectedItem), - WRAPM(Gui, showAnnouncement), - WRAPM(Gui, showPopupAnnouncement), - { NULL, NULL } -}; - -static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } -static bool jobItemEqual(df::job_item *job1, df::job_item *job2) { return *job1 == *job2; } - -static const LuaWrapper::FunctionReg dfhack_job_module[] = { - WRAP(cloneJobStruct), - WRAP(printJobDetails), - WRAP(getJobHolder), - WRAPN(is_equal, jobEqual), - WRAPN(is_item_equal, jobItemEqual), - { NULL, NULL } -}; - -static const LuaWrapper::FunctionReg dfhack_units_module[] = { - WRAPM(Units, getVisibleName), - WRAPM(Units, isDead), - WRAPM(Units, isAlive), - WRAPM(Units, isSane), - { NULL, NULL } -}; - /************************ * Main Open function * ************************/ +void OpenDFHackApi(lua_State *state); + lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) { if (!state) @@ -942,12 +675,7 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) // Initialize the dfhack global luaL_setfuncs(state, dfhack_funcs, 0); - OpenPersistent(state); - - LuaWrapper::SetFunctionWrappers(state, dfhack_module); - OpenModule(state, "gui", dfhack_gui_module); - OpenModule(state, "job", dfhack_job_module); - OpenModule(state, "units", dfhack_units_module); + OpenDFHackApi(state); lua_setglobal(state, "dfhack"); From 0daafef690d6a9e44f7caa7f61b89a63a3870cf9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 6 Apr 2012 19:56:19 +0400 Subject: [PATCH 013/108] Wrap MaterialInfo for lua. --- LUA_API.rst | 53 +++++++ Lua API.html | 60 ++++++- library/LuaApi.cpp | 233 ++++++++++++++++++++++++++++ library/LuaWrapper.cpp | 3 + library/include/modules/Materials.h | 1 + library/lua/dfhack.lua | 4 + library/modules/Materials.cpp | 12 +- 7 files changed, 357 insertions(+), 9 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 422ba6b49..0ec837a2d 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -498,6 +498,59 @@ and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead. +Material info lookup +==================== + +A material info record has fields: + +* ``type``, ``index``, ``material`` + + DF material code pair, and a reference to the material object. + +* ``mode`` + + One of ``'builtin'``, ``'inorganic'``, ``'plant'``, ``'creature'``. + +* ``inorganic``, ``plant``, ``creature`` + + If the material is of the matching type, contains a reference to the raw object. + +* ``figure`` + + For a specific creature material contains a ref to the historical figure. + +Functions: + +* ``dfhack.matinfo.decode(type,index)`` + + Looks up material info for the given number pair; if not found, returs *nil*. + +* ``....decode(matinfo)``, ``....decode(item)``, ``....decode(obj)`` + + Uses ``matinfo.type``/``matinfo.index``, item getter vmethods, + or ``obj.mat_type``/``obj.mat_index`` to get the code pair. + +* ``dfhack.matinfo.find(token[,token...])`` + + Looks up material by a token string, or a pre-split string token sequence. + +* ``dfhack.matinfo.getToken(...)``, ``info:getToken()`` + + Applies ``decode`` and constructs a string token. + +* ``info:toString([temperature[,named]])`` + + Returns the human-readable name at the given temperature. + +* ``info:getCraftClass()`` + + Returns the classification used for craft skills. + +* ``info:matches(obj)`` + + Checks if the material matches job_material_category or job_item. + Accept dfhack_material_category auto-assign table. + C++ function wrappers ===================== diff --git a/Lua API.html b/Lua API.html index ab64eea9a..379432a78 100644 --- a/Lua API.html +++ b/Lua API.html @@ -335,10 +335,11 @@ ul.auto-toc {
  • DFHack utilities
    • Persistent configuration storage
    • -
    • C++ function wrappers @@ -759,8 +760,51 @@ and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead.

      +
      +

      Material info lookup

      +

      A material info record has fields:

      +
        +
      • type, index, material

        +

        DF material code pair, and a reference to the material object.

        +
      • +
      • mode

        +

        One of 'builtin', 'inorganic', 'plant', 'creature'.

        +
      • +
      • inorganic, plant, creature

        +

        If the material is of the matching type, contains a reference to the raw object.

        +
      • +
      • figure

        +

        For a specific creature material contains a ref to the historical figure.

        +
      • +
      +

      Functions:

      +
        +
      • dfhack.matinfo.decode(type,index)

        +

        Looks up material info for the given number pair; if not found, returs nil.

        +
      • +
      • ....decode(matinfo), ....decode(item), ....decode(obj)

        +

        Uses matinfo.type/matinfo.index, item getter vmethods, +or obj.mat_type/obj.mat_index to get the code pair.

        +
      • +
      • dfhack.matinfo.find(token[,token...])

        +

        Looks up material by a token string, or a pre-split string token sequence.

        +
      • +
      • dfhack.matinfo.getToken(...), info:getToken()

        +

        Applies decode and constructs a string token.

        +
      • +
      • info:toString([temperature[,named]])

        +

        Returns the human-readable name at the given temperature.

        +
      • +
      • info:getCraftClass()

        +

        Returns the classification used for craft skills.

        +
      • +
      • info:matches(obj)

        +

        Checks if the material matches job_material_category or job_item.

        +
      • +
      +
      -

      C++ function wrappers

      +

      C++ function wrappers

      Thin wrappers around C++ functions, similar to the ones for virtual methods.

      • dfhack.TranslateName(name,in_english,only_last_name)

        @@ -768,7 +812,7 @@ However, currently every entry has a 180+-byte dead-weight overhead.

      -

      Gui module

      +

      Gui module

      • dfhack.gui.getSelectedWorkshopJob(silent)

        When a job is selected in 'q' mode, returns the job, else @@ -797,7 +841,7 @@ The is_bright boolean actually seems to invert the brightness.

      -

      Job module

      +

      Job module

      • dfhack.job.cloneJobStruct(job)

        Creates a deep copy of the given job.

        @@ -817,7 +861,7 @@ The is_bright boolean actually seems to invert the brightness.

      -

      Units module

      +

      Units module

      • dfhack.units.getVisibleName(unit)

        Returns the name visible in game, accounting for false identities.

        diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 45680f8ef..f1d86580a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -42,6 +42,7 @@ distribution. #include "modules/Job.h" #include "modules/Translation.h" #include "modules/Units.h" +#include "modules/Materials.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -53,6 +54,13 @@ distribution. #include "df/building.h" #include "df/unit.h" #include "df/item.h" +#include "df/material.h" +#include "df/historical_figure.h" +#include "df/plant_raw.h" +#include "df/creature_raw.h" +#include "df/inorganic_raw.h" +#include "df/dfhack_material_category.h" +#include "df/job_material_category.h" #include #include @@ -280,6 +288,230 @@ static void OpenPersistent(lua_State *state) lua_pop(state, 1); } +/************************ + * Material info lookup * + ************************/ + +static void push_matinfo(lua_State *state, MaterialInfo &info) +{ + if (!info.isValid()) + { + lua_pushnil(state); + return; + } + + lua_newtable(state); + lua_pushvalue(state, lua_upvalueindex(1)); + lua_setmetatable(state, -2); + + lua_pushinteger(state, info.type); + lua_setfield(state, -2, "type"); + lua_pushinteger(state, info.index); + lua_setfield(state, -2, "index"); + +#define SETOBJ(name) { \ + Lua::PushDFObject(state, info.name); \ + lua_setfield(state, -2, #name); \ +} + SETOBJ(material); + if (info.plant) SETOBJ(plant); + if (info.creature) SETOBJ(creature); + if (info.inorganic) SETOBJ(inorganic); + if (info.figure) SETOBJ(figure); +#undef SETOBJ + + if (info.mode != MaterialInfo::Builtin) + { + lua_pushinteger(state, info.subtype); + lua_setfield(state, -2, "subtype"); + } + + const char *id = "builtin"; + switch (info.mode) + { + case MaterialInfo::Plant: id = "plant"; break; + case MaterialInfo::Creature: id = "creature"; break; + case MaterialInfo::Inorganic: id = "inorganic"; break; + } + + lua_pushstring(state, id); + lua_setfield(state, -2, "mode"); +} + +static int dfhack_matinfo_find(lua_State *state) +{ + MaterialInfo info; + int argc = lua_gettop(state); + + if (argc == 1) + info.find(luaL_checkstring(state, 1)); + else + { + std::vector tokens; + + for (int i = 1; i < argc; i++) + tokens.push_back(luaL_checkstring(state, i)); + + info.find(tokens); + } + + push_matinfo(state, info); + return 1; +} + +static bool decode_matinfo(lua_State *state, MaterialInfo *info, bool numpair = false) +{ + int curtop = lua_gettop(state); + + luaL_checkany(state, 1); + + if (!lua_isnumber(state, 1)) + { + if (lua_isnil(state, 1)) + return false; + + if (lua_getmetatable(state, 1)) + { + if (lua_rawequal(state, -1, lua_upvalueindex(1))) + { + lua_getfield(state, 1, "type"); + lua_getfield(state, 1, "index"); + goto int_pair; + } + + lua_pop(state, 1); + } + + if (lua_isuserdata(state, 1)) + { + if (auto item = Lua::GetDFObject(state, 1)) + return info->decode(item); + if (auto mvec = Lua::GetDFObject(state, 1)) + return info->decode(*mvec, luaL_checkint(state, 2)); + } + + lua_getfield(state, 1, "mat_type"); + lua_getfield(state, 1, "mat_index"); + goto int_pair; + } + else + { + if (!numpair) + luaL_argerror(state, 1, "material info object expected"); + + if (curtop < 2) + lua_settop(state, 2); + } + +int_pair: + { + int ok; + int type = lua_tointegerx(state, -2, &ok); + if (!ok) + luaL_argerror(state, 1, "material id is not a number"); + int index = lua_tointegerx(state, -1, &ok); + if (!ok) + index = -1; + + lua_settop(state, curtop); + + return info->decode(type, index); + } +} + +static int dfhack_matinfo_decode(lua_State *state) +{ + MaterialInfo info; + decode_matinfo(state, &info, true); + push_matinfo(state, info); + return 1; +} + +static int dfhack_matinfo_getToken(lua_State *state) +{ + MaterialInfo info; + decode_matinfo(state, &info, true); + auto str = info.getToken(); + lua_pushstring(state, str.c_str()); + return 1; +} + +static int dfhack_matinfo_toString(lua_State *state) +{ + MaterialInfo info; + decode_matinfo(state, &info); + + lua_settop(state, 3); + auto str = info.toString(luaL_optint(state, 2, 10015), lua_toboolean(state, 3)); + lua_pushstring(state, str.c_str()); + return 1; +} + +static int dfhack_matinfo_getCraftClass(lua_State *state) +{ + MaterialInfo info; + if (decode_matinfo(state, &info, true)) + lua_pushinteger(state, info.getCraftClass()); + else + lua_pushnil(state); + return 1; +} + +static int dfhack_matinfo_matches(lua_State *state) +{ + MaterialInfo info; + if (!decode_matinfo(state, &info)) + luaL_argerror(state, 1, "material info object expected"); + + luaL_checkany(state, 2); + + if (lua_isuserdata(state, 2)) + { + if (auto mc = Lua::GetDFObject(state, 2)) + lua_pushboolean(state, info.matches(*mc)); + else if (auto mc = Lua::GetDFObject(state, 2)) + lua_pushboolean(state, info.matches(*mc)); + else if (auto mc = Lua::GetDFObject(state, 2)) + lua_pushboolean(state, info.matches(*mc)); + else + luaL_argerror(state, 2, "material category object expected"); + } + else if (lua_istable(state, 2)) + { + df::dfhack_material_category tmp; + if (!Lua::AssignDFObject(*Lua::GetOutput(state), state, &tmp, 2, false)) + lua_error(state); + lua_pushboolean(state, info.matches(tmp)); + } + else + luaL_argerror(state, 2, "material category object expected"); + + return 1; +} + +static const luaL_Reg dfhack_matinfo_funcs[] = { + { "find", dfhack_matinfo_find }, + { "decode", dfhack_matinfo_decode }, + { "getToken", dfhack_matinfo_getToken }, + { "toString", dfhack_matinfo_toString }, + { "getCraftClass", dfhack_matinfo_getCraftClass }, + { "matches", dfhack_matinfo_matches }, + { NULL, NULL } +}; + +static void OpenMatinfo(lua_State *state) +{ + luaL_getsubtable(state, lua_gettop(state), "matinfo"); + + lua_dup(state); + luaL_setfuncs(state, dfhack_matinfo_funcs, 1); + + lua_dup(state); + lua_setfield(state, -2, "__index"); + + lua_pop(state, 1); +} + /************************ * Wrappers for C++ API * ************************/ @@ -337,6 +569,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { void OpenDFHackApi(lua_State *state) { OpenPersistent(state); + OpenMatinfo(state); LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 2f3958efe..1c3b61c15 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -699,6 +699,9 @@ static int meta_assign(lua_State *state) { type_identity *id = get_object_identity(state, 1, "df.assign()", false); + if (lua_getmetatable(state, 2)) + luaL_error(state, "cannot use lua tables with metatable in df.assign()"); + int base = lua_gettop(state); // x:assign{ assign = foo } => x:assign(foo) diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index f3c526d76..3d70dc9fa 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -114,6 +114,7 @@ namespace DFHack } bool find(const std::string &token); + bool find(const std::vector &tokens); bool findBuiltin(const std::string &token); bool findInorganic(const std::string &token); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 9e2cdf257..d662efed7 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -70,5 +70,9 @@ function dfhack.persistent:__tostring() ..self.value.."\":"..table.concat(self.ints,",")..">" end +function dfhack.matinfo:__tostring() + return "" +end + -- Feed the table back to the require() mechanism. return dfhack diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 8f5739c7e..0172e24f8 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -164,6 +164,13 @@ bool MaterialInfo::find(const std::string &token) { std::vector items; split_string(&items, token, ":"); + return find(items); +} + +bool MaterialInfo::find(const std::vector &items) +{ + if (items.empty()) + return false; if (items[0] == "INORGANIC" && items.size() > 1) return findInorganic(vector_get(items,1)); @@ -204,8 +211,11 @@ bool MaterialInfo::findBuiltin(const std::string &token) df::world_raws &raws = world->raws; for (int i = 1; i < NUM_BUILTIN; i++) - if (raws.mat_table.builtin[i]->id == token) + { + auto obj = raws.mat_table.builtin[i]; + if (obj && obj->id == token) return decode(i, -1); + } return decode(-1); } From eff5189acb039da0b7b74f94c5cf352bcfe73cb7 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sat, 7 Apr 2012 02:31:10 +0200 Subject: [PATCH 014/108] added autobutcher: watch lifestock, mark excess animals for slaughter. Not quite done yet (doesn't save config etc) but already works fine. --- README.rst | 30 +++ plugins/zone.cpp | 684 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 689 insertions(+), 25 deletions(-) diff --git a/README.rst b/README.rst index 60cdc330f..ce0228161 100644 --- a/README.rst +++ b/README.rst @@ -862,3 +862,33 @@ Options: :start: Start running every X frames (df simulation ticks). Default: X=6000, which would be every 60 seconds at 100fps. :stop: Stop running automatically. :sleep: Must be followed by number X. Changes the timer to sleep X frames between runs. + +autobutcher +=========== +Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units of the own civilization will be processed. Named units will be completely ignored (you can give animals nicknames with the tool 'rename unit' to protect them from autobutcher). Once you have too much adults, the oldest will be butchered first. Once you have too much kids, the youngest will be butchered first. If you don't set any target count the following default will be used: 1 male kid, 5 female kids, 1 male adult, 5 female adults. + +Options: +-------- +:start: Start running every X frames (df simulation ticks). Default: X=6000, which would be every 60 seconds at 100fps. +:stop: Stop running automatically. +:sleep: Must be followed by number X. Changes the timer to sleep X frames between runs. +:watch R: Start watching a race. R must be a valid race RAW id (ALPACA, BIRD_TURKEY, etc). +:unwatch R: Stop watching a race. The current target settings will be remembered (currently only until you save or quit the game). +:forget R: Stop watching a race and forget it's target settings. +:list Print a list of watched races. +:target fk mk fa ma R: Set target count for specified race(s). + fk = number of female kids + mk = number of male kids + fa = number of female adults + ma = number of female adults +:example: Print some usage examples. + +Examples: +--------- +You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, 1 male) of the race alpaca. Once the kids grow up the oldest adults will get slaughtered. Excess kids will get slaughtered starting with the youngest to allow that the older ones grow into adults. +:: + + autobutcher target 4 3 2 1 ALPACA + autobutcher watch ALPACA + autobutcher start + diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 9aa8547c9..45d18fa27 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -4,10 +4,6 @@ // - dump info about pastures, pastured animals, count non-pastured tame animals, print gender info // - help finding caged dwarves? (maybe even allow to build their cages for fast release) // - dump info about caged goblins, animals, ... -// - allow to mark old animals for slaughter automatically? -// that should include a check to ensure that at least one male and one female remain for breeding -// allow some fine-tuning like how many males/females should per race should be left alive -// and at what age they are being marked for slaughter. don't slaughter pregnant females? // - count grass tiles on pastures, move grazers to new pasture if old pasture is empty // move hungry unpastured grazers to pasture with grass // @@ -21,11 +17,18 @@ // go through all pens, check if they are empty and placed over a nestbox // find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture // maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds +// - full automation of marking live-stock for slaughtering +// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive +// TODO: - parse more than one race in one command +// - support keywords 'all' and 'new' +// - autowatch +// - save config #include #include #include #include +#include #include #include #include @@ -67,7 +70,7 @@ using namespace DFHack::Gui; command_result df_zone (color_ostream &out, vector & parameters); command_result df_autonestbox (color_ostream &out, vector & parameters); -//command_result df_autoslaughter (color_ostream &out, vector & parameters); +command_result df_autobutcher(color_ostream &out, vector & parameters); DFHACK_PLUGIN("zone"); @@ -152,6 +155,63 @@ const string autonestbox_help = " stop - stop running automatically\n" " sleep X - change timer to sleep X frames between runs.\n"; +const string autobutcher_help = + "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n" + "that you add the target race(s) to a watch list. Only tame units of your own\n" + "civilization will be processed. Named units will be completely ignored (you can\n" + "give animals nicknames with the tool 'rename unit' to protect them from\n" + "getting slaughtered automatically.\n" + "Once you have too much adults, the oldest will be butchered first.\n" + "Once you have too much kids, the youngest will be butchered first.\n" + "If you don't set a target count the following default will be used:\n" + "1 male kid, 5 female kids, 1 male adult, 5 female adults.\n" + "Options:\n" + " start - run every X frames (df simulation ticks)\n" + " default: X=6000 (~60 seconds at 100fps)\n" + " stop - stop running automatically\n" + " sleep X - change timer to sleep X frames between runs.\n" + " watch R - start watching race(s)\n" + " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n" + //" or a list of RAW ids seperated by spaces\n" + //" or the keyword 'all' which adds all races with\n" + //" at least one owned tame unit in your fortress\n" + " unwatch R - stop watching race\n" + " the current target settings will be remembered\n" + " forget R - unwatch race and forget target settings for it\n" + //" autowatch - automatically adds all new races (animals you buy\n" + //" from merchants, tame yourself or get from migrants)\n" + //" to the watch list using default target count\n" + " list - print a list of watched races\n" + " target fk mk fa ma R\n" + " - set target count for specified race:\n" + " fk = number of female kids\n" + " mk = number of male kids\n" + " fa = number of female adults\n" + " ma = number of female adults\n" + //" R = 'all' sets count for all races on the current watchlist\n" + //" including the races which are currenly set to 'unwatched'\n" + //" and sets the new default for future watch commands\n" + //" R = 'new' sets the new default for future watch commands\n" + //" without changing your current watchlist\n" + " example - print some usage examples\n"; + +const string autobutcher_help_example = + "Examples:\n" + " autobutcher target 4 3 2 1 ALPACA\n" + " autobutcher watch ALPACA\n" + " autobutcher start\n" + " This means you want to have max 7 kids (4 female, 3 male) and max 3 adults\n" + " (2 female, 1 male) of the race alpaca. Once the kids grow up the oldest\n" + " adults will get slaughtered. Excess kids will get slaughtered starting with\n" + " the youngest to allow that the older ones grow into adults.\n" + //" autobutcher target 0 0 0 0 all\n" + //" autobutcher autowatch\n" + //" autobutcher start\n" + //" This tells autobutcher to automatically put all new races onto the watchlist\n" + //" and mark unnamed tame units for slaughter as soon as they arrive in your\n" + //" fortress. Settings already made for some races will be left untouched.\n" + ; + DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { @@ -163,32 +223,34 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector = sleep_autoslaughter) + if(++ticks_autobutcher >= sleep_autobutcher) { - ticks_autoslaughter = 0; - autoSlaughter(out, false); + ticks_autobutcher= 0; + autoButcher(out, false); } } @@ -459,6 +522,20 @@ bool isFemale(df::unit* unit) return unit->sex == 0; } +// found a unit with weird position values on one of my maps (negative and in the thousands) +// it didn't appear in the animal stocks screen, but looked completely fine otherwise (alive, tame, own, etc) +// maybe a rare but, but better avoid assigning such units to zones or slaughter etc. +bool hasValidMapPos(df::unit* unit) +{ + if( unit->pos.x >=0 && unit->pos.y >= 0 && unit->pos.z >= 0 + && unit->pos.x < world->map.x_count + && unit->pos.y < world->map.y_count + && unit->pos.z < world->map.z_count) + return true; + else + return false; +} + // dump some unit info void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) { @@ -1563,10 +1640,18 @@ command_result df_zone (color_ostream &out, vector & parameters) ) continue; + if(!hasValidMapPos(unit)) + { + uint32_t max_x, max_y, max_z; + Maps::getSize(max_x, max_y, max_z); + out << "("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << "). will skip this unit" << endl; + continue; + } + if(unit_info) { - if(unit->pos.x <0 || unit->pos.y<0 || unit->pos.z<0) - out << "invalid unit pos" << endl; unitInfo(out, unit, verbose); } else if(zone_assign) @@ -1779,8 +1864,557 @@ command_result autoNestbox( color_ostream &out, bool verbose = false ) return CR_OK; } -command_result autoSlaughter( color_ostream &out, bool verbose = false ) + +// getUnitAge() returns 0 if born in current year, therefore the look at birth_time in that case +// (assuming that the value from there indicates in which tick of the current year the unit was born) +bool compareUnitAgesYounger(df::unit* i, df::unit* j) +{ + int32_t age_i = getUnitAge(i); + int32_t age_j = getUnitAge(j); + if(age_i == 0 && age_j == 0) + { + age_i = i->relations.birth_time; + age_j = j->relations.birth_time; + } + return (age_i < age_j); +} +bool compareUnitAgesOlder(df::unit* i, df::unit* j) +{ + int32_t age_i = getUnitAge(i); + int32_t age_j = getUnitAge(j); + if(age_i == 0 && age_j == 0) + { + age_i = i->relations.birth_time; + age_j = j->relations.birth_time; + } + return (age_i > age_j); +} + +//enum WatchedRaceSubtypes +//{ +// femaleKid=0, +// maleKid, +// femaleAdult, +// maleAdult +//}; + +struct WatchedRace +{ +public: + + bool isWatched; // if true, autobutcher will process this race + int raceId; + int fk; // max female kids + int mk; // max male kids + int fa; // max female adults + int ma; // max male adults + + // bah, this should better be an array of 4 vectors + // that way there's no need for the 4 ugly process methods + vector fk_ptr; + vector mk_ptr; + vector fa_ptr; + vector ma_ptr; + + WatchedRace(bool watch, int id, int _fk, int _mk, int _fa, int _ma) + { + isWatched = watch; + raceId = id; + fk = _fk; + mk = _mk; + fa = _fa; + ma = _ma; + } + + ~WatchedRace() + { + ClearUnits(); + } + + void SortUnitsByAge() + { + sort(fk_ptr.begin(), fk_ptr.end(), compareUnitAgesOlder); + sort(mk_ptr.begin(), mk_ptr.end(), compareUnitAgesOlder); + sort(fa_ptr.begin(), fa_ptr.end(), compareUnitAgesYounger); + sort(ma_ptr.begin(), ma_ptr.end(), compareUnitAgesYounger); + } + + void PushUnit(df::unit * unit) + { + if(isFemale(unit)) + { + if(isBaby(unit) || isChild(unit)) + fk_ptr.push_back(unit); + else + fa_ptr.push_back(unit); + } + else //treat sex n/a like it was male + { + if(isBaby(unit) || isChild(unit)) + mk_ptr.push_back(unit); + else + ma_ptr.push_back(unit); + } + } + + void ClearUnits() + { + fk_ptr.clear(); + mk_ptr.clear(); + fa_ptr.clear(); + ma_ptr.clear(); + } + + int ProcessUnits_fk() + { + int subcount = 0; + while(fk_ptr.size() > fk) + { + df::unit* unit = fk_ptr.back(); + doMarkForSlaughter(unit); + fk_ptr.pop_back(); + subcount++; + } + return subcount; + } + int ProcessUnits_mk() + { + int subcount = 0; + while(mk_ptr.size() > mk) + { + df::unit* unit = mk_ptr.back(); + doMarkForSlaughter(unit); + mk_ptr.pop_back(); + subcount++; + } + return subcount; + } + int ProcessUnits_fa() + { + int subcount = 0; + while(fa_ptr.size() > fa) + { + df::unit* unit = fa_ptr.back(); + doMarkForSlaughter(unit); + fa_ptr.pop_back(); + subcount++; + } + return subcount; + } + int ProcessUnits_ma() + { + int subcount = 0; + while(ma_ptr.size() > ma) + { + df::unit* unit = ma_ptr.back(); + doMarkForSlaughter(unit); + ma_ptr.pop_back(); + subcount++; + } + return subcount; + } + + int ProcessUnits() + { + SortUnitsByAge(); + int slaughter_count = 0; + slaughter_count += ProcessUnits_fk(); + slaughter_count += ProcessUnits_mk(); + slaughter_count += ProcessUnits_fa(); + slaughter_count += ProcessUnits_ma(); + ClearUnits(); + return slaughter_count; + } +}; +// vector of races handled by autobutcher +// the name is a bit misleading since entries can be set to 'unwatched' +// to ignore them for a while but still keep the target count settings +std::vector watched_races; + +command_result autobutcher_cleanup(color_ostream &out) +{ + for(size_t i=0; i & parameters) +{ + // move those somewhere else for persistency + static int default_fk = 5; + static int default_mk = 1; + static int default_fa = 5; + static int default_ma = 1; + + CoreSuspender suspend; + + bool verbose = false; + bool watch_race = false; + bool unwatch_race = false; + bool forget_race = false; + bool list_watched = false; + bool change_target = false; + string target_racename = ""; + int target_fk = default_fk; + int target_mk = default_mk; + int target_fa = default_fa; + int target_ma = default_ma; + + int32_t target_raceid = -1; + + for (size_t i = 0; i < parameters.size(); i++) + { + string & p = parameters[i]; + + if (p == "help" || p == "?") + { + out << autobutcher_help << endl; + return CR_OK; + } + if (p == "example") + { + out << autobutcher_help_example << endl; + return CR_OK; + } + else if (p == "start") + { + enable_autobutcher = true; + out << "Autobutcher started."; + return autoButcher(out, verbose); + } + else if (p == "stop") + { + enable_autobutcher = false; + out << "Autobutcher stopped."; + return CR_OK; + } + else if(p == "verbose") + { + verbose = true; + } + else if(p == "sleep") + { + if(i == parameters.size()-1) + { + out.printerr("No duration specified!"); + return CR_WRONG_USAGE; + } + else + { + size_t ticks = 0; + stringstream ss(parameters[i+1]); + i++; + ss >> ticks; + if(ticks <= 0) + { + out.printerr("Invalid duration specified (must be > 0)!"); + return CR_WRONG_USAGE; + } + sleep_autobutcher = ticks; + out << "New sleep timer for autobutcher: " << ticks << " ticks." << endl; + return CR_OK; + } + } + else if(p == "watch") + { + if(i == parameters.size()-1) + { + out.printerr("No race specified!"); + return CR_WRONG_USAGE; + } + else + { + // todo: parse more than one race + target_racename = parameters[i+1]; + i++; + out << "Start watching race " << target_racename << endl; + watch_race = true; + } + } + else if(p == "unwatch") + { + if(i == parameters.size()-1) + { + out.printerr("No race specified!"); + return CR_WRONG_USAGE; + } + else + { + // todo: parse more than one race + target_racename = parameters[i+1]; + i++; + out << "Stop watching race " << target_racename << endl; + unwatch_race = true; + } + } + else if(p == "forget") + { + if(i == parameters.size()-1) + { + out.printerr("No race specified!"); + return CR_WRONG_USAGE; + } + else + { + // todo: parse more than one race + target_racename = parameters[i+1]; + i++; + out << "Forget settings for race " << target_racename << endl; + forget_race = true; + } + } + else if(p == "target") + { + // needs at least 5 more parameters: + // fk mk fa ma R (can have more than 1 R) + if(parameters.size() < 6) + { + out.printerr("Not enough parameters!"); + return CR_WRONG_USAGE; + } + else + { + stringstream fk(parameters[i+1]); + stringstream mk(parameters[i+2]); + stringstream fa(parameters[i+3]); + stringstream ma(parameters[i+4]); + fk >> target_fk; + mk >> target_mk; + fa >> target_fa; + ma >> target_ma; + + // todo: parse more than one race, handle 'all' and 'new' + target_racename = parameters[i+5]; + i+=5; + out << "Target count for " << target_racename << ":" + << " fk=" << target_fk + << " mk=" << target_mk + << " fa=" << target_fa + << " ma=" << target_ma + << endl; + change_target = true; + } + } + else if(p == "autowatch") + { + out << "not supported yet, sorry" << endl; + return CR_OK; + } + else if(p == "noautowatch") + { + out << "not supported yet, sorry" << endl; + return CR_OK; + } + else if(p == "list") + { + list_watched = true; + } + else + { + out << "Unknown command: " << p << endl; + return CR_WRONG_USAGE; + } + } + + if( target_racename == "all" || + target_racename == "new" ) + { + out << "'all' and 'new' are not supported yet, sorry." << endl; + return CR_OK; + } + + if(list_watched) + { + for(size_t i=0; iraws.creatures.all[w->raceId]; + string name = raw->creature_id; + if(w->isWatched) + out << "watched: "; + else + out << "not watched: "; + out << name + << " fk=" << w->fk + << " mk=" << w->mk + << " fa=" << w->fa + << " ma=" << w->ma + << endl; + } + return CR_OK; + } + + size_t num_races = df::global::world->raws.creatures.all.size(); + bool found_race = false; + for(size_t i=0; iraws.creatures.all[i]; + if(raw->creature_id == target_racename) + { + target_raceid = i; + found_race = true; + break; + } + } + if(!found_race) + { + out << "Race not found!" << endl; + return CR_OK; + } + + if(unwatch_race) + { + bool found = false; + for(size_t i=0; iraceId == target_raceid) + { + found=true; + w->isWatched=false; + break; + } + } + if(found) + out << target_racename << " is not watched anymore." << endl; + else + out << target_racename << " was not being watched!" << endl; + return CR_OK; + } + + if(watch_race || change_target) + { + bool watching = false; + for(size_t i=0; iraceId == target_raceid) + { + if(watch_race) + { + if(w->isWatched) + out << target_racename << " is already being watched." << endl; + w->isWatched = true; + watching = true; + } + + if(change_target) + { + w->fk = target_fk; + w->mk = target_mk; + w->fa = target_fa; + w->ma = target_ma; + } + break; + } + } + if(!watching) + { + WatchedRace * w = new WatchedRace(watch_race, target_raceid, target_fk, target_mk, target_fa, target_ma); + watched_races.push_back(w); + } + out << target_racename << " is now being watched." << endl; + return CR_OK; + } + + if(forget_race) + { + bool watched = false; + for(size_t i=0; iraceId == target_raceid) + { + watched_races.erase(watched_races.begin()+i); + + if(w->isWatched) + out << target_racename << " is already being watched." << endl; + w->isWatched = true; + watched = true; + break; + } + } + if(!watched) + { + out << target_racename << " was not on the watchlist." << endl; + return CR_OK; + } + out << target_racename << " was forgotten." << endl; + return CR_OK; + } + + return CR_OK; +} + +int getWatchedIndex(int id) { - out << "Autoslaughter would run now." << endl; + for(size_t i=0; iraceId == id && w->isWatched) + return i; + } + return -1; +} + +command_result autoButcher( color_ostream &out, bool verbose = false ) +{ + // don't run if not supposed to + if(!Maps::IsValid()) + return CR_OK; + + // check if there is anything to watch before walking through units vector + bool watching = false; + for(size_t i=0; iisWatched) + { + watching = true; + break; + } + } + if(!watching) + return CR_OK; + + for(size_t i=0; iunits.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + if( isDead(unit) + || isMarkedForSlaughter(unit) + || !isOwnCiv(unit) + || !isTame(unit) + || unit->name.has_name + ) + continue; + + // found a bugged unit which had invalid coordinates. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!hasValidMapPos(unit)) + continue; + + int watched_index = getWatchedIndex(unit->race); + if(watched_index != -1) + { + WatchedRace * w = watched_races[watched_index]; + w->PushUnit(unit); + } + } + + int slaughter_count = 0; + for(size_t i=0; iProcessUnits(); + slaughter_count += slaughter_subcount; + df::creature_raw *raw = df::global::world->raws.creatures.all[w->raceId]; + out << raw->creature_id << " marked for slaughter: " << slaughter_subcount << endl; + } + out << slaughter_count << " units total marked for slaughter." << endl; + return CR_OK; } \ No newline at end of file From 1b3818b8fdf088ef0d388248c69953c619173de5 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sat, 7 Apr 2012 02:35:00 +0200 Subject: [PATCH 015/108] minor: fix readme.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ce0228161..f9d816f5e 100644 --- a/README.rst +++ b/README.rst @@ -875,7 +875,7 @@ Options: :watch R: Start watching a race. R must be a valid race RAW id (ALPACA, BIRD_TURKEY, etc). :unwatch R: Stop watching a race. The current target settings will be remembered (currently only until you save or quit the game). :forget R: Stop watching a race and forget it's target settings. -:list Print a list of watched races. +:list: Print a list of watched races. :target fk mk fa ma R: Set target count for specified race(s). fk = number of female kids mk = number of male kids From c5fc8aab9fc08c7a4ba3db072a10933a25acc367 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sat, 7 Apr 2012 05:05:42 +0200 Subject: [PATCH 016/108] autobutcher: now accepts list of races in one command --- plugins/zone.cpp | 399 ++++++++++++++++++++++------------------------- 1 file changed, 187 insertions(+), 212 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 45d18fa27..2179d2b06 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -172,12 +172,12 @@ const string autobutcher_help = " sleep X - change timer to sleep X frames between runs.\n" " watch R - start watching race(s)\n" " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n" - //" or a list of RAW ids seperated by spaces\n" + " or a list of RAW ids seperated by spaces\n" //" or the keyword 'all' which adds all races with\n" //" at least one owned tame unit in your fortress\n" - " unwatch R - stop watching race\n" + " unwatch R - stop watching race(s)\n" " the current target settings will be remembered\n" - " forget R - unwatch race and forget target settings for it\n" + " forget R - unwatch race(s) and forget target settings for it/them\n" //" autowatch - automatically adds all new races (animals you buy\n" //" from merchants, tame yourself or get from migrants)\n" //" to the watch list using default target count\n" @@ -197,13 +197,13 @@ const string autobutcher_help = const string autobutcher_help_example = "Examples:\n" - " autobutcher target 4 3 2 1 ALPACA\n" - " autobutcher watch ALPACA\n" + " autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY\n" + " autobutcher watch ALPACA BIRD_TURKEY\n" " autobutcher start\n" " This means you want to have max 7 kids (4 female, 3 male) and max 3 adults\n" - " (2 female, 1 male) of the race alpaca. Once the kids grow up the oldest\n" - " adults will get slaughtered. Excess kids will get slaughtered starting with\n" - " the youngest to allow that the older ones grow into adults.\n" + " (2 female, 1 male) of the races alpaca and turkey. Once the kids grow up the\n" + " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n" + " the the youngest to allow that the older ones grow into adults.\n" //" autobutcher target 0 0 0 0 all\n" //" autobutcher autowatch\n" //" autobutcher start\n" @@ -2058,7 +2058,9 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) bool forget_race = false; bool list_watched = false; bool change_target = false; - string target_racename = ""; + vector target_racenames; + vector target_raceids; + int target_fk = default_fk; int target_mk = default_mk; int target_fa = default_fa; @@ -2066,166 +2068,118 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) int32_t target_raceid = -1; - for (size_t i = 0; i < parameters.size(); i++) + if(!parameters.size()) { - string & p = parameters[i]; - - if (p == "help" || p == "?") - { - out << autobutcher_help << endl; - return CR_OK; - } - if (p == "example") - { - out << autobutcher_help_example << endl; - return CR_OK; - } - else if (p == "start") - { - enable_autobutcher = true; - out << "Autobutcher started."; - return autoButcher(out, verbose); - } - else if (p == "stop") - { - enable_autobutcher = false; - out << "Autobutcher stopped."; - return CR_OK; - } - else if(p == "verbose") - { - verbose = true; - } - else if(p == "sleep") - { - if(i == parameters.size()-1) - { - out.printerr("No duration specified!"); - return CR_WRONG_USAGE; - } - else - { - size_t ticks = 0; - stringstream ss(parameters[i+1]); - i++; - ss >> ticks; - if(ticks <= 0) - { - out.printerr("Invalid duration specified (must be > 0)!"); - return CR_WRONG_USAGE; - } - sleep_autobutcher = ticks; - out << "New sleep timer for autobutcher: " << ticks << " ticks." << endl; - return CR_OK; - } - } - else if(p == "watch") - { - if(i == parameters.size()-1) - { - out.printerr("No race specified!"); - return CR_WRONG_USAGE; - } - else - { - // todo: parse more than one race - target_racename = parameters[i+1]; - i++; - out << "Start watching race " << target_racename << endl; - watch_race = true; - } - } - else if(p == "unwatch") - { - if(i == parameters.size()-1) - { - out.printerr("No race specified!"); - return CR_WRONG_USAGE; - } - else - { - // todo: parse more than one race - target_racename = parameters[i+1]; - i++; - out << "Stop watching race " << target_racename << endl; - unwatch_race = true; - } - } - else if(p == "forget") + out << "You must specify a command!" << endl; + out << autobutcher_help << endl; + return CR_OK; + } + + // parse main command + string & p = parameters[0]; + if (p == "help" || p == "?") + { + out << autobutcher_help << endl; + return CR_OK; + } + if (p == "example") + { + out << autobutcher_help_example << endl; + return CR_OK; + } + else if (p == "start") + { + enable_autobutcher = true; + out << "Autobutcher started."; + return autoButcher(out, verbose); + } + else if (p == "stop") + { + enable_autobutcher = false; + out << "Autobutcher stopped."; + return CR_OK; + } + else if(p == "sleep") + { + parameters.erase(parameters.begin()); + if(!parameters.size()) { - if(i == parameters.size()-1) - { - out.printerr("No race specified!"); - return CR_WRONG_USAGE; - } - else - { - // todo: parse more than one race - target_racename = parameters[i+1]; - i++; - out << "Forget settings for race " << target_racename << endl; - forget_race = true; - } + out.printerr("No duration specified!"); + return CR_WRONG_USAGE; } - else if(p == "target") + else { - // needs at least 5 more parameters: - // fk mk fa ma R (can have more than 1 R) - if(parameters.size() < 6) + size_t ticks = 0; + stringstream ss(parameters.back()); + //i++; + ss >> ticks; + if(ticks <= 0) { - out.printerr("Not enough parameters!"); + out.printerr("Invalid duration specified (must be > 0)!"); return CR_WRONG_USAGE; } - else - { - stringstream fk(parameters[i+1]); - stringstream mk(parameters[i+2]); - stringstream fa(parameters[i+3]); - stringstream ma(parameters[i+4]); - fk >> target_fk; - mk >> target_mk; - fa >> target_fa; - ma >> target_ma; - - // todo: parse more than one race, handle 'all' and 'new' - target_racename = parameters[i+5]; - i+=5; - out << "Target count for " << target_racename << ":" - << " fk=" << target_fk - << " mk=" << target_mk - << " fa=" << target_fa - << " ma=" << target_ma - << endl; - change_target = true; - } - } - else if(p == "autowatch") - { - out << "not supported yet, sorry" << endl; + sleep_autobutcher = ticks; + out << "New sleep timer for autobutcher: " << ticks << " ticks." << endl; return CR_OK; } - else if(p == "noautowatch") - { - out << "not supported yet, sorry" << endl; - return CR_OK; - } - else if(p == "list") + } + else if(p == "watch") + { + parameters.erase(parameters.begin()); + watch_race = true; + } + else if(p == "unwatch") + { + parameters.erase(parameters.begin()); + unwatch_race = true; + } + else if(p == "forget") + { + parameters.erase(parameters.begin()); + forget_race = true; + } + else if(p == "target") + { + // needs at least 5 more parameters: + // fk mk fa ma R (can have more than 1 R) + if(parameters.size() < 6) { - list_watched = true; + out.printerr("Not enough parameters!"); + return CR_WRONG_USAGE; } else { - out << "Unknown command: " << p << endl; - return CR_WRONG_USAGE; + stringstream fk(parameters[1]); + stringstream mk(parameters[2]); + stringstream fa(parameters[3]); + stringstream ma(parameters[4]); + fk >> target_fk; + mk >> target_mk; + fa >> target_fa; + ma >> target_ma; + parameters.erase(parameters.begin(), parameters.begin()+5); + change_target = true; } } - - if( target_racename == "all" || - target_racename == "new" ) + else if(p == "autowatch") { - out << "'all' and 'new' are not supported yet, sorry." << endl; + out << "not supported yet, sorry" << endl; return CR_OK; } + else if(p == "noautowatch") + { + out << "not supported yet, sorry" << endl; + return CR_OK; + } + else if(p == "list") + { + list_watched = true; + } + else + { + out << "Unknown command: " << p << endl; + return CR_WRONG_USAGE; + } if(list_watched) { @@ -2248,102 +2202,123 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) return CR_OK; } - size_t num_races = df::global::world->raws.creatures.all.size(); - bool found_race = false; - for(size_t i=0; iraws.creatures.all[i]; - if(raw->creature_id == target_racename) + if(!parameters.size()) { - target_raceid = i; - found_race = true; - break; + out.printerr("No race(s) specified!"); + return CR_WRONG_USAGE; + } + while(parameters.size()) + { + string tr = parameters.back(); + target_racenames.push_back(tr); + parameters.pop_back(); } } - if(!found_race) + + if( target_racenames.size() && + (target_racenames[0] == "all" || + target_racenames[0] == "new") ) { - out << "Race not found!" << endl; + out << "'all' and 'new' are not supported yet, sorry." << endl; return CR_OK; } - if(unwatch_race) + size_t num_races = df::global::world->raws.creatures.all.size(); + while(target_racenames.size()) { - bool found = false; - for(size_t i=0; iraceId == target_raceid) + df::creature_raw *raw = df::global::world->raws.creatures.all[i]; + if(raw->creature_id == target_racenames.back()) { - found=true; - w->isWatched=false; + target_raceids.push_back(i); + target_racenames.pop_back(); + found_race = true; break; } } - if(found) - out << target_racename << " is not watched anymore." << endl; - else - out << target_racename << " was not being watched!" << endl; - return CR_OK; + if(!found_race) + { + out << "Race not found: " << target_racenames.back() << endl; + return CR_OK; + } } - if(watch_race || change_target) + if(unwatch_race) { - bool watching = false; - for(size_t i=0; iraceId == target_raceid) + for(size_t i=0; iraceId == target_raceids.back()) { - if(w->isWatched) - out << target_racename << " is already being watched." << endl; - w->isWatched = true; - watching = true; + w->isWatched=false; + target_raceids.pop_back(); + break; } + } + } + return CR_OK; + } - if(change_target) + if(watch_race || change_target) + { + while(target_raceids.size()) + { + bool entry_found = false; + for(size_t i=0; iraceId == target_raceids.back()) { - w->fk = target_fk; - w->mk = target_mk; - w->fa = target_fa; - w->ma = target_ma; + if(watch_race) + { + w->isWatched = true; + } + else if(change_target) + { + w->fk = target_fk; + w->mk = target_mk; + w->fa = target_fa; + w->ma = target_ma; + } + entry_found = true; + break; } - break; } + if(!entry_found) + { + WatchedRace * w = new WatchedRace(watch_race, target_raceids.back(), target_fk, target_mk, target_fa, target_ma); + watched_races.push_back(w); + } + target_raceids.pop_back(); } - if(!watching) - { - WatchedRace * w = new WatchedRace(watch_race, target_raceid, target_fk, target_mk, target_fa, target_ma); - watched_races.push_back(w); - } - out << target_racename << " is now being watched." << endl; return CR_OK; } if(forget_race) { - bool watched = false; - for(size_t i=0; iraceId == target_raceid) + for(size_t i=0; iisWatched) - out << target_racename << " is already being watched." << endl; - w->isWatched = true; - watched = true; - break; + WatchedRace * w = watched_races[i]; + if(w->raceId == target_raceids.back()) + { + watched_races.erase(watched_races.begin()+i); + break; + } } + target_raceids.pop_back(); } - if(!watched) - { - out << target_racename << " was not on the watchlist." << endl; - return CR_OK; - } - out << target_racename << " was forgotten." << endl; return CR_OK; } From e0a535897b2f198bd5897e9050378fc797891f77 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sat, 7 Apr 2012 05:21:16 +0200 Subject: [PATCH 017/108] minor: updated readme.rst --- README.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index f9d816f5e..ef6de9195 100644 --- a/README.rst +++ b/README.rst @@ -872,7 +872,7 @@ Options: :start: Start running every X frames (df simulation ticks). Default: X=6000, which would be every 60 seconds at 100fps. :stop: Stop running automatically. :sleep: Must be followed by number X. Changes the timer to sleep X frames between runs. -:watch R: Start watching a race. R must be a valid race RAW id (ALPACA, BIRD_TURKEY, etc). +:watch R: Start watching a race. R must be a valid race RAW id (ALPACA, BIRD_TURKEY, etc) or a list of ids seperated by spaces. :unwatch R: Stop watching a race. The current target settings will be remembered (currently only until you save or quit the game). :forget R: Stop watching a race and forget it's target settings. :list: Print a list of watched races. @@ -885,10 +885,11 @@ Options: Examples: --------- -You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, 1 male) of the race alpaca. Once the kids grow up the oldest adults will get slaughtered. Excess kids will get slaughtered starting with the youngest to allow that the older ones grow into adults. +You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, 1 male) of the race alpaca. Once the kids grow up the oldest adults will get slaughtered. Excess kids will get slaughtered starting with the youngest to allow that the older ones grow into adults. Any unnamed cats will be slaughtered as soon as possible. :: - autobutcher target 4 3 2 1 ALPACA - autobutcher watch ALPACA + autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY + autobutcher target 0 0 0 0 CAT + autobutcher watch ALPACA BIRD_TURKEY CAT autobutcher start From e3fb922f53a1d9a97eaa181f60845caf6a1cfd24 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sat, 7 Apr 2012 06:47:32 +0200 Subject: [PATCH 018/108] fixed handling of units in cages who have an invalid map pos. --- plugins/zone.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 2179d2b06..4930060e1 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -597,7 +597,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) if(verbose) { - //out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ")" << endl; + out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ")" << endl; if(isEggLayer(unit)) out << ", egglayer"; if(isGrazer(unit)) @@ -1640,13 +1640,17 @@ command_result df_zone (color_ostream &out, vector & parameters) ) continue; - if(!hasValidMapPos(unit)) + // animals bought in cages have an invalid map pos until they are freed for the first time + // but if they are not in a cage and have an invalid pos it's better not to touch them + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) { - uint32_t max_x, max_y, max_z; - Maps::getSize(max_x, max_y, max_z); - out << "("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << "). will skip this unit" << endl; + if(verbose) + { + out << "----------"< & parameters) return CR_OK; } + // map race names to ids size_t num_races = df::global::world->raws.creatures.all.size(); while(target_racenames.size()) { @@ -2363,13 +2368,14 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) || isMarkedForSlaughter(unit) || !isOwnCiv(unit) || !isTame(unit) + || (isContainedInItem(unit) && hasValidMapPos(unit) && isBuiltCageAtPos(unit->pos)) || unit->name.has_name ) continue; - // found a bugged unit which had invalid coordinates. + // found a bugged unit which had invalid coordinates but was not in a cage. // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!hasValidMapPos(unit)) + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) continue; int watched_index = getWatchedIndex(unit->race); From 55e059c40fd1e72af24fd49489cf1113526aecd3 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sat, 7 Apr 2012 11:15:49 +0200 Subject: [PATCH 019/108] autobutcher: added keywords 'all' and 'new' for handling the whole watchlist. added option 'autowatch' which will put all new tame animal races onto the watchlist using the current default settings --- README.rst | 24 +++++-- plugins/zone.cpp | 171 +++++++++++++++++++++-------------------------- 2 files changed, 95 insertions(+), 100 deletions(-) diff --git a/README.rst b/README.rst index ef6de9195..2183d7064 100644 --- a/README.rst +++ b/README.rst @@ -872,16 +872,19 @@ Options: :start: Start running every X frames (df simulation ticks). Default: X=6000, which would be every 60 seconds at 100fps. :stop: Stop running automatically. :sleep: Must be followed by number X. Changes the timer to sleep X frames between runs. -:watch R: Start watching a race. R must be a valid race RAW id (ALPACA, BIRD_TURKEY, etc) or a list of ids seperated by spaces. +:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, BIRD_TURKEY, etc) or a list of ids seperated by spaces or the keyword 'all' which adds all races with at least one owned tame unit in your fortress to the list. :unwatch R: Stop watching a race. The current target settings will be remembered (currently only until you save or quit the game). :forget R: Stop watching a race and forget it's target settings. -:list: Print a list of watched races. +:autowatch: Automatically adds all new races (animals you buy from merchants, tame yourself or get from migrants) + to the watch list using default target count. +:noautowatch: Stop auto-adding new races to the watchlist. +:list: Print a list of watched races. :target fk mk fa ma R: Set target count for specified race(s). - fk = number of female kids - mk = number of male kids - fa = number of female adults - ma = number of female adults -:example: Print some usage examples. + fk = number of female kids, + mk = number of male kids, + fa = number of female adults, + ma = number of female adults. +:example: Print some usage examples. Examples: --------- @@ -893,3 +896,10 @@ You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, 1 mal autobutcher watch ALPACA BIRD_TURKEY CAT autobutcher start +Automatically put all new races onto the watchlist and mark unnamed tame units for slaughter as soon as they arrive in your fort. Settings already made for specific races will be left untouched. +:: + + autobutcher target 0 0 0 0 new + autobutcher autowatch + autobutcher start + diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 4930060e1..61ed2007c 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -19,10 +19,8 @@ // maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds // - full automation of marking live-stock for slaughtering // races can be added to a watchlist and it can be set how many male/female kids/adults are left alive -// TODO: - parse more than one race in one command -// - support keywords 'all' and 'new' -// - autowatch -// - save config +// adding to the watchlist can be automated as well. +// TODO: - save config #include #include @@ -173,14 +171,15 @@ const string autobutcher_help = " watch R - start watching race(s)\n" " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n" " or a list of RAW ids seperated by spaces\n" - //" or the keyword 'all' which adds all races with\n" - //" at least one owned tame unit in your fortress\n" + " or the keyword 'all' which adds all races with\n" + " at least one owned tame unit in your fortress\n" " unwatch R - stop watching race(s)\n" " the current target settings will be remembered\n" " forget R - unwatch race(s) and forget target settings for it/them\n" - //" autowatch - automatically adds all new races (animals you buy\n" - //" from merchants, tame yourself or get from migrants)\n" - //" to the watch list using default target count\n" + " autowatch - automatically adds all new races (animals you buy\n" + " from merchants, tame yourself or get from migrants)\n" + " to the watch list using default target count\n" + " noautowatch - stop auto-adding new races to the watch list\n" " list - print a list of watched races\n" " target fk mk fa ma R\n" " - set target count for specified race:\n" @@ -188,11 +187,11 @@ const string autobutcher_help = " mk = number of male kids\n" " fa = number of female adults\n" " ma = number of female adults\n" - //" R = 'all' sets count for all races on the current watchlist\n" - //" including the races which are currenly set to 'unwatched'\n" - //" and sets the new default for future watch commands\n" - //" R = 'new' sets the new default for future watch commands\n" - //" without changing your current watchlist\n" + " R = 'all' sets count for all races on the current watchlist\n" + " including the races which are currenly set to 'unwatched'\n" + " and sets the new default for future watch commands\n" + " R = 'new' sets the new default for future watch commands\n" + " without changing your current watchlist\n" " example - print some usage examples\n"; const string autobutcher_help_example = @@ -204,13 +203,12 @@ const string autobutcher_help_example = " (2 female, 1 male) of the races alpaca and turkey. Once the kids grow up the\n" " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n" " the the youngest to allow that the older ones grow into adults.\n" - //" autobutcher target 0 0 0 0 all\n" - //" autobutcher autowatch\n" - //" autobutcher start\n" - //" This tells autobutcher to automatically put all new races onto the watchlist\n" - //" and mark unnamed tame units for slaughter as soon as they arrive in your\n" - //" fortress. Settings already made for some races will be left untouched.\n" - ; + " autobutcher target 0 0 0 0 new\n" + " autobutcher autowatch\n" + " autobutcher start\n" + " This tells autobutcher to automatically put all new races onto the watchlist\n" + " and mark unnamed tame units for slaughter as soon as they arrive in your\n" + " fortress. Settings already made for some races will be left untouched.\n"; DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) @@ -249,6 +247,7 @@ command_result autoButcher( color_ostream &out, bool verbose ); static bool enable_autonestbox = false; static bool enable_autobutcher = false; +static bool enable_autobutcher_autowatch = false; static size_t sleep_autonestbox = 6000; static size_t sleep_autobutcher = 6000; static bool autonestbox_did_complain = false; // avoids message spam @@ -2035,6 +2034,14 @@ public: // to ignore them for a while but still keep the target count settings std::vector watched_races; +// default target values +// move those somewhere else for persistency +static int default_fk = 5; +static int default_mk = 1; +static int default_fa = 5; +static int default_ma = 1; + + command_result autobutcher_cleanup(color_ostream &out) { for(size_t i=0; i & parameters) { - // move those somewhere else for persistency - static int default_fk = 5; - static int default_mk = 1; - static int default_fa = 5; - static int default_ma = 1; - CoreSuspender suspend; bool verbose = false; @@ -2115,7 +2116,6 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) { size_t ticks = 0; stringstream ss(parameters.back()); - //i++; ss >> ticks; if(ticks <= 0) { @@ -2167,12 +2167,14 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) } else if(p == "autowatch") { - out << "not supported yet, sorry" << endl; + out << "Auto-adding to watchlist started." << endl; + enable_autobutcher_autowatch = true; return CR_OK; } else if(p == "noautowatch") { - out << "not supported yet, sorry" << endl; + out << "Auto-adding to watchlist stopped." << endl; + enable_autobutcher_autowatch = false; return CR_OK; } else if(p == "list") @@ -2225,11 +2227,26 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) } } - if( target_racenames.size() && - (target_racenames[0] == "all" || - target_racenames[0] == "new") ) + if(target_racenames.size() && target_racenames[0] == "all") + { + out << "Setting target count for all races on watchlist." << endl; + for(size_t i=0; ifk = target_fk; + w->mk = target_mk; + w->fa = target_fa; + w->ma = target_ma; + } + } + + if(target_racenames.size() && (target_racenames[0] == "all" || target_racenames[0] == "new")) { - out << "'all' and 'new' are not supported yet, sorry." << endl; + out << "Setting target count for the future." << endl; + default_fk = target_fk; + default_mk = target_mk; + default_fa = target_fa; + default_ma = target_ma; return CR_OK; } @@ -2256,75 +2273,37 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) } } - if(unwatch_race) + while(target_raceids.size()) { - while(target_raceids.size()) + bool entry_found = false; + for(size_t i=0; iraceId == target_raceids.back()) { - WatchedRace * w = watched_races[i]; - if(w->raceId == target_raceids.back()) - { + if(unwatch_race) w->isWatched=false; - target_raceids.pop_back(); - break; - } - } - } - return CR_OK; - } - - if(watch_race || change_target) - { - while(target_raceids.size()) - { - bool entry_found = false; - for(size_t i=0; iraceId == target_raceids.back()) + else if(forget_race) + watched_races.erase(watched_races.begin()+i); + else if(watch_race) + w->isWatched = true; + else if(change_target) { - if(watch_race) - { - w->isWatched = true; - } - else if(change_target) - { - w->fk = target_fk; - w->mk = target_mk; - w->fa = target_fa; - w->ma = target_ma; - } - entry_found = true; - break; + w->fk = target_fk; + w->mk = target_mk; + w->fa = target_fa; + w->ma = target_ma; } + entry_found = true; + break; } - if(!entry_found) - { - WatchedRace * w = new WatchedRace(watch_race, target_raceids.back(), target_fk, target_mk, target_fa, target_ma); - watched_races.push_back(w); - } - target_raceids.pop_back(); } - return CR_OK; - } - - if(forget_race) - { - while(target_raceids.size()) + if(!entry_found && (watch_race||change_target)) { - for(size_t i=0; iraceId == target_raceids.back()) - { - watched_races.erase(watched_races.begin()+i); - break; - } - } - target_raceids.pop_back(); + WatchedRace * w = new WatchedRace(watch_race, target_raceids.back(), target_fk, target_mk, target_fa, target_ma); + watched_races.push_back(w); } - return CR_OK; + target_raceids.pop_back(); } return CR_OK; @@ -2384,6 +2363,12 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) WatchedRace * w = watched_races[watched_index]; w->PushUnit(unit); } + else if(enable_autobutcher_autowatch) + { + WatchedRace * w = new WatchedRace(true, unit->race, default_fk, default_mk, default_fa, default_ma); + watched_races.push_back(w); + w->PushUnit(unit); + } } int slaughter_count = 0; From e74788cb26edd41e484071ac3a371422c6be8773 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 7 Apr 2012 14:21:38 +0400 Subject: [PATCH 020/108] Add a generic facility for object finalization during stack unwind. Supports two modes of finalization: - try {...} finally {...} - try {...} catch { ...; throw } Argument passing discipline is designed with vararg tail calls in mind. --- LUA_API.rst | 25 ++++++ Lua API.html | 22 +++++- library/LuaTools.cpp | 167 +++++++++++++++++++++++++++++++++++------ library/lua/dfhack.lua | 15 ++++ 4 files changed, 203 insertions(+), 26 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 0ec837a2d..57a3fa16c 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -456,6 +456,31 @@ Currently it defines the following features: to group operations together in one big critical section. A plugin can choose to run all lua code inside a C++-side suspend lock. +* ``dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])`` + + Invokes ``fn`` with ``args``, and after it returns or throws an + error calls ``cleanup_fn`` with ``cleanup_args``. Any return values from + ``fn`` are propagated, and errors are re-thrown. + + The ``num_cleanup_args`` integer specifies the number of ``cleanup_args``, + and the ``always`` boolean specifies if cleanup should be called in any case, + or only in case of an error. + +* ``dfhack.with_finalize(cleanup_fn,fn[,args...])`` + + Calls ``fn`` with arguments, then finalizes with ``cleanup_fn``. + Implemented using ``call_with_finalizer(0,true,...)``. + +* ``dfhack.with_onerror(cleanup_fn,fn[,args...])`` + + Calls ``fn`` with arguments, then finalizes with ``cleanup_fn`` on any thrown error. + Implemented using ``call_with_finalizer(0,false,...)``. + +* ``dfhack.with_temp_object(obj,fn[,args...])`` + + Calls ``fn(obj,args...)``, then finalizes with ``obj:delete()``. + + Persistent configuration storage ================================ diff --git a/Lua API.html b/Lua API.html index 379432a78..19f912b72 100644 --- a/Lua API.html +++ b/Lua API.html @@ -724,6 +724,25 @@ the lock. It is safe to nest suspends.

        to group operations together in one big critical section. A plugin can choose to run all lua code inside a C++-side suspend lock.

      • +
      • dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])

        +

        Invokes fn with args, and after it returns or throws an +error calls cleanup_fn with cleanup_args. Any return values from +fn are propagated, and errors are re-thrown.

        +

        The num_cleanup_args integer specifies the number of cleanup_args, +and the always boolean specifies if cleanup should be called in any case, +or only in case of an error.

        +
      • +
      • dfhack.with_finalize(cleanup_fn,fn[,args...])

        +

        Calls fn with arguments, then finalizes with cleanup_fn. +Implemented using call_with_finalizer(0,true,...).

        +
      • +
      • dfhack.with_onerror(cleanup_fn,fn[,args...])

        +

        Calls fn with arguments, then finalizes with cleanup_fn on any thrown error. +Implemented using call_with_finalizer(0,false,...).

        +
      • +
      • dfhack.with_temp_object(obj,fn[,args...])

        +

        Calls fn(obj,args...), then finalizes with obj:delete().

        +

      Persistent configuration storage

      @@ -799,7 +818,8 @@ or obj.mat_type/o

      Returns the classification used for craft skills.

    • info:matches(obj)

      -

      Checks if the material matches job_material_category or job_item.

      +

      Checks if the material matches job_material_category or job_item. +Accept dfhack_material_category auto-assign table.

    diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 1e7a83b30..471ffd550 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -226,10 +226,14 @@ static int lua_dfhack_lineedit(lua_State *S) static int DFHACK_EXCEPTION_META_TOKEN = 0; -static void error_tostring(lua_State *L) +static void error_tostring(lua_State *L, bool keep_old = false) { lua_getglobal(L, "tostring"); - lua_pushvalue(L, -2); + if (keep_old) + lua_pushvalue(L, -2); + else + lua_swap(L); + bool ok = lua_pcall(L, 1, 1, 0) == LUA_OK; const char *msg = lua_tostring(L, -1); @@ -248,8 +252,7 @@ static void error_tostring(lua_State *L) static void report_error(lua_State *L, color_ostream *out = NULL) { - lua_dup(L); - error_tostring(L); + error_tostring(L, true); const char *msg = lua_tostring(L, -1); assert(msg); @@ -290,7 +293,7 @@ static bool convert_to_exception(lua_State *L) lua_setfield(L, base, "message"); else { - error_tostring(L); + error_tostring(L, true); lua_setfield(L, base, "message"); lua_setfield(L, base, "object"); } @@ -346,24 +349,51 @@ static int dfhack_exception_tostring(lua_State *L) if (!lua_isstring(L, -1)) lua_pop(L, 2); + lua_pushstring(L, "\ncaused by:\n"); + lua_getfield(L, 1, "cause"); + if (lua_isnil(L, -1)) + lua_pop(L, 2); + else + error_tostring(L); + lua_concat(L, lua_gettop(L) - base); return 1; } -static int finish_dfhack_safecall (lua_State *L, bool success) +static void push_simple_error(lua_State *L, const char *str) { - if (!lua_checkstack(L, 2)) + lua_pushstring(L, str); + + if (lua_checkstack(L, 5)) + convert_to_exception(L); + + if (lua_checkstack(L, LUA_MINSTACK)) + { + luaL_traceback(L, L, NULL, 1); + lua_setfield(L, -2, "stacktrace"); + } +} + +static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space = 2) +{ + if (!lua_checkstack(L, space)) { - lua_settop(L, 0); /* create space for return values */ + lua_settop(L, base-1); /* create space for return values */ lua_pushboolean(L, 0); - lua_pushstring(L, "stack overflow in dfhack.safecall()"); - success = false; + push_simple_error(L, "stack overflow"); + return false; } else { lua_pushboolean(L, success); - lua_replace(L, 1); /* put first result in first slot */ + lua_replace(L, base); /* put first result in first slot */ + return true; } +} + +static int finish_dfhack_safecall (lua_State *L, bool success) +{ + success = do_finish_pcall(L, success); if (!success) report_error(L); @@ -599,32 +629,118 @@ static int lua_dfhack_interpreter(lua_State *state) return 1; } -static int lua_dfhack_with_suspend(lua_State *L) +static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool success) { - int rv = lua_getctx(L, NULL); + bool ok = lua_pcall(L, nargs, 0, errorfun) == LUA_OK; - // Non-resume entry point: - if (rv == LUA_OK) + if (!ok) { - int nargs = lua_gettop(L); + // If finalization failed, attach the previous error + if (lua_istable(L, -1) && !success) + { + lua_swap(L); + lua_setfield(L, -2, "cause"); + } - luaL_checktype(L, 1, LUA_TFUNCTION); + success = false; + } + + return success; +} - Core::getInstance().Suspend(); +static int finish_dfhack_cleanup (lua_State *L, bool success) +{ + int nargs = lua_tointeger(L, 1); + bool always = lua_toboolean(L, 2); + int rvbase = 4+nargs; + + // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [rvals/error...] - lua_pushcfunction(L, dfhack_onerror); - lua_insert(L, 1); + int numret = lua_gettop(L) - rvbase; + + if (!success || always) + { + if (numret > 0) + { + if (numret == 1) + { + // Inject the only result instead of pulling cleanup args + lua_insert(L, 4); + } + else if (!lua_checkstack(L, nargs+1)) + { + success = false; + lua_settop(L, rvbase); + push_simple_error(L, "stack overflow"); + lua_insert(L, 4); + } + else + { + for (int i = 0; i <= nargs; i++) + lua_pushvalue(L, 4+i); + } + } - rv = lua_pcallk(L, nargs-1, LUA_MULTRET, 1, 0, lua_dfhack_with_suspend); + success = do_invoke_cleanup(L, nargs, 3, success); } - // Return, resume, or error entry point: - lua_remove(L, 1); + if (!success) + lua_error(L); + + return numret; +} + +static int dfhack_cleanup_cont (lua_State *L) +{ + int status = lua_getctx(L, NULL); + return finish_dfhack_cleanup(L, (status == LUA_YIELD)); +} - Core::getInstance().Resume(); +static int dfhack_call_with_finalizer (lua_State *L) +{ + int nargs = luaL_checkint(L, 1); + if (nargs < 0) + luaL_argerror(L, 1, "invalid cleanup argument count"); + luaL_checktype(L, 3, LUA_TFUNCTION); - if (rv != LUA_OK && rv != LUA_YIELD) + // Inject errorfun + lua_pushcfunction(L, dfhack_onerror); + lua_insert(L, 3); + + int rvbase = 4+nargs; // rvbase+1 points to the function argument + + if (lua_gettop(L) < rvbase) + luaL_error(L, "not enough arguments even to invoke cleanup"); + + // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...] + + // Not enough stack to call and post-cleanup, or nothing to call? + bool no_args = lua_gettop(L) == rvbase; + + if (!lua_checkstack(L, nargs+2) || no_args) + { + push_simple_error(L, no_args ? "fn argument expected" : "stack overflow"); + lua_insert(L, 4); + + // stack: ... [errorfun] [error] [cleanup fun] [cleanup args...] + do_invoke_cleanup(L, nargs, 3, false); lua_error(L); + } + + // Actually invoke + + // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...] + int status = lua_pcallk(L, lua_gettop(L)-rvbase-1, LUA_MULTRET, 3, 0, dfhack_cleanup_cont); + return finish_dfhack_cleanup(L, (status == LUA_OK)); +} + +static int lua_dfhack_with_suspend(lua_State *L) +{ + int nargs = lua_gettop(L); + luaL_checktype(L, 1, LUA_TFUNCTION); + + CoreSuspender suspend; + lua_call(L, nargs-1, LUA_MULTRET); return lua_gettop(L); } @@ -639,6 +755,7 @@ static const luaL_Reg dfhack_funcs[] = { { "interpreter", lua_dfhack_interpreter }, { "safecall", lua_dfhack_safecall }, { "onerror", dfhack_onerror }, + { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, { NULL, NULL } }; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index d662efed7..b37183cb6 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -29,6 +29,21 @@ function dfhack.pcall(f, ...) return xpcall(f, dfhack.onerror, ...) end +function dfhack.with_finalize(...) + return dfhack.call_with_finalizer(0,true,...) +end +function dfhack.with_onerror(...) + return dfhack.call_with_finalizer(0,false,...) +end + +local function call_delete(obj) + if obj then obj:delete() end +end + +function dfhack.with_temp_object(obj,fn,...) + return dfhack.call_with_finalizer(1,true,call_delete,obj,fn,obj,...) +end + -- Module loading function mkmodule(module,env) From 45ae2ed67f9aba4d635a7565253bd9098a87bbde Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 7 Apr 2012 19:08:30 +0400 Subject: [PATCH 021/108] Pull a few utility functions into the core and publish to lua. --- LUA_API.rst | 10 ++++- Lua API.html | 8 +++- library/LuaApi.cpp | 3 ++ library/include/modules/Translation.h | 2 + library/include/modules/Units.h | 8 ++++ library/modules/Gui.cpp | 4 ++ library/modules/Translation.cpp | 15 +++++++ library/modules/Units.cpp | 59 ++++++++++++++++++++++++++ plugins/advtools.cpp | 33 +++------------ plugins/rename.cpp | 60 +++------------------------ 10 files changed, 117 insertions(+), 85 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 57a3fa16c..71cb6030f 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -644,9 +644,17 @@ Job module Units module ------------ +* ``dfhack.units.setNickname(unit,nick)`` + + Sets the unit's nickname properly. + * ``dfhack.units.getVisibleName(unit)`` - Returns the name visible in game, accounting for false identities. + Returns the language_name object visible in game, accounting for false identities. + +* ``dfhack.units.getNemesis(unit)`` + + Returns the nemesis record of the unit if it has one, or *nil*. * ``dfhack.units.isDead(unit)`` diff --git a/Lua API.html b/Lua API.html index 19f912b72..569940f56 100644 --- a/Lua API.html +++ b/Lua API.html @@ -883,8 +883,14 @@ The is_bright boolean actually seems to invert the brightness.

    Units module

      +
    • dfhack.units.setNickname(unit,nick)

      +

      Sets the unit's nickname properly.

      +
    • dfhack.units.getVisibleName(unit)

      -

      Returns the name visible in game, accounting for false identities.

      +

      Returns the language_name object visible in game, accounting for false identities.

      +
    • +
    • dfhack.units.getNemesis(unit)

      +

      Returns the nemesis record of the unit if it has one, or nil.

    • dfhack.units.isDead(unit)

      The unit is completely dead and passive.

      diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index f1d86580a..5e55460d7 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -55,6 +55,7 @@ distribution. #include "df/unit.h" #include "df/item.h" #include "df/material.h" +#include "df/nemesis_record.h" #include "df/historical_figure.h" #include "df/plant_raw.h" #include "df/creature_raw.h" @@ -555,7 +556,9 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { }; static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, setNickname), WRAPM(Units, getVisibleName), + WRAPM(Units, getNemesis), WRAPM(Units, isDead), WRAPM(Units, isAlive), WRAPM(Units, isSane), diff --git a/library/include/modules/Translation.h b/library/include/modules/Translation.h index 19a3ed9a6..6d74431e3 100644 --- a/library/include/modules/Translation.h +++ b/library/include/modules/Translation.h @@ -50,6 +50,8 @@ DFHACK_EXPORT bool IsValid (); DFHACK_EXPORT bool readName(t_name & name, df::language_name * address); DFHACK_EXPORT bool copyName(df::language_name * address, df::language_name * target); +DFHACK_EXPORT void setNickname(df::language_name *name, std::string nick); + // translate a name using the loaded dictionaries DFHACK_EXPORT std::string TranslateName (const df::language_name * name, bool inEnglish = true, bool onlyLastPart = false); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index b966a39be..838d1d596 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -33,6 +33,11 @@ distribution. #include "DataDefs.h" #include "df/unit.h" +namespace df +{ + struct nemesis_record; +} + /** * \defgroup grp_units Unit module parts * @ingroup grp_modules @@ -193,8 +198,11 @@ DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); DFHACK_EXPORT bool RemoveOwnedItemByIdx(const uint32_t index, int32_t id); DFHACK_EXPORT bool RemoveOwnedItemByPtr(df::unit * unit, int32_t id); +DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); +DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); + DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit); diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index aae2a90b9..592f27c0b 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -46,6 +46,7 @@ using namespace DFHack; #include "df/global_objects.h" #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dungeonmodest.h" +#include "df/viewscreen_dungeon_monsterstatusst.h" #include "df/viewscreen_joblistst.h" #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_itemst.h" @@ -284,6 +285,9 @@ static df::unit *getAnyUnit(df::viewscreen *top) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitlistst, top)) return vector_get(screen->units[screen->page], screen->cursor_pos[screen->page]); + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_dungeon_monsterstatusst, top)) + return screen->unit; + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_itemst, top)) { df::general_ref *ref = vector_get(screen->entry_ref, screen->cursor_pos); diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index bc0724707..6b055a4ac 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -92,6 +92,21 @@ void addNameWord (string &out, const string &word) out.append(upper); } +void Translation::setNickname(df::language_name *name, std::string nick) +{ + CHECK_NULL_POINTER(name); + + if (!name->has_name) + { + *name = df::language_name(); + + name->language = 0; + name->has_name = true; + } + + name->nickname = nick; +} + string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart) { CHECK_NULL_POINTER(name); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 3dc5d1b04..e210a9028 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -48,6 +48,8 @@ using namespace std; #include "df/world.h" #include "df/ui.h" #include "df/unit_inventory_item.h" +#include "df/unit_soul.h" +#include "df/nemesis_record.h" #include "df/historical_entity.h" #include "df/historical_figure.h" #include "df/historical_figure_info.h" @@ -527,6 +529,47 @@ void Units::CopyNameTo(df::unit * creature, df::language_name * target) Translation::copyName(&creature->name, target); } +void Units::setNickname(df::unit *unit, std::string nick) +{ + CHECK_NULL_POINTER(unit); + + // There are >=3 copies of the name, and the one + // in the unit is not the authoritative one. + // This is the reason why military units often + // lose nicknames set from Dwarf Therapist. + Translation::setNickname(&unit->name, nick); + + if (unit->status.current_soul) + Translation::setNickname(&unit->status.current_soul->name, nick); + + df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); + if (figure) + { + Translation::setNickname(&figure->name, nick); + + // v0.34.01: added the vampire's assumed identity + if (figure->info && figure->info->reputation) + { + auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity); + + if (identity) + { + auto id_hfig = df::historical_figure::find(identity->histfig_id); + + if (id_hfig) + { + // Even DF doesn't do this bit, because it's apparently + // only used for demons masquerading as gods, so you + // can't ever change their nickname in-game. + Translation::setNickname(&id_hfig->name, nick); + } + else + Translation::setNickname(&identity->name, nick); + } + } + } +} + df::language_name *Units::getVisibleName(df::unit *unit) { CHECK_NULL_POINTER(unit); @@ -555,6 +598,22 @@ df::language_name *Units::getVisibleName(df::unit *unit) return &unit->name; } +df::nemesis_record *Units::getNemesis(df::unit *unit) +{ + if (!unit) + return NULL; + + for (unsigned i = 0; i < unit->refs.size(); i++) + { + df::nemesis_record *rv = unit->refs[i]->getNemesis(); + if (rv && rv->unit == unit) + return rv; + } + + return NULL; +} + + bool DFHack::Units::isDead(df::unit *unit) { CHECK_NULL_POINTER(unit); diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index 4fc1b4e5d..ffb428fd8 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -8,6 +8,8 @@ #include "modules/Materials.h" #include "modules/Maps.h" #include "modules/Items.h" +#include "modules/Gui.h" +#include "modules/Units.h" #include "DataDefs.h" #include "df/world.h" @@ -159,31 +161,6 @@ static bool bodyswap_hotkey(df::viewscreen *top) !!virtual_cast(top); } -df::unit *getCurUnit() -{ - auto top = Core::getTopViewscreen(); - - if (VIRTUAL_CAST_VAR(ms, df::viewscreen_dungeon_monsterstatusst, top)) - return ms->unit; - - return NULL; -} - -df::nemesis_record *getNemesis(df::unit *unit) -{ - if (!unit) - return NULL; - - for (unsigned i = 0; i < unit->refs.size(); i++) - { - df::nemesis_record *rv = unit->refs[i]->getNemesis(); - if (rv && rv->unit == unit) - return rv; - } - - return NULL; -} - bool bodySwap(color_ostream &out, df::unit *player) { if (!player) @@ -219,7 +196,7 @@ df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap) if (restore_swap) { df::unit *ctl = world->units.other[0][0]; - auto ctl_nemesis = getNemesis(ctl); + auto ctl_nemesis = Units::getNemesis(ctl); if (ctl_nemesis != real_nemesis) { @@ -672,8 +649,8 @@ command_result adv_bodyswap (color_ostream &out, std::vector & par return CR_FAILURE; // Get the unit to swap to - auto new_unit = getCurUnit(); - auto new_nemesis = getNemesis(new_unit); + auto new_unit = Gui::getSelectedUnit(out, true); + auto new_nemesis = Units::getNemesis(new_unit); if (!new_nemesis) { diff --git a/plugins/rename.cpp b/plugins/rename.cpp index d983d0962..7302aea60 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -4,6 +4,8 @@ #include "PluginManager.h" #include "modules/Gui.h" +#include "modules/Translation.h" +#include "modules/Units.h" #include "DataDefs.h" #include "df/ui.h" @@ -59,19 +61,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -static void set_nickname(df::language_name *name, std::string nick) -{ - if (!name->has_name) - { - *name = df::language_name(); - - name->language = 0; - name->has_name = true; - } - - name->nickname = nick; -} - static df::squad *getSquadByIndex(unsigned idx) { auto entity = df::historical_entity::find(ui->group_id); @@ -84,45 +73,6 @@ static df::squad *getSquadByIndex(unsigned idx) return df::squad::find(entity->squads[idx]); } -void setUnitNickname(df::unit *unit, const std::string &nick) -{ - // There are >=3 copies of the name, and the one - // in the unit is not the authoritative one. - // This is the reason why military units often - // lose nicknames set from Dwarf Therapist. - set_nickname(&unit->name, nick); - - if (unit->status.current_soul) - set_nickname(&unit->status.current_soul->name, nick); - - df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); - if (figure) - { - set_nickname(&figure->name, nick); - - // v0.34.01: added the vampire's assumed identity - if (figure->info && figure->info->reputation) - { - auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity); - - if (identity) - { - auto id_hfig = df::historical_figure::find(identity->histfig_id); - - if (id_hfig) - { - // Even DF doesn't do this bit, because it's apparently - // only used for demons masquerading as gods, so you - // can't ever change their nickname in-game. - set_nickname(&id_hfig->name, nick); - } - else - set_nickname(&identity->name, nick); - } - } - } -} - static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in) { df::squad *squad = df::squad::find(in->squad_id()); @@ -130,7 +80,7 @@ static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in return CR_NOT_FOUND; if (in->has_nickname()) - set_nickname(&squad->name, UTF2DF(in->nickname())); + Translation::setNickname(&squad->name, UTF2DF(in->nickname())); if (in->has_alias()) squad->alias = UTF2DF(in->alias()); @@ -144,7 +94,7 @@ static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) return CR_NOT_FOUND; if (in->has_nickname()) - setUnitNickname(unit, UTF2DF(in->nickname())); + Units::setNickname(unit, UTF2DF(in->nickname())); if (in->has_profession()) unit->custom_profession = UTF2DF(in->profession()); @@ -204,7 +154,7 @@ static command_result rename(color_ostream &out, vector ¶meters) if (!unit) return CR_WRONG_USAGE; - setUnitNickname(unit, parameters[1]); + Units::setNickname(unit, parameters[1]); } else if (cmd == "unit-profession") { From f931060dd992cbef8aa92aa25295a3893bcd5872 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 7 Apr 2012 12:11:53 -0500 Subject: [PATCH 022/108] Exclude worn items from counts for workflow --- plugins/workflow.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 7e28893f5..5bdeb5d58 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1128,6 +1128,10 @@ static void map_job_items(color_ostream &out) bool is_invalid = false; + // don't count worn items + if (item->getWear() >= 1) + is_invalid = true; + // Special handling switch (itype) { case item_type::BUCKET: From dac84080c074811e6dc398415bd374f663067169 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 7 Apr 2012 20:26:56 -0500 Subject: [PATCH 023/108] Exclude grazing units from autonestbox to avoid starving da poor boids --- plugins/zone.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 61ed2007c..c66ec6b53 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -963,6 +963,7 @@ bool isFreeEgglayer(df::unit * unit) && isOwnCiv(unit) && isEggLayer(unit) && !isAssigned(unit) + && !isGrazer(unit) // exclude grazing birds because they're messy ) return true; else From 2e6ee0ac849e13b463e63ff5cd1dc8faad8480ba Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 7 Apr 2012 20:36:29 -0500 Subject: [PATCH 024/108] Don't assign dead animals to pastures. --- plugins/zone.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c66ec6b53..6e87d75f0 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -1609,6 +1609,11 @@ command_result df_zone (color_ostream &out, vector & parameters) for(size_t c = 0; c < world->units.all.size(); c++) { df::unit *unit = world->units.all[c]; + + // ignore dead units + if (isDead(unit)) + continue; + if(find_race && getRaceName(unit) != target_race) continue; // ignore own dwarves by default From de960e65f5557b2a49b015d354cdad88d4e6578e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 7 Apr 2012 23:25:40 -0500 Subject: [PATCH 025/108] Stealing animals from traders is bad. --- plugins/zone.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 6e87d75f0..44eb8eb1c 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -309,6 +309,7 @@ bool isTrained(df::unit* creature); bool isWar(df::unit* creature); bool isHunter(df::unit* creature); bool isOwnCiv(df::unit* creature); +bool isMerchant(df::unit* creature); bool isActivityZone(df::building * building); bool isPenPasture(df::building * building); @@ -344,6 +345,11 @@ bool isDead(df::unit* unit) return unit->flags1.bits.dead; } +bool isMerchant(df::unit* unit) +{ + return unit->flags1.bits.merchant; +} + bool isMarkedForSlaughter(df::unit* unit) { return unit->flags2.bits.slaughter; @@ -1614,6 +1620,9 @@ command_result df_zone (color_ostream &out, vector & parameters) if (isDead(unit)) continue; + // ignore merchant units + if (isMerchant(unit)) + continue; if(find_race && getRaceName(unit) != target_race) continue; // ignore own dwarves by default From ad657c68a36ddba6462dd24999e8788535d10ae4 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 8 Apr 2012 01:07:29 -0500 Subject: [PATCH 026/108] Leave merchant stuff alone, even if they're certainly dwarfs. --- plugins/zone.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 44eb8eb1c..a117bddab 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -310,6 +310,7 @@ bool isWar(df::unit* creature); bool isHunter(df::unit* creature); bool isOwnCiv(df::unit* creature); bool isMerchant(df::unit* creature); +bool isForest(df::unit* creature); bool isActivityZone(df::building * building); bool isPenPasture(df::building * building); @@ -350,6 +351,11 @@ bool isMerchant(df::unit* unit) return unit->flags1.bits.merchant; } +bool isForest(df::unit* unit) +{ + return unit->flags1.bits.forest; +} + bool isMarkedForSlaughter(df::unit* unit) { return unit->flags2.bits.slaughter; @@ -590,8 +596,8 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) } out << ")"; out << ", age: " << getUnitAge(unit); - - if(isTame(unit)) + + if(isTame(unit)) out << ", tame"; if(isOwnCiv(unit)) out << ", owned"; @@ -599,6 +605,10 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << ", war"; if(isHunter(unit)) out << ", hunter"; + if(isMerchant(unit)) + out << ", merchant"; + if(isForest(unit)) + out << ", forest"; if(verbose) { @@ -970,6 +980,8 @@ bool isFreeEgglayer(df::unit * unit) && isEggLayer(unit) && !isAssigned(unit) && !isGrazer(unit) // exclude grazing birds because they're messy + && !isMerchant(unit) // don't steal merchant mounts + && !isForest(unit) // don't steal birds from traders, they hate that ) return true; else @@ -1621,8 +1633,9 @@ command_result df_zone (color_ostream &out, vector & parameters) continue; // ignore merchant units - if (isMerchant(unit)) + if (isMerchant(unit) || isForest(unit)) continue; + if(find_race && getRaceName(unit) != target_race) continue; // ignore own dwarves by default From f609aa0db77824e4efcb2470d865de65f66becb1 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sun, 8 Apr 2012 12:51:03 +0200 Subject: [PATCH 027/108] zone: can now search for merchants (to allow pitting them). autobutcher: ignore merchant animals, ignore war/hunting creatures, fixed autowatch, stop spamming the console if there is nothing to report. updated readme.rst --- README.rst | 11 ++--- plugins/zone.cpp | 108 +++++++++++++++++++++++++++++------------------ 2 files changed, 73 insertions(+), 46 deletions(-) diff --git a/README.rst b/README.rst index 2183d7064..36e47685d 100644 --- a/README.rst +++ b/README.rst @@ -822,8 +822,9 @@ Filters: :unassigned: Not assigned to zone, chain or built cage. :caged: In a built cage. :uncaged: Not in a cage (in case you want your stockpiles to be left alone). -:foreign: Not of own civilization (i.e. own fortress). -:own: From own civilization (i.e. own fortress). +:foreign: Not of own civilization. +:own: From own civilization. +:merchant: Is a merchant / belongs to a merchant. Should only be used for pitting, not for stealing animals (slaughter should work). :war: Trained war creature. :tamed: Creature is tame. :trained: Creature is trained. @@ -842,7 +843,7 @@ One convenient way to use the zone tool is to bind the command 'zone assign' to Usage with filters ------------------ -All filters can be used together with the 'assign' command. The only restriction is that it's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. Usually you should always use the filter 'own' (which implies tame) unless you want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you specify 'race DWARF' (so it's safe to use 'assign all own' to one big pasture if you want to have all your animals at the same place). 'egglayer' and 'milkable' should be used together with 'female' unless you have a mod with egg-laying male elves who give milk or whatever. +All filters can be used together with the 'assign' command. The only restriction is that it's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. Usually you should always use the filter 'own' (which implies tame) unless you want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you specify 'race DWARF' (so it's safe to use 'assign all own' to one big pasture if you want to have all your animals at the same place). 'egglayer' and 'milkable' should be used together with 'female' unless you have a mod with egg-laying male elves who give milk or whatever. Merchants and their animals are ignored unless you specify 'merchant' (pitting them should be no problem, but stealing and pasturing their animals is not a good idea since currently they are not properly added to your own stocks; slaughtering them should work). ``zone assign all own ALPACA minage 3 maxage 10`` Assign all own alpacas who are between 3 and 10 years old to the selected pasture. @@ -855,7 +856,7 @@ All filters can be used together with the 'assign' command. The only restriction autonestbox =========== -Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless of the size. The age of the units is currently not checked, most birds grow up quite fast. When called without options autonestbox will instantly run once. +Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless of the size. The age of the units is currently not checked, most birds grow up quite fast. Egglayers who are also grazers will be ignored, since confining them to a 1x1 pasture is not a good idea. When called without options autonestbox will instantly run once. Options: -------- @@ -865,7 +866,7 @@ Options: autobutcher =========== -Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units of the own civilization will be processed. Named units will be completely ignored (you can give animals nicknames with the tool 'rename unit' to protect them from autobutcher). Once you have too much adults, the oldest will be butchered first. Once you have too much kids, the youngest will be butchered first. If you don't set any target count the following default will be used: 1 male kid, 5 female kids, 1 male adult, 5 female adults. +Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units of the own civilization will be processed. Named units will be completely ignored (you can give animals nicknames with the tool 'rename unit' to protect them from autobutcher). Creatures trained for war or hunting will be ignored as well. Once you have too much adults, the oldest will be butchered first. Once you have too much kids, the youngest will be butchered first. If you don't set any target count the following default will be used: 1 male kid, 5 female kids, 1 male adult, 5 female adults. Options: -------- diff --git a/plugins/zone.cpp b/plugins/zone.cpp index a117bddab..770d6c052 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -101,11 +101,14 @@ const string zone_help_filters = " caged - in a built cage\n" " uncaged - not in cage\n" " foreign - not of own civilization (i.e. own fortress)\n" - " own - from own civilization (fortress)\n" + " own - from own civilization\n" " war - trained war creature\n" " tamed - tamed\n" " named - has name or nickname\n" " can be used to mark named units for slaughter\n" + " merchant - is a merchant / belongs to a merchant\n" + " can be used to pit merchants and slaughter their animals\n" + " (could have weird effects during trading, be careful)\n" " trained - obvious\n" " untrained - obvious\n" " male - obvious\n" @@ -136,6 +139,7 @@ const string zone_help_examples = " 'own' ignores own dwarves unless you specify 'race DWARF'\n" " (so it's safe to use 'assign all own' to one big pasture\n" " if you want to have all your animals at the same place).\n" + " Merchants and their animals are ignored by default.\n" " 'egglayer' and 'milkable' should be used together with 'female'\n" " unless you have a mod with egg-laying male elves who give milk.\n"; @@ -158,7 +162,7 @@ const string autobutcher_help = "that you add the target race(s) to a watch list. Only tame units of your own\n" "civilization will be processed. Named units will be completely ignored (you can\n" "give animals nicknames with the tool 'rename unit' to protect them from\n" - "getting slaughtered automatically.\n" + "getting slaughtered automatically. Trained war or hunting pets will be ignored.\n" "Once you have too much adults, the oldest will be butchered first.\n" "Once you have too much kids, the youngest will be butchered first.\n" "If you don't set a target count the following default will be used:\n" @@ -348,12 +352,12 @@ bool isDead(df::unit* unit) bool isMerchant(df::unit* unit) { - return unit->flags1.bits.merchant; + return unit->flags1.bits.merchant; } bool isForest(df::unit* unit) { - return unit->flags1.bits.forest; + return unit->flags1.bits.forest; } bool isMarkedForSlaughter(df::unit* unit) @@ -535,7 +539,7 @@ bool isFemale(df::unit* unit) // found a unit with weird position values on one of my maps (negative and in the thousands) // it didn't appear in the animal stocks screen, but looked completely fine otherwise (alive, tame, own, etc) -// maybe a rare but, but better avoid assigning such units to zones or slaughter etc. +// maybe a rare bug, but better avoid assigning such units to zones or slaughter etc. bool hasValidMapPos(df::unit* unit) { if( unit->pos.x >=0 && unit->pos.y >= 0 && unit->pos.z >= 0 @@ -550,9 +554,6 @@ bool hasValidMapPos(df::unit* unit) // dump some unit info void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) { - if(isDead(unit)) - return; - out.print("Unit %d ", unit->id); //race %d, civ %d,", creature->race, creature->civ_id if(unit->name.has_name) { @@ -597,7 +598,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << ")"; out << ", age: " << getUnitAge(unit); - if(isTame(unit)) + if(isTame(unit)) out << ", tame"; if(isOwnCiv(unit)) out << ", owned"; @@ -605,10 +606,10 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << ", war"; if(isHunter(unit)) out << ", hunter"; - if(isMerchant(unit)) - out << ", merchant"; - if(isForest(unit)) - out << ", forest"; + if(isMerchant(unit)) + out << ", merchant"; + if(isForest(unit)) + out << ", forest"; if(verbose) { @@ -979,9 +980,9 @@ bool isFreeEgglayer(df::unit * unit) && isOwnCiv(unit) && isEggLayer(unit) && !isAssigned(unit) - && !isGrazer(unit) // exclude grazing birds because they're messy - && !isMerchant(unit) // don't steal merchant mounts - && !isForest(unit) // don't steal birds from traders, they hate that + && !isGrazer(unit) // exclude grazing birds because they're messy + && !isMerchant(unit) // don't steal merchant mounts + && !isForest(unit) // don't steal birds from traders, they hate that ) return true; else @@ -1048,18 +1049,10 @@ bool unassignUnitFromZone(df::unit* unit) // assign to pen or pit command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) { - //if(!isOwnCiv(unit) || !isTame(unit)) - //{ - // out << "Creature must be tame and your own." << endl; - // return CR_WRONG_USAGE; - //} - // building must be a pen/pasture or pit - //df::building * building = world->buildings.all.at(index); if(!isPenPasture(building) && !isPit(building)) { out << "Invalid building type. This is not a pen/pasture or pit." << endl; - //target_zone = -1; return CR_WRONG_USAGE; } @@ -1279,6 +1272,7 @@ command_result df_zone (color_ostream &out, vector & parameters) bool find_war = false; bool find_own = false; bool find_tame = false; + bool find_merchant = false; bool find_male = false; bool find_female = false; bool find_egglayer = false; @@ -1518,6 +1512,10 @@ command_result df_zone (color_ostream &out, vector & parameters) { find_grazer = true; } + else if(p == "merchant") + { + find_merchant = true; + } else if(p == "milkable") { find_milkable = true; @@ -1628,13 +1626,16 @@ command_result df_zone (color_ostream &out, vector & parameters) { df::unit *unit = world->units.all[c]; - // ignore dead units - if (isDead(unit)) - continue; + // ignore dead units + if (isDead(unit)) + continue; - // ignore merchant units - if (isMerchant(unit) || isForest(unit)) - continue; + // ignore merchant units by default + if (!find_merchant && (isMerchant(unit) || isForest(unit))) + continue; + // but allow pitting them and stealing from them if specified :) + if (find_merchant && !isMerchant(unit) && !isForest(unit)) + continue; if(find_race && getRaceName(unit) != target_race) continue; @@ -1828,7 +1829,6 @@ command_result df_autonestbox(color_ostream &out, vector & parameters) } } return autoNestbox(out, verbose); - //return CR_OK; } command_result autoNestbox( color_ostream &out, bool verbose = false ) @@ -2008,6 +2008,7 @@ public: } return subcount; } + int ProcessUnits_mk() { int subcount = 0; @@ -2020,6 +2021,7 @@ public: } return subcount; } + int ProcessUnits_fa() { int subcount = 0; @@ -2032,6 +2034,7 @@ public: } return subcount; } + int ProcessUnits_ma() { int subcount = 0; @@ -2123,13 +2126,13 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) else if (p == "start") { enable_autobutcher = true; - out << "Autobutcher started."; + out << "Autobutcher started." << endl; return autoButcher(out, verbose); } else if (p == "stop") { enable_autobutcher = false; - out << "Autobutcher stopped."; + out << "Autobutcher stopped." << endl; return CR_OK; } else if(p == "sleep") @@ -2137,7 +2140,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) parameters.erase(parameters.begin()); if(!parameters.size()) { - out.printerr("No duration specified!"); + out.printerr("No duration specified!\n"); return CR_WRONG_USAGE; } else @@ -2147,7 +2150,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) ss >> ticks; if(ticks <= 0) { - out.printerr("Invalid duration specified (must be > 0)!"); + out.printerr("Invalid duration specified (must be > 0)!\n"); return CR_WRONG_USAGE; } sleep_autobutcher = ticks; @@ -2159,16 +2162,19 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) { parameters.erase(parameters.begin()); watch_race = true; + out << "Start watching race(s)." << endl; } else if(p == "unwatch") { parameters.erase(parameters.begin()); unwatch_race = true; + out << "Stop watching race(s)." << endl; } else if(p == "forget") { parameters.erase(parameters.begin()); forget_race = true; + out << "Removing race(s) from watchlist." << endl; } else if(p == "target") { @@ -2176,7 +2182,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) // fk mk fa ma R (can have more than 1 R) if(parameters.size() < 6) { - out.printerr("Not enough parameters!"); + out.printerr("Not enough parameters!\n"); return CR_WRONG_USAGE; } else @@ -2191,6 +2197,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) ma >> target_ma; parameters.erase(parameters.begin(), parameters.begin()+5); change_target = true; + out << "Setting new target count for race(s)." << endl; } } else if(p == "autowatch") @@ -2217,6 +2224,13 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) if(list_watched) { + if(!watched_races.size()) + { + out << "The autobutcher race list is empty." << endl; + return CR_OK; + } + + out << "Races on autobutcher list: " << endl; for(size_t i=0; i & parameters) { if(!parameters.size()) { - out.printerr("No race(s) specified!"); + out.printerr("No race(s) specified!\n"); return CR_WRONG_USAGE; } while(parameters.size()) @@ -2252,7 +2266,9 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) string tr = parameters.back(); target_racenames.push_back(tr); parameters.pop_back(); + out << tr << " "; } + out << endl; } if(target_racenames.size() && target_racenames[0] == "all") @@ -2337,12 +2353,14 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) return CR_OK; } +// check watched_races vector for a race id, return -1 if nothing found +// calling method needs to check itself if the race is currently being watched or ignored int getWatchedIndex(int id) { for(size_t i=0; iraceId == id && w->isWatched) + if(w->raceId == id) // && w->isWatched) return i; } return -1; @@ -2373,8 +2391,12 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) df::unit * unit = world->units.all[i]; if( isDead(unit) || isMarkedForSlaughter(unit) + || isMerchant(unit) // ignore merchants' draught animals + || isForest(unit) // ignore merchants' caged animals || !isOwnCiv(unit) || !isTame(unit) + || isWar(unit) // ignore war dogs etc + || isHunter(unit) // ignore hunting dogs etc || (isContainedInItem(unit) && hasValidMapPos(unit) && isBuiltCageAtPos(unit->pos)) || unit->name.has_name ) @@ -2389,13 +2411,16 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) if(watched_index != -1) { WatchedRace * w = watched_races[watched_index]; - w->PushUnit(unit); + if(w->isWatched) + w->PushUnit(unit); } else if(enable_autobutcher_autowatch) { WatchedRace * w = new WatchedRace(true, unit->race, default_fk, default_mk, default_fa, default_ma); watched_races.push_back(w); w->PushUnit(unit); + df::creature_raw *raw = df::global::world->raws.creatures.all[w->raceId]; + out << "New race added to autoslaughter watchlist: " << raw->creature_id << endl; } } @@ -2406,9 +2431,10 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) int slaughter_subcount = w->ProcessUnits(); slaughter_count += slaughter_subcount; df::creature_raw *raw = df::global::world->raws.creatures.all[w->raceId]; - out << raw->creature_id << " marked for slaughter: " << slaughter_subcount << endl; + if(slaughter_subcount) + out << raw->creature_id << " marked for slaughter: " << slaughter_subcount << endl; } - out << slaughter_count << " units total marked for slaughter." << endl; + //out << slaughter_count << " units total marked for slaughter." << endl; return CR_OK; } \ No newline at end of file From 9bba09b78c9848c66d0c88c18053a6f26cb7e7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Mon, 9 Apr 2012 00:59:22 +0200 Subject: [PATCH 028/108] Fix digv command in init script example --- dfhack.init-example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index e4d1d3d37..7bceda2d0 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -5,8 +5,8 @@ keybinding add Ctrl-W twaterlvl # with cursor: -keybinding add Ctrl-V vdig -keybinding add Ctrl-Shift-V "vdig x" +keybinding add Ctrl-V digv +keybinding add Ctrl-Shift-V "digv x" keybinding add Ctrl-C spotclean keybinding add Ctrl-Shift-K autodump-destroy-here From 585de77489f8325fc40807723017fb52e1618a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Mon, 9 Apr 2012 00:59:57 +0200 Subject: [PATCH 029/108] Track structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index b630809b2..baeee2ce1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b630809b25a3e3b541e40f0cb145b6f5bd3b7316 +Subproject commit baeee2ce115e0b3432e5b66b843e8883e5f03b34 From a9ff1d0423c4dfecf274acefb6e6323b60118396 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 9 Apr 2012 15:03:26 +0200 Subject: [PATCH 030/108] autonestbox and autobutcher store settings in the savegame. added command list_export to autobutcher which prints the current settings and watchlist in a batch file format (to allow importing settings to other savegames). updated readme.rst --- README.rst | 15 +- plugins/zone.cpp | 426 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 382 insertions(+), 59 deletions(-) diff --git a/README.rst b/README.rst index 36e47685d..a460a57cc 100644 --- a/README.rst +++ b/README.rst @@ -866,7 +866,7 @@ Options: autobutcher =========== -Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units of the own civilization will be processed. Named units will be completely ignored (you can give animals nicknames with the tool 'rename unit' to protect them from autobutcher). Creatures trained for war or hunting will be ignored as well. Once you have too much adults, the oldest will be butchered first. Once you have too much kids, the youngest will be butchered first. If you don't set any target count the following default will be used: 1 male kid, 5 female kids, 1 male adult, 5 female adults. +Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed. Named units will be completely ignored (you can give animals nicknames with the tool 'rename unit' to protect them from autobutcher). Creatures trained for war or hunting will be ignored as well. Once you have too much adults, the oldest will be butchered first. Once you have too much kids, the youngest will be butchered first. If you don't set any target count the following default will be used: 1 male kid, 5 female kids, 1 male adult, 5 female adults. Options: -------- @@ -879,7 +879,8 @@ Options: :autowatch: Automatically adds all new races (animals you buy from merchants, tame yourself or get from migrants) to the watch list using default target count. :noautowatch: Stop auto-adding new races to the watchlist. -:list: Print a list of watched races. +:list: Print the current status and watchlist. +:list_export: Print status and watchlist in a format which can be used to import them to another savegame (see notes). :target fk mk fa ma R: Set target count for specified race(s). fk = number of female kids, mk = number of male kids, @@ -904,3 +905,13 @@ Automatically put all new races onto the watchlist and mark unnamed tame units f autobutcher autowatch autobutcher start +Note: +----- +Settings and watchlist are stored in the savegame, so that you can have different settings for each world. If you want to copy your watchlist to another savegame you can use the command list_export: +:: + + Load savegame where you made the settings. + Start a CMD shell and navigate to the df directory. Type the following into the shell: + dfhack-run autobutcher list_export > autobutcher.bat + Load the savegame where you want to copy the settings to, run the batch file (from the shell): + autobutcher.bat diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 770d6c052..ef999f51b 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -17,10 +17,12 @@ // go through all pens, check if they are empty and placed over a nestbox // find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture // maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds +// state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used) // - full automation of marking live-stock for slaughtering // races can be added to a watchlist and it can be set how many male/female kids/adults are left alive // adding to the watchlist can be automated as well. -// TODO: - save config +// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started +// config for watchlist entries is saved when they are created or modified #include #include @@ -43,6 +45,7 @@ using namespace std; #include "modules/Materials.h" #include "modules/MapCache.h" #include "modules/Buildings.h" +#include "modules/World.h" #include "MiscUtils.h" #include @@ -100,8 +103,8 @@ const string zone_help_filters = " unassigned - not assigned to zone, chain or built cage\n" " caged - in a built cage\n" " uncaged - not in cage\n" - " foreign - not of own civilization (i.e. own fortress)\n" " own - from own civilization\n" + " foreign - not of own civilization\n" " war - trained war creature\n" " tamed - tamed\n" " named - has name or nickname\n" @@ -133,15 +136,14 @@ const string zone_help_examples = " zone assign all own race DWARF maxage 2\n" " throw all useless kids into a pit :)\n" "Notes:\n" - " Assigning per filters ignores built cages and chains currently.\n" - " Usually you should always use the filter 'own' (which implies tame)\n" - " unless you want to use the zone tool for pitting hostiles.\n" - " 'own' ignores own dwarves unless you specify 'race DWARF'\n" - " (so it's safe to use 'assign all own' to one big pasture\n" + " Assigning per filters ignores built cages and chains currently. Usually you\n" + " should always use the filter 'own' (which implies tame) unless you want to\n" + " use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you\n" + " specify 'race DWARF' and it ignores merchants and their animals unless you\n" + " specify 'merchant' (so it's safe to use 'assign all own' to one big pasture\n" " if you want to have all your animals at the same place).\n" - " Merchants and their animals are ignored by default.\n" " 'egglayer' and 'milkable' should be used together with 'female'\n" - " unless you have a mod with egg-laying male elves who give milk.\n"; + " well, unless you have a mod with egg-laying male elves who give milk...\n"; const string autonestbox_help = @@ -159,10 +161,10 @@ const string autonestbox_help = const string autobutcher_help = "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n" - "that you add the target race(s) to a watch list. Only tame units of your own\n" - "civilization will be processed. Named units will be completely ignored (you can\n" - "give animals nicknames with the tool 'rename unit' to protect them from\n" - "getting slaughtered automatically. Trained war or hunting pets will be ignored.\n" + "that you add the target race(s) to a watch list. Only tame units will be\n" + "processed. Named units will be completely ignored (you can give animals\n" + "nicknames with the tool 'rename unit' to protect them from getting slaughtered\n" + "automatically. Trained war or hunting pets will be ignored.\n" "Once you have too much adults, the oldest will be butchered first.\n" "Once you have too much kids, the youngest will be butchered first.\n" "If you don't set a target count the following default will be used:\n" @@ -184,7 +186,10 @@ const string autobutcher_help = " from merchants, tame yourself or get from migrants)\n" " to the watch list using default target count\n" " noautowatch - stop auto-adding new races to the watch list\n" - " list - print a list of watched races\n" + " list - print status and watchlist\n" + " list_export - print status and watchlist in batchfile format\n" + " can be used to copy settings into another savegame\n" + " usage: 'dfhack-run autobutcher list_export > xyz.bat' \n" " target fk mk fa ma R\n" " - set target count for specified race:\n" " fk = number of female kids\n" @@ -214,6 +219,13 @@ const string autobutcher_help_example = " and mark unnamed tame units for slaughter as soon as they arrive in your\n" " fortress. Settings already made for some races will be left untouched.\n"; +command_result init_autobutcher(color_ostream &out); +command_result cleanup_autobutcher(color_ostream &out); +command_result start_autobutcher(color_ostream &out); + +command_result init_autonestbox(color_ostream &out); +command_result cleanup_autonestbox(color_ostream &out); +command_result start_autonestbox(color_ostream &out); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { @@ -232,13 +244,15 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector flags1.bits.dead; } +// ignore vampires, they should be treated like normal dwarves +bool isUndead(df::unit* unit) +{ + return (unit->flags3.bits.ghostly || + ( (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || unit->curse.add_tags1.bits.NOT_LIVING) + && !unit->curse.add_tags1.bits.BLOODSUCKER )); +} + bool isMerchant(df::unit* unit) { return unit->flags1.bits.merchant; @@ -456,6 +485,11 @@ bool isOwnRace(df::unit* creature) return creature->race == ui->race_id; } +string getRaceName(int32_t id) +{ + df::creature_raw *raw = df::global::world->raws.creatures.all[id]; + return raw->creature_id; +} string getRaceName(df::unit* unit) { df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; @@ -610,16 +644,16 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << ", merchant"; if(isForest(unit)) out << ", forest"; + if(isEggLayer(unit)) + out << ", egglayer"; + if(isGrazer(unit)) + out << ", grazer"; + if(isMilkable(unit)) + out << ", milkable"; if(verbose) { out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ")" << endl; - if(isEggLayer(unit)) - out << ", egglayer"; - if(isGrazer(unit)) - out << ", grazer"; - if(isMilkable(unit)) - out << ", milkable"; } out << endl; @@ -974,7 +1008,7 @@ df::building* findFreeNestboxZone() bool isFreeEgglayer(df::unit * unit) { - if( !isDead(unit) + if( !isDead(unit) && !isUndead(unit) && isFemale(unit) && isTame(unit) && isOwnCiv(unit) @@ -1086,9 +1120,8 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building df::building_civzonest * civz = (df::building_civzonest *) building; civz->assigned_creature.push_back(unit->id); - df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; out << "Unit " << unit->id - << "(" << raw->creature_id << ")" + << "(" << getRaceName(unit) << ")" << " assigned to zone " << building->id; if(isPit(building)) out << " (pit)."; @@ -1626,8 +1659,8 @@ command_result df_zone (color_ostream &out, vector & parameters) { df::unit *unit = world->units.all[c]; - // ignore dead units - if (isDead(unit)) + // ignore dead and undead units + if (isDead(unit) || isUndead(unit)) continue; // ignore merchant units by default @@ -1766,6 +1799,9 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } +//////////////////// +// autonestbox stuff + command_result df_autonestbox(color_ostream &out, vector & parameters) { CoreSuspender suspend; @@ -1783,16 +1819,16 @@ command_result df_autonestbox(color_ostream &out, vector & parameters) } if (p == "start") { - enable_autonestbox = true; autonestbox_did_complain = false; - out << "Autonestbox started."; + start_autonestbox(out); return autoNestbox(out, verbose); - //return CR_OK; } if (p == "stop") { enable_autonestbox = false; - out << "Autonestbox stopped."; + if(config_autonestbox.isValid()) + config_autonestbox.ival(0) = 0; + out << "Autonestbox stopped." << endl; return CR_OK; } else if(p == "verbose") @@ -1818,6 +1854,8 @@ command_result df_autonestbox(color_ostream &out, vector & parameters) return CR_WRONG_USAGE; } sleep_autonestbox = ticks; + if(config_autonestbox.isValid()) + config_autonestbox.ival(1) = sleep_autonestbox; out << "New sleep timer for autonestbox: " << ticks << " ticks." << endl; return CR_OK; } @@ -1853,8 +1891,6 @@ command_result autoNestbox( color_ostream &out, bool verbose = false ) if(result != CR_OK) return result; processed ++; - //if(find_count && processed >= target_count) - // stop = true; } else { @@ -1895,6 +1931,8 @@ command_result autoNestbox( color_ostream &out, bool verbose = false ) return CR_OK; } +//////////////////// +// autobutcher stuff // getUnitAge() returns 0 if born in current year, therefore the look at birth_time in that case // (assuming that the value from there indicates in which tick of the current year the unit was born) @@ -1932,6 +1970,7 @@ bool compareUnitAgesOlder(df::unit* i, df::unit* j) struct WatchedRace { public: + PersistentDataItem rconfig; bool isWatched; // if true, autobutcher will process this race int raceId; @@ -1962,6 +2001,40 @@ public: ClearUnits(); } + void UpdateConfig(color_ostream & out) + { + if(!rconfig.isValid()) + { + string keyname = "autobutcher/watchlist/" + getRaceName(raceId); + auto pworld = Core::getInstance().getWorld(); + rconfig = pworld->GetPersistentData(keyname); + if(!rconfig.isValid()) + rconfig = pworld->AddPersistentData(keyname); + } + if(rconfig.isValid()) + { + rconfig.ival(0) = raceId; + rconfig.ival(1) = isWatched; + rconfig.ival(2) = fk; + rconfig.ival(3) = mk; + rconfig.ival(4) = fa; + rconfig.ival(5) = ma; + } + else + { + // this should never happen + string keyname = "autobutcher/watchlist/" + getRaceName(raceId); + out << "Something failed, could not find/create config key " << keyname << "!" << endl; + } + } + + void RemoveConfig(color_ostream & out) + { + if(!rconfig.isValid()) + return; + Core::getInstance().getWorld()->DeletePersistentData(rconfig); + } + void SortUnitsByAge() { sort(fk_ptr.begin(), fk_ptr.end(), compareUnitAgesOlder); @@ -2065,25 +2138,12 @@ public: // to ignore them for a while but still keep the target count settings std::vector watched_races; -// default target values -// move those somewhere else for persistency +// default target values for autobutcher static int default_fk = 5; static int default_mk = 1; static int default_fa = 5; static int default_ma = 1; - -command_result autobutcher_cleanup(color_ostream &out) -{ - for(size_t i=0; i & parameters) { CoreSuspender suspend; @@ -2093,6 +2153,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) bool unwatch_race = false; bool forget_race = false; bool list_watched = false; + bool list_export = false; bool change_target = false; vector target_racenames; vector target_raceids; @@ -2126,12 +2187,14 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) else if (p == "start") { enable_autobutcher = true; - out << "Autobutcher started." << endl; + start_autobutcher(out); return autoButcher(out, verbose); } else if (p == "stop") { enable_autobutcher = false; + if(config_autobutcher.isValid()) + config_autobutcher.ival(0) = enable_autobutcher; out << "Autobutcher stopped." << endl; return CR_OK; } @@ -2154,6 +2217,8 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) return CR_WRONG_USAGE; } sleep_autobutcher = ticks; + if(config_autobutcher.isValid()) + config_autobutcher.ival(1) = sleep_autobutcher; out << "New sleep timer for autobutcher: " << ticks << " ticks." << endl; return CR_OK; } @@ -2197,25 +2262,33 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) ma >> target_ma; parameters.erase(parameters.begin(), parameters.begin()+5); change_target = true; - out << "Setting new target count for race(s)." << endl; + out << "Setting new target count for race(s):" << endl; } } else if(p == "autowatch") { out << "Auto-adding to watchlist started." << endl; enable_autobutcher_autowatch = true; + if(config_autobutcher.isValid()) + config_autobutcher.ival(2) = enable_autobutcher_autowatch; return CR_OK; } else if(p == "noautowatch") { out << "Auto-adding to watchlist stopped." << endl; enable_autobutcher_autowatch = false; + if(config_autobutcher.isValid()) + config_autobutcher.ival(2) = enable_autobutcher_autowatch; return CR_OK; } else if(p == "list") { list_watched = true; } + else if(p == "list_export") + { + list_export = true; + } else { out << "Unknown command: " << p << endl; @@ -2224,6 +2297,27 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) if(list_watched) { + out << "Autobutcher status: "; + + if(enable_autobutcher) + out << "enabled,"; + else + out << "not enabled,"; + + if (enable_autobutcher_autowatch) + out << " autowatch,"; + else + out << " noautowatch,"; + + out << " sleep: " << sleep_autobutcher << endl; + + out << "Default setting for new races:" + << " fk=" << default_fk + << " mk=" << default_mk + << " fa=" << default_fa + << " ma=" << default_ma + << endl; + if(!watched_races.size()) { out << "The autobutcher race list is empty." << endl; @@ -2250,6 +2344,48 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) return CR_OK; } + if(list_export) + { + string run = "dfhack-run autobutcher "; +#ifdef LINUX_BUILD + run = "./dfhack-run autobutcher " +#endif + // force creation of config + out << run << "start" << endl; + + if(!enable_autobutcher) + out << run << "stop" << endl; + + if (enable_autobutcher_autowatch) + out << run << "autowatch" << endl; + + out << run << "sleep " << sleep_autobutcher << endl; + out << run << "target" + << " " << default_fk + << " " << default_mk + << " " << default_fa + << " " << default_ma + << " new" << endl; + + for(size_t i=0; iraws.creatures.all[w->raceId]; + string name = raw->creature_id; + + out << run << "target" + << " " << w->fk + << " " << w->mk + << " " << w->fa + << " " << w->ma + << " " << name << endl; + + if(w->isWatched) + out << run << "watch " << name << endl; + } + return CR_OK; + } + // parse rest of parameters for commands followed by a list of races if( watch_race || unwatch_race @@ -2281,6 +2417,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) w->mk = target_mk; w->fa = target_fa; w->ma = target_ma; + w->UpdateConfig(out); } } @@ -2291,6 +2428,13 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) default_mk = target_mk; default_fa = target_fa; default_ma = target_ma; + if(config_autobutcher.isValid()) + { + config_autobutcher.ival(3) = default_fk; + config_autobutcher.ival(4) = default_mk; + config_autobutcher.ival(5) = default_fa; + config_autobutcher.ival(6) = default_ma; + } return CR_OK; } @@ -2301,8 +2445,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) bool found_race = false; for(size_t i=0; iraws.creatures.all[i]; - if(raw->creature_id == target_racenames.back()) + if(getRaceName(i) == target_racenames.back()) { target_raceids.push_back(i); target_racenames.pop_back(); @@ -2326,17 +2469,27 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) if(w->raceId == target_raceids.back()) { if(unwatch_race) + { w->isWatched=false; + w->UpdateConfig(out); + } else if(forget_race) + { + w->RemoveConfig(out); watched_races.erase(watched_races.begin()+i); + } else if(watch_race) + { w->isWatched = true; + w->UpdateConfig(out); + } else if(change_target) { w->fk = target_fk; w->mk = target_mk; w->fa = target_fa; w->ma = target_ma; + w->UpdateConfig(out); } entry_found = true; break; @@ -2345,6 +2498,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) if(!entry_found && (watch_race||change_target)) { WatchedRace * w = new WatchedRace(watch_race, target_raceids.back(), target_fk, target_mk, target_fa, target_ma); + w->UpdateConfig(out); watched_races.push_back(w); } target_raceids.pop_back(); @@ -2390,6 +2544,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) { df::unit * unit = world->units.all[i]; if( isDead(unit) + || isUndead(unit) || isMarkedForSlaughter(unit) || isMerchant(unit) // ignore merchants' draught animals || isForest(unit) // ignore merchants' caged animals @@ -2417,10 +2572,14 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) else if(enable_autobutcher_autowatch) { WatchedRace * w = new WatchedRace(true, unit->race, default_fk, default_mk, default_fa, default_ma); + w->UpdateConfig(out); watched_races.push_back(w); w->PushUnit(unit); - df::creature_raw *raw = df::global::world->raws.creatures.all[w->raceId]; - out << "New race added to autoslaughter watchlist: " << raw->creature_id << endl; + + string announce; + announce = "New race added to autobutcher watchlist: " + getRaceName(w->raceId); + Gui::showAnnouncement(announce, 2, false); + //out << announce << endl; } } @@ -2430,11 +2589,164 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) WatchedRace * w = watched_races[i]; int slaughter_subcount = w->ProcessUnits(); slaughter_count += slaughter_subcount; - df::creature_raw *raw = df::global::world->raws.creatures.all[w->raceId]; if(slaughter_subcount) - out << raw->creature_id << " marked for slaughter: " << slaughter_subcount << endl; + { + stringstream ss; + ss << slaughter_subcount; + string announce; + announce = getRaceName(w->raceId) + " marked for slaughter: " + ss.str(); + Gui::showAnnouncement(announce, 2, false); + //out << announce << endl; + } } //out << slaughter_count << " units total marked for slaughter." << endl; return CR_OK; -} \ No newline at end of file +} + +//////////////////////////////////////////////////// +// autobutcher and autonestbox start/init/cleanup + +command_result start_autobutcher(color_ostream &out) +{ + auto pworld = Core::getInstance().getWorld(); + + enable_autobutcher = true; + if (!config_autobutcher.isValid()) + { + config_autobutcher = pworld->AddPersistentData("autobutcher/config"); + config_autobutcher.ival(0) = enable_autobutcher; + config_autobutcher.ival(1) = sleep_autobutcher; + config_autobutcher.ival(2) = enable_autobutcher_autowatch; + config_autobutcher.ival(3) = default_fk; + config_autobutcher.ival(4) = default_mk; + config_autobutcher.ival(5) = default_fa; + config_autobutcher.ival(6) = default_ma; + } + + out << "Starting autobutcher." << endl; + cleanup_autobutcher(out); + init_autobutcher(out); + return CR_OK; +} + +command_result init_autobutcher(color_ostream &out) +{ + auto pworld = Core::getInstance().getWorld(); + if(!pworld) + { + out << "Autobutcher has no world to read from!" << endl; + return CR_OK; + } + + config_autobutcher = pworld->GetPersistentData("autobutcher/config"); + if(config_autobutcher.isValid()) + { + if (config_autobutcher.ival(0) == -1) + { + config_autobutcher.ival(0) = enable_autobutcher; + config_autobutcher.ival(1) = sleep_autobutcher; + config_autobutcher.ival(2) = enable_autobutcher_autowatch; + config_autobutcher.ival(3) = default_fk; + config_autobutcher.ival(4) = default_mk; + config_autobutcher.ival(5) = default_fa; + config_autobutcher.ival(6) = default_ma; + out << "Autobutcher's persistent config object was invalid!" << endl; + } + else + { + enable_autobutcher = config_autobutcher.ival(0); + sleep_autobutcher = config_autobutcher.ival(1); + enable_autobutcher_autowatch = config_autobutcher.ival(2); + default_fk = config_autobutcher.ival(3); + default_mk = config_autobutcher.ival(4); + default_fa = config_autobutcher.ival(5); + default_ma = config_autobutcher.ival(6); + } + } + + if(!enable_autobutcher) + return CR_OK; + + // read watchlist from save + + std::vector items; + pworld->GetPersistentData(&items, "autobutcher/watchlist/", true); + for (auto p = items.begin(); p != items.end(); p++) + { + string key = p->key(); + out << "Reading from save: " << key << endl; + //out << " raceid: " << p->ival(0) << endl; + //out << " watched: " << p->ival(1) << endl; + //out << " fk: " << p->ival(2) << endl; + //out << " mk: " << p->ival(3) << endl; + //out << " fa: " << p->ival(4) << endl; + //out << " ma: " << p->ival(5) << endl; + + WatchedRace * w = new WatchedRace(p->ival(1), p->ival(0), p->ival(2), p->ival(3),p->ival(4),p->ival(5)); + w->rconfig = *p; + watched_races.push_back(w); + } + return CR_OK; +} + +command_result cleanup_autobutcher(color_ostream &out) +{ + for(size_t i=0; iAddPersistentData("autonestbox/config"); + config_autonestbox.ival(0) = enable_autonestbox; + config_autonestbox.ival(1) = sleep_autonestbox; + //out << "autonestbox created persistent config object." << endl; + } + out << "Starting autonestbox." << endl; + cleanup_autonestbox(out); + init_autonestbox(out); + return CR_OK; +} + +command_result init_autonestbox(color_ostream &out) +{ + auto pworld = Core::getInstance().getWorld(); + if(!pworld) + { + out << "Autonestbox has no world to read from!" << endl; + return CR_OK; + } + + config_autonestbox = pworld->GetPersistentData("autonestbox/config"); + if(config_autonestbox.isValid()) + { + if (config_autonestbox.ival(0) == -1) + { + config_autonestbox.ival(0) = enable_autonestbox; + config_autonestbox.ival(1) = sleep_autonestbox; + out << "Autonestbox's persistent config object was invalid!" << endl; + } + else + { + enable_autonestbox = config_autonestbox.ival(0); + sleep_autonestbox = config_autonestbox.ival(1); + } + } + return CR_OK; +} + +command_result cleanup_autonestbox(color_ostream &out) +{ + // nothing to cleanup currently + // (future version of autonestbox could store info about cages for useless male kids) + return CR_OK; +} From afba2842833bb306209c109a567614fdd746be15 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 9 Apr 2012 15:35:25 +0200 Subject: [PATCH 031/108] autobutcher: added a forgotten ';' --- plugins/zone.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index ef999f51b..d2f2b58e4 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -2348,7 +2348,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) { string run = "dfhack-run autobutcher "; #ifdef LINUX_BUILD - run = "./dfhack-run autobutcher " + run = "./dfhack-run autobutcher "; #endif // force creation of config out << run << "start" << endl; From eb4aa44697a857e495df61ca6968475c3c61e8b5 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 9 Apr 2012 15:47:27 +0200 Subject: [PATCH 032/108] minor: replaced some tabs with spaces in readme.rst --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index a460a57cc..9bf9551a7 100644 --- a/README.rst +++ b/README.rst @@ -911,7 +911,7 @@ Settings and watchlist are stored in the savegame, so that you can have differen :: Load savegame where you made the settings. - Start a CMD shell and navigate to the df directory. Type the following into the shell: - dfhack-run autobutcher list_export > autobutcher.bat + Start a CMD shell and navigate to the df directory. Type the following into the shell: + dfhack-run autobutcher list_export > autobutcher.bat Load the savegame where you want to copy the settings to, run the batch file (from the shell): - autobutcher.bat + autobutcher.bat From 27c195995fdf12ee1042dc03dcd19fa889eea094 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 9 Apr 2012 15:51:27 +0200 Subject: [PATCH 033/108] minor: updated readme.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9bf9551a7..3b3fca62c 100644 --- a/README.rst +++ b/README.rst @@ -874,7 +874,7 @@ Options: :stop: Stop running automatically. :sleep: Must be followed by number X. Changes the timer to sleep X frames between runs. :watch R: Start watching a race. R can be a valid race RAW id (ALPACA, BIRD_TURKEY, etc) or a list of ids seperated by spaces or the keyword 'all' which adds all races with at least one owned tame unit in your fortress to the list. -:unwatch R: Stop watching a race. The current target settings will be remembered (currently only until you save or quit the game). +:unwatch R: Stop watching a race. The current target settings will be remembered. :forget R: Stop watching a race and forget it's target settings. :autowatch: Automatically adds all new races (animals you buy from merchants, tame yourself or get from migrants) to the watch list using default target count. From 8ae774fbd50a5de2f83258f60a7cd916f28642ce Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 9 Apr 2012 16:02:03 +0200 Subject: [PATCH 034/108] fixed wrong description of the behaviour for using keyword 'all' in autobutcher (affects all races on the watchlist, not all races in your fort) --- README.rst | 6 +++--- plugins/zone.cpp | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 3b3fca62c..a26cb9e7c 100644 --- a/README.rst +++ b/README.rst @@ -873,9 +873,9 @@ Options: :start: Start running every X frames (df simulation ticks). Default: X=6000, which would be every 60 seconds at 100fps. :stop: Stop running automatically. :sleep: Must be followed by number X. Changes the timer to sleep X frames between runs. -:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, BIRD_TURKEY, etc) or a list of ids seperated by spaces or the keyword 'all' which adds all races with at least one owned tame unit in your fortress to the list. -:unwatch R: Stop watching a race. The current target settings will be remembered. -:forget R: Stop watching a race and forget it's target settings. +:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, BIRD_TURKEY, etc) or a list of ids seperated by spaces or the keyword 'all' which affects all races on your current watchlist. +:unwatch R: Stop watching race(s). The current target settings will be remembered. +:forget R: Stop watching race(s) and forget it's/their target settings. :autowatch: Automatically adds all new races (animals you buy from merchants, tame yourself or get from migrants) to the watch list using default target count. :noautowatch: Stop auto-adding new races to the watchlist. diff --git a/plugins/zone.cpp b/plugins/zone.cpp index d2f2b58e4..f741f7ae9 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -177,8 +177,7 @@ const string autobutcher_help = " watch R - start watching race(s)\n" " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n" " or a list of RAW ids seperated by spaces\n" - " or the keyword 'all' which adds all races with\n" - " at least one owned tame unit in your fortress\n" + " or the keyword 'all' which affects your whole current watchlist.\n" " unwatch R - stop watching race(s)\n" " the current target settings will be remembered\n" " forget R - unwatch race(s) and forget target settings for it/them\n" From 64a7168b834cefa3c1a0eb75328f847cca20e544 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 9 Apr 2012 16:19:06 +0200 Subject: [PATCH 035/108] added a usage example for 'autobutcher unwatch' to readme.rst --- README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.rst b/README.rst index a26cb9e7c..b0a20e1dd 100644 --- a/README.rst +++ b/README.rst @@ -904,6 +904,11 @@ Automatically put all new races onto the watchlist and mark unnamed tame units f autobutcher target 0 0 0 0 new autobutcher autowatch autobutcher start + +Stop watching the races alpaca and cat, but remember the target count settings so that you can use 'unwatch' without the need to enter the values again. Note: 'autobutcher unwatch all' works, but only makes sense if you want to keep the plugin running with the 'autowatch' feature or manually add some new races with 'watch'. If you simply want to stop it completely use 'autobutcher stop' instead. +:: + + autobutcher unwatch ALPACA CAT Note: ----- From 37f3056fdd08ad31621b0ff3a310c1bbee72b7fb Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 9 Apr 2012 18:36:01 +0200 Subject: [PATCH 036/108] autobutcher: fixed behaviour of keywords 'all' and 'new'. (didn't work properly with watch, unwatch and forget) --- plugins/zone.cpp | 90 +++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index f741f7ae9..0038a707b 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -2226,19 +2226,19 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) { parameters.erase(parameters.begin()); watch_race = true; - out << "Start watching race(s)." << endl; + out << "Start watching race(s): "; // << endl; } else if(p == "unwatch") { parameters.erase(parameters.begin()); unwatch_race = true; - out << "Stop watching race(s)." << endl; + out << "Stop watching race(s): "; // << endl; } else if(p == "forget") { parameters.erase(parameters.begin()); forget_race = true; - out << "Removing race(s) from watchlist." << endl; + out << "Removing race(s) from watchlist: "; // << endl; } else if(p == "target") { @@ -2261,7 +2261,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) ma >> target_ma; parameters.erase(parameters.begin(), parameters.begin()+5); change_target = true; - out << "Setting new target count for race(s):" << endl; + out << "Setting new target count for race(s): "; // << endl; } } else if(p == "autowatch") @@ -2406,7 +2406,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) out << endl; } - if(target_racenames.size() && target_racenames[0] == "all") + if(change_target && target_racenames.size() && target_racenames[0] == "all") { out << "Setting target count for all races on watchlist." << endl; for(size_t i=0; i & parameters) if(target_racenames.size() && (target_racenames[0] == "all" || target_racenames[0] == "new")) { - out << "Setting target count for the future." << endl; - default_fk = target_fk; - default_mk = target_mk; - default_fa = target_fa; - default_ma = target_ma; - if(config_autobutcher.isValid()) + if(change_target) { - config_autobutcher.ival(3) = default_fk; - config_autobutcher.ival(4) = default_mk; - config_autobutcher.ival(5) = default_fa; - config_autobutcher.ival(6) = default_ma; + out << "Setting target count for the future." << endl; + default_fk = target_fk; + default_mk = target_mk; + default_fa = target_fa; + default_ma = target_ma; + if(config_autobutcher.isValid()) + { + config_autobutcher.ival(3) = default_fk; + config_autobutcher.ival(4) = default_mk; + config_autobutcher.ival(5) = default_fa; + config_autobutcher.ival(6) = default_ma; + } + return CR_OK; + } + else if(target_racenames[0] == "new") + { + out << "The only valid usage of 'new' is in combination when setting a target count!" << endl; + + // hm, maybe instead of complaining start/stop autowatch instead? and get rid of the autowatch option? + if(unwatch_race) + out << "'unwatch new' makes no sense! Use 'noautowatch' instead." << endl; + else if(forget_race) + out << "'forget new' makes no sense, 'forget' is only for existing watchlist entries! Use 'noautowatch' instead." << endl; + else if(watch_race) + out << "'watch new' makes no sense! Use 'autowatch' instead." << endl; + return CR_WRONG_USAGE; } - return CR_OK; } - // map race names to ids - size_t num_races = df::global::world->raws.creatures.all.size(); - while(target_racenames.size()) + if(target_racenames.size() && target_racenames[0] == "all") { - bool found_race = false; - for(size_t i=0; iraceId); } - if(!found_race) + } + else + { + // map race names from parameter list to ids + size_t num_races = df::global::world->raws.creatures.all.size(); + while(target_racenames.size()) { - out << "Race not found: " << target_racenames.back() << endl; - return CR_OK; + bool found_race = false; + for(size_t i=0; i Date: Mon, 9 Apr 2012 18:46:43 +0200 Subject: [PATCH 037/108] minor: updated readme.rst --- README.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index b0a20e1dd..332919152 100644 --- a/README.rst +++ b/README.rst @@ -874,8 +874,8 @@ Options: :stop: Stop running automatically. :sleep: Must be followed by number X. Changes the timer to sleep X frames between runs. :watch R: Start watching a race. R can be a valid race RAW id (ALPACA, BIRD_TURKEY, etc) or a list of ids seperated by spaces or the keyword 'all' which affects all races on your current watchlist. -:unwatch R: Stop watching race(s). The current target settings will be remembered. -:forget R: Stop watching race(s) and forget it's/their target settings. +:unwatch R: Stop watching race(s). The current target settings will be remembered. R can be a list of ids or the keyword 'all'. +:forget R: Stop watching race(s) and forget it's/their target settings. R can be a list of ids or the keyword 'all'. :autowatch: Automatically adds all new races (animals you buy from merchants, tame yourself or get from migrants) to the watch list using default target count. :noautowatch: Stop auto-adding new races to the watchlist. @@ -886,6 +886,7 @@ Options: mk = number of male kids, fa = number of female adults, ma = number of female adults. + R can be a list of ids or the keyword 'all' or 'new'. R = 'all': change target count for all races on watchlist and set the new default for the future. R = 'new': don't touch current settings on the watchlist, only set the new default for future entries. :example: Print some usage examples. Examples: From 674337e3aea18203d69820c8f6f429d8af439738 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Tue, 10 Apr 2012 00:22:38 +0200 Subject: [PATCH 038/108] added tweak clear-resident which fixes bugged migrants and makes them proper members of the fortress. added tweak clear-merchant which assimilates merchants who linger at the map edge into the fortress. updated readme.rst --- README.rst | 6 ++++-- plugins/tweak.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++++++ plugins/zone.cpp | 14 +++++++++++++- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 332919152..be601180b 100644 --- a/README.rst +++ b/README.rst @@ -613,8 +613,10 @@ Contains various tweaks for minor bugs (currently just one). Options ------- -:tweak clear-missing: Remove the missing status from the selected unit. This allows engraving slabs for ghostly, but not yet found, creatures. -:tweak clear-ghostly: Remove the ghostly status from the selected unit and mark it as dead. This allows getting rid of bugged ghosts which do not show up in the engraving slab menu at all, even after using clear-missing. It works, but is potentially very dangerous - so use with care. Probably (almost certainly) it does not have the same effects like a proper burial. You've been warned. +:tweak clear-missing: Remove the missing status from the selected unit. This allows engraving slabs for ghostly, but not yet found, creatures. +:tweak clear-ghostly: Remove the ghostly status from the selected unit and mark it as dead. This allows getting rid of bugged ghosts which do not show up in the engraving slab menu at all, even after using clear-missing. It works, but is potentially very dangerous - so use with care. Probably (almost certainly) it does not have the same effects like a proper burial. You've been warned. +:tweak clear-resident: Remove the resident flag from the selected unit. Intended to fix bugged migrants who stay at the map edge and don't enter your fort. Only works for dwarves of the own civilization. +:tweak clear-merchant: Remove the merchant flag from the selected unit. Assimilates bugged merchants who don't leave the map into your fort. Only works for dwarves of the own civilization. tubefill ======== diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index d2fef313b..edfa7081a 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -50,6 +50,14 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector ¶meters) return CR_FAILURE; } } + else if (cmd == "clear-resident") + { + df::unit *unit = getSelectedUnit(out); + if (!unit) + return CR_FAILURE; + + // must be own race and civ and a merchant + if ( unit->flags2.bits.resident + && unit->race == df::global::ui->race_id + && unit->civ_id == df::global::ui->civ_id) + { + // remove resident flag + unit->flags2.bits.resident = 0; + } + else + { + out.print("That's not a resident dwarf of your civilization!\n"); + return CR_FAILURE; + } + } + else if (cmd == "clear-merchant") + { + df::unit *unit = getSelectedUnit(out); + if (!unit) + return CR_FAILURE; + + // must be own race and civ and a merchant + if ( unit->flags1.bits.merchant + && unit->race == df::global::ui->race_id + && unit->civ_id == df::global::ui->civ_id) + { + // remove merchant flag + unit->flags1.bits.merchant = 0; + } + else + { + out.print("That's not a dwarf merchant of your civilization!\n"); + return CR_FAILURE; + } + } else return CR_WRONG_USAGE; return CR_OK; diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 0038a707b..8affd2a6d 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -584,6 +584,17 @@ bool hasValidMapPos(df::unit* unit) return false; } +int getUnitIndexFromId(df::unit* unit_) +{ + for (size_t i=0; i < world->units.all.size(); i++) + { + df::unit* unit = world->units.all[i]; + if(unit->id == unit_->id) + return i; + } + return -1; +} + // dump some unit info void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) { @@ -652,7 +663,8 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) if(verbose) { - out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ")" << endl; + out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl; + out << "index in units vector: " << getUnitIndexFromId(unit) << endl; } out << endl; From b2d976b06b0ecc195e64a523557ad2a07abf6aac Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Tue, 10 Apr 2012 04:10:07 +0200 Subject: [PATCH 039/108] zone tool: added new filter 'nograzer' --- README.rst | 1 + plugins/zone.cpp | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index be601180b..e8e444bb7 100644 --- a/README.rst +++ b/README.rst @@ -835,6 +835,7 @@ Filters: :female: Creature is female. :egglayer: Race lays eggs. :grazer: Race is a grazer. +:nograzer: Race is not a grazer. :milkable: Race is milkable. :minage: Minimum age. Must be followed by number. :maxage: Maximum age. Must be followed by number. diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 8affd2a6d..d16578a60 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -117,7 +117,8 @@ const string zone_help_filters = " male - obvious\n" " female - obvious\n" " egglayer - race lays eggs (use together with 'female')\n" - " grazer - obvious\n" + " grazer - is a grazer\n" + " nograzer - not a grazer\n" " milkable - race is milkable (use together with 'female')\n" " minage - minimum age. must be followed by number\n" " maxage - maximum age. must be followed by number\n"; @@ -469,7 +470,6 @@ bool isHunter(df::unit* creature) return false; } - // check if creature belongs to the player's civilization // (don't try to pasture/slaughter random untame animals) bool isOwnCiv(df::unit* creature) @@ -1321,6 +1321,7 @@ command_result df_zone (color_ostream &out, vector & parameters) bool find_female = false; bool find_egglayer = false; bool find_grazer = false; + bool find_nograzer = false; bool find_milkable = false; bool find_named = false; @@ -1556,6 +1557,10 @@ command_result df_zone (color_ostream &out, vector & parameters) { find_grazer = true; } + else if(p == "nograzer") + { + find_nograzer = true; + } else if(p == "merchant") { find_merchant = true; @@ -1602,6 +1607,21 @@ command_result df_zone (color_ostream &out, vector & parameters) find_female=false; } + // search for trained and untrained is exclusive, so drop the flags if both are specified + // (there is no trained filter, since it doesn't make much sense to throw war and hunting pets together) + //if(find_trained && find_untrained) + //{ + // find_trained=false; + // find_female=false; + //} + + // search for grazer and nograzer is exclusive, so drop the flags if both are specified + if(find_grazer && find_nograzer) + { + find_grazer=false; + find_nograzer=false; + } + // try to cope with user dumbness if(target_agemin > target_agemax) { @@ -1704,6 +1724,7 @@ command_result df_zone (color_ostream &out, vector & parameters) || (find_agemin && getUnitAge(unit)target_agemax) || (find_grazer && !isGrazer(unit)) + || (find_nograzer && isGrazer(unit)) || (find_egglayer && !isEggLayer(unit)) || (find_milkable && !isMilkable(unit)) || (find_male && !isMale(unit)) From 0e0740fddf26a849c97c26bd8e6dcb6cf58b9bf9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 10 Apr 2012 10:34:03 +0400 Subject: [PATCH 040/108] Stop printall(df.global) from breaking if there are unknown addresses. --- LUA_API.rst | 5 +++++ Lua API.html | 4 ++++ library/LuaTypes.cpp | 15 +++++++++------ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 71cb6030f..3ce10c766 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -68,6 +68,11 @@ All typed objects have the following built-in features: and values. Fields are enumerated in memory order. Methods and lua wrapper properties are not included in the iteration. + **WARNING**: a few of the data structures (like ui_look_list) + contain unions with pointers to different types with vtables. + Using pairs on such structs is an almost sure way to crash with + an access violation. + * ``ref._kind`` Returns one of: ``primitive``, ``struct``, ``container``, diff --git a/Lua API.html b/Lua API.html index 569940f56..82943fadf 100644 --- a/Lua API.html +++ b/Lua API.html @@ -395,6 +395,10 @@ Every structured field access produces a new userdata instance.

      Returns an iterator for the sequence of actual C++ field names and values. Fields are enumerated in memory order. Methods and lua wrapper properties are not included in the iteration.

      +

      WARNING: a few of the data structures (like ui_look_list) +contain unions with pointers to different types with vtables. +Using pairs on such structs is an almost sure way to crash with +an access violation.

    • ref._kind

      Returns one of: primitive, struct, container, diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 4213499eb..2f9ef9e81 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1072,10 +1072,10 @@ void LuaWrapper::SetFunctionWrappers(lua_State *state, const FunctionReg *reg) /** * Add fields in the array to the UPVAL_FIELDTABLE candidates on the stack. */ -static void IndexFields(lua_State *state, int base, struct_identity *pstruct) +static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bool globals) { if (pstruct->getParent()) - IndexFields(state, base, pstruct->getParent()); + IndexFields(state, base, pstruct->getParent(), globals); auto fields = pstruct->getFields(); if (!fields) @@ -1105,7 +1105,10 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct) break; default: - AssociateId(state, base+3, ++cnt, name.c_str()); + // Do not add invalid globals to the enumeration order + if (!globals || *(void**)fields[i].offset) + AssociateId(state, base+3, ++cnt, name.c_str()); + lua_pushlightuserdata(state, (void*)&fields[i]); lua_setfield(state, base+2, name.c_str()); break; @@ -1143,7 +1146,7 @@ void LuaWrapper::IndexStatics(lua_State *state, int meta_idx, int ftable_idx, st * Make a struct-style object metatable. */ static void MakeFieldMetatable(lua_State *state, struct_identity *pstruct, - lua_CFunction reader, lua_CFunction writer) + lua_CFunction reader, lua_CFunction writer, bool globals = false) { int base = lua_gettop(state); @@ -1152,7 +1155,7 @@ static void MakeFieldMetatable(lua_State *state, struct_identity *pstruct, // Index the fields lua_newtable(state); - IndexFields(state, base, pstruct); + IndexFields(state, base, pstruct, globals); // Add the iteration metamethods PushStructMethod(state, base+1, base+3, meta_struct_next); @@ -1304,7 +1307,7 @@ void struct_identity::build_metatable(lua_State *state) void global_identity::build_metatable(lua_State *state) { - MakeFieldMetatable(state, this, meta_global_index, meta_global_newindex); + MakeFieldMetatable(state, this, meta_global_index, meta_global_newindex, true); } /** From f8123c3b4060ad4e949e833fb4f1106c705dc925 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Tue, 10 Apr 2012 09:15:38 +0200 Subject: [PATCH 041/108] zone: can now assign to and from built cages. autobutcher: fixed initializing when getting SC_MAP_LOADED event while a map is currently running --- plugins/zone.cpp | 276 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 215 insertions(+), 61 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index d16578a60..5d5c196c0 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -11,6 +11,7 @@ // - print detailed info about activity zone and units under cursor (mostly for checking refs and stuff) // - mark a zone which is used for future assignment commands // - assign single selected creature to a zone +// - mass-assign creatures using filters // - unassign single creature under cursor from current zone // - pitting own dwarves :) // - full automation of handling mini-pastures over nestboxes: @@ -76,16 +77,17 @@ command_result df_autobutcher(color_ostream &out, vector & parameters); DFHACK_PLUGIN("zone"); const string zone_help = - "Allows easier management of pens/pastures and pits.\n" + "Allows easier management of pens/pastures, pits and cages.\n" "Options:\n" " set - set zone under cursor as default for future assigns\n" " assign - assign creature(s) to a pen or pit\n" " if no filters are used, a single unit must be selected.\n" - " can be followed by valid zone id which will then be set.\n" + " can be followed by valid building id which will then be set.\n" + " building must be a pen/pasture, pit or cage.\n" " slaughter - mark creature(s) for slaughter\n" " if no filters are used, a single unit must be selected.\n" " with filters named units are ignored unless specified.\n" - " unassign - unassign selected creature from it's zone\n" + " unassign - unassign selected creature(s) from it's zone or cage\n" " uinfo - print info about selected unit\n" " zinfo - print info about zone(s) under cursor\n" " verbose - print some more info, mostly useless debug stuff\n" @@ -137,7 +139,7 @@ const string zone_help_examples = " zone assign all own race DWARF maxage 2\n" " throw all useless kids into a pit :)\n" "Notes:\n" - " Assigning per filters ignores built cages and chains currently. Usually you\n" + " Unassigning per filters ignores built cages and chains currently. Usually you\n" " should always use the filter 'own' (which implies tame) unless you want to\n" " use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you\n" " specify 'race DWARF' and it ignores merchants and their animals unless you\n" @@ -348,7 +350,7 @@ int32_t findCageAtCursor(); int32_t findChainAtCursor(); df::general_ref_building_civzone_assignedst * createCivzoneRef(); -bool unassignUnitFromZone(df::unit* unit); +bool unassignUnitFromBuilding(df::unit* unit); command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose); void unitInfo(color_ostream & out, df::unit* creature, bool verbose); void zoneInfo(color_ostream & out, df::building* building, bool verbose); @@ -948,6 +950,31 @@ bool isContainedInItem(df::unit* unit) return contained; } +bool isInBuiltCage(df::unit* unit) +{ + bool caged = false; + for (size_t b=0; b < world->buildings.all.size(); b++) + { + df::building* building = world->buildings.all[b]; + if( building->getType() == building_type::Cage) + { + df::building_cagest* oldcage = (df::building_cagest*) building; + for(size_t oc=0; ocassigned_creature.size(); oc++) + { + if(oldcage->assigned_creature[oc] == unit->id) + { + oldcage->assigned_creature.erase(oldcage->assigned_creature.begin() + oc); + caged = true; + break; + } + } + } + if(caged) + break; + } + return caged; +} + // check a map position for a built cage // animals in cages are CONTAINED_IN_ITEM, no matter if they are on a stockpile or inside a built cage // if they are on animal stockpiles they should count as unassigned to allow pasturing them @@ -970,6 +997,24 @@ bool isBuiltCageAtPos(df::coord pos) return cage; } +df::building * getBuiltCageAtPos(df::coord pos) +{ + df::building* cage = NULL; + for (size_t b=0; b < world->buildings.all.size(); b++) + { + df::building* building = world->buildings.all[b]; + if( building->getType() == building_type::Cage + && building->x1 == pos.x + && building->y1 == pos.y + && building->z == pos.z ) + { + cage = building; + break; + } + } + return cage; +} + bool isNestboxAtPos(int32_t x, int32_t y, int32_t z) { bool found = false; @@ -1062,30 +1107,88 @@ size_t countFreeEgglayers() } // check if unit is already assigned to a zone, remove that ref from unit and old zone -// returns false if no pasture information was found +// check if unit is already assigned to a cage, remove that ref from the cage +// returns false if no cage or pasture information was found // helps as workaround for http://www.bay12games.com/dwarves/mantisbt/view.php?id=4475 by the way // (pastured animals assigned to chains will get hauled back and forth because the pasture ref is not deleted) -bool unassignUnitFromZone(df::unit* unit) +bool unassignUnitFromBuilding(df::unit* unit) { bool success = false; for (std::size_t idx = 0; idx < unit->refs.size(); idx++) { df::general_ref * oldref = unit->refs[idx]; - if(oldref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) + switch(oldref->getType()) { - unit->refs.erase(unit->refs.begin() + idx); - df::building_civzonest * oldciv = (df::building_civzonest *) oldref->getBuilding(); - for(size_t oc=0; ocassigned_creature.size(); oc++) + case df::general_ref_type::BUILDING_CIVZONE_ASSIGNED: { - if(oldciv->assigned_creature[oc] == unit->id) + unit->refs.erase(unit->refs.begin() + idx); + df::building_civzonest * oldciv = (df::building_civzonest *) oldref->getBuilding(); + for(size_t oc=0; ocassigned_creature.size(); oc++) { - oldciv->assigned_creature.erase(oldciv->assigned_creature.begin() + oc); - break; + if(oldciv->assigned_creature[oc] == unit->id) + { + oldciv->assigned_creature.erase(oldciv->assigned_creature.begin() + oc); + break; + } } + delete oldref; + success = true; + break; + } + + case df::general_ref_type::CONTAINED_IN_ITEM: + { + // game does not erase the ref until creature gets removed from cage + //unit->refs.erase(unit->refs.begin() + idx); + + // walk through buildings, check cages for inhabitants, compare ids + for (size_t b=0; b < world->buildings.all.size(); b++) + { + bool found = false; + df::building* building = world->buildings.all[b]; + if(isCage(building)) + { + df::building_cagest* oldcage = (df::building_cagest*) building; + for(size_t oc=0; ocassigned_creature.size(); oc++) + { + if(oldcage->assigned_creature[oc] == unit->id) + { + oldcage->assigned_creature.erase(oldcage->assigned_creature.begin() + oc); + found = true; + break; + } + } + } + if(found) + break; + } + success = true; + break; + } + + case df::general_ref_type::BUILDING_CHAIN: + { + // try not erasing the ref and see what happens + //unit->refs.erase(unit->refs.begin() + idx); + // probably need to delete chain reference here + //success = true; + break; + } + + case df::general_ref_type::BUILDING_CAGED: + { + // not sure what to do here, doesn't seem to get used by the game + //unit->refs.erase(unit->refs.begin() + idx); + //success = true; + break; + } + + default: + { + // some reference which probably shouldn't get deleted + // (animals who are historical figures and have a NEMESIS reference or whatever) + break; } - delete oldref; - success = true; - break; } } return success; @@ -1112,10 +1215,11 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building } // check if unit is already pastured, remove that ref from unit and old pasture - // testing showed that this only seems to be necessary for pastured creatures + // testing showed that removing the ref from the unit only seems to be necessary for pastured creatures // if they are in cages on stockpiles the game unassigns them automatically - // (need to check if that is also true for chains and built cages) - bool cleared_old = unassignUnitFromZone(unit); + // if they are in built cages the pointer to the creature needs to be removed from the cage + // TODO: check what needs to be done for chains + bool cleared_old = unassignUnitFromBuilding(unit); if(verbose) { @@ -1143,19 +1247,56 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building return CR_OK; } -command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) +command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building* building, bool verbose) { - out << "sorry. assigning to cages is not possible yet." << endl; - return CR_WRONG_USAGE; + // building must be a pen/pasture or pit + if(!isCage(building)) + { + out << "Invalid building type. This is not a cage." << endl; + return CR_WRONG_USAGE; + } + + // try to get a fresh civzone ref + //df::general_ref_building_civzone_assignedst * ref = createCivzoneRef(); + //if(!ref) + //{ + // out << "Could not find a clonable activity zone reference" << endl + // << "You need to pen/pasture/pit at least one creature" << endl + // << "before using 'assign' for the first time." << endl; + // return CR_WRONG_USAGE; + //} + + // check if unit is already pastured or caged, remove refs where necessary + bool cleared_old = unassignUnitFromBuilding(unit); + if(verbose) + { + if(cleared_old) + out << "old zone info cleared."; + else + out << "no old zone info found."; + } + + //ref->building_id = building->id; + //unit->refs.push_back(ref); + + df::building_cagest* civz = (df::building_cagest*) building; + civz->assigned_creature.push_back(unit->id); + + out << "Unit " << unit->id + << "(" << getRaceName(unit) << ")" + << " assigned to cage " << building->id; + out << endl; + + return CR_OK; } -command_result assignUnitToChain(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) +command_result assignUnitToChain(color_ostream& out, df::unit* unit, df::building* building, bool verbose) { out << "sorry. assigning to chains is not possible yet." << endl; return CR_WRONG_USAGE; } -command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) +command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::building* building, bool verbose) { command_result result = CR_WRONG_USAGE; @@ -1172,7 +1313,7 @@ command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::buil } // dump some zone info -void zoneInfo(color_ostream & out, df::building* building, bool verbose = false) +void zoneInfo(color_ostream & out, df::building* building, bool verbose) { if(building->getType()!= building_type::Civzone) return; @@ -1231,7 +1372,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose = false) } // dump some cage info -void cageInfo(color_ostream & out, df::building* building, bool verbose = false) +void cageInfo(color_ostream & out, df::building* building, bool verbose) { if(!isCage(building)) return; @@ -1336,13 +1477,13 @@ command_result df_zone (color_ostream &out, vector & parameters) bool find_race = false; string target_race = ""; - bool zone_assign = false; - bool zone_unassign = false; - bool zone_set = false; + bool building_assign = false; + bool building_unassign = false; + bool building_set = false; bool verbose = false; bool all = false; bool unit_slaughter = false; - static int target_zone = -1; + static int target_building = -1; for (size_t i = 0; i < parameters.size(); i++) { @@ -1377,7 +1518,7 @@ command_result df_zone (color_ostream &out, vector & parameters) } else if(p == "unassign") { - zone_unassign = true; + building_unassign = true; } else if(p == "assign") { @@ -1385,24 +1526,24 @@ command_result df_zone (color_ostream &out, vector & parameters) if(i < parameters.size()-1) { stringstream ss(parameters[i+1]); - int new_zone = -1; - ss >> new_zone; - if(new_zone != -1) + int new_building = -1; + ss >> new_building; + if(new_building != -1) { i++; - target_zone = new_zone; - out << "Assign selected unit(s) to zone #" << target_zone < & parameters) } else if(p == "set") { - zone_set = true; + building_set = true; } else if(p == "all") { @@ -1591,7 +1732,7 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_FAILURE; } - if((zone_info && !all) || zone_set) + if((zone_info && !all) || building_set) need_cursor = true; if(need_cursor && cursor->x == -30000) @@ -1653,31 +1794,43 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } - // set building at cursor position to be new target zone - if(zone_set) + // set building at cursor position to be new target building + if(building_set) { - target_zone = findPenPitAtCursor(); - if(target_zone==-1) + target_building = findCageAtCursor(); + if(target_building != -1) { - out << "No pen/pasture or pit under cursor!" << endl; - return CR_WRONG_USAGE; + out << "Target building type: cage." << endl; } - out << "Current zone set to #" << target_zone << endl; + else + { + target_building = findPenPitAtCursor(); + if(target_building == -1) + { + out << "No pen/pasture or pit under cursor!" << endl; + return CR_WRONG_USAGE; + } + else + { + out << "Target building type: pen/pasture or pit." << endl; + } + } + out << "Current building set to #" << target_building << endl; return CR_OK; } // assign to pen or pit - if(zone_assign || unit_info || unit_slaughter) + if(building_assign || unit_info || unit_slaughter) { df::building * building; - if(zone_assign) + if(building_assign) { // try to get building index from the id - int32_t index = findBuildingIndexById(target_zone); + int32_t index = findBuildingIndexById(target_building); if(index == -1) { out << "Invalid building id." << endl; - target_zone = -1; + target_building = -1; return CR_WRONG_USAGE; } building = world->buildings.all.at(index); @@ -1713,7 +1866,8 @@ command_result df_zone (color_ostream &out, vector & parameters) } if( (find_unassigned && isAssigned(unit)) - || (isContainedInItem(unit) && (find_uncaged || isBuiltCageAtPos(unit->pos))) + // avoid tampering with creatures who are currently being hauled to a built cage + || (isContainedInItem(unit) && (find_uncaged || isInBuiltCage(unit))) || (isChained(unit)) || (find_caged && !isContainedInItem(unit)) || (find_own && !isOwnCiv(unit)) @@ -1751,7 +1905,7 @@ command_result df_zone (color_ostream &out, vector & parameters) { unitInfo(out, unit, verbose); } - else if(zone_assign) + else if(building_assign) { command_result result = assignUnitToBuilding(out, unit, building, verbose); if(result != CR_OK) @@ -1786,7 +1940,7 @@ command_result df_zone (color_ostream &out, vector & parameters) unitInfo(out, unit, verbose); return CR_OK; } - else if(zone_assign) + else if(building_assign) { return assignUnitToBuilding(out, unit, building, verbose); } @@ -1810,7 +1964,7 @@ command_result df_zone (color_ostream &out, vector & parameters) // using the zone tool to free creatures from cages or chains // is pointless imo since that is already quite easy using the ingame UI. // but it's easy to implement so I might as well add it later - if(zone_unassign) + if(building_unassign) { // must have unit selected df::unit *unit = getSelectedUnit(out); @@ -1821,7 +1975,7 @@ command_result df_zone (color_ostream &out, vector & parameters) } // remove assignment reference from unit and old zone - if(unassignUnitFromZone(unit)) + if(unassignUnitFromBuilding(unit)) out << "Unit unassigned." << endl; else out << "Unit is not assigned to an activity zone!" << endl; @@ -2685,13 +2839,13 @@ command_result start_autobutcher(color_ostream &out) } out << "Starting autobutcher." << endl; - cleanup_autobutcher(out); init_autobutcher(out); return CR_OK; } command_result init_autobutcher(color_ostream &out) { + cleanup_autobutcher(out); auto pworld = Core::getInstance().getWorld(); if(!pworld) { @@ -2772,13 +2926,13 @@ command_result start_autonestbox(color_ostream &out) //out << "autonestbox created persistent config object." << endl; } out << "Starting autonestbox." << endl; - cleanup_autonestbox(out); init_autonestbox(out); return CR_OK; } command_result init_autonestbox(color_ostream &out) { + cleanup_autonestbox(out); auto pworld = Core::getInstance().getWorld(); if(!pworld) { From 93c795cfc3eebf60157cc56e5aa38c521a60b0b3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 10 Apr 2012 11:43:36 +0400 Subject: [PATCH 042/108] Job module api tweaks: add a namespace to match others and some funcs. --- LUA_API.rst | 16 +++++++++- Lua API.html | 13 +++++++- library/LuaApi.cpp | 45 ++++++++++++++++++++++++---- library/include/modules/Job.h | 26 ++++++++++------ library/modules/Job.cpp | 56 ++++++++++++++++++++++++++++++----- plugins/jobutils.cpp | 16 +++++----- plugins/workflow.cpp | 22 +++++++------- 7 files changed, 152 insertions(+), 42 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 3ce10c766..ee3b1077c 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -634,10 +634,18 @@ Job module Prints info about the job. -* ``dfhack.job.getJobHolder(job)`` +* ``dfhack.job.printItemDetails(jobitem,idx)`` + + Prints info about the job item. + +* ``dfhack.job.getHolder(job)`` Returns the building holding the job. +* ``dfhack.job.getWorker(job)`` + + Returns the unit performing the job. + * ``dfhack.job.is_equal(job1,job2)`` Compares important fields in the job and nested item structures. @@ -646,6 +654,12 @@ Job module Compares important fields in the job item structures. +* ``dfhack.job.listNewlyCreated(first_id)`` + + Returns the current value of ``df.global.job_next_id``, and + if there are any jobs with ``first_id <= id < job_next_id``, + a lua list containing them. + Units module ------------ diff --git a/Lua API.html b/Lua API.html index 82943fadf..89e158037 100644 --- a/Lua API.html +++ b/Lua API.html @@ -873,15 +873,26 @@ The is_bright boolean actually seems to invert the brightness.

    • dfhack.job.printJobDetails(job)

      Prints info about the job.

    • -
    • dfhack.job.getJobHolder(job)

      +
    • dfhack.job.printItemDetails(jobitem,idx)

      +

      Prints info about the job item.

      +
    • +
    • dfhack.job.getHolder(job)

      Returns the building holding the job.

    • +
    • dfhack.job.getWorker(job)

      +

      Returns the unit performing the job.

      +
    • dfhack.job.is_equal(job1,job2)

      Compares important fields in the job and nested item structures.

    • dfhack.job.is_item_equal(job_item1,job_item2)

      Compares important fields in the job item structures.

    • +
    • dfhack.job.listNewlyCreated(first_id)

      +

      Returns the current value of df.global.job_next_id, and +if there are any jobs with first_id <= id < job_next_id, +a lua list containing them.

      +
    diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 5e55460d7..c336e309c 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -517,10 +517,13 @@ static void OpenMatinfo(lua_State *state) * Wrappers for C++ API * ************************/ -static void OpenModule(lua_State *state, const char *mname, const LuaWrapper::FunctionReg *reg) +static void OpenModule(lua_State *state, const char *mname, + const LuaWrapper::FunctionReg *reg, const luaL_Reg *reg2 = NULL) { luaL_getsubtable(state, lua_gettop(state), mname); LuaWrapper::SetFunctionWrappers(state, reg); + if (reg2) + luaL_setfuncs(state, reg2, 0); lua_pop(state, 1); } @@ -547,14 +550,46 @@ static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } static bool jobItemEqual(df::job_item *job1, df::job_item *job2) { return *job1 == *job2; } static const LuaWrapper::FunctionReg dfhack_job_module[] = { - WRAP(cloneJobStruct), - WRAP(printJobDetails), - WRAP(getJobHolder), + WRAPM(Job,cloneJobStruct), + WRAPM(Job,printItemDetails), + WRAPM(Job,printJobDetails), + WRAPM(Job,getHolder), + WRAPM(Job,getWorker), WRAPN(is_equal, jobEqual), WRAPN(is_item_equal, jobItemEqual), { NULL, NULL } }; +static int job_listNewlyCreated(lua_State *state) +{ + int nxid = luaL_checkint(state, 1); + + lua_settop(state, 1); + + std::vector pvec; + if (Job::listNewlyCreated(&pvec, &nxid)) + { + lua_pushinteger(state, nxid); + lua_newtable(state); + + for (size_t i = 0; i < pvec.size(); i++) + { + Lua::PushDFObject(state, pvec[i]); + lua_rawseti(state, -2, i+1); + } + + return 2; + } + else + return 1; +} + +static const luaL_Reg dfhack_job_funcs[] = { + { "listNewlyCreated", job_listNewlyCreated }, + { NULL, NULL } +}; + + static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, setNickname), WRAPM(Units, getVisibleName), @@ -576,6 +611,6 @@ void OpenDFHackApi(lua_State *state) LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); - OpenModule(state, "job", dfhack_job_module); + OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "units", dfhack_units_module); } diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 8a82958ee..d733c542d 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -36,24 +36,32 @@ namespace df struct job_item; struct job_item_filter; struct building; + struct unit; } namespace DFHack { - // Duplicate the job structure. It is not linked into any DF lists. - DFHACK_EXPORT df::job *cloneJobStruct(df::job *job); + namespace Job { + // Duplicate the job structure. It is not linked into any DF lists. + DFHACK_EXPORT df::job *cloneJobStruct(df::job *job); - // Delete a cloned structure. - DFHACK_EXPORT void deleteJobStruct(df::job *job); + // Delete a cloned structure. + DFHACK_EXPORT void deleteJobStruct(df::job *job); - DFHACK_EXPORT bool operator== (const df::job_item &a, const df::job_item &b); - DFHACK_EXPORT bool operator== (const df::job &a, const df::job &b); + DFHACK_EXPORT void printItemDetails(color_ostream &out, df::job_item *item, int idx); + DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job); + + DFHACK_EXPORT df::building *getHolder(df::job *job); + DFHACK_EXPORT df::unit *getWorker(df::job *job); - DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job); + DFHACK_EXPORT bool linkIntoWorld(df::job *job, bool new_id = true); - DFHACK_EXPORT df::building *getJobHolder(df::job *job); + // lists jobs with ids >= *id_var, and sets *id_var = *job_next_id; + DFHACK_EXPORT bool listNewlyCreated(std::vector *pvec, int *id_var); + } - DFHACK_EXPORT bool linkJobIntoWorld(df::job *job, bool new_id = true); + DFHACK_EXPORT bool operator== (const df::job_item &a, const df::job_item &b); + DFHACK_EXPORT bool operator== (const df::job &a, const df::job &b); } #endif diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 679ecc0e7..5ce6647d9 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -53,7 +53,7 @@ using namespace std; using namespace DFHack; using namespace df::enums; -df::job *DFHack::cloneJobStruct(df::job *job) +df::job *DFHack::Job::cloneJobStruct(df::job *job) { CHECK_NULL_POINTER(job); @@ -87,7 +87,7 @@ df::job *DFHack::cloneJobStruct(df::job *job) return pnew; } -void DFHack::deleteJobStruct(df::job *job) +void DFHack::Job::deleteJobStruct(df::job *job) { if (!job) return; @@ -148,7 +148,7 @@ bool DFHack::operator== (const df::job &a, const df::job &b) return true; } -static void print_job_item_details(color_ostream &out, df::job *job, unsigned idx, df::job_item *item) +void DFHack::Job::printItemDetails(color_ostream &out, df::job_item *item, int idx) { CHECK_NULL_POINTER(item); @@ -184,7 +184,7 @@ static void print_job_item_details(color_ostream &out, df::job *job, unsigned id out << " tool use: " << ENUM_KEY_STR(tool_uses, item->has_tool_use) << endl; } -void DFHack::printJobDetails(color_ostream &out, df::job *job) +void DFHack::Job::printJobDetails(color_ostream &out, df::job *job) { CHECK_NULL_POINTER(job); @@ -224,10 +224,10 @@ void DFHack::printJobDetails(color_ostream &out, df::job *job) out << " reaction: " << job->reaction_name << endl; for (size_t i = 0; i < job->job_items.size(); i++) - print_job_item_details(out, job, i, job->job_items[i]); + printItemDetails(out, job->job_items[i], i); } -df::building *DFHack::getJobHolder(df::job *job) +df::building *DFHack::Job::getHolder(df::job *job) { CHECK_NULL_POINTER(job); @@ -241,7 +241,21 @@ df::building *DFHack::getJobHolder(df::job *job) return NULL; } -bool DFHack::linkJobIntoWorld(df::job *job, bool new_id) +df::unit *DFHack::Job::getWorker(df::job *job) +{ + CHECK_NULL_POINTER(job); + + for (size_t i = 0; i < job->references.size(); i++) + { + VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->references[i]); + if (ref) + return ref->getUnit(); + } + + return NULL; +} + +bool DFHack::Job::linkIntoWorld(df::job *job, bool new_id) { using df::global::world; using df::global::job_next_id; @@ -269,3 +283,31 @@ bool DFHack::linkJobIntoWorld(df::job *job, bool new_id) return true; } } + +bool DFHack::Job::listNewlyCreated(std::vector *pvec, int *id_var) +{ + using df::global::world; + using df::global::job_next_id; + + pvec->clear(); + + if (!job_next_id || *job_next_id <= *id_var) + return false; + + int old_id = *id_var; + int cur_id = *job_next_id; + + *id_var = cur_id; + + pvec->reserve(std::min(20,cur_id - old_id)); + + df::job_list_link *link = world->job_list.next; + for (; link; link = link->next) + { + int id = link->item->id; + if (id >= old_id) + pvec->push_back(link->item); + } + + return true; +} diff --git a/plugins/jobutils.cpp b/plugins/jobutils.cpp index 603346c33..511095943 100644 --- a/plugins/jobutils.cpp +++ b/plugins/jobutils.cpp @@ -289,9 +289,9 @@ static command_result job_duplicate(color_ostream &out, vector & parame } // Actually clone - df::job *pnew = cloneJobStruct(job); + df::job *pnew = Job::cloneJobStruct(job); - linkJobIntoWorld(pnew); + Job::linkIntoWorld(pnew); vector_insert_at(building->jobs, ++*ui_workshop_job_cursor, pnew); return CR_OK; @@ -325,14 +325,14 @@ static command_result job_cmd(color_ostream &out, vector & parameters) return CR_WRONG_USAGE; if (cmd == "query") { - printJobDetails(out, job); + Job::printJobDetails(out, job); } else { if (!Gui::workshop_job_hotkey(Core::getTopViewscreen())) return CR_WRONG_USAGE; df::building *selected = world->selected_building; for (size_t i = 0; i < selected->jobs.size(); i++) - printJobDetails(out, selected->jobs[i]); + Job::printJobDetails(out, selected->jobs[i]); } } else if (cmd == "item-material") @@ -355,7 +355,7 @@ static command_result job_cmd(color_ostream &out, vector & parameters) if (minfo.isValid() && !iinfo.matches(*item, &minfo)) { out.printerr("Material does not match the requirements.\n"); - printJobDetails(out, job); + Job::printJobDetails(out, job); return CR_FAILURE; } @@ -376,7 +376,7 @@ static command_result job_cmd(color_ostream &out, vector & parameters) out.printerr("WARNING: Due to a probable bug, creature & plant material subtype\n" " is ignored unless the item type is also specified.\n"); - printJobDetails(out, job); + Job::printJobDetails(out, job); return CR_OK; } else if (cmd == "item-type") @@ -399,7 +399,7 @@ static command_result job_cmd(color_ostream &out, vector & parameters) if (iinfo.isValid() && !iinfo.matches(*item, &minfo)) { out.printerr("Item type does not match the requirements.\n"); - printJobDetails(out, job); + Job::printJobDetails(out, job); return CR_FAILURE; } @@ -407,7 +407,7 @@ static command_result job_cmd(color_ostream &out, vector & parameters) item->item_subtype = iinfo.subtype; out << "Job item updated." << endl; - printJobDetails(out, job); + Job::printJobDetails(out, job); return CR_OK; } else diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 5bdeb5d58..480379261 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -184,9 +184,9 @@ public: ProtectedJob(df::job *job) : id(job->id) { tick_idx = cur_tick_idx; - holder = getJobHolder(job); + holder = Job::getHolder(job); building_id = holder ? holder->id : -1; - job_copy = cloneJobStruct(job); + job_copy = Job::cloneJobStruct(job); actual_job = job; reaction_id = -1; @@ -196,7 +196,7 @@ public: ~ProtectedJob() { - deleteJobStruct(job_copy); + Job::deleteJobStruct(job_copy); } bool isActuallyResumed() { @@ -214,8 +214,8 @@ public: return; reaction_id = -1; - deleteJobStruct(job_copy); - job_copy = cloneJobStruct(job); + Job::deleteJobStruct(job_copy); + job_copy = Job::cloneJobStruct(job); } void tick_job(df::job *job, int ticks) @@ -365,7 +365,7 @@ static ProtectedJob *get_known(int id) static bool isSupportedJob(df::job *job) { return job->misc_links.empty() && - getJobHolder(job) && + Job::getHolder(job) && (!job->job_items.empty() || job->job_type == job_type::CollectClay || job->job_type == job_type::CollectSand); @@ -526,11 +526,11 @@ static bool recover_job(color_ostream &out, ProtectedJob *pj) } // Create and link in the actual job structure - df::job *recovered = cloneJobStruct(pj->job_copy); + df::job *recovered = Job::cloneJobStruct(pj->job_copy); - if (!linkJobIntoWorld(recovered, false)) // reuse same id + if (!Job::linkIntoWorld(recovered, false)) // reuse same id { - deleteJobStruct(recovered); + Job::deleteJobStruct(recovered); out.printerr("Inconsistency: job %d (%s) already in list.\n", pj->id, ENUM_KEY_STR(job_type, pj->job_copy->job_type).c_str()); @@ -1435,7 +1435,7 @@ static void print_job(color_ostream &out, ProtectedJob *pj) df::job *job = pj->isLive() ? pj->actual_job : pj->job_copy; - printJobDetails(out, job); + Job::printJobDetails(out, job); if (job->job_type == job_type::MeltMetalObject && isOptionEnabled(CF_AUTOMELT)) @@ -1560,7 +1560,7 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet pending = true; } - printJobDetails(out, pending_recover[i]->job_copy); + Job::printJobDetails(out, pending_recover[i]->job_copy); } } From 249be0c1a0e267e806a8b0b1342f1f58f53e44fd Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 10 Apr 2012 12:11:00 +0400 Subject: [PATCH 043/108] Change SC_MAP_LOADED handling: only NULL/not NULL change is meaningful. --- library/Core.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index b3c91034f..a752ae628 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -893,26 +893,31 @@ int Core::Update() if (new_wdata != last_world_data_ptr) { // we check for map change too - bool mapchange = new_mapdata != last_local_map_ptr; + bool had_map = isMapLoaded(); last_world_data_ptr = new_wdata; last_local_map_ptr = new_mapdata; getWorld()->ClearPersistentCache(); // and if the world is going away, we report the map change first - if(!new_wdata && mapchange) - plug_mgr->OnStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED); + if(had_map) + plug_mgr->OnStateChange(out, SC_MAP_UNLOADED); // and if the world is appearing, we report map change after that plug_mgr->OnStateChange(out, new_wdata ? SC_WORLD_LOADED : SC_WORLD_UNLOADED); - if(new_wdata && mapchange) - plug_mgr->OnStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED); + if(isMapLoaded()) + plug_mgr->OnStateChange(out, SC_MAP_LOADED); } // otherwise just check for map change... else if (new_mapdata != last_local_map_ptr) { + bool had_map = isMapLoaded(); last_local_map_ptr = new_mapdata; - getWorld()->ClearPersistentCache(); - plug_mgr->OnStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED); + + if (isMapLoaded() != had_map) + { + getWorld()->ClearPersistentCache(); + plug_mgr->OnStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED); + } } // detect if the viewscreen changed From b15d2da819b434f94dc7bd2cd22bbab93c992565 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 10 Apr 2012 18:21:19 +0400 Subject: [PATCH 044/108] Get rid of some obsolete api functions, and restructure MapCache. --- library/include/df/custom/coord2d.methods.inc | 4 + library/include/modules/MapCache.h | 418 +++++--------- library/include/modules/Maps.h | 93 +--- library/modules/Maps.cpp | 508 +++++++++--------- plugins/autodump.cpp | 2 +- plugins/changelayer.cpp | 6 +- plugins/dig.cpp | 11 +- plugins/liquids.cpp | 16 +- plugins/mapexport/mapexport.cpp | 14 +- plugins/probe.cpp | 10 +- plugins/prospector.cpp | 14 +- 11 files changed, 414 insertions(+), 682 deletions(-) diff --git a/library/include/df/custom/coord2d.methods.inc b/library/include/df/custom/coord2d.methods.inc index c27629458..f15475b6f 100644 --- a/library/include/df/custom/coord2d.methods.inc +++ b/library/include/df/custom/coord2d.methods.inc @@ -39,3 +39,7 @@ coord2d operator%(int number) const { return coord2d((x+number)%number, (y+number)%number); } +coord2d operator&(int number) const +{ + return coord2d(x&number, y&number); +} \ No newline at end of file diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index b847d63f5..b0ee31aa5 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -33,251 +33,167 @@ distribution. #include "df/map_block.h" #include "df/block_square_event_mineralst.h" #include "df/construction.h" + using namespace DFHack; + namespace MapExtras { -void SquashVeins (DFCoord bcoord, mapblock40d & mb, t_blockmaterials & materials) -{ - memset(materials,-1,sizeof(materials)); - std::vector veins; - Maps::SortBlockEvents(bcoord.x,bcoord.y,bcoord.z,&veins); - for (uint32_t x = 0;x<16;x++) for (uint32_t y = 0; y< 16;y++) - { - df::tiletype tt = mb.tiletypes[x][y]; - if (tileMaterial(tt) == tiletype_material::MINERAL) - { - for (size_t i = 0; i < veins.size(); i++) - { - if (veins[i]->getassignment(x,y)) - materials[x][y] = veins[i]->inorganic_mat; - } - } - } -} -void SquashFrozenLiquids (DFCoord bcoord, mapblock40d & mb, tiletypes40d & frozen) -{ - std::vector ices; - Maps::SortBlockEvents(bcoord.x,bcoord.y,bcoord.z,NULL,&ices); - for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) - { - df::tiletype tt = mb.tiletypes[x][y]; - frozen[x][y] = tiletype::Void; - if (tileMaterial(tt) == tiletype_material::FROZEN_LIQUID) - { - for (size_t i = 0; i < ices.size(); i++) - { - df::tiletype tt2 = ices[i]->tiles[x][y]; - if (tt2 != tiletype::Void) - { - frozen[x][y] = tt2; - break; - } - } - } - } -} +class DFHACK_EXPORT MapCache; -void SquashConstructions (DFCoord bcoord, mapblock40d & mb, tiletypes40d & constructions) -{ - for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) - { - df::tiletype tt = mb.tiletypes[x][y]; - constructions[x][y] = tiletype::Void; - if (tileMaterial(tt) == tiletype_material::CONSTRUCTION) - { - DFCoord coord(bcoord.x*16 + x, bcoord.y*16 + y, bcoord.z); - df::construction *con = df::construction::find(coord); - if (con) - constructions[x][y] = con->original_tile; - } - } +template inline R index_tile(T &v, df::coord2d p) { + return v[p.x&15][p.y&15]; } -void SquashRocks ( std::vector< std::vector > * layerassign, mapblock40d & mb, t_blockmaterials & materials) +class DFHACK_EXPORT Block { - // get the layer materials - for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) - { - materials[x][y] = -1; - uint8_t test = mb.designation[x][y].bits.biome; - if ((test < sizeof(mb.biome_indices)) && (mb.biome_indices[test] < layerassign->size())) - materials[x][y] = layerassign->at(mb.biome_indices[test])[mb.designation[x][y].bits.geolayer_index]; - } -} +public: + Block(MapCache *parent, DFCoord _bcoord); -class Block -{ - public: - Block(DFCoord _bcoord, std::vector< std::vector > * layerassign = 0) - { - dirty_designations = false; - dirty_tiletypes = false; - dirty_temperatures = false; - dirty_blockflags = false; - dirty_occupancies = false; - valid = false; - bcoord = _bcoord; - if(Maps::ReadBlock40d(bcoord.x,bcoord.y,bcoord.z,&raw)) - { - Maps::ReadTemperatures(bcoord.x,bcoord.y, bcoord.z,&temp1,&temp2); - SquashVeins(bcoord,raw,veinmats); - SquashConstructions(bcoord, raw, contiles); - SquashFrozenLiquids(bcoord, raw, icetiles); - if(layerassign) - SquashRocks(layerassign,raw,basemats); - else - memset(basemats,-1,sizeof(basemats)); - valid = true; - } + /* + * All coordinates are taken mod 16. + */ + + //Arbitrary tag field for flood fills etc. + int16_t &tag(df::coord2d p) { + return index_tile(tags, p); } + int16_t veinMaterialAt(df::coord2d p) { - return veinmats[p.x][p.y]; + return index_tile(veinmats,p); } int16_t baseMaterialAt(df::coord2d p) { - return basemats[p.x][p.y]; - } - - // the clear methods are used by the floodfill in digv and digl to mark tiles which were processed - void ClearBaseMaterialAt(df::coord2d p) - { - basemats[p.x][p.y] = -1; - } - void ClearVeinMaterialAt(df::coord2d p) - { - veinmats[p.x][p.y] = -1; + return index_tile(basemats,p); } df::tiletype BaseTileTypeAt(df::coord2d p) { - if (contiles[p.x][p.y] != tiletype::Void) - return contiles[p.x][p.y]; - else if (icetiles[p.x][p.y] != tiletype::Void) - return icetiles[p.x][p.y]; - else - return raw.tiletypes[p.x][p.y]; + auto tt = index_tile(contiles,p); + if (tt != tiletype::Void) return tt; + tt = index_tile(icetiles,p); + if (tt != tiletype::Void) return tt; + return index_tile(rawtiles,p); } df::tiletype TileTypeAt(df::coord2d p) { - return raw.tiletypes[p.x][p.y]; + return index_tile(rawtiles,p); } bool setTiletypeAt(df::coord2d p, df::tiletype tiletype) { if(!valid) return false; dirty_tiletypes = true; //printf("setting block %d/%d/%d , %d %d\n",x,y,z, p.x, p.y); - raw.tiletypes[p.x][p.y] = tiletype; + index_tile(rawtiles,p) = tiletype; return true; } uint16_t temperature1At(df::coord2d p) { - return temp1[p.x][p.y]; + return index_tile(temp1,p); } bool setTemp1At(df::coord2d p, uint16_t temp) { if(!valid) return false; dirty_temperatures = true; - temp1[p.x][p.y] = temp; + index_tile(temp1,p) = temp; return true; } uint16_t temperature2At(df::coord2d p) { - return temp2[p.x][p.y]; + return index_tile(temp2,p); } bool setTemp2At(df::coord2d p, uint16_t temp) { if(!valid) return false; dirty_temperatures = true; - temp2[p.x][p.y] = temp; + index_tile(temp2,p) = temp; return true; } df::tile_designation DesignationAt(df::coord2d p) { - return raw.designation[p.x][p.y]; + return index_tile(designation,p); } bool setDesignationAt(df::coord2d p, df::tile_designation des) { if(!valid) return false; dirty_designations = true; //printf("setting block %d/%d/%d , %d %d\n",x,y,z, p.x, p.y); - raw.designation[p.x][p.y] = des; + index_tile(designation,p) = des; if(des.bits.dig) { dirty_blockflags = true; - raw.blockflags.bits.designated = true; + blockflags.bits.designated = true; } return true; } df::tile_occupancy OccupancyAt(df::coord2d p) { - return raw.occupancy[p.x][p.y]; + return index_tile(occupancy,p); } bool setOccupancyAt(df::coord2d p, df::tile_occupancy des) { if(!valid) return false; dirty_occupancies = true; - raw.occupancy[p.x][p.y] = des; + index_tile(occupancy,p) = des; return true; } t_blockflags BlockFlags() { - return raw.blockflags; + return blockflags; } bool setBlockFlags(t_blockflags des) { if(!valid) return false; dirty_blockflags = true; //printf("setting block %d/%d/%d , %d %d\n",x,y,z, p.x, p.y); - raw.blockflags = des; + blockflags = des; return true; } - bool Write () - { - if(!valid) return false; - if(dirty_designations) - { - Maps::WriteDesignations(bcoord.x,bcoord.y,bcoord.z, &raw.designation); - Maps::WriteDirtyBit(bcoord.x,bcoord.y,bcoord.z,true); - dirty_designations = false; - } - if(dirty_tiletypes) - { - Maps::WriteTileTypes(bcoord.x,bcoord.y,bcoord.z, &raw.tiletypes); - dirty_tiletypes = false; - } - if(dirty_temperatures) - { - Maps::WriteTemperatures(bcoord.x,bcoord.y,bcoord.z, &temp1, &temp2); - dirty_temperatures = false; - } - if(dirty_blockflags) - { - Maps::WriteBlockFlags(bcoord.x,bcoord.y,bcoord.z,raw.blockflags); - dirty_blockflags = false; - } - if(dirty_occupancies) - { - Maps::WriteOccupancy(bcoord.x,bcoord.y,bcoord.z,&raw.occupancy); - dirty_occupancies = false; - } - return true; - } - bool valid:1; + bool Write(); + + int16_t GeoIndexAt(df::coord2d p); + + bool GetGlobalFeature(t_feature *out); + bool GetLocalFeature(t_feature *out); + + bool is_valid() { return valid; } + df::map_block *getRaw() { return block; } + +private: + friend class MapCache; + + MapCache *parent; + df::map_block *block; + + static void SquashVeins(df::map_block *mb, t_blockmaterials & materials); + static void SquashFrozenLiquids (df::map_block *mb, tiletypes40d & frozen); + static void SquashConstructions (df::map_block *mb, tiletypes40d & constructions); + static void SquashRocks (df::map_block *mb, t_blockmaterials & materials, + std::vector< std::vector > * layerassign); + + bool valid; bool dirty_designations:1; bool dirty_tiletypes:1; bool dirty_temperatures:1; bool dirty_blockflags:1; bool dirty_occupancies:1; - mapblock40d raw; + DFCoord bcoord; + + int16_t tags[16][16]; + + tiletypes40d rawtiles; + designations40d designation; + occupancies40d occupancy; + t_blockflags blockflags; + t_blockmaterials veinmats; t_blockmaterials basemats; t_temperatures temp1; @@ -286,14 +202,15 @@ class Block tiletypes40d icetiles; // what's underneath ice }; -class MapCache +class DFHACK_EXPORT MapCache { public: MapCache() { valid = 0; Maps::getSize(x_bmax, y_bmax, z_max); - validgeo = Maps::ReadGeology( layerassign ); + x_tmax = x_bmax*16; y_tmax = y_bmax*16; + validgeo = Maps::ReadGeology(&layer_mats, &geoidx); valid = true; }; ~MapCache() @@ -304,192 +221,107 @@ class MapCache { return valid; } + /// get the map block at a *block* coord. Block coord = tile coord / 16 - Block * BlockAt (DFCoord blockcoord) - { - if(!valid) - return 0; - std::map ::iterator iter = blocks.find(blockcoord); - if(iter != blocks.end()) - { - return (*iter).second; - } - else - { - if(blockcoord.x >= 0 && blockcoord.x < x_bmax && - blockcoord.y >= 0 && blockcoord.y < y_bmax && - blockcoord.z >= 0 && blockcoord.z < z_max) - { - Block * nblo; - if(validgeo) - nblo = new Block(blockcoord, &layerassign); - else - nblo = new Block(blockcoord); - blocks[blockcoord] = nblo; - return nblo; - } - return 0; - } + Block *BlockAt(DFCoord blockcoord); + /// get the map block at a tile coord. + Block *BlockAtTile(DFCoord coord) { + return BlockAt(df::coord(coord.x>>4,coord.y>>4,coord.z)); } + df::tiletype baseTiletypeAt (DFCoord tilecoord) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - return b->BaseTileTypeAt(tilecoord % 16); - } - return tiletype::Void; + Block * b= BlockAtTile(tilecoord); + return b ? b->BaseTileTypeAt(tilecoord) : tiletype::Void; } df::tiletype tiletypeAt (DFCoord tilecoord) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - return b->TileTypeAt(tilecoord % 16); - } - return tiletype::Void; + Block * b= BlockAtTile(tilecoord); + return b ? b->TileTypeAt(tilecoord) : tiletype::Void; } bool setTiletypeAt(DFCoord tilecoord, df::tiletype tiletype) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - b->setTiletypeAt(tilecoord % 16, tiletype); - return true; - } + if (Block * b= BlockAtTile(tilecoord)) + return b->setTiletypeAt(tilecoord, tiletype); return false; } uint16_t temperature1At (DFCoord tilecoord) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - return b->temperature1At(tilecoord % 16); - } - return 0; + Block * b= BlockAtTile(tilecoord); + return b ? b->temperature1At(tilecoord) : 0; } bool setTemp1At(DFCoord tilecoord, uint16_t temperature) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - b->setTemp1At(tilecoord % 16, temperature); - return true; - } + if (Block * b= BlockAtTile(tilecoord)) + return b->setTemp1At(tilecoord, temperature); return false; } uint16_t temperature2At (DFCoord tilecoord) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - return b->temperature2At(tilecoord % 16); - } - return 0; + Block * b= BlockAtTile(tilecoord); + return b ? b->temperature2At(tilecoord) : 0; } bool setTemp2At(DFCoord tilecoord, uint16_t temperature) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - b->setTemp2At(tilecoord % 16, temperature); - return true; - } + if (Block * b= BlockAtTile(tilecoord)) + return b->setTemp2At(tilecoord, temperature); return false; } int16_t veinMaterialAt (DFCoord tilecoord) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - return b->veinMaterialAt(tilecoord % 16); - } - return 0; + Block * b= BlockAtTile(tilecoord); + return b ? b->veinMaterialAt(tilecoord) : -1; } int16_t baseMaterialAt (DFCoord tilecoord) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - return b->baseMaterialAt(tilecoord % 16); - } - return 0; + Block * b= BlockAtTile(tilecoord); + return b ? b->baseMaterialAt(tilecoord) : -1; } - bool clearVeinMaterialAt (DFCoord tilecoord) + + int16_t tagAt(DFCoord tilecoord) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - b->ClearVeinMaterialAt(tilecoord % 16); - } - return 0; + Block * b= BlockAtTile(tilecoord); + return b ? b->tag(tilecoord) : 0; } - bool clearBaseMaterialAt (DFCoord tilecoord) + void setTagAt(DFCoord tilecoord, int16_t val) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - b->ClearBaseMaterialAt(tilecoord % 16); - } - return 0; + Block * b= BlockAtTile(tilecoord); + if (b) b->tag(tilecoord) = val; } - + df::tile_designation designationAt (DFCoord tilecoord) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - return b->DesignationAt(tilecoord % 16); - } - df::tile_designation temp; - temp.whole = 0; - return temp; + Block * b= BlockAtTile(tilecoord); + return b ? b->DesignationAt(tilecoord) : df::tile_designation(0); } bool setDesignationAt (DFCoord tilecoord, df::tile_designation des) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - b->setDesignationAt(tilecoord % 16, des); - return true; - } + if(Block * b= BlockAtTile(tilecoord)) + return b->setDesignationAt(tilecoord, des); return false; } - + df::tile_occupancy occupancyAt (DFCoord tilecoord) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - return b->OccupancyAt(tilecoord % 16); - } - df::tile_occupancy temp; - temp.whole = 0; - return temp; + Block * b= BlockAtTile(tilecoord); + return b ? b->OccupancyAt(tilecoord) : df::tile_occupancy(0); } bool setOccupancyAt (DFCoord tilecoord, df::tile_occupancy occ) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - b->setOccupancyAt(tilecoord % 16, occ); - return true; - } + if (Block * b= BlockAtTile(tilecoord)) + return b->setOccupancyAt(tilecoord, occ); return false; } - + bool testCoord (DFCoord tilecoord) { - Block * b= BlockAt(tilecoord / 16); - if(b && b->valid) - { - return true; - } - return false; + Block * b= BlockAtTile(tilecoord); + return (b && b->valid); } + bool WriteAll() { std::map::iterator p; @@ -508,15 +340,25 @@ class MapCache } blocks.clear(); } - private: - volatile bool valid; - volatile bool validgeo; + + uint32_t maxBlockX() { return x_bmax; } + uint32_t maxBlockY() { return y_bmax; } + uint32_t maxTileX() { return x_tmax; } + uint32_t maxTileY() { return y_tmax; } + uint32_t maxZ() { return z_max; } + +private: + friend class Block; + + bool valid; + bool validgeo; uint32_t x_bmax; uint32_t y_bmax; uint32_t x_tmax; uint32_t y_tmax; uint32_t z_max; - std::vector< std::vector > layerassign; + std::vector geoidx; + std::vector< std::vector > layer_mats; std::map blocks; }; } diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 873da7a9e..492512ca9 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -103,31 +103,11 @@ enum BiomeOffset eBiomeCount }; -/** - * map block flags - * \ingroup grp_maps - */ -struct naked_blockflags -{ - /// designated for jobs (digging and stuff like that) - unsigned int designated : 1; - /// possibly related to the designated flag - unsigned int unk_1 : 1; - /// two flags required for liquid flow. - unsigned int liquid_1 : 1; - unsigned int liquid_2 : 1; - /// rest of the flags is completely unknown - unsigned int unk_2: 4; -}; /** * map block flags wrapper * \ingroup grp_maps */ -union t_blockflags -{ - uint32_t whole; - naked_blockflags bits; -}; +typedef df::block_flags t_blockflags; /** * 16x16 array of tile types @@ -161,30 +141,6 @@ typedef uint8_t biome_indices40d [9]; * \ingroup grp_maps */ typedef uint16_t t_temperatures [16][16]; -/** - * structure for holding whole blocks - * \ingroup grp_maps - */ -typedef struct -{ - DFCoord position; - /// type of the tiles - tiletypes40d tiletypes; - /// flags determining the state of the tiles - designations40d designation; - /// flags determining what's on the tiles - occupancies40d occupancy; - /// values used for geology/biome assignment - biome_indices40d biome_indices; - /// the address where the block came from - df::map_block * origin; - t_blockflags blockflags; - /// index into the global feature vector - int32_t global_feature; - /// index into the local feature vector... complicated - int32_t local_feature; - int32_t mystery; -} mapblock40d; /** * The Maps module @@ -231,16 +187,8 @@ void DfMap::applyGeoMatgloss(Block * b) * @endcode */ -extern DFHACK_EXPORT bool ReadGeology( std::vector < std::vector >& assign ); - -/** - * Get the feature indexes of a block - */ -extern DFHACK_EXPORT bool ReadFeatures(uint32_t x, uint32_t y, uint32_t z, int32_t & local, int32_t & global); -/** - * Set the feature indexes of a block - */ -extern DFHACK_EXPORT bool WriteFeatures(uint32_t x, uint32_t y, uint32_t z, const int32_t & local, const int32_t & global); +extern DFHACK_EXPORT bool ReadGeology(std::vector > *layer_mats, + std::vector *geoidx); /** * Get pointers to features of a block */ @@ -248,7 +196,7 @@ extern DFHACK_EXPORT bool ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_fea /** * Get pointers to features of an already read block */ -extern DFHACK_EXPORT bool ReadFeatures(mapblock40d * block,t_feature * local, t_feature * global); +extern DFHACK_EXPORT bool ReadFeatures(df::map_block * block,t_feature * local, t_feature * global); /** * Read a specific global or local feature directly @@ -274,43 +222,12 @@ extern DFHACK_EXPORT df::map_block * getBlockAbs (int32_t x, int32_t y, int32_t inline df::map_block * getBlock (df::coord pos) { return getBlock(pos.x, pos.y, pos.z); } inline df::map_block * getBlockAbs (df::coord pos) { return getBlockAbs(pos.x, pos.y, pos.z); } -/// copy the whole map block at block coords (see DFTypes.h for the block structure) -extern DFHACK_EXPORT bool ReadBlock40d(uint32_t blockx, uint32_t blocky, uint32_t blockz, mapblock40d * buffer); - -/// copy/write block tile types -extern DFHACK_EXPORT bool ReadTileTypes(uint32_t blockx, uint32_t blocky, uint32_t blockz, tiletypes40d *buffer); -extern DFHACK_EXPORT bool WriteTileTypes(uint32_t blockx, uint32_t blocky, uint32_t blockz, tiletypes40d *buffer); - -/// copy/write block designations -extern DFHACK_EXPORT bool ReadDesignations(uint32_t blockx, uint32_t blocky, uint32_t blockz, designations40d *buffer); -extern DFHACK_EXPORT bool WriteDesignations (uint32_t blockx, uint32_t blocky, uint32_t blockz, designations40d *buffer); - -/// copy/write temperatures -extern DFHACK_EXPORT bool ReadTemperatures(uint32_t blockx, uint32_t blocky, uint32_t blockz, t_temperatures *temp1, t_temperatures *temp2); -extern DFHACK_EXPORT bool WriteTemperatures (uint32_t blockx, uint32_t blocky, uint32_t blockz, t_temperatures *temp1, t_temperatures *temp2); - -/// copy/write block occupancies -extern DFHACK_EXPORT bool ReadOccupancy(uint32_t blockx, uint32_t blocky, uint32_t blockz, occupancies40d *buffer); -extern DFHACK_EXPORT bool WriteOccupancy(uint32_t blockx, uint32_t blocky, uint32_t blockz, occupancies40d *buffer); - -/// copy/write the block dirty bit - this is used to mark a map block so that DF scans it for designated jobs like digging -extern DFHACK_EXPORT bool ReadDirtyBit(uint32_t blockx, uint32_t blocky, uint32_t blockz, bool &dirtybit); -extern DFHACK_EXPORT bool WriteDirtyBit(uint32_t blockx, uint32_t blocky, uint32_t blockz, bool dirtybit); - -/// copy/write the block flags -extern DFHACK_EXPORT bool ReadBlockFlags(uint32_t blockx, uint32_t blocky, uint32_t blockz, t_blockflags &blockflags); -extern DFHACK_EXPORT bool WriteBlockFlags(uint32_t blockx, uint32_t blocky, uint32_t blockz, t_blockflags blockflags); - -/// copy/write features -extern DFHACK_EXPORT bool SetBlockLocalFeature(uint32_t blockx, uint32_t blocky, uint32_t blockz, int32_t local = -1); -extern DFHACK_EXPORT bool SetBlockGlobalFeature(uint32_t blockx, uint32_t blocky, uint32_t blockz, int32_t global = -1); - /// copy region offsets of a block - used for determining layer stone matgloss extern DFHACK_EXPORT bool ReadRegionOffsets(uint32_t blockx, uint32_t blocky, uint32_t blockz, biome_indices40d *buffer); /// sorts the block event vector into multiple vectors by type /// mineral veins, what's under ice, blood smears and mud -extern DFHACK_EXPORT bool SortBlockEvents(uint32_t x, uint32_t y, uint32_t z, +extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, std::vector* veins, std::vector* ices = 0, std::vector* splatter = 0, diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index db66086e0..4d312fa8d 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -33,11 +33,13 @@ distribution. using namespace std; #include "modules/Maps.h" +#include "modules/MapCache.h" #include "Error.h" #include "VersionInfo.h" #include "MemAccess.h" #include "ModuleFactory.h" #include "Core.h" +#include "MiscUtils.h" #include "DataDefs.h" #include "df/world_data.h" @@ -136,182 +138,6 @@ df::map_block *Maps::getBlockAbs (int32_t x, int32_t y, int32_t z) return world->map.block_index[x >> 4][y >> 4][z]; } -bool Maps::ReadBlock40d(uint32_t x, uint32_t y, uint32_t z, mapblock40d * buffer) -{ - df::map_block * block = getBlock(x,y,z); - if (block) - { - buffer->position = DFCoord(x,y,z); - memcpy(buffer->tiletypes,block->tiletype, sizeof(tiletypes40d)); - memcpy(buffer->designation,block->designation, sizeof(designations40d)); - memcpy(buffer->occupancy,block->occupancy, sizeof(occupancies40d)); - memcpy(buffer->biome_indices,block->region_offset, sizeof(block->region_offset)); - buffer->global_feature = block->global_feature; - buffer->local_feature = block->local_feature; - buffer->mystery = block->unk2; - buffer->origin = block; - buffer->blockflags.whole = block->flags.whole; - return true; - } - return false; -} - -/* - * Tiletypes - */ - -bool Maps::ReadTileTypes (uint32_t x, uint32_t y, uint32_t z, tiletypes40d *buffer) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - memcpy(buffer, block->tiletype, sizeof(tiletypes40d)); - return true; - } - return false; -} - -bool Maps::WriteTileTypes (uint32_t x, uint32_t y, uint32_t z, tiletypes40d *buffer) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - memcpy(block->tiletype, buffer, sizeof(tiletypes40d)); - return true; - } - return false; -} - -/* - * Dirty bit - */ -bool Maps::ReadDirtyBit(uint32_t x, uint32_t y, uint32_t z, bool &dirtybit) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - dirtybit = block->flags.bits.designated; - return true; - } - return false; -} - -bool Maps::WriteDirtyBit(uint32_t x, uint32_t y, uint32_t z, bool dirtybit) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - block->flags.bits.designated = true; - return true; - } - return false; -} -/* - * Block flags - */ -// FIXME: maybe truncates, still bullshit -bool Maps::ReadBlockFlags(uint32_t x, uint32_t y, uint32_t z, t_blockflags &blockflags) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - blockflags.whole = block->flags.whole; - return true; - } - return false; -} -//FIXME: maybe truncated, still bullshit -bool Maps::WriteBlockFlags(uint32_t x, uint32_t y, uint32_t z, t_blockflags blockflags) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - block->flags.whole = blockflags.whole; - return true; - } - return false; -} - -/* - * Designations - */ -bool Maps::ReadDesignations (uint32_t x, uint32_t y, uint32_t z, designations40d *buffer) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - memcpy(buffer, block->designation, sizeof(designations40d)); - return true; - } - return false; -} - -bool Maps::WriteDesignations (uint32_t x, uint32_t y, uint32_t z, designations40d *buffer) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - memcpy(block->designation, buffer, sizeof(designations40d)); - return true; - } - return false; -} - -/* - * Occupancies - */ -bool Maps::ReadOccupancy (uint32_t x, uint32_t y, uint32_t z, occupancies40d *buffer) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - memcpy(buffer, block->occupancy, sizeof(occupancies40d)); - return true; - } - return false; -} - -bool Maps::WriteOccupancy (uint32_t x, uint32_t y, uint32_t z, occupancies40d *buffer) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - memcpy(block->occupancy, buffer, sizeof(occupancies40d)); - return true; - } - return false; -} - -/* - * Temperatures - */ -bool Maps::ReadTemperatures(uint32_t x, uint32_t y, uint32_t z, t_temperatures *temp1, t_temperatures *temp2) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - if(temp1) - memcpy(temp1, block->temperature_1, sizeof(t_temperatures)); - if(temp2) - memcpy(temp2, block->temperature_2, sizeof(t_temperatures)); - return true; - } - return false; -} -bool Maps::WriteTemperatures (uint32_t x, uint32_t y, uint32_t z, t_temperatures *temp1, t_temperatures *temp2) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - if(temp1) - memcpy(block->temperature_1, temp1, sizeof(t_temperatures)); - if(temp2) - memcpy(block->temperature_2, temp2, sizeof(t_temperatures)); - return true; - } - return false; -} - /* * Region Offsets - used for layer geology */ @@ -381,58 +207,15 @@ bool Maps::GetLocalFeature(t_feature &feature, df::coord2d coord, int32_t index) return true; } -bool Maps::ReadFeatures(uint32_t x, uint32_t y, uint32_t z, int32_t & local, int32_t & global) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - local = block->local_feature; - global = block->global_feature; - return true; - } - return false; -} - -bool Maps::WriteFeatures(uint32_t x, uint32_t y, uint32_t z, const int32_t & local, const int32_t & global) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - block->local_feature = local; - block->global_feature = global; - return true; - } - return false; -} - bool Maps::ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_feature *local, t_feature *global) { - int32_t loc, glob; - if (!ReadFeatures(x, y, z, loc, glob)) + df::map_block *block = getBlock(x,y,z); + if (!block) return false; - - bool result = true; - if (global) - { - if (glob != -1) - result &= GetGlobalFeature(*global, glob); - else - global->type = (df::feature_type)-1; - } - if (local) - { - if (loc != -1) - { - df::coord2d coord(x,y); - result &= GetLocalFeature(*local, coord, loc); - } - else - local->type = (df::feature_type)-1; - } - return result; + return ReadFeatures(block, local, global); } -bool Maps::ReadFeatures(mapblock40d * block, t_feature * local, t_feature * global) +bool Maps::ReadFeatures(df::map_block * block, t_feature * local, t_feature * global) { bool result = true; if (global) @@ -445,39 +228,17 @@ bool Maps::ReadFeatures(mapblock40d * block, t_feature * local, t_feature * glob if (local) { if (block->local_feature != -1) - result &= GetLocalFeature(*local, block->position, block->local_feature); + result &= GetLocalFeature(*local, block->map_pos/16, block->local_feature); else local->type = (df::feature_type)-1; } return result; } -bool Maps::SetBlockLocalFeature(uint32_t x, uint32_t y, uint32_t z, int32_t local) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - block->local_feature = local; - return true; - } - return false; -} - -bool Maps::SetBlockGlobalFeature(uint32_t x, uint32_t y, uint32_t z, int32_t global) -{ - df::map_block *block = getBlock(x,y,z); - if (block) - { - block->global_feature = global; - return true; - } - return false; -} - /* * Block events */ -bool Maps::SortBlockEvents(uint32_t x, uint32_t y, uint32_t z, +bool Maps::SortBlockEvents(df::map_block *block, vector * veins, vector * ices, vector *splatter, @@ -495,7 +256,6 @@ bool Maps::SortBlockEvents(uint32_t x, uint32_t y, uint32_t z, if (constructions) constructions->clear(); - df::map_block * block = getBlock(x,y,z); if (!block) return false; @@ -535,27 +295,35 @@ bool Maps::RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square df::map_block * block = getBlock(x,y,z); if (!block) return false; - for (size_t i = 0; i < block->block_events.size(); i++) + + int idx = linear_index(block->block_events, which); + if (idx >= 0) { - if (block->block_events[i] == which) - { - delete which; - block->block_events.erase(block->block_events.begin() + i); - return true; - } + delete which; + vector_erase_at(block->block_events, idx); + return true; } - return false; + else + return false; } /* * Layer geology */ -bool Maps::ReadGeology (vector < vector >& assign) +bool Maps::ReadGeology(vector > *layer_mats, vector *geoidx) { if (!world->world_data) return false; - vector v_geology[eBiomeCount]; + layer_mats->resize(eBiomeCount); + geoidx->resize(eBiomeCount); + + for (int i = 0; i < eBiomeCount; i++) + { + (*layer_mats)[i].clear(); + (*geoidx)[i] = -1; + } + // iterate over 8 surrounding regions + local region for (int i = eNorthWest; i < eBiomeCount; i++) { @@ -571,7 +339,9 @@ bool Maps::ReadGeology (vector < vector >& assign) if (bioRY >= world->world_data->world_height) bioRY = world->world_data->world_height - 1; // get index into geoblock vector - uint16_t geoindex = world->world_data->region_map[bioRX][bioRY].geo_index; + int16_t geoindex = world->world_data->region_map[bioRX][bioRY].geo_index; + + (*geoidx)[i] = geoindex; /// geology blocks have a vector of layer descriptors // get the vector with pointer to layers @@ -579,20 +349,17 @@ bool Maps::ReadGeology (vector < vector >& assign) if (!geo_biome) continue; - vector &geolayers = geo_biome->layers; + auto &geolayers = geo_biome->layers; + auto &matvec = (*layer_mats)[i]; /// layer descriptor has a field that determines the type of stone/soil - v_geology[i].reserve(geolayers.size()); + matvec.resize(geolayers.size()); // finally, read the layer matgloss for (size_t j = 0; j < geolayers.size(); j++) - v_geology[i].push_back(geolayers[j]->mat_index); + matvec[j] = geolayers[j]->mat_index; } - assign.clear(); - assign.reserve(eBiomeCount); - for (int i = 0; i < eBiomeCount; i++) - assign.push_back(v_geology[i]); return true; } @@ -605,3 +372,212 @@ bool Maps::ReadVegetation(uint32_t x, uint32_t y, uint32_t z, std::vectorplants; return true; } + +#define COPY(a,b) memcpy(&a,&b,sizeof(a)) + +MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) +{ + dirty_designations = false; + dirty_tiletypes = false; + dirty_temperatures = false; + dirty_blockflags = false; + dirty_occupancies = false; + valid = false; + bcoord = _bcoord; + block = Maps::getBlock(bcoord); + + memset(tags,0,sizeof(tags)); + + if(block) + { + COPY(rawtiles, block->tiletype); + COPY(designation, block->designation); + COPY(occupancy, block->occupancy); + blockflags = block->flags; + + COPY(temp1, block->temperature_1); + COPY(temp2, block->temperature_2); + + SquashVeins(block,veinmats); + SquashConstructions(block, contiles); + SquashFrozenLiquids(block, icetiles); + if(parent->validgeo) + SquashRocks(block,basemats,&parent->layer_mats); + else + memset(basemats,-1,sizeof(basemats)); + valid = true; + } + else + { + blockflags.whole = 0; + memset(rawtiles,0,sizeof(rawtiles)); + memset(designation,0,sizeof(designation)); + memset(occupancy,0,sizeof(occupancy)); + memset(temp1,0,sizeof(temp1)); + memset(temp2,0,sizeof(temp2)); + memset(veinmats,-1,sizeof(veinmats)); + memset(contiles,0,sizeof(contiles)); + memset(icetiles,0,sizeof(icetiles)); + memset(basemats,-1,sizeof(basemats)); + } +} + +bool MapExtras::Block::Write () +{ + if(!valid) return false; + + if(dirty_blockflags) + { + block->flags = blockflags; + dirty_blockflags = false; + } + if(dirty_designations) + { + COPY(block->designation, designation); + block->flags.bits.designated = true; + dirty_designations = false; + } + if(dirty_tiletypes) + { + COPY(block->tiletype, rawtiles); + dirty_tiletypes = false; + } + if(dirty_temperatures) + { + COPY(block->temperature_1, temp1); + COPY(block->temperature_2, temp2); + dirty_temperatures = false; + } + if(dirty_occupancies) + { + COPY(block->occupancy, occupancy); + dirty_occupancies = false; + } + return true; +} + +void MapExtras::Block::SquashVeins(df::map_block *mb, t_blockmaterials & materials) +{ + memset(materials,-1,sizeof(materials)); + std::vector veins; + Maps::SortBlockEvents(mb,&veins); + for (uint32_t x = 0;x<16;x++) for (uint32_t y = 0; y< 16;y++) + { + df::tiletype tt = mb->tiletype[x][y]; + if (tileMaterial(tt) == tiletype_material::MINERAL) + { + for (size_t i = 0; i < veins.size(); i++) + { + if (veins[i]->getassignment(x,y)) + materials[x][y] = veins[i]->inorganic_mat; + } + } + } +} + +void MapExtras::Block::SquashFrozenLiquids(df::map_block *mb, tiletypes40d & frozen) +{ + std::vector ices; + Maps::SortBlockEvents(mb,NULL,&ices); + for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) + { + df::tiletype tt = mb->tiletype[x][y]; + frozen[x][y] = tiletype::Void; + if (tileMaterial(tt) == tiletype_material::FROZEN_LIQUID) + { + for (size_t i = 0; i < ices.size(); i++) + { + df::tiletype tt2 = ices[i]->tiles[x][y]; + if (tt2 != tiletype::Void) + { + frozen[x][y] = tt2; + break; + } + } + } + } +} + +void MapExtras::Block::SquashConstructions (df::map_block *mb, tiletypes40d & constructions) +{ + for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) + { + df::tiletype tt = mb->tiletype[x][y]; + constructions[x][y] = tiletype::Void; + if (tileMaterial(tt) == tiletype_material::CONSTRUCTION) + { + DFCoord coord = mb->map_pos + df::coord(x,y,0); + df::construction *con = df::construction::find(coord); + if (con) + constructions[x][y] = con->original_tile; + } + } +} + +void MapExtras::Block::SquashRocks (df::map_block *mb, t_blockmaterials & materials, + std::vector< std::vector > * layerassign) +{ + // get the layer materials + for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) + { + materials[x][y] = -1; + uint8_t test = mb->designation[x][y].bits.biome; + if (test >= 9) + continue; + uint8_t idx = mb->region_offset[test]; + if (idx < layerassign->size()) + materials[x][y] = layerassign->at(idx)[mb->designation[x][y].bits.geolayer_index]; + } +} + +int16_t MapExtras::Block::GeoIndexAt(df::coord2d p) +{ + auto des = index_tile(designation,p); + uint8_t idx = des.bits.biome; + if (idx >= 9) + return -1; + idx = block->region_offset[idx]; + if (idx >= parent->geoidx.size()) + return -1; + return parent->geoidx[idx]; +} + +bool MapExtras::Block::GetGlobalFeature(t_feature *out) +{ + out->type = (df::feature_type)-1; + if (!valid || block->global_feature < 0) + return false; + return Maps::GetGlobalFeature(*out, block->global_feature); +} + +bool MapExtras::Block::GetLocalFeature(t_feature *out) +{ + out->type = (df::feature_type)-1; + if (!valid || block->local_feature < 0) + return false; + return Maps::GetLocalFeature(*out, block->map_pos/16, block->local_feature); +} + +MapExtras::Block *MapExtras::MapCache::BlockAt(DFCoord blockcoord) +{ + if(!valid) + return 0; + std::map ::iterator iter = blocks.find(blockcoord); + if(iter != blocks.end()) + { + return (*iter).second; + } + else + { + if(blockcoord.x >= 0 && blockcoord.x < x_bmax && + blockcoord.y >= 0 && blockcoord.y < y_bmax && + blockcoord.z >= 0 && blockcoord.z < z_max) + { + Block * nblo = new Block(this, blockcoord); + blocks[blockcoord] = nblo; + return nblo; + } + return 0; + } +} + diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 9d42694a3..3f1157f41 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -272,7 +272,7 @@ static command_result autodump_main(color_ostream &out, vector & parame // write map changes back to DF. MC.WriteAll(); // Is this necessary? Is "forbid" a dirtyable attribute like "dig" is? - Maps::WriteDirtyBit(cx/16, cy/16, cz, true); + //Maps::WriteDirtyBit(cx/16, cy/16, cz, true); } out.print("Done. %d items %s.\n", dumped_total, destroy ? "marked for destruction" : "quickdumped"); return CR_OK; diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp index d56360d83..317a0fa36 100644 --- a/plugins/changelayer.cpp +++ b/plugins/changelayer.cpp @@ -189,13 +189,13 @@ command_result changelayer (color_ostream &out, std::vector & para uint32_t tileY = cursorY % 16; MapExtras::Block * b = mc.BlockAt(cursor/16); - if(!b && !b->valid) + if(!b || !b->is_valid()) { out.printerr("No data.\n"); return CR_OK; } - mapblock40d & block = b->raw; - df::tile_designation &des = block.designation[tileX][tileY]; + + df::tile_designation des = b->DesignationAt(cursor%16); // get biome and geolayer at cursor position uint32_t biome = des.bits.biome; diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 91d963fc3..64161a3aa 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -90,7 +90,7 @@ bool dig (MapExtras::MapCache & MCache, { DFCoord at (x,y,z); auto b = MCache.BlockAt(at/16); - if(!b || !b->valid) + if(!b || !b->is_valid()) return false; if(x == 0 || x == x_max * 16 - 1) { @@ -1027,6 +1027,8 @@ command_result digv (color_ostream &out, vector & parameters) { DFHack::DFCoord current = flood.top(); flood.pop(); + if (MCache->tagAt(current)) + continue; int16_t vmat2 = MCache->veinMaterialAt(current); tt = MCache->tiletypeAt(current); if(!DFHack::isWallTerrain(tt)) @@ -1061,7 +1063,8 @@ command_result digv (color_ostream &out, vector & parameters) } if(MCache->testCoord(current)) { - MCache->clearVeinMaterialAt(current); + MCache->setTagAt(current, 1); + if(current.x < tx_max - 2) { flood.push(DFHack::DFCoord(current.x + 1, current.y, current.z)); @@ -1209,6 +1212,8 @@ command_result digl (color_ostream &out, vector & parameters) { DFHack::DFCoord current = flood.top(); flood.pop(); + if (MCache->tagAt(current)) + continue; int16_t vmat2 = MCache->veinMaterialAt(current); int16_t bmat2 = MCache->baseMaterialAt(current); tt = MCache->tiletypeAt(current); @@ -1239,7 +1244,7 @@ command_result digl (color_ostream &out, vector & parameters) if(MCache->testCoord(current)) { - MCache->clearBaseMaterialAt(current); + MCache->setTagAt(current, 1); if(current.x < tx_max - 2) { flood.push(DFHack::DFCoord(current.x + 1, current.y, current.z)); diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index a7483a886..6be432a5b 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -413,8 +413,8 @@ command_result df_liquids_execute(color_ostream &out) Block * b = mcache.BlockAt((*iter)/16); DFHack::t_blockflags bf = b->BlockFlags(); - bf.bits.liquid_1 = true; - bf.bits.liquid_2 = true; + bf.bits.update_liquid = true; + bf.bits.update_liquid_twice = true; b->setBlockFlags(bf); iter++; @@ -500,20 +500,20 @@ command_result df_liquids_execute(color_ostream &out) DFHack::t_blockflags bflags = (*biter)->BlockFlags(); if(flowmode == "f+") { - bflags.bits.liquid_1 = true; - bflags.bits.liquid_2 = true; + bflags.bits.update_liquid = true; + bflags.bits.update_liquid_twice = true; (*biter)->setBlockFlags(bflags); } else if(flowmode == "f-") { - bflags.bits.liquid_1 = false; - bflags.bits.liquid_2 = false; + bflags.bits.update_liquid = false; + bflags.bits.update_liquid_twice = false; (*biter)->setBlockFlags(bflags); } else { - out << "flow bit 1 = " << bflags.bits.liquid_1 << endl; - out << "flow bit 2 = " << bflags.bits.liquid_2 << endl; + out << "flow bit 1 = " << bflags.bits.update_liquid << endl; + out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl; } biter ++; } diff --git a/plugins/mapexport/mapexport.cpp b/plugins/mapexport/mapexport.cpp index d7d9daea9..805261c21 100644 --- a/plugins/mapexport/mapexport.cpp +++ b/plugins/mapexport/mapexport.cpp @@ -151,7 +151,7 @@ command_result mapexport (color_ostream &out, std::vector & parame // Get the map block df::coord2d blockCoord(b_x, b_y); MapExtras::Block *b = map.BlockAt(DFHack::DFCoord(b_x, b_y, z)); - if (!b || !b->valid) + if (!b || !b->is_valid()) { continue; } @@ -161,15 +161,9 @@ command_result mapexport (color_ostream &out, std::vector & parame protoblock.set_y(b_y); protoblock.set_z(z); - { // Find features - uint32_t index = b->raw.global_feature; - if (index != -1) - Maps::GetGlobalFeature(blockFeatureGlobal, index); - - index = b->raw.local_feature; - if (index != -1) - Maps::GetLocalFeature(blockFeatureLocal, blockCoord, index); - } + // Find features + b->GetGlobalFeature(&blockFeatureGlobal); + b->GetLocalFeature(&blockFeatureLocal); int global_z = df::global::world->map.region_z + z; diff --git a/plugins/probe.cpp b/plugins/probe.cpp index c2a82840f..b34f68d42 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -127,13 +127,14 @@ command_result df_probe (color_ostream &out, vector & parameters) uint32_t tileY = cursorY % 16; MapExtras::Block * b = mc.BlockAt(cursor/16); - if(!b && !b->valid) + if(!b || !b->is_valid()) { out.printerr("No data.\n"); return CR_OK; } - mapblock40d & block = b->raw; - out.print("block addr: 0x%x\n\n", block.origin); + + auto &block = *b->getRaw(); + out.print("block addr: 0x%x\n\n", &block); /* if (showBlock) { @@ -250,7 +251,7 @@ command_result df_probe (color_ostream &out, vector & parameters) t_feature local; t_feature global; - Maps::ReadFeatures(&(b->raw),&local,&global); + Maps::ReadFeatures(&block,&local,&global); PRINT_FLAG( des, feature_local ); if(local.type != -1) { @@ -273,7 +274,6 @@ command_result df_probe (color_ostream &out, vector & parameters) << endl; out << "global feature idx: " << block.global_feature << endl; - out << "mystery: " << block.mystery << endl; out << std::endl; return CR_OK; } diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 5afbd2d0a..80ce365cc 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -426,20 +426,14 @@ command_result prospector (color_ostream &con, vector & parameters) // Get the map block df::coord2d blockCoord(b_x, b_y); MapExtras::Block *b = map.BlockAt(DFHack::DFCoord(b_x, b_y, z)); - if (!b || !b->valid) + if (!b || !b->is_valid()) { continue; } - { // Find features - uint32_t index = b->raw.global_feature; - if (index != -1) - Maps::GetGlobalFeature(blockFeatureGlobal, index); - - index = b->raw.local_feature; - if (index != -1) - Maps::GetLocalFeature(blockFeatureLocal, blockCoord, index); - } + // Find features + b->GetGlobalFeature(&blockFeatureGlobal); + b->GetLocalFeature(&blockFeatureLocal); int global_z = world->map.region_z + z; From 59ddbfacb70a8f56e3fa2088ada1df71d8d158de Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 10 Apr 2012 20:19:41 +0400 Subject: [PATCH 045/108] Implement item occupancy tracking in MapCache. --- library/include/modules/MapCache.h | 28 +++++++++ library/modules/Maps.cpp | 77 +++++++++++++++++++++++++ plugins/autodump.cpp | 91 +++++------------------------- 3 files changed, 119 insertions(+), 77 deletions(-) diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index b0ee31aa5..f2aebc23d 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -33,6 +33,7 @@ distribution. #include "df/map_block.h" #include "df/block_square_event_mineralst.h" #include "df/construction.h" +#include "df/item.h" using namespace DFHack; @@ -45,10 +46,15 @@ template inline R index_tile(T &v, df::coord2d p) { return v[p.x&15][p.y&15]; } +inline bool is_valid_tile_coord(df::coord2d p) { + return (p.x & ~15) == 0 && (p.y & ~15) == 0; +} + class DFHACK_EXPORT Block { public: Block(MapCache *parent, DFCoord _bcoord); + ~Block(); /* * All coordinates are taken mod 16. @@ -143,6 +149,12 @@ public: return true; } + int itemCountAt(df::coord2d p) + { + if (!item_counts) init_item_counts(); + return index_tile(item_counts,p); + } + t_blockflags BlockFlags() { return blockflags; @@ -189,6 +201,13 @@ private: int16_t tags[16][16]; + typedef int T_item_counts[16]; + T_item_counts *item_counts; + void init_item_counts(); + + bool addItemOnGround(df::item *item); + bool removeItemOnGround(df::item *item); + tiletypes40d rawtiles; designations40d designation; occupancies40d occupancy; @@ -322,6 +341,15 @@ class DFHACK_EXPORT MapCache return (b && b->valid); } + bool addItemOnGround(df::item *item) { + Block * b= BlockAtTile(item->pos); + return b ? b->addItemOnGround(item) : false; + } + bool removeItemOnGround(df::item *item) { + Block * b= BlockAtTile(item->pos); + return b ? b->removeItemOnGround(item) : false; + } + bool WriteAll() { std::map::iterator p; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 4d312fa8d..848454468 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -385,6 +385,7 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) valid = false; bcoord = _bcoord; block = Maps::getBlock(bcoord); + item_counts = NULL; memset(tags,0,sizeof(tags)); @@ -422,6 +423,11 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) } } +MapExtras::Block::~Block() +{ + delete[] item_counts; +} + bool MapExtras::Block::Write () { if(!valid) return false; @@ -558,6 +564,77 @@ bool MapExtras::Block::GetLocalFeature(t_feature *out) return Maps::GetLocalFeature(*out, block->map_pos/16, block->local_feature); } +void MapExtras::Block::init_item_counts() +{ + if (item_counts) return; + + item_counts = new T_item_counts[16]; + memset(item_counts, 0, sizeof(T_item_counts)); + + if (!block) return; + + for (size_t i = 0; i < block->items.size(); i++) + { + auto it = df::item::find(block->items[i]); + if (!it || !it->flags.bits.on_ground) + continue; + + df::coord tidx = it->pos - block->map_pos; + if (!is_valid_tile_coord(tidx) || tidx.z != 0) + continue; + + item_counts[tidx.x][tidx.y]++; + } +} + +bool MapExtras::Block::addItemOnGround(df::item *item) +{ + if (!block) + return false; + + init_item_counts(); + + bool inserted; + insert_into_vector(block->items, item->id, &inserted); + + if (inserted) + { + int &count = index_tile(item_counts,item->pos); + + if (count++ == 0) + { + index_tile(occupancy,item->pos).bits.item = true; + index_tile(block->occupancy,item->pos).bits.item = true; + } + } + + return inserted; +} + +bool MapExtras::Block::removeItemOnGround(df::item *item) +{ + if (!block) + return false; + + init_item_counts(); + + int idx = binsearch_index(block->items, item->id); + if (idx < 0) + return false; + + vector_erase_at(block->items, idx); + + int &count = index_tile(item_counts,item->pos); + + if (--count == 0) + { + index_tile(occupancy,item->pos).bits.item = false; + index_tile(block->occupancy,item->pos).bits.item = false; + } + + return true; +} + MapExtras::Block *MapExtras::MapCache::BlockAt(DFCoord blockcoord) { if(!valid) diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 3f1157f41..a71555b7a 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -147,26 +147,13 @@ static command_result autodump_main(color_ostream &out, vector & parame } } } - coordmap counts; + // proceed with the dumpification operation for(size_t i=0; i< numItems; i++) { df::item * itm = world->items.all[i]; DFCoord pos_item(itm->pos.x, itm->pos.y, itm->pos.z); - // keep track how many items are at places. all items. - coordmap::iterator it = counts.find(pos_item); - if(it == counts.end()) - { - pair< coordmap::iterator, bool > inserted = counts.insert(make_pair(pos_item,1)); - it = inserted.first; - } - else - { - it->second ++; - } - // iterator is valid here, we use it later to decrement the pile counter if the item is moved - // only dump the stuff marked for dumping and laying on the ground if ( !itm->flags.bits.dump || !itm->flags.bits.on_ground @@ -194,38 +181,16 @@ static command_result autodump_main(color_ostream &out, vector & parame itm->flags.bits.forbid = true; // Don't move items if they're already at the cursor - if (pos_cursor == pos_item) - continue; - - // Do we need to fix block-local item ID vector? - if(pos_item/16 != pos_cursor/16) + if (pos_cursor != pos_item) { - // yes... - cerr << "Moving from block to block!" << endl; - df::map_block * bl_src = Maps::getBlockAbs(itm->pos.x, itm->pos.y, itm->pos.z); - df::map_block * bl_tgt = Maps::getBlockAbs(cx, cy, cz); - if(bl_src) - { - remove(bl_src->items.begin(), bl_src->items.end(),itm->id); - } - else - { - cerr << "No source block" << endl; - } - if(bl_tgt) - { - bl_tgt->items.push_back(itm->id); - } - else - { - cerr << "No target block" << endl; - } - } + if (!MC.removeItemOnGround(itm)) + out.printerr("Item %d wasn't in the source block.\n", itm->id); - // Move the item - itm->pos.x = pos_cursor.x; - itm->pos.y = pos_cursor.y; - itm->pos.z = pos_cursor.z; + itm->pos = pos_cursor; + + if (!MC.addItemOnGround(itm)) + out.printerr("Could not add item %d to destination block.\n", itm->id); + } } else // destroy { @@ -238,42 +203,14 @@ static command_result autodump_main(color_ostream &out, vector & parame itm->flags.bits.forbid = true; itm->flags.bits.hidden = true; } - // keeping track of item pile sizes ;) - it->second --; + dumped_total++; } - if(!destroy) // TODO: do we have to do any of this when destroying items? - { - // for each item pile, see if it reached zero. if so, unset item flag on the tile it's on - coordmap::iterator it = counts.begin(); - coordmap::iterator end = counts.end(); - while(it != end) - { - if(it->second == 0) - { - df::tile_occupancy occ = MC.occupancyAt(it->first); - occ.bits.item = false; - MC.setOccupancyAt(it->first, occ); - } - it++; - } - // Set "item here" flag on target tile, if we moved any items to the target tile. - if (dumped_total > 0) - { - // assume there is a possibility the cursor points at some weird location with missing block data - Block * b = MC.BlockAt(pos_cursor / 16); - if(b) - { - df::tile_occupancy occ = MC.occupancyAt(pos_cursor); - occ.bits.item = 1; - MC.setOccupancyAt(pos_cursor,occ); - } - } - // write map changes back to DF. + + // write map changes back to DF. + if(!destroy) MC.WriteAll(); - // Is this necessary? Is "forbid" a dirtyable attribute like "dig" is? - //Maps::WriteDirtyBit(cx/16, cy/16, cz, true); - } + out.print("Done. %d items %s.\n", dumped_total, destroy ? "marked for destruction" : "quickdumped"); return CR_OK; } From 6cf8220f28001c9b044cf7dc2717c882fe2d2ecc Mon Sep 17 00:00:00 2001 From: Will Rogers Date: Tue, 10 Apr 2012 20:41:54 -0400 Subject: [PATCH 046/108] Add SetUnitLabors protobuf API. --- library/RemoteTools.cpp | 16 ++++++++++++++++ library/proto/Basic.proto | 6 ++++++ library/proto/BasicApi.proto | 7 ++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 00344d6a2..7f86134b1 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -614,6 +614,20 @@ static command_result ListSquads(color_ostream &stream, return CR_OK; } +static command_result SetUnitLabors(color_ostream &stream, const SetUnitLaborsIn *in) +{ + for (size_t i = 0; i < in->change_size(); i++) + { + auto change = in->change(i); + auto unit = df::unit::find(change.unit_id()); + + if (unit) + unit->status.labors[change.labor()] = change.value(); + } + + return CR_OK; +} + CoreService::CoreService() { suspend_depth = 0; @@ -637,6 +651,8 @@ CoreService::CoreService() { addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE); addFunction("ListUnits", ListUnits); addFunction("ListSquads", ListSquads); + + addFunction("SetUnitLabors", SetUnitLabors); } CoreService::~CoreService() diff --git a/library/proto/Basic.proto b/library/proto/Basic.proto index 246fba22e..64eaea8eb 100755 --- a/library/proto/Basic.proto +++ b/library/proto/Basic.proto @@ -188,3 +188,9 @@ message BasicSquadInfo { // Member histfig ids: repeated sint32 members = 4; }; + +message UnitLaborState { + required int32 unit_id = 1; + required int32 labor = 2; + required bool value = 3; +}; diff --git a/library/proto/BasicApi.proto b/library/proto/BasicApi.proto index 3072f9cad..a5a07aa1c 100755 --- a/library/proto/BasicApi.proto +++ b/library/proto/BasicApi.proto @@ -99,4 +99,9 @@ message ListUnitsOut { message ListSquadsIn {} message ListSquadsOut { repeated BasicSquadInfo value = 1; -} +}; + +// RPC SetUnitLabors : SetUnitLaborsIn -> EmptyMessage +message SetUnitLaborsIn { + repeated UnitLaborState change = 1; +}; From 8c40a27ea03f80419088e022e2675ac64d4bb962 Mon Sep 17 00:00:00 2001 From: Will Rogers Date: Tue, 10 Apr 2012 20:42:23 -0400 Subject: [PATCH 047/108] Add unit_misc_trait list to BasicUnitInfo protobuf API. --- library/RemoteTools.cpp | 14 ++++++++++++++ library/proto/Basic.proto | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 7f86134b1..c8af46cbf 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -61,6 +61,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "df/world.h" #include "df/world_data.h" #include "df/unit.h" +#include "df/unit_misc_trait.h" #include "df/unit_soul.h" #include "df/unit_skill.h" #include "df/material.h" @@ -316,6 +317,19 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, } } + if (mask && mask->misc_traits()) + { + auto &vec = unit -> status.misc_traits; + + for (size_t i = 0; i < vec.size(); i++) + { + auto trait = vec[i]; + auto item = info->add_misc_traits(); + item->set_id(trait->id); + item->set_value(trait->value); + } + } + if (unit->curse.add_tags1.whole || unit->curse.add_tags2.whole || unit->curse.rem_tags1.whole || diff --git a/library/proto/Basic.proto b/library/proto/Basic.proto index 64eaea8eb..c387bef01 100755 --- a/library/proto/Basic.proto +++ b/library/proto/Basic.proto @@ -130,6 +130,11 @@ message SkillInfo { required int32 experience = 3; }; +message UnitMiscTrait { + required int32 id = 1; + required int32 value = 2; +}; + message BasicUnitInfo { required int32 unit_id = 1; @@ -166,6 +171,9 @@ message BasicUnitInfo { // IF mask.skills: repeated SkillInfo skills = 12; + // IF mask.misc_traits: + repeated UnitMiscTrait misc_traits = 24; + optional UnitCurseInfo curse = 16; repeated int32 burrows = 21; @@ -175,6 +183,7 @@ message BasicUnitInfoMask { optional bool labors = 1 [default = false]; optional bool skills = 2 [default = false]; optional bool profession = 3 [default = false]; + optional bool misc_traits = 4 [default = false]; }; message BasicSquadInfo { From c7b922250b5e41cb78b8636a706fdc07be615974 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 11 Apr 2012 12:01:27 +0400 Subject: [PATCH 048/108] More maps api refactoring and renaming. getBlockAbs is a very confusing name; getTileBlock is better. --- library/include/modules/MapCache.h | 3 +- library/include/modules/Maps.h | 26 +++-- library/modules/Maps.cpp | 147 +++++++++++++++++------------ plugins/advtools.cpp | 2 +- plugins/changevein.cpp | 2 +- plugins/cleaners.cpp | 2 +- plugins/deramp.cpp | 2 +- plugins/fixveins.cpp | 3 +- plugins/mapexport/mapexport.cpp | 4 +- plugins/plants.cpp | 10 +- plugins/prospector.cpp | 5 +- plugins/reveal.cpp | 2 +- plugins/tubefill.cpp | 7 +- 13 files changed, 123 insertions(+), 92 deletions(-) diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index f2aebc23d..8eac514be 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -170,6 +170,7 @@ public: bool Write(); + df::coord2d biomeRegionAt(df::coord2d p); int16_t GeoIndexAt(df::coord2d p); bool GetGlobalFeature(t_feature *out); @@ -385,7 +386,7 @@ private: uint32_t x_tmax; uint32_t y_tmax; uint32_t z_max; - std::vector geoidx; + std::vector geoidx; std::vector< std::vector > layer_mats; std::map blocks; }; diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 492512ca9..91b8ae7c2 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -49,6 +49,7 @@ distribution. #include "df/tile_liquid.h" #include "df/tile_dig_designation.h" #include "df/tile_traffic.h" +#include "df/world_data.h" /** * \defgroup grp_maps Maps module and its types @@ -188,7 +189,7 @@ void DfMap::applyGeoMatgloss(Block * b) * @endcode */ extern DFHACK_EXPORT bool ReadGeology(std::vector > *layer_mats, - std::vector *geoidx); + std::vector *geoidx); /** * Get pointers to features of a block */ @@ -198,11 +199,22 @@ extern DFHACK_EXPORT bool ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_fea */ extern DFHACK_EXPORT bool ReadFeatures(df::map_block * block,t_feature * local, t_feature * global); + +/** + * Get a pointer to a specific global feature directly. + */ +DFHACK_EXPORT df::feature_init *getGlobalInitFeature(int32_t index); +/** + * Get a pointer to a specific local feature directly. rgn_coord is in the world region grid. + */ +DFHACK_EXPORT df::feature_init *getLocalInitFeature(df::coord2d rgn_coord, int32_t index); + /** * Read a specific global or local feature directly */ extern DFHACK_EXPORT bool GetGlobalFeature(t_feature &feature, int32_t index); -extern DFHACK_EXPORT bool GetLocalFeature(t_feature &feature, df::coord2d coord, int32_t index); +//extern DFHACK_EXPORT bool GetLocalFeature(t_feature &feature, df::coord2d rgn_coord, int32_t index); + /* * BLOCK DATA @@ -217,13 +229,12 @@ extern DFHACK_EXPORT void getPosition(int32_t& x, int32_t& y, int32_t& z); * Get the map block or NULL if block is not valid */ extern DFHACK_EXPORT df::map_block * getBlock (int32_t blockx, int32_t blocky, int32_t blockz); -extern DFHACK_EXPORT df::map_block * getBlockAbs (int32_t x, int32_t y, int32_t z); +extern DFHACK_EXPORT df::map_block * getTileBlock (int32_t x, int32_t y, int32_t z); inline df::map_block * getBlock (df::coord pos) { return getBlock(pos.x, pos.y, pos.z); } -inline df::map_block * getBlockAbs (df::coord pos) { return getBlockAbs(pos.x, pos.y, pos.z); } +inline df::map_block * getTileBlock (df::coord pos) { return getTileBlock(pos.x, pos.y, pos.z); } -/// copy region offsets of a block - used for determining layer stone matgloss -extern DFHACK_EXPORT bool ReadRegionOffsets(uint32_t blockx, uint32_t blocky, uint32_t blockz, biome_indices40d *buffer); +DFHACK_EXPORT df::world_data::T_region_map *getRegionBiome(df::coord2d rgn_pos); /// sorts the block event vector into multiple vectors by type /// mineral veins, what's under ice, blood smears and mud @@ -238,9 +249,6 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, /// remove a block event from the block by address extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); -/// read all plants in this block -extern DFHACK_EXPORT bool ReadVegetation(uint32_t x, uint32_t y, uint32_t z, std::vector*& plants); - } } #endif diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 848454468..4d653b4fa 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -127,7 +127,7 @@ df::map_block *Maps::getBlock (int32_t blockx, int32_t blocky, int32_t blockz) return world->map.block_index[blockx][blocky][blockz]; } -df::map_block *Maps::getBlockAbs (int32_t x, int32_t y, int32_t z) +df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z) { if (!IsValid()) return NULL; @@ -138,31 +138,40 @@ df::map_block *Maps::getBlockAbs (int32_t x, int32_t y, int32_t z) return world->map.block_index[x >> 4][y >> 4][z]; } -/* - * Region Offsets - used for layer geology - */ -bool Maps::ReadRegionOffsets (uint32_t x, uint32_t y, uint32_t z, biome_indices40d *buffer) +df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos) { - df::map_block *block = getBlock(x,y,z); - if (block) - { - memcpy(buffer, block->region_offset,sizeof(biome_indices40d)); - return true; - } - return false; + auto data = world->world_data; + if (!data) + return NULL; + + if (rgn_pos.x < 0 || rgn_pos.x >= data->world_width || + rgn_pos.y < 0 || rgn_pos.y >= data->world_height) + return NULL; + + return &data->region_map[rgn_pos.x][rgn_pos.y]; +} + +df::feature_init *Maps::getGlobalInitFeature(int32_t index) +{ + auto data = world->world_data; + if (!data) + return NULL; + + auto rgn = vector_get(data->underground_regions, index); + if (!rgn) + return NULL; + + return rgn->feature_init; } bool Maps::GetGlobalFeature(t_feature &feature, int32_t index) { feature.type = (df::feature_type)-1; - if (!world->world_data) - return false; - if ((index < 0) || (index >= world->world_data->underground_regions.size())) + auto f = Maps::getGlobalInitFeature(index); + if (!f) return false; - df::feature_init *f = world->world_data->underground_regions[index]->feature_init; - feature.discovered = false; feature.origin = f; feature.type = f->getType(); @@ -170,35 +179,38 @@ bool Maps::GetGlobalFeature(t_feature &feature, int32_t index) return true; } -bool Maps::GetLocalFeature(t_feature &feature, df::coord2d coord, int32_t index) +df::feature_init *Maps::getLocalInitFeature(df::coord2d rgn_pos, int32_t index) { - feature.type = (df::feature_type)-1; - if (!world->world_data) - return false; - - // regionX and regionY are in embark squares! - // we convert to full region tiles - // this also works in adventure mode - // region X coord - whole regions - uint32_t region_x = ( (coord.x / 3) + world->map.region_x ) / 16; - // region Y coord - whole regions - uint32_t region_y = ( (coord.y / 3) + world->map.region_y ) / 16; + auto data = world->world_data; + if (!data) + return NULL; - uint32_t bigregion_x = region_x / 16; - uint32_t bigregion_y = region_y / 16; + if (rgn_pos.x < 0 || rgn_pos.x >= data->world_width || + rgn_pos.y < 0 || rgn_pos.y >= data->world_height) + return NULL; - uint32_t sub_x = region_x % 16; - uint32_t sub_y = region_y % 16; // megaregions = 16x16 squares of regions = 256x256 squares of embark squares + df::coord2d bigregion = rgn_pos / 16; // bigregion is 16x16 regions. for each bigregion in X dimension: - if (!world->world_data->unk_204[bigregion_x][bigregion_y].features) - return false; + auto fptr = data->unk_204[bigregion.x][bigregion.y].features; + if (!fptr) + return NULL; + + df::coord2d sub = rgn_pos & 15; + + vector &features = fptr->feature_init[sub.x][sub.y]; + + return vector_get(features, index); +} + +static bool GetLocalFeature(t_feature &feature, df::coord2d rgn_pos, int32_t index) +{ + feature.type = (df::feature_type)-1; - vector &features = world->world_data->unk_204[bigregion_x][bigregion_y].features->feature_init[sub_x][sub_y]; - if ((index < 0) || (index >= features.size())) + auto f = Maps::getLocalInitFeature(rgn_pos, index); + if (!f) return false; - df::feature_init *f = features[index]; feature.discovered = false; feature.origin = f; @@ -228,7 +240,7 @@ bool Maps::ReadFeatures(df::map_block * block, t_feature * local, t_feature * gl if (local) { if (block->local_feature != -1) - result &= GetLocalFeature(*local, block->map_pos/16, block->local_feature); + result &= GetLocalFeature(*local, block->region_pos, block->local_feature); else local->type = (df::feature_type)-1; } @@ -310,7 +322,7 @@ bool Maps::RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square /* * Layer geology */ -bool Maps::ReadGeology(vector > *layer_mats, vector *geoidx) +bool Maps::ReadGeology(vector > *layer_mats, vector *geoidx) { if (!world->world_data) return false; @@ -321,9 +333,12 @@ bool Maps::ReadGeology(vector > *layer_mats, vector *ge for (int i = 0; i < eBiomeCount; i++) { (*layer_mats)[i].clear(); - (*geoidx)[i] = -1; + (*geoidx)[i] = df::coord2d(-30000,-30000); } + int world_width = world->world_data->world_width; + int world_height = world->world_data->world_height; + // iterate over 8 surrounding regions + local region for (int i = eNorthWest; i < eBiomeCount; i++) { @@ -332,16 +347,18 @@ bool Maps::ReadGeology(vector > *layer_mats, vector *ge // regionX/16 is in 16x16 embark square regions // i provides -1 .. +1 offset from the current region int bioRX = world->map.region_x / 16 + ((i % 3) - 1); - if (bioRX < 0) bioRX = 0; - if (bioRX >= world->world_data->world_width) bioRX = world->world_data->world_width - 1; int bioRY = world->map.region_y / 16 + ((i / 3) - 1); - if (bioRY < 0) bioRY = 0; - if (bioRY >= world->world_data->world_height) bioRY = world->world_data->world_height - 1; - // get index into geoblock vector - int16_t geoindex = world->world_data->region_map[bioRX][bioRY].geo_index; + df::coord2d rgn_pos(clip_range(bioRX,0,world_width-1),clip_range(bioRX,0,world_height-1)); + + (*geoidx)[i] = rgn_pos; + + auto biome = getRegionBiome(rgn_pos); + if (!biome) + continue; - (*geoidx)[i] = geoindex; + // get index into geoblock vector + int16_t geoindex = biome->geo_index; /// geology blocks have a vector of layer descriptors // get the vector with pointer to layers @@ -363,16 +380,6 @@ bool Maps::ReadGeology(vector > *layer_mats, vector *ge return true; } -bool Maps::ReadVegetation(uint32_t x, uint32_t y, uint32_t z, std::vector*& plants) -{ - df::map_block *block = getBlock(x,y,z); - if (!block) - return false; - - plants = &block->plants; - return true; -} - #define COPY(a,b) memcpy(&a,&b,sizeof(a)) MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) @@ -536,18 +543,34 @@ void MapExtras::Block::SquashRocks (df::map_block *mb, t_blockmaterials & materi } } -int16_t MapExtras::Block::GeoIndexAt(df::coord2d p) +df::coord2d MapExtras::Block::biomeRegionAt(df::coord2d p) { + if (!block) + return df::coord2d(-30000,-30000); + auto des = index_tile(designation,p); uint8_t idx = des.bits.biome; if (idx >= 9) - return -1; + return block->region_pos; idx = block->region_offset[idx]; if (idx >= parent->geoidx.size()) - return -1; + return block->region_pos; return parent->geoidx[idx]; } +int16_t MapExtras::Block::GeoIndexAt(df::coord2d p) +{ + df::coord2d biome = biomeRegionAt(p); + if (!biome.isValid()) + return -1; + + auto pinfo = Maps::getRegionBiome(biome); + if (!pinfo) + return -1; + + return pinfo->geo_index; +} + bool MapExtras::Block::GetGlobalFeature(t_feature *out) { out->type = (df::feature_type)-1; @@ -561,7 +584,7 @@ bool MapExtras::Block::GetLocalFeature(t_feature *out) out->type = (df::feature_type)-1; if (!valid || block->local_feature < 0) return false; - return Maps::GetLocalFeature(*out, block->map_pos/16, block->local_feature); + return ::GetLocalFeature(*out, block->region_pos, block->local_feature); } void MapExtras::Block::init_item_counts() diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index ffb428fd8..69841849a 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -780,7 +780,7 @@ command_result adv_tools (color_ostream &out, std::vector & parame if (!num) continue; - df::map_block *block = Maps::getBlockAbs(item->pos); + df::map_block *block = Maps::getTileBlock(item->pos); if (!block) continue; diff --git a/plugins/changevein.cpp b/plugins/changevein.cpp index 133d7ef4a..1d08cfeaf 100644 --- a/plugins/changevein.cpp +++ b/plugins/changevein.cpp @@ -50,7 +50,7 @@ command_result df_changevein (color_ostream &out, vector & parameters) return CR_FAILURE; } - df::map_block *block = Maps::getBlockAbs(cursor->x, cursor->y, cursor->z); + df::map_block *block = Maps::getTileBlock(cursor->x, cursor->y, cursor->z); if (!block) { out.printerr("Invalid tile selected.\n"); diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index ea5edc991..30befab2f 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -127,7 +127,7 @@ command_result spotclean (color_ostream &out, vector & parameters) out.printerr("Map is not available.\n"); return CR_FAILURE; } - df::map_block *block = Maps::getBlockAbs(cursor->x, cursor->y, cursor->z); + df::map_block *block = Maps::getTileBlock(cursor->x, cursor->y, cursor->z); if (block == NULL) { out.printerr("Invalid map block selected!\n"); diff --git a/plugins/deramp.cpp b/plugins/deramp.cpp index c872f33dd..e5d0331d9 100644 --- a/plugins/deramp.cpp +++ b/plugins/deramp.cpp @@ -38,7 +38,7 @@ command_result df_deramp (color_ostream &out, vector & parameters) for (int i = 0; i < blocks_total; i++) { df::map_block *block = world->map.map_blocks[i]; - df::map_block *above = Maps::getBlockAbs(block->map_pos.x, block->map_pos.y, block->map_pos.z + 1); + df::map_block *above = Maps::getTileBlock(block->map_pos + df::coord(0,0,1)); for (int x = 0; x < 16; x++) { diff --git a/plugins/fixveins.cpp b/plugins/fixveins.cpp index 474201a81..f02f61673 100644 --- a/plugins/fixveins.cpp +++ b/plugins/fixveins.cpp @@ -65,8 +65,7 @@ command_result df_fixveins (color_ostream &out, vector & parameters) has_mineral[k] |= mineral->tile_bitmask[k]; } t_feature local, global; - Maps::GetGlobalFeature(global, block->global_feature); - Maps::GetLocalFeature(local, df::coord2d(block->map_pos.x / 16, block->map_pos.y / 16), block->local_feature); + Maps::ReadFeatures(block, &local, &global); for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) diff --git a/plugins/mapexport/mapexport.cpp b/plugins/mapexport/mapexport.cpp index 805261c21..e0a7e5e69 100644 --- a/plugins/mapexport/mapexport.cpp +++ b/plugins/mapexport/mapexport.cpp @@ -241,9 +241,9 @@ command_result mapexport (color_ostream &out, std::vector & parame } } - PlantList *plants; - if (Maps::ReadVegetation(b_x, b_y, z, plants)) + if (b->getRaw()) { + PlantList *plants = &b->getRaw()->plants; for (PlantList::const_iterator it = plants->begin(); it != plants->end(); it++) { const df::plant & plant = *(*it); diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 09220c653..5ab09868f 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -125,8 +125,9 @@ static command_result immolations (color_ostream &out, do_what what, bool shrubs int32_t x,y,z; if(Gui::getCursorCoords(x,y,z)) { - vector * alltrees; - if(Maps::ReadVegetation(x/16,y/16,z,alltrees)) + auto block = Maps::getTileBlock(x,y,z); + vector *alltrees = block ? &block->plants : NULL; + if(alltrees) { bool didit = false; for(size_t i = 0 ; i < alltrees->size(); i++) @@ -206,8 +207,9 @@ command_result df_grow (color_ostream &out, vector & parameters) int32_t x,y,z; if(Gui::getCursorCoords(x,y,z)) { - vector * alltrees; - if(Maps::ReadVegetation(x/16,y/16,z,alltrees)) + auto block = Maps::getTileBlock(x,y,z); + vector *alltrees = block ? &block->plants : NULL; + if(alltrees) { for(size_t i = 0 ; i < alltrees->size(); i++) { diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 80ce365cc..c90a66c5a 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -544,8 +544,9 @@ command_result prospector (color_ostream &con, vector & parameters) // and we can check visibility more easily here if (showPlants) { - PlantList * plants; - if (Maps::ReadVegetation(b_x, b_y, z, plants)) + auto block = Maps::getBlock(b_x,b_y,z); + vector *plants = block ? &block->plants : NULL; + if(plants) { for (PlantList::const_iterator it = plants->begin(); it != plants->end(); it++) { diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 39a2ed9cd..513eededb 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -290,7 +290,7 @@ command_result unreveal(color_ostream &out, vector & params) for(size_t i = 0; i < hidesaved.size();i++) { hideblock & hb = hidesaved[i]; - df::map_block * b = Maps::getBlockAbs(hb.c.x,hb.c.y,hb.c.z); + df::map_block * b = Maps::getTileBlock(hb.c.x,hb.c.y,hb.c.z); for (uint32_t x = 0; x < 16;x++) for (uint32_t y = 0; y < 16;y++) { b->designation[x][y].bits.hidden = hb.hiddens[x][y]; diff --git a/plugins/tubefill.cpp b/plugins/tubefill.cpp index 0dd045343..0d20136de 100644 --- a/plugins/tubefill.cpp +++ b/plugins/tubefill.cpp @@ -59,12 +59,9 @@ command_result tubefill(color_ostream &out, std::vector & params) for (size_t i = 0; i < world->map.map_blocks.size(); i++) { df::map_block *block = world->map.map_blocks[i]; - df::map_block *above = Maps::getBlockAbs(block->map_pos.x, block->map_pos.y, block->map_pos.z + 1); - if (block->local_feature == -1) - continue; + df::map_block *above = Maps::getTileBlock(block->map_pos + df::coord(0,0,1)); DFHack::t_feature feature; - DFCoord coord(block->map_pos.x >> 4, block->map_pos.y >> 4, block->map_pos.z); - if (!Maps::GetLocalFeature(feature, coord, block->local_feature)) + if (!Maps::ReadFeatures(block, &feature, NULL)) continue; if (feature.type != feature_type::deep_special_tube) continue; From ae7ce9e5d7e955885888933854b32aa146fe046d Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Wed, 11 Apr 2012 14:08:47 +0200 Subject: [PATCH 049/108] zone: fixed assigning to pit/pond. regrass: look for grass events in the map block, increase amount of grass at map position instead of simply changing the tile type --- plugins/devel/regrass.cpp | 102 ++++++++++++++++++++++++++++++-------- plugins/zone.cpp | 28 ++++++----- 2 files changed, 96 insertions(+), 34 deletions(-) diff --git a/plugins/devel/regrass.cpp b/plugins/devel/regrass.cpp index f4143f465..4c8f19346 100644 --- a/plugins/devel/regrass.cpp +++ b/plugins/devel/regrass.cpp @@ -8,19 +8,47 @@ #include "DataDefs.h" #include "df/world.h" +#include "df/world_raws.h" +#include "df/plant_raw.h" + #include "df/map_block.h" +#include "df/block_square_event.h" +//#include "df/block_square_event_type.h" +#include "df/block_square_event_grassst.h" #include "TileTypes.h" using std::string; using std::vector; +using namespace std; using namespace DFHack; using df::global::world; +DFHACK_PLUGIN("regrass"); + +command_result df_regrass (color_ostream &out, vector & parameters); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand("regrass", "Regrows surface grass.", df_regrass)); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + command_result df_regrass (color_ostream &out, vector & parameters) { + bool max = false; if (!parameters.empty()) - return CR_WRONG_USAGE; + { + if(parameters[0] == "max") + max = true; + else + return CR_WRONG_USAGE; + } CoreSuspender suspend; @@ -28,19 +56,62 @@ command_result df_regrass (color_ostream &out, vector & parameters) for (size_t i = 0; i < world->map.map_blocks.size(); i++) { df::map_block *cur = world->map.map_blocks[i]; - for (int x = 0; x < 16; x++) + + // check block for grass events before looking at 16x16 tiles + df::block_square_event_grassst * grev = NULL; + for(size_t e=0; eblock_events.size(); e++) + { + df::block_square_event * blev = cur->block_events[e]; + df::block_square_event_type blevtype = blev->getType(); + if(blevtype == df::block_square_event_type::grass) + { + grev = (df::block_square_event_grassst *)blev; + break; + } + } + if(!grev) { - for (int y = 0; y < 16; y++) + // in this worst case we should check other blocks, create a new event etc + // but looking at some maps that should happen very very rarely if at all + // a standard map block seems to always have up to 10 grass events we can refresh + continue; + } + + for (int y = 0; y < 16; y++) + { + for (int x = 0; x < 16; x++) { - if (tileShape(cur->tiletype[x][y]) != tiletype_shape::FLOOR) - continue; - if (tileMaterial(cur->tiletype[x][y]) != tiletype_material::SOIL) + if ( tileShape(cur->tiletype[x][y]) != tiletype_shape::FLOOR + || cur->designation[x][y].bits.subterranean + || cur->occupancy[x][y].bits.building) continue; - if (cur->designation[x][y].bits.subterranean) - continue; - if (cur->occupancy[x][y].bits.building) + + int mat = tileMaterial(cur->tiletype[x][y]); + if ( mat != tiletype_material::SOIL + && mat != tiletype_material::GRASS_DARK // refill existing grass, too + && mat != tiletype_material::GRASS_LIGHT // refill existing grass, too + ) continue; + // max = set amounts of all grass events on that tile to 100 + if(max) + { + for(size_t e=0; eblock_events.size(); e++) + { + df::block_square_event * blev = cur->block_events[e]; + df::block_square_event_type blevtype = blev->getType(); + if(blevtype == df::block_square_event_type::grass) + { + df::block_square_event_grassst * gr_ev = (df::block_square_event_grassst *)blev; + gr_ev->amount[x][y] = 100; + break; + } + } + } + else + { + grev->amount[x][y]=100; + } cur->tiletype[x][y] = findRandomVariant((rand() & 1) ? tiletype::GrassLightFloor1 : tiletype::GrassDarkFloor1); count++; } @@ -51,16 +122,3 @@ command_result df_regrass (color_ostream &out, vector & parameters) out.print("Regrew %d tiles of grass.\n", count); return CR_OK; } - -DFHACK_PLUGIN("regrass"); - -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand("regrass", "Regrows all surface grass, restoring outdoor plant growth for pre-0.31.19 worlds.", df_regrass)); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 5d5c196c0..c668abe99 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -341,7 +341,7 @@ bool isForest(df::unit* creature); bool isActivityZone(df::building * building); bool isPenPasture(df::building * building); -bool isPit(df::building * building); +bool isPitPond(df::building * building); bool isActive(df::building * building); int32_t findBuildingIndexById(int32_t id); @@ -727,14 +727,14 @@ bool isPenPasture(df::building * building) return false; } -bool isPit(df::building * building) +bool isPitPond(df::building * building) { if(!isActivityZone(building)) return false; df::building_civzonest * civ = (df::building_civzonest *) building; - if(civ->zone_flags.bits.pit_pond && civ->pit_flags==0) + if(civ->zone_flags.bits.pit_pond) // && civ->pit_flags==0) return true; else return false; @@ -790,7 +790,7 @@ int32_t findPenPitAtCursor() building->z == cursor->z)) continue; - if(isPenPasture(building) || isPit(building)) + if(isPenPasture(building) || isPitPond(building)) { foundID = building->id; break; @@ -1198,9 +1198,9 @@ bool unassignUnitFromBuilding(df::unit* unit) command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) { // building must be a pen/pasture or pit - if(!isPenPasture(building) && !isPit(building)) + if(!isPenPasture(building) && !isPitPond(building)) { - out << "Invalid building type. This is not a pen/pasture or pit." << endl; + out << "Invalid building type. This is not a pen/pasture or pit/pond." << endl; return CR_WRONG_USAGE; } @@ -1238,8 +1238,8 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building out << "Unit " << unit->id << "(" << getRaceName(unit) << ")" << " assigned to zone " << building->id; - if(isPit(building)) - out << " (pit)."; + if(isPitPond(building)) + out << " (pit/pond)."; if(isPenPasture(building)) out << " (pen/pasture)."; out << endl; @@ -1341,10 +1341,14 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) if(civ->zone_flags.bits.pen_pasture) out << ", pen/pasture"; - else if (civ->zone_flags.bits.pit_pond && civ->pit_flags==0) - out << ", pit"; - else - return; + else if (civ->zone_flags.bits.pit_pond) + { + out << " (pit flags:" << civ->pit_flags << ")"; + if(civ->pit_flags & 1) + out << ", pond"; + else + out << ", pit"; + } out << endl; out << "x1:" <x1 << " x2:" <x2 From 61245711f7840a4d3c36739d5842180b95995136 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 11 Apr 2012 16:20:16 +0400 Subject: [PATCH 050/108] Export a few maps functions to lua. --- LUA_API.rst | 31 +++++++++++++++++++++++++++++++ Lua API.html | 27 +++++++++++++++++++++++++++ library/LuaApi.cpp | 17 ++++++++++++++--- library/LuaTypes.cpp | 9 +++++++++ library/lua/dfhack.lua | 10 ++++++++++ 5 files changed, 91 insertions(+), 3 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index ee3b1077c..4d7e0df76 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -686,3 +686,34 @@ Units module * ``dfhack.units.isSane(unit)`` The unit is capable of rational action, i.e. not dead, insane or zombie. + +Maps module +----------- + +* ``dfhack.maps.getSize()`` + + Returns map size in blocks: *x, y, z* + +* ``dfhack.maps.getTileSize()`` + + Returns map size in tiles: *x, y, z* + +* ``dfhack.maps.getBlock(x,y,z)`` + + Returns a map block object for given x,y,z in local block coordinates. + +* ``dfhack.maps.getTileBlock(coords)`` + + Returns a map block object for given df::coord in local tile coordinates. + +* ``dfhack.maps.getRegionBiome(region_coord2d)`` + + Returns the biome info struct for the given global map region. + +* ``dfhack.maps.getGlobalInitFeature(index)`` + + Returns the global feature object with the given index. + +* ``dfhack.maps.getLocalInitFeature(region_coord2d,index)`` + + Returns the local feature object with the given region coords and index. diff --git a/Lua API.html b/Lua API.html index 89e158037..0f5f18deb 100644 --- a/Lua API.html +++ b/Lua API.html @@ -340,6 +340,7 @@ ul.auto-toc {
  • Gui module
  • Job module
  • Units module
  • +
  • Maps module
  • @@ -918,6 +919,32 @@ a lua list containing them.

    +
    +

    Maps module

    +
      +
    • dfhack.maps.getSize()

      +

      Returns map size in blocks: x, y, z

      +
    • +
    • dfhack.maps.getTileSize()

      +

      Returns map size in tiles: x, y, z

      +
    • +
    • dfhack.maps.getBlock(x,y,z)

      +

      Returns a map block object for given x,y,z in local block coordinates.

      +
    • +
    • dfhack.maps.getTileBlock(coords)

      +

      Returns a map block object for given df::coord in local tile coordinates.

      +
    • +
    • dfhack.maps.getRegionBiome(region_coord2d)

      +

      Returns the biome info struct for the given global map region.

      +
    • +
    • dfhack.maps.getGlobalInitFeature(index)

      +

      Returns the global feature object with the given index.

      +
    • +
    • dfhack.maps.getLocalInitFeature(region_coord2d,index)

      +

      Returns the local feature object with the given region coords and index.

      +
    • +
    +
    diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index c336e309c..ba989ecf2 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -43,6 +43,7 @@ distribution. #include "modules/Translation.h" #include "modules/Units.h" #include "modules/Materials.h" +#include "modules/Maps.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -527,9 +528,9 @@ static void OpenModule(lua_State *state, const char *mname, lua_pop(state, 1); } -#define WRAPM(module, function) { #function, df::wrap_function(&module::function) } -#define WRAP(function) { #function, df::wrap_function(&function) } -#define WRAPN(name, function) { #name, df::wrap_function(&function) } +#define WRAPM(module, function) { #function, df::wrap_function(module::function) } +#define WRAP(function) { #function, df::wrap_function(function) } +#define WRAPN(name, function) { #name, df::wrap_function(function) } static const LuaWrapper::FunctionReg dfhack_module[] = { WRAPM(Translation, TranslateName), @@ -600,6 +601,15 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { { NULL, NULL } }; +static const LuaWrapper::FunctionReg dfhack_maps_module[] = { + WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock), + WRAPN(getTileBlock, (df::map_block* (*)(df::coord))Maps::getTileBlock), + WRAPM(Maps, getRegionBiome), + WRAPM(Maps, getGlobalInitFeature), + WRAPM(Maps, getLocalInitFeature), + { NULL, NULL } +}; + /************************ * Main Open function * ************************/ @@ -613,4 +623,5 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "units", dfhack_units_module); + OpenModule(state, "maps", dfhack_maps_module); } diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 2f9ef9e81..40269da80 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -80,6 +80,15 @@ void constructed_identity::lua_write(lua_State *state, int fname_idx, void *ptr, { invoke_assign(state, this, ptr, val_index); } + // Allow by-value assignment for wrapped function parameters + else if (fname_idx == UPVAL_METHOD_NAME && lua_isuserdata(state, val_index)) + { + void *nval = get_object_internal(state, this, val_index, false); + if (!nval) + field_error(state, fname_idx, "incompatible type in complex assignment", "write"); + if (!copy(ptr, nval)) + field_error(state, fname_idx, "no copy support", "write"); + } else field_error(state, fname_idx, "complex object", "write"); } diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index b37183cb6..25d8c2ba8 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -89,5 +89,15 @@ function dfhack.matinfo:__tostring() return "" end +function dfhack.maps.getSize() + local map = df.global.world.map + return map.x_count_block, map.y_count_block, map.z_count_block +end + +function dfhack.maps.getTileSize() + local map = df.global.world.map + return map.x_count, map.y_count, map.z_count +end + -- Feed the table back to the require() mechanism. return dfhack From e8e8f293289e755fa40f64301f0e747dfaa40c79 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Wed, 11 Apr 2012 17:01:20 +0200 Subject: [PATCH 051/108] regrass: fix --- plugins/devel/regrass.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/plugins/devel/regrass.cpp b/plugins/devel/regrass.cpp index 4c8f19346..b4106ba5a 100644 --- a/plugins/devel/regrass.cpp +++ b/plugins/devel/regrass.cpp @@ -104,13 +104,32 @@ command_result df_regrass (color_ostream &out, vector & parameters) { df::block_square_event_grassst * gr_ev = (df::block_square_event_grassst *)blev; gr_ev->amount[x][y] = 100; - break; } } } else { - grev->amount[x][y]=100; + // try to find the 'original' event + bool regrew = false; + for(size_t e=0; eblock_events.size(); e++) + { + df::block_square_event * blev = cur->block_events[e]; + df::block_square_event_type blevtype = blev->getType(); + if(blevtype == df::block_square_event_type::grass) + { + df::block_square_event_grassst * gr_ev = (df::block_square_event_grassst *)blev; + if(gr_ev->amount[x][y] > 0) + { + gr_ev->amount[x][y] = 100; + regrew = true; + break; + } + } + } + // if original could not be found (meaning it was already depleted): + // refresh the first grass event in the map block + if(!regrew) + grev->amount[x][y]=100; } cur->tiletype[x][y] = findRandomVariant((rand() & 1) ? tiletype::GrassLightFloor1 : tiletype::GrassDarkFloor1); count++; From 0c2b78b96b0aca46988f9d1e3c94f70cdfbc8e02 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 11 Apr 2012 19:42:05 +0400 Subject: [PATCH 052/108] Add api for manipulating burrows to the core. --- LUA_API.rst | 33 +++++++ Lua API.html | 24 +++++ library/LuaApi.cpp | 61 ++++++++++-- library/include/DataFuncs.h | 8 ++ library/include/MiscUtils.h | 16 +++ .../df/custom/block_burrow.methods.inc | 19 ++++ .../block_square_event_mineralst.methods.inc | 2 +- library/include/modules/Maps.h | 31 +++++- library/include/modules/Units.h | 5 + library/lua/dfhack.lua | 6 ++ library/modules/Maps.cpp | 98 +++++++++++++++++++ library/modules/Units.cpp | 39 ++++++++ library/xml | 2 +- 13 files changed, 331 insertions(+), 13 deletions(-) create mode 100644 library/include/df/custom/block_burrow.methods.inc diff --git a/LUA_API.rst b/LUA_API.rst index 4d7e0df76..8698e1b23 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -687,6 +687,15 @@ Units module The unit is capable of rational action, i.e. not dead, insane or zombie. +* ``dfhack.units.isInBurrow(unit,burrow)`` + + Checks if the unit is in the burrow. + +* ``dfhack.units.setInBurrow(unit,burrow,enable)`` + + Adds or removes the unit from the burrow. + + Maps module ----------- @@ -717,3 +726,27 @@ Maps module * ``dfhack.maps.getLocalInitFeature(region_coord2d,index)`` Returns the local feature object with the given region coords and index. + +* ``dfhack.maps.findBurrowByName(name)`` + + Returns the burrow pointer or *nil*. + +* ``dfhack.maps.listBurrowBlocks(burrow)`` + + Returns a table of map block pointers. + +* ``dfhack.maps.isBurrowTile(burrow,tile_coord)`` + + Checks if the tile is in burrow. + +* ``dfhack.maps.setBurrowTile(burrow,tile_coord,enable)`` + + Adds or removes the tile from the burrow. Returns *false* if invalid coords. + +* ``dfhack.maps.isBlockBurrowTile(burrow,block,x,y)`` + + Checks if the tile within the block is in burrow. + +* ``dfhack.maps.setBlockBurrowTile(burrow,block,x,y,enable)`` + + Adds or removes the tile from the burrow. Returns *false* if invalid coords. diff --git a/Lua API.html b/Lua API.html index 0f5f18deb..0ca88966c 100644 --- a/Lua API.html +++ b/Lua API.html @@ -917,6 +917,12 @@ a lua list containing them.

  • dfhack.units.isSane(unit)

    The unit is capable of rational action, i.e. not dead, insane or zombie.

  • +
  • dfhack.units.isInBurrow(unit,burrow)

    +

    Checks if the unit is in the burrow.

    +
  • +
  • dfhack.units.setInBurrow(unit,burrow,enable)

    +

    Adds or removes the unit from the burrow.

    +
  • @@ -943,6 +949,24 @@ a lua list containing them.

  • dfhack.maps.getLocalInitFeature(region_coord2d,index)

    Returns the local feature object with the given region coords and index.

  • +
  • dfhack.maps.findBurrowByName(name)

    +

    Returns the burrow pointer or nil.

    +
  • +
  • dfhack.maps.listBurrowBlocks(burrow)

    +

    Returns a table of map block pointers.

    +
  • +
  • dfhack.maps.isBurrowTile(burrow,tile_coord)

    +

    Checks if the tile is in burrow.

    +
  • +
  • dfhack.maps.setBurrowTile(burrow,tile_coord,enable)

    +

    Adds or removes the tile from the burrow. Returns false if invalid coords.

    +
  • +
  • dfhack.maps.isBlockBurrowTile(burrow,block,x,y)

    +

    Checks if the tile within the block is in burrow.

    +
  • +
  • dfhack.maps.setBlockBurrowTile(burrow,block,x,y,enable)

    +

    Adds or removes the tile from the burrow. Returns false if invalid coords.

    +
  • diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index ba989ecf2..fb818c206 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -63,6 +63,7 @@ distribution. #include "df/inorganic_raw.h" #include "df/dfhack_material_category.h" #include "df/job_material_category.h" +#include "df/burrow.h" #include #include @@ -71,6 +72,18 @@ distribution. using namespace DFHack; using namespace DFHack::LuaWrapper; +template +void push_pointer_vector(lua_State *state, const std::vector &pvec) +{ + lua_createtable(state,pvec.size(),0); + + for (size_t i = 0; i < pvec.size(); i++) + { + Lua::PushDFObject(state, pvec[i]); + lua_rawseti(state, -2, i+1); + } +} + /************************************************** * Per-world persistent configuration storage API * **************************************************/ @@ -571,14 +584,7 @@ static int job_listNewlyCreated(lua_State *state) if (Job::listNewlyCreated(&pvec, &nxid)) { lua_pushinteger(state, nxid); - lua_newtable(state); - - for (size_t i = 0; i < pvec.size(); i++) - { - Lua::PushDFObject(state, pvec[i]); - lua_rawseti(state, -2, i+1); - } - + push_pointer_vector(state, pvec); return 2; } else @@ -598,18 +604,55 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isDead), WRAPM(Units, isAlive), WRAPM(Units, isSane), + WRAPM(Units, isInBurrow), + WRAPM(Units, setInBurrow), { NULL, NULL } }; +static bool maps_isBlockBurrowTile(df::burrow *burrow, df::map_block *block, int x, int y) +{ + return Maps::isBlockBurrowTile(burrow, block, df::coord2d(x,y)); +} + +static bool maps_setBlockBurrowTile(df::burrow *burrow, df::map_block *block, int x, int y, bool enable) +{ + return Maps::setBlockBurrowTile(burrow, block, df::coord2d(x,y), enable); +} + static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock), WRAPN(getTileBlock, (df::map_block* (*)(df::coord))Maps::getTileBlock), WRAPM(Maps, getRegionBiome), WRAPM(Maps, getGlobalInitFeature), WRAPM(Maps, getLocalInitFeature), + WRAPM(Maps, findBurrowByName), + WRAPN(isBlockBurrowTile, maps_isBlockBurrowTile), + WRAPN(setBlockBurrowTile, maps_setBlockBurrowTile), + WRAPM(Maps, isBurrowTile), + WRAPM(Maps, setBurrowTile), + { NULL, NULL } +}; + +static int maps_listBurrowBlocks(lua_State *state) +{ + luaL_checkany(state, 1); + + auto ptr = Lua::GetDFObject(state, 1); + if (!ptr) + luaL_argerror(state, 1, "invalid burrow type"); + + std::vector pvec; + Maps::listBurrowBlocks(&pvec, ptr); + push_pointer_vector(state, pvec); + return 1; +} + +static const luaL_Reg dfhack_maps_funcs[] = { + { "listBurrowBlocks", maps_listBurrowBlocks }, { NULL, NULL } }; + /************************ * Main Open function * ************************/ @@ -623,5 +666,5 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "units", dfhack_units_module); - OpenModule(state, "maps", dfhack_maps_module); + OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs); } diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 6cd7d42f4..d78fe9e27 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -139,6 +139,14 @@ INSTANTIATE_WRAPPERS(3, (OSTREAM_ARG,A1,A2,A3), (out,vA1,vA2,vA3), INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4)) INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (vA1,vA2,vA3,vA4), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);) +INSTANTIATE_WRAPPERS(4, (OSTREAM_ARG,A1,A2,A3,A4), (out,vA1,vA2,vA3,vA4), + LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);) +#undef FW_TARGS + +#define FW_TARGS class A1, class A2, class A3, class A4, class A5 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5)) +INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (vA1,vA2,vA3,vA4,vA5), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); LOAD_ARG(A5);) #undef FW_TARGS #undef FW_TARGSC diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index c2a153eb1..0cf34c489 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -207,6 +207,22 @@ unsigned insert_into_vector(std::vector &vec, FT CT::*field, CT *obj, bool return pos; } +template +bool erase_from_vector(std::vector &vec, FT key) +{ + int pos = binsearch_index(vec, key); + vector_erase_at(vec, pos); + return pos >= 0; +} + +template +bool erase_from_vector(std::vector &vec, FT CT::*field, FT key) +{ + int pos = binsearch_index(vec, field, key); + vector_erase_at(vec, pos); + return pos >= 0; +} + template CT *binsearch_in_vector(const std::vector &vec, KT value) { diff --git a/library/include/df/custom/block_burrow.methods.inc b/library/include/df/custom/block_burrow.methods.inc new file mode 100644 index 000000000..7754ab0db --- /dev/null +++ b/library/include/df/custom/block_burrow.methods.inc @@ -0,0 +1,19 @@ +inline bool getassignment( const df::coord2d &xy ) +{ + return getassignment(xy.x,xy.y); +} +inline bool getassignment( int x, int y ) +{ + return (tile_bitmask[y] & (1 << x)); +} +inline void setassignment( const df::coord2d &xy, bool bit ) +{ + return setassignment(xy.x,xy.y, bit); +} +inline void setassignment( int x, int y, bool bit ) +{ + if(bit) + tile_bitmask[y] |= (1 << x); + else + tile_bitmask[y] &= ~(1 << x); +} \ No newline at end of file diff --git a/library/include/df/custom/block_square_event_mineralst.methods.inc b/library/include/df/custom/block_square_event_mineralst.methods.inc index e01d22f72..7754ab0db 100644 --- a/library/include/df/custom/block_square_event_mineralst.methods.inc +++ b/library/include/df/custom/block_square_event_mineralst.methods.inc @@ -15,5 +15,5 @@ inline void setassignment( int x, int y, bool bit ) if(bit) tile_bitmask[y] |= (1 << x); else - tile_bitmask[y] &= 0xFFFF ^ (1 << x); + tile_bitmask[y] &= ~(1 << x); } \ No newline at end of file diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 91b8ae7c2..0de262264 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -38,7 +38,7 @@ distribution. #include "modules/Materials.h" #include "df/world.h" -#include "df/feature_init.h" +#include "df/world_data.h" #include "df/map_block.h" #include "df/block_square_event.h" #include "df/block_square_event_mineralst.h" @@ -49,13 +49,20 @@ distribution. #include "df/tile_liquid.h" #include "df/tile_dig_designation.h" #include "df/tile_traffic.h" -#include "df/world_data.h" +#include "df/feature_init.h" /** * \defgroup grp_maps Maps module and its types * @ingroup grp_modules */ +namespace df +{ + struct burrow; + struct world_data; + struct block_burrow; +} + namespace DFHack { /*************************************************************************** @@ -249,6 +256,26 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, /// remove a block event from the block by address extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); +/* + * BURROWS + */ + +DFHACK_EXPORT df::burrow *findBurrowByName(std::string name); + +void listBurrowBlocks(std::vector *pvec, df::burrow *burrow); + +df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false); + +bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile); +bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable); + +inline bool isBurrowTile(df::burrow *burrow, df::coord tile) { + return isBlockBurrowTile(burrow, getTileBlock(tile), tile); +} +inline bool setBurrowTile(df::burrow *burrow, df::coord tile, bool enable) { + return setBlockBurrowTile(burrow, getTileBlock(tile), tile, enable); +} + } } #endif diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 838d1d596..35701b6ec 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -36,6 +36,7 @@ distribution. namespace df { struct nemesis_record; + struct burrow; } /** @@ -206,6 +207,10 @@ DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit); + +DFHACK_EXPORT bool isInBurrow(df::unit *unit, df::burrow *burrow); +DFHACK_EXPORT void setInBurrow(df::unit *unit, df::burrow *burrow, bool enable); + } } #endif diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 25d8c2ba8..a6606d152 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -80,6 +80,12 @@ function printall(table) end end +function copyall(table) + local rv = {} + for k,v in pairs(table) do rv[k] = v end + return rv +end + function dfhack.persistent:__tostring() return "" diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 4d653b4fa..c0aa9b634 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -47,6 +47,10 @@ using namespace std; #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" #include "df/feature_init.h" +#include "df/world_data.h" +#include "df/burrow.h" +#include "df/block_burrow.h" +#include "df/block_burrow_link.h" using namespace DFHack; using namespace df::enums; @@ -681,3 +685,97 @@ MapExtras::Block *MapExtras::MapCache::BlockAt(DFCoord blockcoord) } } +df::burrow *Maps::findBurrowByName(std::string name) +{ + auto &vec = df::burrow::get_vector(); + + for (size_t i = 0; i < vec.size(); i++) + if (vec[i]->name == name) + return vec[i]; + + return NULL; +} + +void Maps::listBurrowBlocks(std::vector *pvec, df::burrow *burrow) +{ + CHECK_NULL_POINTER(burrow); + + pvec->clear(); + pvec->reserve(burrow->block_x.size()); + + df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); + + for (size_t i = 0; i < burrow->block_x.size(); i++) + { + df::coord pos(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); + + auto block = getBlock(pos - base); + if (block) + pvec->push_back(block); + } +} + +df::block_burrow *Maps::getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create) +{ + CHECK_NULL_POINTER(burrow); + CHECK_NULL_POINTER(block); + + int32_t id = burrow->id; + df::block_burrow_link *prev = &block->block_burrows; + df::block_burrow_link *link = prev->next; + + for (; link; prev = link, link = link->next) + if (link->item->id == id) + return link->item; + + if (create) + { + link = new df::block_burrow_link; + link->item = new df::block_burrow; + + link->item->id = burrow->id; + memset(link->item->tile_bitmask,0,sizeof(link->item->tile_bitmask)); + link->item->link = link; + + link->next = NULL; + link->prev = prev; + prev->next = link; + + df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); + df::coord pos = base + block->map_pos/16; + + burrow->block_x.push_back(pos.x); + burrow->block_y.push_back(pos.y); + burrow->block_z.push_back(pos.z); + + return link->item; + } + + return NULL; +} + +bool Maps::isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile) +{ + CHECK_NULL_POINTER(burrow); + + if (!block) return false; + + auto mask = getBlockBurrowMask(burrow, block); + + return mask ? mask->getassignment(tile & 15) : false; +} + +bool Maps::setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable) +{ + CHECK_NULL_POINTER(burrow); + + if (!block) return false; + + auto mask = getBlockBurrowMask(burrow, block, enable); + + if (mask) + mask->setassignment(tile & 15, enable); + + return true; +} + diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index e210a9028..47208dcbc 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -44,6 +44,7 @@ using namespace std; #include "modules/Translation.h" #include "ModuleFactory.h" #include "Core.h" +#include "MiscUtils.h" #include "df/world.h" #include "df/ui.h" @@ -54,6 +55,7 @@ using namespace std; #include "df/historical_figure.h" #include "df/historical_figure_info.h" #include "df/assumed_identity.h" +#include "df/burrow.h" using namespace DFHack; using namespace df::enums; @@ -652,3 +654,40 @@ bool DFHack::Units::isSane(df::unit *unit) return true; } + +bool DFHack::Units::isInBurrow(df::unit *unit, df::burrow *burrow) +{ + CHECK_NULL_POINTER(unit); + CHECK_NULL_POINTER(burrow); + + return binsearch_index(unit->burrows, burrow->id) >= 0; +} + +void DFHack::Units::setInBurrow(df::unit *unit, df::burrow *burrow, bool enable) +{ + using df::global::ui; + + CHECK_NULL_POINTER(unit); + CHECK_NULL_POINTER(burrow); + + if (enable) + { + insert_into_vector(unit->burrows, burrow->id); + insert_into_vector(burrow->units, unit->id); + } + else + { + erase_from_vector(unit->burrows, burrow->id); + erase_from_vector(burrow->units, unit->id); + } + + // Sync ui if active + if (ui && ui->main.mode == ui_sidebar_mode::Burrows && + ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id) + { + int idx = linear_index(ui->burrows.list_units, unit); + if (idx >= 0) + ui->burrows.sel_units[idx] = enable; + } +} + diff --git a/library/xml b/library/xml index baeee2ce1..27fec9797 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit baeee2ce115e0b3432e5b66b843e8883e5f03b34 +Subproject commit 27fec9797d7d1215bd3f5530b44a55a9d394fe75 From 5d5502ae341030c58478dae2d04ba31ab2bfb93e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 11 Apr 2012 20:10:31 +0400 Subject: [PATCH 053/108] Update the item owner modification api and export it to lua. --- LUA_API.rst | 13 ++++++ Lua API.html | 17 ++++++- library/LuaApi.cpp | 8 ++++ library/include/DataDefs.h | 3 ++ library/include/modules/Items.h | 11 ++--- library/include/modules/Units.h | 6 --- library/modules/Items.cpp | 78 +++++++++++++++++++-------------- library/modules/Units.cpp | 31 ------------- plugins/cleanowned.cpp | 7 ++- 9 files changed, 93 insertions(+), 81 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 8698e1b23..fc86070c3 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -696,6 +696,19 @@ Units module Adds or removes the unit from the burrow. +Items module +------------ + +* ``dfhack.items.getOwner(item)`` + + Returns the owner unit or *nil*. + +* ``dfhack.items.setOwner(item,unit)`` + + Replaces the owner of the item. If unit is *nil*, removes ownership. + Returns *false* in case of error. + + Maps module ----------- diff --git a/Lua API.html b/Lua API.html index 0ca88966c..7c3eeddb9 100644 --- a/Lua API.html +++ b/Lua API.html @@ -340,7 +340,8 @@ ul.auto-toc {
  • Gui module
  • Job module
  • Units module
  • -
  • Maps module
  • +
  • Items module
  • +
  • Maps module
  • @@ -925,8 +926,20 @@ a lua list containing them.

    +
    +

    Items module

    +
      +
    • dfhack.items.getOwner(item)

      +

      Returns the owner unit or nil.

      +
    • +
    • dfhack.items.setOwner(item,unit)

      +

      Replaces the owner of the item. If unit is nil, removes ownership. +Returns false in case of error.

      +
    • +
    +
    -

    Maps module

    +

    Maps module

    • dfhack.maps.getSize()

      Returns map size in blocks: x, y, z

      diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index fb818c206..febc59026 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -42,6 +42,7 @@ distribution. #include "modules/Job.h" #include "modules/Translation.h" #include "modules/Units.h" +#include "modules/Items.h" #include "modules/Materials.h" #include "modules/Maps.h" @@ -609,6 +610,12 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { { NULL, NULL } }; +static const LuaWrapper::FunctionReg dfhack_items_module[] = { + WRAPM(Items, getOwner), + WRAPM(Items, setOwner), + { NULL, NULL } +}; + static bool maps_isBlockBurrowTile(df::burrow *burrow, df::map_block *block, int x, int y) { return Maps::isBlockBurrowTile(burrow, block, df::coord2d(x,y)); @@ -666,5 +673,6 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "units", dfhack_units_module); + OpenModule(state, "items", dfhack_items_module); OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs); } diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index b9b4707d9..d4d757d94 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -425,6 +425,9 @@ namespace df static compound_identity *get() { return &T::_identity; } }; + template + inline T* allocate() { return (T*)identity_traits::get()->allocate(); } + template struct enum_traits {}; diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index e58c9214b..9bab02cc8 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -122,16 +122,17 @@ DFHACK_EXPORT bool copyItem(df::item * source, dfh_item & target); /// write copied item back to its origin DFHACK_EXPORT bool writeItem(const dfh_item & item); -/// who owns this item we already read? -DFHACK_EXPORT int32_t getItemOwnerID(const df::item * item); -DFHACK_EXPORT df::unit *getItemOwner(const df::item * item); +/// Retrieve the owner of the item. +DFHACK_EXPORT df::unit *getOwner(df::item *item); +/// Set the owner of the item. Pass NULL as unit to remove the owner. +DFHACK_EXPORT bool setOwner(df::item *item, df::unit *unit); + /// which item is it contained in? DFHACK_EXPORT int32_t getItemContainerID(const df::item * item); DFHACK_EXPORT df::item *getItemContainer(const df::item * item); /// which items does it contain? DFHACK_EXPORT bool getContainedItems(const df::item * item, /*output*/ std::vector &items); -/// wipe out the owner records -DFHACK_EXPORT bool removeItemOwner(df::item * item); + /// read item references, filtered by class DFHACK_EXPORT bool readItemRefs(const df::item * item, const df::general_ref_type type, /*output*/ std::vector &values); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 35701b6ec..12e687112 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -185,9 +185,6 @@ DFHACK_EXPORT bool ReadJob(const df::unit * unit, std::vector & mat) DFHACK_EXPORT bool ReadInventoryByIdx(const uint32_t index, std::vector & item); DFHACK_EXPORT bool ReadInventoryByPtr(const df::unit * unit, std::vector & item); -DFHACK_EXPORT bool ReadOwnedItemsByIdx(const uint32_t index, std::vector & item); -DFHACK_EXPORT bool ReadOwnedItemsByPtr(const df::unit * unit, std::vector & item); - DFHACK_EXPORT int32_t FindIndexById(int32_t id); /* Getters */ @@ -196,9 +193,6 @@ DFHACK_EXPORT int32_t GetDwarfCivId ( void ); DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); -DFHACK_EXPORT bool RemoveOwnedItemByIdx(const uint32_t index, int32_t id); -DFHACK_EXPORT bool RemoveOwnedItemByPtr(df::unit * unit, int32_t id); - DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 19618fc10..d884a734e 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -41,6 +41,7 @@ using namespace std; #include "modules/Units.h" #include "ModuleFactory.h" #include "Core.h" +#include "Error.h" #include "MiscUtils.h" #include "df/world.h" @@ -63,6 +64,7 @@ using namespace std; #include "df/trapcomp_flags.h" #include "df/job_item.h" #include "df/general_ref.h" +#include "df/general_ref_unit_itemownerst.h" using namespace DFHack; using namespace df::enums; @@ -415,28 +417,62 @@ bool Items::copyItem(df::item * itembase, DFHack::dfh_item &item) return true; } -int32_t Items::getItemOwnerID(const df::item * item) +df::unit *Items::getOwner(df::item * item) { + CHECK_NULL_POINTER(item); + for (size_t i = 0; i < item->itemrefs.size(); i++) { df::general_ref *ref = item->itemrefs[i]; - if (ref->getType() == general_ref_type::UNIT_ITEMOWNER) - return ref->getID(); + if (strict_virtual_cast(ref)) + return ref->getUnit(); } - return -1; + + return NULL; } -df::unit *Items::getItemOwner(const df::item * item) +bool Items::setOwner(df::item *item, df::unit *unit) { - for (size_t i = 0; i < item->itemrefs.size(); i++) + CHECK_NULL_POINTER(item); + + for (int i = item->itemrefs.size()-1; i >= 0; i--) { df::general_ref *ref = item->itemrefs[i]; - if (ref->getType() == general_ref_type::UNIT_ITEMOWNER) - return ref->getUnit(); + + if (!strict_virtual_cast(ref)) + continue; + + if (auto cur = ref->getUnit()) + { + if (cur == unit) + return true; + + erase_from_vector(cur->owned_items, item->id); + } + + delete ref; + vector_erase_at(item->itemrefs, i); } - return NULL; + + if (unit) + { + auto ref = df::allocate(); + if (!ref) + return false; + + item->flags.bits.owned = true; + ref->unit_id = unit->id; + + insert_into_vector(unit->owned_items, item->id); + item->itemrefs.push_back(ref); + } + else + item->flags.bits.owned = false; + + return true; } + int32_t Items::getItemContainerID(const df::item * item) { for (size_t i = 0; i < item->itemrefs.size(); i++) @@ -477,27 +513,3 @@ bool Items::readItemRefs(const df::item * item, df::general_ref_type type, std:: return !values.empty(); } - -bool Items::removeItemOwner(df::item * item) -{ - for (size_t i = 0; i < item->itemrefs.size(); i++) - { - df::general_ref *ref = item->itemrefs[i]; - if (ref->getType() != general_ref_type::UNIT_ITEMOWNER) - continue; - - df::unit *unit = ref->getUnit(); - - if (unit == NULL || !Units::RemoveOwnedItemByPtr(unit, item->id)) - { - cerr << "RemoveOwnedItemIdx: CREATURE " << ref->getID() << " ID " << item->id << " FAILED!" << endl; - return false; - } - delete ref; - item->itemrefs.erase(item->itemrefs.begin() + i--); - } - - item->flags.bits.owned = 0; - - return true; -} diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 47208dcbc..3e58e0d5c 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -495,37 +495,6 @@ bool Units::ReadInventoryByPtr(const df::unit * unit, std::vector & return true; } -bool Units::ReadOwnedItemsByIdx(const uint32_t index, std::vector & item) -{ - if(index >= world->units.all.size()) return false; - df::unit * temp = world->units.all[index]; - return ReadOwnedItemsByPtr(temp, item); -} - -bool Units::ReadOwnedItemsByPtr(const df::unit * unit, std::vector & items) -{ - if(!isValid()) return false; - if(!unit) return false; - items = unit->owned_items; - return true; -} - -bool Units::RemoveOwnedItemByIdx(const uint32_t index, int32_t id) -{ - if(index >= world->units.all.size()) return false; - df::unit * temp = world->units.all[index]; - return RemoveOwnedItemByPtr(temp, id); -} - -bool Units::RemoveOwnedItemByPtr(df::unit * unit, int32_t id) -{ - if(!isValid()) return false; - if(!unit) return false; - vector & vec = unit->owned_items; - vec.erase(std::remove(vec.begin(), vec.end(), id), vec.end()); - return true; -} - void Units::CopyNameTo(df::unit * creature, df::language_name * target) { Translation::copyName(&creature->name, target); diff --git a/plugins/cleanowned.cpp b/plugins/cleanowned.cpp index 280f2e1c5..c1521b8de 100644 --- a/plugins/cleanowned.cpp +++ b/plugins/cleanowned.cpp @@ -98,8 +98,7 @@ command_result df_cleanowned (color_ostream &out, vector & parameters) if (!item->flags.bits.owned) { - int32_t owner = Items::getItemOwnerID(item); - if (owner >= 0) + if (Items::getOwner(item)) { out.print("Fixing a misflagged item: \t"); confiscate = true; @@ -168,14 +167,14 @@ command_result df_cleanowned (color_ostream &out, vector & parameters) item->getWear() ); - df::unit *owner = Items::getItemOwner(item); + df::unit *owner = Items::getOwner(item); if (owner) out.print(", owner %s", Translation::TranslateName(&owner->name,false).c_str()); if (!dry_run) { - if (!Items::removeItemOwner(item)) + if (!Items::setOwner(item,NULL)) out.print("(unsuccessfully) "); if (dump) item->flags.bits.dump = 1; From 2cf078419b22be998c1d06ae0bca94774c04c527 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Wed, 11 Apr 2012 18:22:39 +0200 Subject: [PATCH 054/108] cprobe: list items worn by unit and checks if they are owned or not --- plugins/probe.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/probe.cpp b/plugins/probe.cpp index c2a82840f..d5f03b81f 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -15,6 +15,7 @@ using namespace std; #include "Export.h" #include "PluginManager.h" #include "modules/Units.h" +#include "df/unit_inventory_item.h" #include "modules/Maps.h" #include "modules/Gui.h" #include "modules/Materials.h" @@ -75,7 +76,26 @@ command_result df_cprobe (color_ostream &out, vector & parameters) if(unit->pos.x == cursorX && unit->pos.y == cursorY && unit->pos.z == cursorZ) { out.print("Creature %d, race %d (%x), civ %d (%x)\n", unit->id, unit->race, unit->race, unit->civ_id, unit->civ_id); - break; + + for(size_t j=0; jinventory.size(); j++) + { + df::unit_inventory_item* inv_item = unit->inventory[j]; + df::item* item = inv_item->item; + if(inv_item->mode == df::unit_inventory_item::T_mode::Worn) + { + out << " wears item: #" << item->id; + if(item->flags.bits.owned) + out << " (owned)"; + else + out << " (not owned)"; + if(item->getEffectiveArmorLevel() != 0) + out << ", armor"; + out << endl; + } + } + + // don't leave loop, there may be more than 1 creature at the cursor position + //break; } } } From 42e4fa79c7be8c5c77d3da30201d1add415a0c9b Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Wed, 11 Apr 2012 21:07:54 +0200 Subject: [PATCH 055/108] tweak clear-merchant, clear-resident: mark the clothes the migrants wear as "owned" (they still drop them, though. needs more fixes) --- plugins/tweak.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index edfa7081a..3ac9b625b 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -7,6 +7,8 @@ #include "PluginManager.h" #include "modules/Gui.h" +#include "modules/Units.h" +#include "modules/Items.h" #include "DataDefs.h" #include "df/ui.h" @@ -21,6 +23,7 @@ #include "df/language_name.h" #include "df/death_info.h" #include "df/criminal_case.h" +#include "df/unit_inventory_item.h" #include @@ -69,6 +72,55 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) static command_result lair(color_ostream &out, std::vector & params); + +// to be called by tweak-merchant and tweak-resident +// units forced into the fort by removing the flags do not own their clothes +// which has the result that they drop all their clothes and become unhappy because they are naked +command_result fix_clothing_ownership(color_ostream &out, df::unit* unit) +{ + // first, find one owned item to initialize the vtable + bool vt_initialized = false; + size_t numItems = world->items.all.size(); + for(size_t i=0; i< numItems; i++) + { + df::item * item = world->items.all[i]; + if(Items::getOwner(item)) + { + vt_initialized = true; + break; + } + } + if(!vt_initialized) + { + out << "fix_clothing_ownership: could not initialize vtable!" << endl; + return CR_FAILURE; + } + + int fixcount = 0; + for(size_t j=0; jinventory.size(); j++) + { + df::unit_inventory_item* inv_item = unit->inventory[j]; + df::item* item = inv_item->item; + if(inv_item->mode == df::unit_inventory_item::T_mode::Worn) + { + // ignore armor? + // it could be leather boots, for example, in which case it would not be nice to forbid ownership + //if(item->getEffectiveArmorLevel() != 0) + // continue; + + if(!Items::getOwner(item)) + { + if(Items::setOwner(item, unit)) + fixcount++; + else + out << "could not change ownership for item!" << endl; + } + } + } + out << "ownership for " << fixcount << " clothes fixed" << endl; + return CR_OK; +} + static command_result tweak(color_ostream &out, vector ¶meters) { CoreSuspender suspend; @@ -127,6 +179,7 @@ static command_result tweak(color_ostream &out, vector ¶meters) { // remove resident flag unit->flags2.bits.resident = 0; + return fix_clothing_ownership(out, unit); } else { @@ -147,6 +200,7 @@ static command_result tweak(color_ostream &out, vector ¶meters) { // remove merchant flag unit->flags1.bits.merchant = 0; + return fix_clothing_ownership(out, unit); } else { From d874c3b5386b5efad30a4a23afbb6fff03aba25f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Wed, 11 Apr 2012 21:33:45 +0200 Subject: [PATCH 056/108] Track stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index d1c87ec27..c50af342c 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit d1c87ec273ef5c928989437379943a9a71a1d4e0 +Subproject commit c50af342c27542c3b2c51ddd9099798339b7aa62 From 2caf3fb0643a30decd8d13c981e8c378ccfd1aa1 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Wed, 11 Apr 2012 22:29:56 +0200 Subject: [PATCH 057/108] tweak: removed stuff, added new command 'fixmigrants' which will deal with merchants (traders) and other types of bugged migrants. having more than one command for a bug which is basically the same makes no sense. --- plugins/tweak.cpp | 67 +++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 3ac9b625b..5f65fe27d 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -53,14 +53,12 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector ¶meters) return CR_FAILURE; } } - else if (cmd == "clear-resident") + else if (cmd == "fixmigrant") { df::unit *unit = getSelectedUnit(out); - if (!unit) - return CR_FAILURE; - // must be own race and civ and a merchant - if ( unit->flags2.bits.resident - && unit->race == df::global::ui->race_id - && unit->civ_id == df::global::ui->civ_id) + if (!unit) { - // remove resident flag - unit->flags2.bits.resident = 0; - return fix_clothing_ownership(out, unit); + out << "No unit selected!" << endl; + return CR_FAILURE; } - else + + if(unit->race != df::global::ui->race_id) { - out.print("That's not a resident dwarf of your civilization!\n"); + out << "Selected unit does not belong to your race!" << endl; return CR_FAILURE; } - } - else if (cmd == "clear-merchant") - { - df::unit *unit = getSelectedUnit(out); - if (!unit) - return CR_FAILURE; - // must be own race and civ and a merchant - if ( unit->flags1.bits.merchant - && unit->race == df::global::ui->race_id - && unit->civ_id == df::global::ui->civ_id) - { - // remove merchant flag + if (unit->flags2.bits.resident) + unit->flags2.bits.resident = 0; + + if(unit->flags1.bits.merchant) unit->flags1.bits.merchant = 0; - return fix_clothing_ownership(out, unit); - } - else - { - out.print("That's not a dwarf merchant of your civilization!\n"); - return CR_FAILURE; - } + + // this one is a cheat, but bugged migrants usually have the same civ_id + // but if it happens that the player has 'foreign' units of the same race + // (dwarves not from moutainhome in vanilla df) on his map, just grab them + if(unit->civ_id != df::global::ui->civ_id) + unit->civ_id = df::global::ui->civ_id; + + return fix_clothing_ownership(out, unit); } - else return CR_WRONG_USAGE; + else + return CR_WRONG_USAGE; return CR_OK; } From 583ccdcc0c33c396849fae93ecf7fbf69e0ff7bf Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 12 Apr 2012 10:54:53 +0400 Subject: [PATCH 058/108] Support pre-initializing vtable pointers from symbols.xml --- library/DataDefs.cpp | 29 ++++++++++++----------------- library/VersionInfoFactory.cpp | 9 +++++++-- library/include/VersionInfo.h | 26 +++++++++++++++++++------- library/modules/Items.cpp | 4 ++-- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index cf8a32fcc..76c872445 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -128,17 +128,6 @@ void compound_identity::Init(Core *core) // 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, @@ -223,17 +212,23 @@ virtual_identity::virtual_identity(size_t size, TAllocateFn alloc, { } +/* Vtable name to identity lookup. */ static std::map name_lookup; +/* Vtable pointer to identity lookup. */ +std::map virtual_identity::known; + void virtual_identity::doInit(Core *core) { struct_identity::doInit(core); - name_lookup[getOriginalName()] = this; -} + auto vtname = getOriginalName(); + name_lookup[vtname] = this; -/* Vtable to identity lookup. */ -std::map virtual_identity::known; + vtable_ptr = core->vinfo->getVTable(vtname); + if (vtable_ptr) + known[vtable_ptr] = this; +} virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) { @@ -265,8 +260,8 @@ virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) << ", previous 0x" << unsigned(p->vtable_ptr) << std::dec << std::endl; abort(); } else if (!p->vtable_ptr) { - std::cerr << "class '" << p->getName() << "': vtable = 0x" - << std::hex << unsigned(vtable) << std::dec << std::endl; + std::cerr << "" << std::endl; } known[vtable] = p; diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index cf63e3b1a..36dbd9aae 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -118,7 +118,8 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) string type, name, value; const char *cstr_type = pMemEntry->Value(); type = cstr_type; - if(type == "global-address") + bool is_vtable = (type == "vtable-address"); + if(is_vtable || type == "global-address") { const char *cstr_key = pMemEntry->Attribute("name"); if(!cstr_key) @@ -129,7 +130,11 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) cerr << "Dummy symbol table entry: " << cstr_key << endl; continue; } - mem->setAddress(cstr_key, strtol(cstr_value, 0, 0)); + uint32_t addr = strtol(cstr_value, 0, 0); + if (is_vtable) + mem->setVTable(cstr_key, addr); + else + mem->setAddress(cstr_key, addr); } else if (type == "md5-hash") { diff --git a/library/include/VersionInfo.h b/library/include/VersionInfo.h index d996722c0..7f82163ef 100644 --- a/library/include/VersionInfo.h +++ b/library/include/VersionInfo.h @@ -51,6 +51,7 @@ namespace DFHack std::vector md5_list; std::vector PE_list; std::map Addresses; + std::map VTables; uint32_t base; std::string version; OSType OS; @@ -66,6 +67,7 @@ namespace DFHack md5_list = rhs.md5_list; PE_list = rhs.PE_list; Addresses = rhs.Addresses; + VTables = rhs.VTables; base = rhs.base; version = rhs.version; OS = rhs.OS; @@ -79,13 +81,10 @@ namespace DFHack int64_t newx = new_base; int64_t rebase = newx - old; base = new_base; - auto iter = Addresses.begin(); - while (iter != Addresses.end()) - { - uint32_t & ref = (*iter).second; - ref += rebase; - iter ++; - } + for (auto iter = Addresses.begin(); iter != Addresses.end(); ++iter) + iter->second += rebase; + for (auto iter = VTables.begin(); iter != VTables.end(); ++iter) + iter->second += rebase; }; void addMD5 (const std::string & _md5) @@ -125,6 +124,7 @@ namespace DFHack value = (T) (*i).second; return true; }; + uint32_t getAddress (const std::string& key) const { auto i = Addresses.find(key); @@ -133,6 +133,18 @@ namespace DFHack return (*i).second; } + void setVTable (const std::string& key, const uint32_t value) + { + VTables[key] = value; + }; + void *getVTable (const std::string& key) const + { + auto i = VTables.find(key); + if(i == VTables.end()) + return 0; + return (void*)i->second; + } + void setOS(const OSType os) { OS = os; diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index d884a734e..20b95bc23 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -454,6 +454,8 @@ bool Items::setOwner(df::item *item, df::unit *unit) vector_erase_at(item->itemrefs, i); } + item->flags.bits.owned = false; + if (unit) { auto ref = df::allocate(); @@ -466,8 +468,6 @@ bool Items::setOwner(df::item *item, df::unit *unit) insert_into_vector(unit->owned_items, item->id); item->itemrefs.push_back(ref); } - else - item->flags.bits.owned = false; return true; } From 38a8c43a25221fdd0dfb21da297cb4c87621d21c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 12 Apr 2012 11:21:25 +0400 Subject: [PATCH 059/108] Reverse-rebase the vtable pointers printed to stderr.log. --- library/DataDefs.cpp | 4 +++- library/include/VersionInfo.h | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 76c872445..05988e419 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -260,8 +260,10 @@ virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) << ", previous 0x" << unsigned(p->vtable_ptr) << std::dec << std::endl; abort(); } else if (!p->vtable_ptr) { + uint32_t pv = unsigned(vtable); + pv -= Core::getInstance().vinfo->getRebaseDelta(); std::cerr << "" << std::endl; + << std::hex << pv << std::dec << "'/>" << std::endl; } known[vtable] = p; diff --git a/library/include/VersionInfo.h b/library/include/VersionInfo.h index 7f82163ef..5ff20516e 100644 --- a/library/include/VersionInfo.h +++ b/library/include/VersionInfo.h @@ -53,12 +53,13 @@ namespace DFHack std::map Addresses; std::map VTables; uint32_t base; + int rebase_delta; std::string version; OSType OS; public: VersionInfo() { - base = 0; + base = 0; rebase_delta = 0; version = "invalid"; OS = OS_BAD; }; @@ -69,11 +70,13 @@ namespace DFHack Addresses = rhs.Addresses; VTables = rhs.VTables; base = rhs.base; + rebase_delta = rhs.rebase_delta; version = rhs.version; OS = rhs.OS; }; uint32_t getBase () const { return base; }; + int getRebaseDelta() const { return rebase_delta; } void setBase (const uint32_t _base) { base = _base; }; void rebaseTo(const uint32_t new_base) { @@ -81,6 +84,7 @@ namespace DFHack int64_t newx = new_base; int64_t rebase = newx - old; base = new_base; + rebase_delta += rebase; for (auto iter = Addresses.begin(); iter != Addresses.end(); ++iter) iter->second += rebase; for (auto iter = VTables.begin(); iter != VTables.end(); ++iter) From 7331485b46636b4c2916d72290812bad97761ae9 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Thu, 12 Apr 2012 14:40:39 +0200 Subject: [PATCH 060/108] removed tweak-merchant and tweak-resident, combined both commands into tweak-migrant. added worn items to their uniform so they don't drop their clothes instantly --- plugins/tweak.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 5f65fe27d..a1197a342 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -10,6 +10,8 @@ #include "modules/Units.h" #include "modules/Items.h" +#include "MiscUtils.h" + #include "DataDefs.h" #include "df/ui.h" #include "df/world.h" @@ -71,9 +73,10 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) static command_result lair(color_ostream &out, std::vector & params); -// to be called by tweak-merchant and tweak-resident +// to be called by tweak-fixmigrant // units forced into the fort by removing the flags do not own their clothes // which has the result that they drop all their clothes and become unhappy because they are naked +// so we need to make them own their clothes and add them to their uniform command_result fix_clothing_ownership(color_ostream &out, df::unit* unit) { // first, find one owned item to initialize the vtable @@ -109,12 +112,18 @@ command_result fix_clothing_ownership(color_ostream &out, df::unit* unit) if(!Items::getOwner(item)) { if(Items::setOwner(item, unit)) + { + // add to uniform, so they know they should wear their clothes + insert_into_vector(unit->military.uniforms[0], item->id); fixcount++; + } else out << "could not change ownership for item!" << endl; } } } + // clear uniform_drop (without this they would drop their clothes and pick them up some time later) + unit->military.uniform_drop.clear(); out << "ownership for " << fixcount << " clothes fixed" << endl; return CR_OK; } @@ -180,15 +189,20 @@ static command_result tweak(color_ostream &out, vector ¶meters) return CR_FAILURE; } + // case #1: migrants who have the resident flag set + // see http://dffd.wimbli.com/file.php?id=6139 for a save if (unit->flags2.bits.resident) unit->flags2.bits.resident = 0; + // case #2: migrants who have the merchant flag + // happens on almost all maps after a few migrant waves if(unit->flags1.bits.merchant) unit->flags1.bits.merchant = 0; - // this one is a cheat, but bugged migrants usually have the same civ_id - // but if it happens that the player has 'foreign' units of the same race - // (dwarves not from moutainhome in vanilla df) on his map, just grab them + // this one is a cheat, but bugged migrants usually have the same civ_id + // so it should not be triggered in most cases + // if it happens that the player has 'foreign' units of the same race + // (vanilla df: dwarves not from mountainhome) on his map, just grab them if(unit->civ_id != df::global::ui->civ_id) unit->civ_id = df::global::ui->civ_id; From 2d8611a4803edc6bd37030ac303230fee208f718 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 12 Apr 2012 18:37:27 +0400 Subject: [PATCH 061/108] Add core api for moving items between ground and containers. --- LUA_API.rst | 20 ++++ Lua API.html | 15 +++ library/LuaApi.cpp | 73 +++++++++++-- library/include/modules/Items.h | 18 ++-- library/lua/dfhack.lua | 15 +++ library/modules/Items.cpp | 177 +++++++++++++++++++++++++++++--- library/modules/Maps.cpp | 2 +- plugins/autodump.cpp | 10 +- 8 files changed, 289 insertions(+), 41 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index fc86070c3..df997008c 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -699,6 +699,10 @@ Units module Items module ------------ +* ``dfhack.items.getPosition(item)`` + + Returns true *x,y,z* of the item. + * ``dfhack.items.getOwner(item)`` Returns the owner unit or *nil*. @@ -708,6 +712,22 @@ Items module Replaces the owner of the item. If unit is *nil*, removes ownership. Returns *false* in case of error. +* ``dfhack.items.getContainer(item)`` + + Returns the container item or *nil*. + +* ``dfhack.items.getContainedItems(item)`` + + Returns a list of items contained in this one. + +* ``dfhack.items.moveToGround(item,pos)`` + + Move the item to the ground at position. Returns *false* if impossible. + +* ``dfhack.items.moveToContainer(item,container)`` + + Move the item to the container. Returns *false* if impossible. + Maps module ----------- diff --git a/Lua API.html b/Lua API.html index 7c3eeddb9..4be1050fb 100644 --- a/Lua API.html +++ b/Lua API.html @@ -929,6 +929,9 @@ a lua list containing them.

      Items module

        +
      • dfhack.items.getPosition(item)

        +

        Returns true x,y,z of the item.

        +
      • dfhack.items.getOwner(item)

        Returns the owner unit or nil.

      • @@ -936,6 +939,18 @@ a lua list containing them.

        Replaces the owner of the item. If unit is nil, removes ownership. Returns false in case of error.

        +
      • dfhack.items.getContainer(item)

        +

        Returns the container item or nil.

        +
      • +
      • dfhack.items.getContainedItems(item)

        +

        Returns a list of items contained in this one.

        +
      • +
      • dfhack.items.moveToGround(item,pos)

        +

        Move the item to the ground at position. Returns false if impossible.

        +
      • +
      • dfhack.items.moveToContainer(item,container)

        +

        Move the item to the container. Returns false if impossible.

        +
      diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index febc59026..25f654b8c 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -45,6 +45,7 @@ distribution. #include "modules/Items.h" #include "modules/Materials.h" #include "modules/Maps.h" +#include "modules/MapCache.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -85,6 +86,33 @@ void push_pointer_vector(lua_State *state, const std::vector &pvec) } } +template +T *get_checked_arg(lua_State *state, int arg) +{ + luaL_checkany(state, arg); + + auto ptr = Lua::GetDFObject(state, arg); + if (!ptr) + luaL_argerror(state, arg, "invalid type"); + return ptr; +} + +int push_pos(lua_State *state, df::coord pos) +{ + if (!pos.isValid()) + { + lua_pushnil(state); + return 1; + } + else + { + lua_pushinteger(state, pos.x); + lua_pushinteger(state, pos.y); + lua_pushinteger(state, pos.z); + return 3; + } +} + /************************************************** * Per-world persistent configuration storage API * **************************************************/ @@ -610,12 +638,47 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { { NULL, NULL } }; +static bool items_moveToGround(df::item *item, df::coord pos) +{ + MapExtras::MapCache mc; + return Items::moveToGround(mc, item, pos); +} + +static bool items_moveToContainer(df::item *item, df::item *container) +{ + MapExtras::MapCache mc; + return Items::moveToContainer(mc, item, container); +} + static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, getOwner), WRAPM(Items, setOwner), + WRAPM(Items, getContainer), + WRAPN(moveToGround, items_moveToGround), + WRAPN(moveToContainer, items_moveToContainer), { NULL, NULL } }; +static int items_getPosition(lua_State *state) +{ + return push_pos(state, Items::getPosition(get_checked_arg(state,1))); +} + +static int items_getContainedItems(lua_State *state) +{ + std::vector pvec; + Items::getContainedItems(get_checked_arg(state,1),&pvec); + push_pointer_vector(state, pvec); + return 1; +} + +static const luaL_Reg dfhack_items_funcs[] = { + { "getPosition", items_getPosition }, + { "getContainedItems", items_getContainedItems }, + { NULL, NULL } +}; + + static bool maps_isBlockBurrowTile(df::burrow *burrow, df::map_block *block, int x, int y) { return Maps::isBlockBurrowTile(burrow, block, df::coord2d(x,y)); @@ -642,14 +705,8 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { static int maps_listBurrowBlocks(lua_State *state) { - luaL_checkany(state, 1); - - auto ptr = Lua::GetDFObject(state, 1); - if (!ptr) - luaL_argerror(state, 1, "invalid burrow type"); - std::vector pvec; - Maps::listBurrowBlocks(&pvec, ptr); + Maps::listBurrowBlocks(&pvec, get_checked_arg(state,1)); push_pointer_vector(state, pvec); return 1; } @@ -673,6 +730,6 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "units", dfhack_units_module); - OpenModule(state, "items", dfhack_items_module); + OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs); OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs); } diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 9bab02cc8..e7720fa91 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -42,6 +42,10 @@ namespace df struct itemdef; } +namespace MapExtras { + class MapCache; +} + /** * \defgroup grp_items Items module and its types * @ingroup grp_modules @@ -128,13 +132,15 @@ DFHACK_EXPORT df::unit *getOwner(df::item *item); DFHACK_EXPORT bool setOwner(df::item *item, df::unit *unit); /// which item is it contained in? -DFHACK_EXPORT int32_t getItemContainerID(const df::item * item); -DFHACK_EXPORT df::item *getItemContainer(const df::item * item); +DFHACK_EXPORT df::item *getContainer(df::item *item); /// which items does it contain? -DFHACK_EXPORT bool getContainedItems(const df::item * item, /*output*/ std::vector &items); +DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector *items); + +/// Returns the true position of the item. +DFHACK_EXPORT df::coord getPosition(df::item *item); + +DFHACK_EXPORT bool moveToGround(MapExtras::MapCache &mc, df::item *item, df::coord pos); +DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container); -/// read item references, filtered by class -DFHACK_EXPORT bool readItemRefs(const df::item * item, const df::general_ref_type type, - /*output*/ std::vector &values); } } \ No newline at end of file diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index a6606d152..626f60bec 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -86,6 +86,21 @@ function copyall(table) return rv end +function pos2xyz(pos) + local x = pos.x + if x and x ~= -30000 then + return x, pos.y, pos.z + end +end + +function xyz2pos(x,y,z) + if x then + return {x=x,y=y,z=z} + else + return {x=-30000,y=-30000,z=-30000} + end +end + function dfhack.persistent:__tostring() return "" diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 20b95bc23..ca15ed278 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -39,6 +39,7 @@ using namespace std; #include "modules/Materials.h" #include "modules/Items.h" #include "modules/Units.h" +#include "modules/MapCache.h" #include "ModuleFactory.h" #include "Core.h" #include "Error.h" @@ -46,6 +47,7 @@ using namespace std; #include "df/world.h" #include "df/item.h" +#include "df/building.h" #include "df/tool_uses.h" #include "df/itemdef_weaponst.h" #include "df/itemdef_trapcompst.h" @@ -65,6 +67,8 @@ using namespace std; #include "df/job_item.h" #include "df/general_ref.h" #include "df/general_ref_unit_itemownerst.h" +#include "df/general_ref_contains_itemst.h" +#include "df/general_ref_contained_in_itemst.h" using namespace DFHack; using namespace df::enums; @@ -472,44 +476,183 @@ bool Items::setOwner(df::item *item, df::unit *unit) return true; } - -int32_t Items::getItemContainerID(const df::item * item) +df::item *Items::getContainer(df::item * item) { + CHECK_NULL_POINTER(item); + for (size_t i = 0; i < item->itemrefs.size(); i++) { df::general_ref *ref = item->itemrefs[i]; if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM) - return ref->getID(); + return ref->getItem(); } - return -1; + + return NULL; } -df::item *Items::getItemContainer(const df::item * item) +void Items::getContainedItems(df::item *item, std::vector *items) { + CHECK_NULL_POINTER(item); + + items->clear(); + for (size_t i = 0; i < item->itemrefs.size(); i++) { df::general_ref *ref = item->itemrefs[i]; - if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM) - return ref->getItem(); + if (ref->getType() != general_ref_type::CONTAINS_ITEM) + continue; + + auto child = ref->getItem(); + if (child) + items->push_back(child); } - return NULL; } -bool Items::getContainedItems(const df::item * item, std::vector &items) +df::coord Items::getPosition(df::item *item) { - return readItemRefs(item, general_ref_type::CONTAINS_ITEM, items); + CHECK_NULL_POINTER(item); + + if (item->flags.bits.in_inventory || + item->flags.bits.in_chest || + item->flags.bits.in_building) + { + for (size_t i = 0; i < item->itemrefs.size(); i++) + { + df::general_ref *ref = item->itemrefs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + if (auto item2 = ref->getItem()) + return getPosition(item2); + break; + + case general_ref_type::UNIT_HOLDER: + if (auto unit = ref->getUnit()) + return unit->pos; + break; + + case general_ref_type::BUILDING_HOLDER: + if (auto bld = ref->getBuilding()) + return df::coord(bld->centerx, bld->centery, bld->z); + break; + } + } + } + + return item->pos; } -bool Items::readItemRefs(const df::item * item, df::general_ref_type type, std::vector &values) +static void removeRef(std::vector &vec, df::general_ref_type type, int id) { - values.clear(); + for (int i = vec.size()-1; i >= 0; i--) + { + df::general_ref *ref = vec[i]; + if (ref->getType() != type || ref->getID() != id) + continue; - for (size_t i = 0; i < item->itemrefs.size(); i++) + vector_erase_at(vec, i); + delete ref; + } +} + +static bool detachItem(MapExtras::MapCache &mc, df::item *item) +{ + if (item->flags.bits.on_ground) { - df::general_ref *ref = item->itemrefs[i]; - if (ref->getType() == type) - values.push_back(ref->getID()); + if (!mc.removeItemOnGround(item)) + Core::printerr("Item was marked on_ground, but not in block: %d (%d,%d,%d)\n", + item->id, item->pos.x, item->pos.y, item->pos.z); + + item->flags.bits.on_ground = false; + return true; } + else if (item->flags.bits.in_inventory) + { + bool found = false; - return !values.empty(); + for (int i = item->itemrefs.size()-1; i >= 0; i--) + { + df::general_ref *ref = item->itemrefs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + if (auto item2 = ref->getItem()) + removeRef(item2->itemrefs, general_ref_type::CONTAINS_ITEM, item->id); + break; + + case general_ref_type::UNIT_HOLDER: + case general_ref_type::BUILDING_HOLDER: + return false; + + default: + continue; + } + + found = true; + vector_erase_at(item->itemrefs, i); + delete ref; + } + + if (!found) + return false; + + item->flags.bits.in_inventory = false; + return true; + } + else + return false; +} + +static void putOnGround(MapExtras::MapCache &mc, df::item *item, df::coord pos) +{ + item->pos = pos; + item->flags.bits.on_ground = true; + + if (!mc.addItemOnGround(item)) + Core::printerr("Could not add item %d to ground at (%d,%d,%d)\n", + item->id, pos.x, pos.y, pos.z); +} + +bool DFHack::Items::moveToGround(MapExtras::MapCache &mc, df::item *item, df::coord pos) +{ + CHECK_NULL_POINTER(item); + + if (!detachItem(mc, item)) + return false; + + putOnGround(mc, item, pos); + return true; +} + +bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container) +{ + CHECK_NULL_POINTER(item); + CHECK_NULL_POINTER(container); + + if (!detachItem(mc, item)) + return false; + + auto ref1 = df::allocate(); + auto ref2 = df::allocate(); + + if (!ref1 || !ref2) + { + delete ref1; delete ref2; + Core::printerr("Could not allocate container refs.\n"); + putOnGround(mc, item, getPosition(container)); + return false; + } + + item->pos = container->pos; + item->flags.bits.in_inventory = true; + container->flags.bits.container = true; + + ref1->item_id = item->id; + container->itemrefs.push_back(ref1); + ref2->item_id = container->id; + item->itemrefs.push_back(ref2); + + return true; } diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index c0aa9b634..fce80d775 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -596,7 +596,7 @@ void MapExtras::Block::init_item_counts() if (item_counts) return; item_counts = new T_item_counts[16]; - memset(item_counts, 0, sizeof(T_item_counts)); + memset(item_counts, 0, sizeof(T_item_counts)*16); if (!block) return; diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index a71555b7a..2620d9dea 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -182,15 +182,7 @@ static command_result autodump_main(color_ostream &out, vector & parame // Don't move items if they're already at the cursor if (pos_cursor != pos_item) - { - if (!MC.removeItemOnGround(itm)) - out.printerr("Item %d wasn't in the source block.\n", itm->id); - - itm->pos = pos_cursor; - - if (!MC.addItemOnGround(itm)) - out.printerr("Could not add item %d to destination block.\n", itm->id); - } + Items::moveToGround(MC, itm, pos_cursor); } else // destroy { From 09608467c16d09d60faaab773941b4b7fc25968c Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Thu, 12 Apr 2012 22:44:53 +0200 Subject: [PATCH 062/108] tweak fixmigrant: updated help text --- plugins/tweak.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index a1197a342..3ac6bae85 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -60,7 +60,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector Date: Thu, 12 Apr 2012 22:48:46 +0200 Subject: [PATCH 063/108] tweak fixmigrant: updated readme.rst --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index e8e444bb7..a563f7bb8 100644 --- a/README.rst +++ b/README.rst @@ -615,8 +615,7 @@ Options ------- :tweak clear-missing: Remove the missing status from the selected unit. This allows engraving slabs for ghostly, but not yet found, creatures. :tweak clear-ghostly: Remove the ghostly status from the selected unit and mark it as dead. This allows getting rid of bugged ghosts which do not show up in the engraving slab menu at all, even after using clear-missing. It works, but is potentially very dangerous - so use with care. Probably (almost certainly) it does not have the same effects like a proper burial. You've been warned. -:tweak clear-resident: Remove the resident flag from the selected unit. Intended to fix bugged migrants who stay at the map edge and don't enter your fort. Only works for dwarves of the own civilization. -:tweak clear-merchant: Remove the merchant flag from the selected unit. Assimilates bugged merchants who don't leave the map into your fort. Only works for dwarves of the own civilization. +:tweak fixmigrant: Remove the resident/merchant flag from the selected unit. Intended to fix bugged migrants/traders who stay at the map edge and don't enter your fort. Only works for dwarves (or generally the player's race in modded games). Can be abused to grab caravan merchants, but that might result into weirdness during trading. tubefill ======== From 37cfb1fdcd01e4a7dce1a2b759cc2655bd42cf03 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 13 Apr 2012 16:10:19 +0400 Subject: [PATCH 064/108] Add unit position and container api. --- LUA_API.rst | 10 +++++++++- Lua API.html | 8 +++++++- library/LuaApi.cpp | 13 ++++++++++++- library/include/modules/Units.h | 5 +++++ library/modules/Items.cpp | 8 +++++++- library/modules/Units.cpp | 29 +++++++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 4 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index df997008c..673053189 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -663,6 +663,14 @@ Job module Units module ------------ +* ``dfhack.units.getPosition(unit)`` + + Returns true *x,y,z* of the unit; may be not equal to unit.pos if caged. + +* ``dfhack.units.getContainer(unit)`` + + Returns the container (cage) item or *nil*. + * ``dfhack.units.setNickname(unit,nick)`` Sets the unit's nickname properly. @@ -701,7 +709,7 @@ Items module * ``dfhack.items.getPosition(item)`` - Returns true *x,y,z* of the item. + Returns true *x,y,z* of the item; may be not equal to item.pos if in inventory. * ``dfhack.items.getOwner(item)`` diff --git a/Lua API.html b/Lua API.html index 4be1050fb..32a3b7f04 100644 --- a/Lua API.html +++ b/Lua API.html @@ -900,6 +900,12 @@ a lua list containing them.

      Units module

        +
      • dfhack.units.getPosition(unit)

        +

        Returns true x,y,z of the unit; may be not equal to unit.pos if caged.

        +
      • +
      • dfhack.units.getContainer(unit)

        +

        Returns the container (cage) item or nil.

        +
      • dfhack.units.setNickname(unit,nick)

        Sets the unit's nickname properly.

      • @@ -930,7 +936,7 @@ a lua list containing them.

        Items module

        • dfhack.items.getPosition(item)

          -

          Returns true x,y,z of the item.

          +

          Returns true x,y,z of the item; may be not equal to item.pos if in inventory.

        • dfhack.items.getOwner(item)

          Returns the owner unit or nil.

          diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 25f654b8c..6792c921e 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -627,6 +627,7 @@ static const luaL_Reg dfhack_job_funcs[] = { static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, getContainer), WRAPM(Units, setNickname), WRAPM(Units, getVisibleName), WRAPM(Units, getNemesis), @@ -638,6 +639,16 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { { NULL, NULL } }; +static int units_getPosition(lua_State *state) +{ + return push_pos(state, Units::getPosition(get_checked_arg(state,1))); +} + +static const luaL_Reg dfhack_units_funcs[] = { + { "getPosition", units_getPosition }, + { NULL, NULL } +}; + static bool items_moveToGround(df::item *item, df::coord pos) { MapExtras::MapCache mc; @@ -729,7 +740,7 @@ void OpenDFHackApi(lua_State *state) LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); - OpenModule(state, "units", dfhack_units_module); + OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs); OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs); } diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 12e687112..d7dadb21b 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -193,6 +193,11 @@ DFHACK_EXPORT int32_t GetDwarfCivId ( void ); DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); +/// Returns the true position of the unit (non-trivial in case of caged). +DFHACK_EXPORT df::coord getPosition(df::unit *unit); + +DFHACK_EXPORT df::item *getContainer(df::unit *unit); + DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index ca15ed278..9d0657e19 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -529,7 +529,7 @@ df::coord Items::getPosition(df::item *item) case general_ref_type::UNIT_HOLDER: if (auto unit = ref->getUnit()) - return unit->pos; + return Units::getPosition(unit); break; case general_ref_type::BUILDING_HOLDER: @@ -579,7 +579,11 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item) { case general_ref_type::CONTAINED_IN_ITEM: if (auto item2 = ref->getItem()) + { + item2->flags.bits.weight_computed = false; + removeRef(item2->itemrefs, general_ref_type::CONTAINS_ITEM, item->id); + } break; case general_ref_type::UNIT_HOLDER: @@ -649,6 +653,8 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df: item->flags.bits.in_inventory = true; container->flags.bits.container = true; + container->flags.bits.weight_computed = false; + ref1->item_id = item->id; container->itemrefs.push_back(ref1); ref2->item_id = container->id; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 3e58e0d5c..7dbb40c97 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -40,6 +40,7 @@ using namespace std; // we connect to those #include "modules/Units.h" +#include "modules/Items.h" #include "modules/Materials.h" #include "modules/Translation.h" #include "ModuleFactory.h" @@ -500,6 +501,34 @@ void Units::CopyNameTo(df::unit * creature, df::language_name * target) Translation::copyName(&creature->name, target); } +df::coord Units::getPosition(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + if (unit->flags1.bits.caged) + { + auto cage = getContainer(unit); + if (cage) + return Items::getPosition(cage); + } + + return unit->pos; +} + +df::item *Units::getContainer(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + for (size_t i = 0; i < unit->refs.size(); i++) + { + df::general_ref *ref = unit->refs[i]; + if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM) + return ref->getItem(); + } + + return NULL; +} + void Units::setNickname(df::unit *unit, std::string nick) { CHECK_NULL_POINTER(unit); From 2f54a48e6391720c62a62cad9839f3d36a6e7385 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 13 Apr 2012 21:41:42 +0400 Subject: [PATCH 065/108] Add a plugin that makes selected burrows auto-grow on digging. --- library/include/TileTypes.h | 12 + library/include/modules/Maps.h | 8 +- plugins/CMakeLists.txt | 1 + plugins/burrows.cpp | 413 +++++++++++++++++++++++++++++++++ 4 files changed, 430 insertions(+), 4 deletions(-) create mode 100644 plugins/burrows.cpp diff --git a/library/include/TileTypes.h b/library/include/TileTypes.h index 26df7c3cd..55628b3df 100644 --- a/library/include/TileTypes.h +++ b/library/include/TileTypes.h @@ -203,6 +203,18 @@ namespace DFHack return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype)); } + inline + bool isWalkable(df::tiletype tiletype) + { + return ENUM_ATTR(tiletype_shape, walkable, tileShape(tiletype)); + } + + inline + bool isWalkableUp(df::tiletype tiletype) + { + return ENUM_ATTR(tiletype_shape, walkable_up, tileShape(tiletype)); + } + inline bool isWallTerrain(df::tiletype tiletype) { diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 0de262264..3cd1187a5 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -262,12 +262,12 @@ extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, d DFHACK_EXPORT df::burrow *findBurrowByName(std::string name); -void listBurrowBlocks(std::vector *pvec, df::burrow *burrow); +DFHACK_EXPORT void listBurrowBlocks(std::vector *pvec, df::burrow *burrow); -df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false); +DFHACK_EXPORT df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false); -bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile); -bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable); +DFHACK_EXPORT bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile); +DFHACK_EXPORT bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable); inline bool isBurrowTile(df::burrow *burrow, df::coord tile) { return isBlockBurrowTile(burrow, getTileBlock(tile), tile); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6c2a537d1..d30d25697 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -97,6 +97,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(feature feature.cpp) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(zone zone.cpp) + DFHACK_PLUGIN(burrows burrows.cpp) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp new file mode 100644 index 000000000..a559573ac --- /dev/null +++ b/plugins/burrows.cpp @@ -0,0 +1,413 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/Maps.h" +#include "modules/MapCache.h" +#include "modules/World.h" +#include "TileTypes.h" + +#include "DataDefs.h" +#include "df/ui.h" +#include "df/world.h" +#include "df/unit.h" +#include "df/burrow.h" +#include "df/map_block.h" +#include "df/job.h" +#include "df/job_list_link.h" + +#include "MiscUtils.h" + +#include + +using std::vector; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; +using namespace dfproto; + +using df::global::ui; +using df::global::world; + +/* + * Initialization. + */ + +static command_result burrow(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("burrows"); + +static void init_map(color_ostream &out); +static void deinit_map(color_ostream &out); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "burrow", "Miscellaneous burrow control.", burrow, false, + " burrow enable options...\n" + " burrow disable options...\n" + " Enable or disable features of the plugin.\n" + " See below for a list and explanation.\n" + "Implemented features:\n" + " auto-grow\n" + " When a wall inside a burrow with a name ending in '+' is dug\n" + " out, the burrow is extended to newly-revealed adjacent walls.\n" + " Digging 1-wide corridors with the miner inside the burrow is SLOW.\n" + )); + + if (Core::getInstance().isMapLoaded()) + init_map(out); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + deinit_map(out); + + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + deinit_map(out); + if (df::global::game_mode && + *df::global::game_mode == GAMEMODE_DWARF) + init_map(out); + break; + case SC_MAP_UNLOADED: + deinit_map(out); + break; + default: + break; + } + + return CR_OK; +} + +/* + * State change tracking. + */ + +static int name_burrow_id = -1; + +static void handle_burrow_rename(color_ostream &out, df::burrow *burrow); + +static void detect_burrow_renames(color_ostream &out) +{ + if (ui->main.mode == ui_sidebar_mode::Burrows && + ui->burrows.in_edit_name_mode && + ui->burrows.sel_id >= 0) + { + name_burrow_id = ui->burrows.sel_id; + } + else if (name_burrow_id >= 0) + { + auto burrow = df::burrow::find(name_burrow_id); + name_burrow_id = -1; + if (burrow) + handle_burrow_rename(out, burrow); + } +} + +struct DigJob { + int id; + df::job_type job; + df::coord pos; + df::tiletype old_tile; +}; + +static int next_job_id_save = 0; +static std::map diggers; + +static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos, + df::tiletype old_tile, df::tiletype new_tile); + +static void detect_digging(color_ostream &out) +{ + for (auto it = diggers.begin(); it != diggers.end();) + { + auto worker = df::unit::find(it->first); + + if (!worker || !worker->job.current_job || + worker->job.current_job->id != it->second.id) + { + //out.print("Dig job %d expired.\n", it->second.id); + + df::coord pos = it->second.pos; + + if (auto block = Maps::getTileBlock(pos)) + { + df::tiletype new_tile = block->tiletype[pos.x&15][pos.y&15]; + + //out.print("Tile %d -> %d\n", it->second.old_tile, new_tile); + + if (new_tile != it->second.old_tile) + { + handle_dig_complete(out, it->second.job, pos, it->second.old_tile, new_tile); + + //if (worker && !worker->job.current_job) + // worker->counters.think_counter = worker->counters.job_counter = 0; + } + } + + auto cur = it; ++it; diggers.erase(cur); + } + else + ++it; + } + + std::vector jvec; + + if (Job::listNewlyCreated(&jvec, &next_job_id_save)) + { + for (size_t i = 0; i < jvec.size(); i++) + { + auto job = jvec[i]; + auto type = ENUM_ATTR(job_type, type, job->job_type); + if (type != job_type_class::Digging) + continue; + + auto worker = Job::getWorker(job); + if (!worker) + continue; + + df::coord pos = job->pos; + auto block = Maps::getTileBlock(pos); + if (!block) + continue; + + auto &info = diggers[worker->id]; + + //out.print("New dig job %d.\n", job->id); + + info.id = job->id; + info.job = job->job_type; + info.pos = pos; + info.old_tile = block->tiletype[pos.x&15][pos.y&15]; + } + } +} + +static bool active = false; +static bool auto_grow = false; +static std::vector grow_burrows; + +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + if (!active || !auto_grow) + return CR_OK; + + detect_burrow_renames(out); + detect_digging(out); + return CR_OK; +} + +/* + * Config and processing + */ + +static void parse_names() +{ + auto &list = ui->burrows.list; + + grow_burrows.clear(); + + for (size_t i = 0; i < list.size(); i++) + { + auto burrow = list[i]; + + if (!burrow->name.empty() && burrow->name[burrow->name.size()-1] == '+') + grow_burrows.push_back(burrow->id); + } +} + +static void reset_tracking() +{ + name_burrow_id = -1; + diggers.clear(); + next_job_id_save = 0; +} + +static void init_map(color_ostream &out) +{ + auto config = Core::getInstance().getWorld()->GetPersistentData("burrows/config"); + if (config.isValid()) + { + auto_grow = !!(config.ival(0) & 1); + } + + parse_names(); + reset_tracking(); + active = true; + + if (auto_grow && !grow_burrows.empty()) + out.print("Auto-growing %d burrows.\n", grow_burrows.size()); +} + +static void deinit_map(color_ostream &out) +{ + active = false; + auto_grow = false; + reset_tracking(); +} + +static PersistentDataItem create_config(color_ostream &out) +{ + bool created; + auto rv = Core::getInstance().getWorld()->GetPersistentData("burrows/config", &created); + if (created && rv.isValid()) + rv.ival(0) = 0; + if (!rv.isValid()) + out.printerr("Could not write configuration."); + return rv; +} + +static void enable_auto_grow(color_ostream &out, bool enable) +{ + if (enable == auto_grow) + return; + + auto config = create_config(out); + if (!config.isValid()) + return; + + if (enable) + config.ival(0) |= 1; + else + config.ival(0) &= ~1; + + auto_grow = enable; + + if (enable) + { + parse_names(); + reset_tracking(); + } +} + +static void handle_burrow_rename(color_ostream &out, df::burrow *burrow) +{ + parse_names(); +} + +static void add_to_burrows(std::vector &burrows, df::coord pos) +{ + for (size_t i = 0; i < burrows.size(); i++) + Maps::setBurrowTile(burrows[i], pos, true); +} + +static void add_walls_to_burrows(color_ostream &out, std::vector &burrows, + MapExtras::MapCache &mc, df::coord pos1, df::coord pos2) +{ + for (int x = pos1.x; x <= pos2.x; x++) + { + for (int y = pos1.y; y <= pos2.y; y++) + { + for (int z = pos1.z; z <= pos2.z; z++) + { + df::coord pos(x,y,z); + + auto tile = mc.tiletypeAt(pos); + + if (isWallTerrain(tile)) + add_to_burrows(burrows, pos); + } + } + } +} + +static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos, + df::tiletype old_tile, df::tiletype new_tile) +{ + if (!isWalkable(new_tile)) + return; + + std::vector to_grow; + + for (size_t i = 0; i < grow_burrows.size(); i++) + { + auto b = df::burrow::find(grow_burrows[i]); + if (b && Maps::isBurrowTile(b, pos)) + to_grow.push_back(b); + } + + //out.print("%d to grow.\n", to_grow.size()); + + if (to_grow.empty()) + return; + + MapExtras::MapCache mc; + + if (!isWalkable(old_tile)) + { + add_walls_to_burrows(out, to_grow, mc, pos+df::coord(-1,-1,0), pos+df::coord(1,1,0)); + + if (isWalkableUp(new_tile)) + add_to_burrows(to_grow, pos+df::coord(0,0,1)); + + if (tileShape(new_tile) == tiletype_shape::RAMP) + { + add_walls_to_burrows(out, to_grow, mc, + pos+df::coord(-1,-1,1), pos+df::coord(1,1,1)); + } + } + + if (LowPassable(new_tile) && !LowPassable(old_tile)) + { + add_to_burrows(to_grow, pos-df::coord(0,0,1)); + + if (tileShape(new_tile) == tiletype_shape::RAMP_TOP) + { + add_walls_to_burrows(out, to_grow, mc, + pos+df::coord(-1,-1,-1), pos+df::coord(1,1,-1)); + } + } +} + +static command_result burrow(color_ostream &out, vector ¶meters) +{ + CoreSuspender suspend; + + if (!active) + { + out.printerr("The plugin cannot be used without map.\n"); + return CR_FAILURE; + } + + string cmd; + if (!parameters.empty()) + cmd = parameters[0]; + + if (cmd == "enable" || cmd == "disable") + { + if (parameters.size() < 2) + return CR_WRONG_USAGE; + + bool state = (cmd == "enable"); + + for (int i = 1; i < parameters.size(); i++) + { + string &option = parameters[i]; + + if (option == "auto-grow") + enable_auto_grow(out, state); + else + return CR_WRONG_USAGE; + } + } + else + { + if (!parameters.empty() && cmd != "?") + out.printerr("Invalid command: %s\n", cmd.c_str()); + return CR_WRONG_USAGE; + } + + return CR_OK; +} From 7c5e49a071395f6235eff022b3f9558e50addce5 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 13 Apr 2012 20:45:40 -0500 Subject: [PATCH 066/108] Merged in some library additions from angavrilov necessary for the burrow plugin --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 27fec9797..73cd75788 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 27fec9797d7d1215bd3f5530b44a55a9d394fe75 +Subproject commit 73cd75788fc45b13790b4e6bb4d3f33691224d30 From 7a34a89f53071a8368ec5bd064accb10354b6d56 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 14 Apr 2012 14:12:59 +0400 Subject: [PATCH 067/108] Add burrow subcommands to modify burrow unit and tile sets. --- LUA_API.rst | 8 + Lua API.html | 6 + library/LuaApi.cpp | 2 + .../df/custom/block_burrow.methods.inc | 9 +- .../block_square_event_mineralst.methods.inc | 9 +- library/include/modules/Maps.h | 8 + library/include/modules/Units.h | 4 + library/modules/Maps.cpp | 71 +++++ library/modules/Units.cpp | 45 +++ plugins/burrows.cpp | 262 +++++++++++++++++- 10 files changed, 413 insertions(+), 11 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 673053189..d5af5e7a4 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -695,6 +695,10 @@ Units module The unit is capable of rational action, i.e. not dead, insane or zombie. +* ``dfhack.units.clearBurrowMembers(burrow)`` + + Removes all units from the burrow. + * ``dfhack.units.isInBurrow(unit,burrow)`` Checks if the unit is in the burrow. @@ -776,6 +780,10 @@ Maps module Returns a table of map block pointers. +* ``dfhack.maps.clearBurrowTiles(burrow)`` + + Removes all tiles from the burrow. + * ``dfhack.maps.isBurrowTile(burrow,tile_coord)`` Checks if the tile is in burrow. diff --git a/Lua API.html b/Lua API.html index 32a3b7f04..66385840b 100644 --- a/Lua API.html +++ b/Lua API.html @@ -924,6 +924,9 @@ a lua list containing them.

        • dfhack.units.isSane(unit)

          The unit is capable of rational action, i.e. not dead, insane or zombie.

        • +
        • dfhack.units.clearBurrowMembers(burrow)

          +

          Removes all units from the burrow.

          +
        • dfhack.units.isInBurrow(unit,burrow)

          Checks if the unit is in the burrow.

        • @@ -989,6 +992,9 @@ Returns false in case of error.

        • dfhack.maps.listBurrowBlocks(burrow)

          Returns a table of map block pointers.

        • +
        • dfhack.maps.clearBurrowTiles(burrow)

          +

          Removes all tiles from the burrow.

          +
        • dfhack.maps.isBurrowTile(burrow,tile_coord)

          Checks if the tile is in burrow.

        • diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 6792c921e..917e44d38 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -634,6 +634,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isDead), WRAPM(Units, isAlive), WRAPM(Units, isSane), + WRAPM(Units, clearBurrowMembers), WRAPM(Units, isInBurrow), WRAPM(Units, setInBurrow), { NULL, NULL } @@ -707,6 +708,7 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPM(Maps, getGlobalInitFeature), WRAPM(Maps, getLocalInitFeature), WRAPM(Maps, findBurrowByName), + WRAPM(Maps, clearBurrowTiles), WRAPN(isBlockBurrowTile, maps_isBlockBurrowTile), WRAPN(setBlockBurrowTile, maps_setBlockBurrowTile), WRAPM(Maps, isBurrowTile), diff --git a/library/include/df/custom/block_burrow.methods.inc b/library/include/df/custom/block_burrow.methods.inc index 7754ab0db..216a58662 100644 --- a/library/include/df/custom/block_burrow.methods.inc +++ b/library/include/df/custom/block_burrow.methods.inc @@ -16,4 +16,11 @@ inline void setassignment( int x, int y, bool bit ) tile_bitmask[y] |= (1 << x); else tile_bitmask[y] &= ~(1 << x); -} \ No newline at end of file +} +bool has_assignments() +{ + for (int i = 0; i < 16; i++) + if (tile_bitmask[i]) + return true; + return false; +} diff --git a/library/include/df/custom/block_square_event_mineralst.methods.inc b/library/include/df/custom/block_square_event_mineralst.methods.inc index 7754ab0db..216a58662 100644 --- a/library/include/df/custom/block_square_event_mineralst.methods.inc +++ b/library/include/df/custom/block_square_event_mineralst.methods.inc @@ -16,4 +16,11 @@ inline void setassignment( int x, int y, bool bit ) tile_bitmask[y] |= (1 << x); else tile_bitmask[y] &= ~(1 << x); -} \ No newline at end of file +} +bool has_assignments() +{ + for (int i = 0; i < 16; i++) + if (tile_bitmask[i]) + return true; + return false; +} diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 3cd1187a5..b7341f87c 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -263,8 +263,16 @@ extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, d DFHACK_EXPORT df::burrow *findBurrowByName(std::string name); DFHACK_EXPORT void listBurrowBlocks(std::vector *pvec, df::burrow *burrow); +DFHACK_EXPORT void clearBurrowTiles(df::burrow *burrow); DFHACK_EXPORT df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false); +DFHACK_EXPORT bool deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block, df::block_burrow *mask); + +inline bool deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block) +{ + return deleteBlockBurrowMask(burrow, block, getBlockBurrowMask(burrow, block)); +} + DFHACK_EXPORT bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile); DFHACK_EXPORT bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index d7dadb21b..e093ed1ef 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -206,6 +206,10 @@ DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit); +DFHACK_EXPORT bool isCitizen(df::unit *unit); +DFHACK_EXPORT bool isDwarf(df::unit *unit); + +DFHACK_EXPORT void clearBurrowMembers(df::burrow *burrow); DFHACK_EXPORT bool isInBurrow(df::unit *unit, df::burrow *burrow); DFHACK_EXPORT void setInBurrow(df::unit *unit, df::burrow *burrow, bool enable); diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index fce80d775..327e26986 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -715,6 +715,42 @@ void Maps::listBurrowBlocks(std::vector *pvec, df::burrow *burro } } +static void destroyBurrowMask(df::block_burrow *mask) +{ + if (!mask) return; + + auto link = mask->link; + + link->prev->next = link->next; + if (link->next) + link->next->prev = link->prev; + delete link; + + delete mask; +} + +void Maps::clearBurrowTiles(df::burrow *burrow) +{ + CHECK_NULL_POINTER(burrow); + + df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); + + for (size_t i = 0; i < burrow->block_x.size(); i++) + { + df::coord pos(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); + + auto block = getBlock(pos - base); + if (!block) + continue; + + destroyBurrowMask(getBlockBurrowMask(burrow, block)); + } + + burrow->block_x.clear(); + burrow->block_y.clear(); + burrow->block_z.clear(); +} + df::block_burrow *Maps::getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create) { CHECK_NULL_POINTER(burrow); @@ -754,6 +790,36 @@ df::block_burrow *Maps::getBlockBurrowMask(df::burrow *burrow, df::map_block *bl return NULL; } +bool Maps::deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block, df::block_burrow *mask) +{ + CHECK_NULL_POINTER(burrow); + CHECK_NULL_POINTER(block); + + if (!mask) + return false; + + df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); + df::coord pos = base + block->map_pos/16; + + destroyBurrowMask(mask); + + for (size_t i = 0; i < burrow->block_x.size(); i++) + { + df::coord cur(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); + + if (cur == pos) + { + vector_erase_at(burrow->block_x, i); + vector_erase_at(burrow->block_y, i); + vector_erase_at(burrow->block_z, i); + + break; + } + } + + return true; +} + bool Maps::isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile) { CHECK_NULL_POINTER(burrow); @@ -774,8 +840,13 @@ bool Maps::setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coor auto mask = getBlockBurrowMask(burrow, block, enable); if (mask) + { mask->setassignment(tile & 15, enable); + if (!enable && !mask->has_assignments()) + deleteBlockBurrowMask(burrow, block, mask); + } + return true; } diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 7dbb40c97..8b44d4cf9 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -653,6 +653,51 @@ bool DFHack::Units::isSane(df::unit *unit) return true; } +bool DFHack::Units::isCitizen(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + return unit->civ_id == ui->civ_id && + !unit->flags1.bits.merchant && + !unit->flags1.bits.diplomat && + !unit->flags2.bits.resident && + !unit->flags1.bits.dead && + !unit->flags3.bits.ghostly; +} + +bool DFHack::Units::isDwarf(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + return unit->race == ui->race_id; +} + +void DFHack::Units::clearBurrowMembers(df::burrow *burrow) +{ + CHECK_NULL_POINTER(burrow); + + for (size_t i = 0; i < burrow->units.size(); i++) + { + auto unit = df::unit::find(burrow->units[i]); + + if (unit) + erase_from_vector(unit->burrows, burrow->id); + } + + burrow->units.clear(); + + // Sync ui if active + if (ui && ui->main.mode == ui_sidebar_mode::Burrows && + ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id) + { + auto &sel = ui->burrows.sel_units; + + for (size_t i = 0; i < sel.size(); i++) + sel[i] = false; + } +} + + bool DFHack::Units::isInBurrow(df::unit *unit, df::burrow *burrow) { CHECK_NULL_POINTER(unit); diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index a559573ac..c973405ee 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -8,6 +8,7 @@ #include "modules/Maps.h" #include "modules/MapCache.h" #include "modules/World.h" +#include "modules/Units.h" #include "TileTypes.h" #include "DataDefs.h" @@ -16,6 +17,7 @@ #include "df/unit.h" #include "df/burrow.h" #include "df/map_block.h" +#include "df/block_burrow.h" #include "df/job.h" #include "df/job_list_link.h" @@ -52,11 +54,28 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector grow_burrows; DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (!active || !auto_grow) + if (!active) return CR_OK; detect_burrow_renames(out); - detect_digging(out); + + if (auto_grow) + detect_digging(out); + return CR_OK; } @@ -213,24 +235,39 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) * Config and processing */ +static std::map name_lookup; + static void parse_names() { auto &list = ui->burrows.list; grow_burrows.clear(); + name_lookup.clear(); for (size_t i = 0; i < list.size(); i++) { auto burrow = list[i]; - if (!burrow->name.empty() && burrow->name[burrow->name.size()-1] == '+') - grow_burrows.push_back(burrow->id); + std::string name = burrow->name; + + if (!name.empty()) + { + name_lookup[name] = burrow->id; + + if (name[name.size()-1] == '+') + { + grow_burrows.push_back(burrow->id); + name.resize(name.size()-1); + } + + if (!name.empty()) + name_lookup[name] = burrow->id; + } } } static void reset_tracking() { - name_burrow_id = -1; diggers.clear(); next_job_id_save = 0; } @@ -244,6 +281,8 @@ static void init_map(color_ostream &out) } parse_names(); + name_burrow_id = -1; + reset_tracking(); active = true; @@ -286,10 +325,7 @@ static void enable_auto_grow(color_ostream &out, bool enable) auto_grow = enable; if (enable) - { - parse_names(); reset_tracking(); - } } static void handle_burrow_rename(color_ostream &out, df::burrow *burrow) @@ -371,6 +407,137 @@ static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord } } +static df::burrow *findByName(color_ostream &out, std::string name, bool silent = false) +{ + int id = -1; + if (name_lookup.count(name)) + id = name_lookup[name]; + auto rv = df::burrow::find(id); + if (!rv && !silent) + out.printerr("Burrow not found: '%s'\n", name.c_str()); + return rv; +} + +static void copyUnits(df::burrow *target, df::burrow *source, bool enable) +{ + if (source == target) + { + if (!enable) + Units::clearBurrowMembers(target); + + return; + } + + for (size_t i = 0; i < source->units.size(); i++) + { + auto unit = df::unit::find(source->units[i]); + + if (unit) + Units::setInBurrow(unit, target, enable); + } +} + +static void copyTiles(df::burrow *target, df::burrow *source, bool enable) +{ + if (source == target) + { + if (!enable) + Maps::clearBurrowTiles(target); + + return; + } + + std::vector pvec; + Maps::listBurrowBlocks(&pvec, source); + + for (size_t i = 0; i < pvec.size(); i++) + { + auto block = pvec[i]; + auto smask = Maps::getBlockBurrowMask(source, block); + if (!smask) + continue; + + auto tmask = Maps::getBlockBurrowMask(target, block, enable); + if (!tmask) + continue; + + if (enable) + { + for (int j = 0; j < 16; j++) + tmask->tile_bitmask[j] |= smask->tile_bitmask[j]; + } + else + { + for (int j = 0; j < 16; j++) + tmask->tile_bitmask[j] &= ~smask->tile_bitmask[j]; + + if (!tmask->has_assignments()) + Maps::deleteBlockBurrowMask(target, block, tmask); + } + } +} + +static void setTilesByDesignation(df::burrow *target, df::tile_designation d_mask, + df::tile_designation d_value, bool enable) +{ + auto &blocks = world->map.map_blocks; + + for (size_t i = 0; i < blocks.size(); i++) + { + auto block = blocks[i]; + df::block_burrow *mask = NULL; + + for (int x = 0; x < 16; x++) + { + for (int y = 0; y < 16; y++) + { + if ((block->designation[x][y].whole & d_mask.whole) != d_value.whole) + continue; + + if (!mask) + mask = Maps::getBlockBurrowMask(target, block, enable); + if (!mask) + goto next_block; + + mask->setassignment(x, y, enable); + } + } + + if (mask && !enable && !mask->has_assignments()) + Maps::deleteBlockBurrowMask(target, block, mask); + + next_block:; + } +} + +static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) +{ + df::tile_designation mask(0); + df::tile_designation value(0); + + if (name == "ABOVE_GROUND") + mask.bits.subterranean = true; + else if (name == "SUBTERRANEAN") + mask.bits.subterranean = value.bits.subterranean = true; + else if (name == "LIGHT") + mask.bits.light = value.bits.light = true; + else if (name == "DARK") + mask.bits.light = true; + else if (name == "OUTSIDE") + mask.bits.outside = value.bits.outside = true; + else if (name == "INSIDE") + mask.bits.outside = true; + else if (name == "HIDDEN") + mask.bits.hidden = value.bits.hidden = true; + else if (name == "REVEALED") + mask.bits.hidden = true; + else + return false; + + setTilesByDesignation(target, mask, value, enable); + return true; +} + static command_result burrow(color_ostream &out, vector ¶meters) { CoreSuspender suspend; @@ -402,6 +569,83 @@ static command_result burrow(color_ostream &out, vector ¶meters) return CR_WRONG_USAGE; } } + else if (cmd == "clear-units") + { + if (parameters.size() < 2) + return CR_WRONG_USAGE; + + for (int i = 1; i < parameters.size(); i++) + { + auto target = findByName(out, parameters[i]); + if (!target) + return CR_WRONG_USAGE; + + Units::clearBurrowMembers(target); + } + } + else if (cmd == "set-units" || cmd == "add-units" || cmd == "remove-units") + { + if (parameters.size() < 3) + return CR_WRONG_USAGE; + + auto target = findByName(out, parameters[1]); + if (!target) + return CR_WRONG_USAGE; + + if (cmd == "set-units") + Units::clearBurrowMembers(target); + + bool enable = (cmd != "remove-units"); + + for (int i = 2; i < parameters.size(); i++) + { + auto source = findByName(out, parameters[i]); + if (!source) + return CR_WRONG_USAGE; + + copyUnits(target, source, enable); + } + } + else if (cmd == "clear-tiles") + { + if (parameters.size() < 2) + return CR_WRONG_USAGE; + + for (int i = 1; i < parameters.size(); i++) + { + auto target = findByName(out, parameters[i]); + if (!target) + return CR_WRONG_USAGE; + + Maps::clearBurrowTiles(target); + } + } + else if (cmd == "set-tiles" || cmd == "add-tiles" || cmd == "remove-tiles") + { + if (parameters.size() < 3) + return CR_WRONG_USAGE; + + auto target = findByName(out, parameters[1]); + if (!target) + return CR_WRONG_USAGE; + + if (cmd == "set-tiles") + Maps::clearBurrowTiles(target); + + bool enable = (cmd != "remove-tiles"); + + for (int i = 2; i < parameters.size(); i++) + { + if (setTilesByKeyword(target, parameters[i], enable)) + continue; + + auto source = findByName(out, parameters[i]); + if (!source) + return CR_WRONG_USAGE; + + copyTiles(target, source, enable); + } + } else { if (!parameters.empty() && cmd != "?") From a2a47c5d63efe42b05a90ecedd2b95544ced2234 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 14 Apr 2012 13:30:48 +0300 Subject: [PATCH 068/108] Fix to init (no need to add " around). Added experimental (crashy as hell) tools.project --- plugins/Dfusion/luafiles/init.lua | 15 +++++---- plugins/Dfusion/luafiles/tools/init.lua | 43 +++++++++++++++++++++---- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/plugins/Dfusion/luafiles/init.lua b/plugins/Dfusion/luafiles/init.lua index 358f93d00..e68684bf7 100644 --- a/plugins/Dfusion/luafiles/init.lua +++ b/plugins/Dfusion/luafiles/init.lua @@ -86,14 +86,15 @@ loadall(plugins) dofile_silent("dfusion/initcustom.lua") local args={...} -for k,v in pairs(args) do - local f,err=load(v) - if f then - f() - else - Console.printerr(err) - end + + +local f,err=load(table.concat(args,' ')) +if f then + f() +else + Console.printerr(err) end + if not INIT then mainmenu(plugins) end diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index e690e9412..b01157c87 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -274,19 +274,50 @@ function tools.changesite(names) print(string.format("%x->%d",off,n2)) engine.poke(off,ptr_site.type,n2) end -function tools.project(unit) +function tools.project(unit,trg) if unit==nil then - unit=getSelectedUnit() - end - - if unit==nil then - unit=getCreatureAtPos(getxyz()) + unit=getCreatureAtPointer() end if unit==nil then error("Failed to project unit. Unit not selected/valid") end -- todo: add projectile to world, point to unit, add flag to unit, add gen-ref to projectile. + local p=df.proj_unitst:new() + local startpos={x=unit.pos.x,y=unit.pos.y,z=unit.pos.z} + p.origin_pos=startpos + p.target_pos=trg + p.cur_pos=startpos + p.prev_pos=startpos + p.unit=unit + --- wtf stuff + p.unk14=100 + p.unk16=-1 + p.unk23=-1 + p.fall_delay=5 + p.fall_counter=5 + p.collided=true + -- end wtf + local citem=df.global.world.proj_list + local maxid=1 + local newlink=df.proj_list_link:new() + newlink.item=p + while citem.item~= nil do + if citem.item.id>maxid then maxid=citem.item.id end + if citem.next ~= nil then + citem=citem.next + else + break + end + end + p.id=maxid+1 + newlink.prev=citem + citem.next=newlink + + local proj_ref=df.general_ref_projectile:new() + proj_ref.projectile_id=p.id + unit.refs:insert(#unit.refs,proj_ref) + unit.flags1.projectile=true end function tools.empregnate(unit) if unit==nil then From 2100a95ad546ad0d4c85483e96357924c05c8b51 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sat, 14 Apr 2012 13:36:22 +0200 Subject: [PATCH 069/108] zone: fixed major derp (assign commands would accidentally free caged creatures). tweak: added cheat to kidnap caravan escorts (works fine even for other races, does NOT work for traders because they can't be assigned any labors for whatever reason) --- plugins/tweak.cpp | 22 ++++++++++++++++++++++ plugins/zone.cpp | 1 - 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 3ac6bae85..4c100cc48 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -101,6 +101,8 @@ command_result fix_clothing_ownership(color_ostream &out, df::unit* unit) { df::unit_inventory_item* inv_item = unit->inventory[j]; df::item* item = inv_item->item; + // unforbid items (for the case of kidnapping caravan escorts who have their stuff forbidden by default) + inv_item->item->flags.bits.forbid = 0; if(inv_item->mode == df::unit_inventory_item::T_mode::Worn) { // ignore armor? @@ -207,6 +209,26 @@ static command_result tweak(color_ostream &out, vector ¶meters) return fix_clothing_ownership(out, unit); } + else if (cmd == "makeown") + { + // force a unit into your fort, regardless of civ or race + // allows to "steal" caravan guards etc + df::unit *unit = getSelectedUnit(out); + if (!unit) + { + out << "No unit selected!" << endl; + return CR_FAILURE; + } + if (unit->flags2.bits.resident) + unit->flags2.bits.resident = 0; + if(unit->flags1.bits.merchant) + unit->flags1.bits.merchant = 0; + if(unit->flags1.bits.forest) + unit->flags1.bits.forest = 0; + if(unit->civ_id != df::global::ui->civ_id) + unit->civ_id = df::global::ui->civ_id; + return fix_clothing_ownership(out, unit); + } else return CR_WRONG_USAGE; diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c668abe99..1e90aa1cf 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -963,7 +963,6 @@ bool isInBuiltCage(df::unit* unit) { if(oldcage->assigned_creature[oc] == unit->id) { - oldcage->assigned_creature.erase(oldcage->assigned_creature.begin() + oc); caged = true; break; } From bbf28eb33d7cd1a851c89dfcb102cbfa50e3f2e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 14 Apr 2012 14:15:46 +0200 Subject: [PATCH 070/108] Disable df2minecraft again. --- plugins/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6c2a537d1..f72245b95 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -20,10 +20,11 @@ if(BUILD_DEV_PLUGINS) add_subdirectory (devel) endif() -OPTION(BUILD_DF2MC "Build DF2MC (needs a checkout first)." OFF) -if(BUILD_DF2MC) - add_subdirectory (df2mc) -endif() +#It's dead :< +#OPTION(BUILD_DF2MC "Build DF2MC (needs a checkout first)." OFF) +#if(BUILD_DF2MC) +# add_subdirectory (df2mc) +#endif() OPTION(BUILD_MAPEXPORT "Build map exporter." ON) if (BUILD_MAPEXPORT) From cb49c92b99a5aab3f95c811a43fe4dadc1115b99 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 14 Apr 2012 19:44:07 +0400 Subject: [PATCH 071/108] Allow plugins to export functions to lua with safe reload support. - To ensure reload safety functions have to be wrapped. Every call checks the loaded state and locks a mutex in Plugin. If the plugin is unloaded, calling its functions throws a lua error. Therefore, plugins may not create closures or export yieldable functions. - The set of function argument and return types supported by LuaWrapper is severely limited when compared to being compiled inside the main library. Currently supported types: numbers, bool, std::string, df::foo, df::foo*, std::vector, std::vector. - To facilitate postponing initialization until after all plugins have been loaded, the core sends a SC_CORE_INITIALIZED event. - As an example, the burrows plugin now exports its functions. --- library/Core.cpp | 20 ++++-- library/LuaTools.cpp | 17 +++++ library/LuaTypes.cpp | 6 ++ library/PluginManager.cpp | 112 ++++++++++++++++++++++++++++++-- library/include/Core.h | 2 + library/include/DataFuncs.h | 6 +- library/include/DataIdentity.h | 48 +++++++++----- library/include/LuaWrapper.h | 11 ++-- library/include/PluginManager.h | 53 ++++++++++++++- library/lua/dfhack.lua | 4 ++ plugins/CMakeLists.txt | 4 ++ plugins/burrows.cpp | 21 ++++++ plugins/lua/burrows.lua | 33 ++++++++++ 13 files changed, 298 insertions(+), 39 deletions(-) create mode 100644 plugins/lua/burrows.lua diff --git a/library/Core.cpp b/library/Core.cpp index a752ae628..e6b9c45ff 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -861,13 +861,9 @@ int Core::TileUpdate() // should always be from simulation thread! int Core::Update() { - if(!started) - Init(); if(errorstate) return -1; - color_ostream_proxy out(con); - // Pretend this thread has suspended the core in the usual way { lock_guard lock(d->AccessMutex); @@ -877,6 +873,22 @@ int Core::Update() d->df_suspend_depth = 1000; } + // Initialize the core + bool first_update = false; + + if(!started) + { + first_update = true; + Init(); + if(errorstate) + return -1; + } + + color_ostream_proxy out(con); + + if (first_update) + plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED); + // detect if the game was loaded or unloaded in the meantime void *new_wdata = NULL; void *new_mapdata = NULL; diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 471ffd550..5129ba648 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -745,6 +745,22 @@ static int lua_dfhack_with_suspend(lua_State *L) return lua_gettop(L); } +static int dfhack_open_plugin(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checktype(L, 2, LUA_TSTRING); + const char *name = lua_tostring(L, 2); + + PluginManager *pmgr = Core::getInstance().getPluginManager(); + Plugin *plugin = pmgr->getPluginByName(name); + + if (!plugin) + luaL_error(L, "plugin not found: '%s'", name); + + plugin->open_lua(L, 1); + return 0; +} + static const luaL_Reg dfhack_funcs[] = { { "print", lua_dfhack_print }, { "println", lua_dfhack_println }, @@ -757,6 +773,7 @@ static const luaL_Reg dfhack_funcs[] = { { "onerror", dfhack_onerror }, { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, + { "open_plugin", dfhack_open_plugin }, { NULL, NULL } }; diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 40269da80..f5ba565d5 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1030,6 +1030,12 @@ static int meta_global_newindex(lua_State *state) static int meta_call_function(lua_State *state) { auto id = (function_identity_base*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + return method_wrapper_core(state, id); +} + +int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id) +{ if (lua_gettop(state) != id->getNumArgs()) field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke"); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index a7faabeec..4d0c06ddf 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -32,6 +32,8 @@ distribution. #include "DataDefs.h" #include "MiscUtils.h" +#include "LuaWrapper.h" + using namespace DFHack; #include @@ -107,8 +109,8 @@ struct Plugin::RefLock void lock_sub() { mut->lock(); - refcount --; - wakeup->notify_one(); + if (--refcount == 0) + wakeup->notify_one(); mut->unlock(); } void wait() @@ -130,6 +132,13 @@ struct Plugin::RefAutolock ~RefAutolock(){ lock->unlock(); }; }; +struct Plugin::RefAutoinc +{ + RefLock * lock; + RefAutoinc(RefLock * lck):lock(lck){ lock->lock_add(); }; + ~RefAutoinc(){ lock->lock_sub(); }; +}; + Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * pm) { filename = filepath; @@ -210,6 +219,7 @@ bool Plugin::load(color_ostream &con) plugin_shutdown = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_shutdown"); plugin_onstatechange = (command_result (*)(color_ostream &, state_change_event)) LookupPlugin(plug, "plugin_onstatechange"); plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect"); + index_lua(plug); this->name = *plug_name; plugin_lib = plug; commands.clear(); @@ -222,6 +232,7 @@ bool Plugin::load(color_ostream &con) else { con.printerr("Plugin %s has failed to initialize properly.\n", filename.c_str()); + reset_lua(); ClosePlugin(plugin_lib); state = PS_BROKEN; return false; @@ -235,13 +246,22 @@ bool Plugin::unload(color_ostream &con) // if we are actually loaded if(state == PS_LOADED) { + // notify the plugin about an attempt to shutdown + if (plugin_onstatechange && + plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) + { + con.printerr("Plugin %s has refused to be unloaded.\n", name.c_str()); + access->unlock(); + return false; + } + // wait for all calls to finish + access->wait(); // notify plugin about shutdown, if it has a shutdown function command_result cr = CR_OK; if(plugin_shutdown) cr = plugin_shutdown(con); - // wait for all calls to finish - access->wait(); // cleanup... + reset_lua(); parent->unregisterCommands(this); commands.clear(); if(cr == CR_OK) @@ -418,6 +438,90 @@ Plugin::plugin_state Plugin::getState() const return state; } +void Plugin::index_lua(DFLibrary *lib) +{ + if (auto cmdlist = (CommandReg*)LookupPlugin(lib, "plugin_lua_commands")) + { + for (; cmdlist->name; ++cmdlist) + { + auto &cmd = lua_commands[cmdlist->name]; + if (!cmd) cmd = new LuaCommand; + cmd->owner = this; + cmd->name = cmdlist->name; + cmd->command = cmdlist->command; + } + } + if (auto funlist = (FunctionReg*)LookupPlugin(lib, "plugin_lua_functions")) + { + for (; funlist->name; ++funlist) + { + auto &cmd = lua_functions[funlist->name]; + if (!cmd) cmd = new LuaFunction; + cmd->owner = this; + cmd->name = funlist->name; + cmd->identity = funlist->identity; + } + } +} + +void Plugin::reset_lua() +{ + for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it) + it->second->command = NULL; + for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) + it->second->identity = NULL; +} + +int Plugin::lua_cmd_wrapper(lua_State *state) +{ + auto cmd = (LuaCommand*)lua_touserdata(state, lua_upvalueindex(1)); + + RefAutoinc lock(cmd->owner->access); + + if (!cmd->command) + luaL_error(state, "plugin command %s() has been unloaded", + (cmd->owner->name+"."+cmd->name).c_str()); + + return cmd->command(state); +} + +int Plugin::lua_fun_wrapper(lua_State *state) +{ + auto cmd = (LuaFunction*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + RefAutoinc lock(cmd->owner->access); + + if (!cmd->identity) + luaL_error(state, "plugin function %s() has been unloaded", + (cmd->owner->name+"."+cmd->name).c_str()); + + return LuaWrapper::method_wrapper_core(state, cmd->identity); +} + +void Plugin::open_lua(lua_State *state, int table) +{ + table = lua_absindex(state, table); + + RefAutolock lock(access); + + for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it) + { + lua_pushlightuserdata(state, it->second); + lua_pushcclosure(state, lua_cmd_wrapper, 1); + lua_setfield(state, table, it->first.c_str()); + } + + for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) + { + lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN); + lua_pushlightuserdata(state, NULL); + lua_pushfstring(state, "%s.%s()", name.c_str(), it->second->name.c_str()); + lua_pushlightuserdata(state, it->second); + lua_pushcclosure(state, lua_fun_wrapper, 4); + lua_setfield(state, table, it->first.c_str()); + } +} + PluginManager::PluginManager(Core * core) { #ifdef LINUX_BUILD diff --git a/library/include/Core.h b/library/include/Core.h index 290a324b9..8c2a284df 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -139,6 +139,8 @@ namespace DFHack static void print(const char *format, ...); static void printerr(const char *format, ...); + PluginManager *getPluginManager() { return plug_mgr; } + private: DFHack::Console con; diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index d78fe9e27..a949e784b 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -32,10 +32,6 @@ distribution. #include "DataIdentity.h" #include "LuaWrapper.h" -#ifndef BUILD_DFHACK_LIB -#error Due to export issues this header is internal to the main library. -#endif - namespace df { // A very simple and stupid implementation of some stuff from boost template struct is_same_type { static const bool value = false; }; @@ -50,7 +46,7 @@ namespace df { template::type,void>::value> struct function_wrapper {}; - class cur_lua_ostream_argument { + class DFHACK_EXPORT cur_lua_ostream_argument { DFHack::color_ostream *out; public: cur_lua_ostream_argument(lua_State *state); diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index bab87e1bb..279400f74 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -160,8 +160,6 @@ namespace DFHack }; } -// Due to export issues, this stuff can only work in the main dll -#ifdef BUILD_DFHACK_LIB namespace df { using DFHack::function_identity_base; @@ -171,7 +169,7 @@ namespace df using DFHack::ptr_container_identity; using DFHack::bit_container_identity; - class number_identity_base : public primitive_identity { + class DFHACK_EXPORT number_identity_base : public primitive_identity { const char *name; public: @@ -197,7 +195,7 @@ namespace df virtual void write(void *ptr, double val) { *(T*)ptr = T(val); } }; - class bool_identity : public primitive_identity { + class DFHACK_EXPORT bool_identity : public primitive_identity { public: bool_identity() : primitive_identity(sizeof(bool)) {}; @@ -207,7 +205,7 @@ namespace df virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); }; - class ptr_string_identity : public primitive_identity { + class DFHACK_EXPORT ptr_string_identity : public primitive_identity { public: ptr_string_identity() : primitive_identity(sizeof(char*)) {}; @@ -217,7 +215,7 @@ namespace df virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); }; - class stl_string_identity : public DFHack::constructed_identity { + class DFHACK_EXPORT stl_string_identity : public DFHack::constructed_identity { public: stl_string_identity() : constructed_identity(sizeof(std::string), &allocator_fn) @@ -233,7 +231,7 @@ namespace df virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); }; - class stl_ptr_vector_identity : public ptr_container_identity { + class DFHACK_EXPORT stl_ptr_vector_identity : public ptr_container_identity { public: typedef std::vector container; @@ -276,6 +274,8 @@ namespace df } }; +// Due to export issues, this stuff can only work in the main dll +#ifdef BUILD_DFHACK_LIB class buffer_container_identity : public container_identity { int size; @@ -370,8 +370,9 @@ namespace df ((container*)ptr)->set(idx, val); } }; +#endif - class stl_bit_vector_identity : public bit_container_identity { + class DFHACK_EXPORT stl_bit_vector_identity : public bit_container_identity { public: typedef std::vector container; @@ -400,6 +401,7 @@ namespace df } }; +#ifdef BUILD_DFHACK_LIB template class enum_list_attr_identity : public container_identity { public: @@ -421,9 +423,10 @@ namespace df return (void*)&((container*)ptr)->items[idx]; } }; +#endif #define NUMBER_IDENTITY_TRAITS(type) \ - template<> struct identity_traits { \ + template<> struct DFHACK_EXPORT identity_traits { \ static number_identity identity; \ static number_identity_base *get() { return &identity; } \ }; @@ -439,37 +442,37 @@ namespace df NUMBER_IDENTITY_TRAITS(uint64_t); NUMBER_IDENTITY_TRAITS(float); - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static bool_identity identity; static bool_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static stl_string_identity identity; static stl_string_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static ptr_string_identity identity; static ptr_string_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static ptr_string_identity identity; static ptr_string_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static pointer_identity identity; static pointer_identity *get() { return &identity; } }; - template<> struct identity_traits > { + template<> struct DFHACK_EXPORT identity_traits > { static stl_ptr_vector_identity identity; static stl_ptr_vector_identity *get() { return &identity; } }; - template<> struct identity_traits > { + template<> struct DFHACK_EXPORT identity_traits > { static stl_bit_vector_identity identity; static stl_bit_vector_identity *get() { return &identity; } }; @@ -478,14 +481,17 @@ namespace df // Container declarations +#ifdef BUILD_DFHACK_LIB template struct identity_traits > { static primitive_identity *get(); }; +#endif template struct identity_traits { static pointer_identity *get(); }; +#ifdef BUILD_DFHACK_LIB template struct identity_traits { static container_identity *get(); }; @@ -493,11 +499,13 @@ namespace df template struct identity_traits > { static container_identity *get(); }; +#endif template struct identity_traits > { static stl_ptr_vector_identity *get(); }; +#ifdef BUILD_DFHACK_LIB template struct identity_traits > { static container_identity *get(); }; @@ -518,13 +526,16 @@ namespace df template struct identity_traits > { static container_identity *get(); }; +#endif // Container definitions +#ifdef BUILD_DFHACK_LIB template inline primitive_identity *identity_traits >::get() { return identity_traits::get(); } +#endif template inline pointer_identity *identity_traits::get() { @@ -532,6 +543,7 @@ namespace df return &identity; } +#ifdef BUILD_DFHACK_LIB template inline container_identity *identity_traits::get() { static buffer_container_identity identity(sz, identity_traits::get()); @@ -544,6 +556,7 @@ namespace df static stl_container_identity identity("vector", identity_traits::get()); return &identity; } +#endif template inline stl_ptr_vector_identity *identity_traits >::get() { @@ -551,6 +564,7 @@ namespace df return &identity; } +#ifdef BUILD_DFHACK_LIB template inline container_identity *identity_traits >::get() { typedef std::deque container; @@ -576,5 +590,5 @@ namespace df static enum_list_attr_identity identity(identity_traits::get()); return &identity; } -} #endif +} diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 479ca0d67..2304b940d 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -30,6 +30,7 @@ distribution. #include #include "DataDefs.h" +#include "PluginManager.h" #include #include @@ -156,7 +157,7 @@ namespace DFHack { namespace LuaWrapper { * Verify that the object is a DF ref with UPVAL_METATABLE. * If everything ok, extract the address. */ - uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode); + DFHACK_EXPORT uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode); bool is_type_compatible(lua_State *state, type_identity *type1, int meta1, type_identity *type2, int meta2, bool exact_equal); @@ -221,16 +222,14 @@ namespace DFHack { namespace LuaWrapper { */ void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); - struct FunctionReg { - const char *name; - function_identity_base *identity; - }; - /** * Wrap functions and add them to the table on the top of the stack. */ + using DFHack::FunctionReg; void SetFunctionWrappers(lua_State *state, const FunctionReg *reg); + int method_wrapper_core(lua_State *state, function_identity_base *id); + void IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct); void AttachDFGlobals(lua_State *state); diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 8f02099be..554860dee 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -33,6 +33,8 @@ distribution. #include "RemoteClient.h" +typedef struct lua_State lua_State; + struct DFLibrary; namespace tthread { @@ -49,6 +51,7 @@ namespace DFHack class PluginManager; class virtual_identity; class RPCService; + class function_identity_base; enum state_change_event { @@ -56,7 +59,17 @@ namespace DFHack SC_WORLD_UNLOADED, SC_MAP_LOADED, SC_MAP_UNLOADED, - SC_VIEWSCREEN_CHANGED + SC_VIEWSCREEN_CHANGED, + SC_CORE_INITIALIZED, + SC_BEGIN_UNLOAD + }; + struct DFHACK_EXPORT CommandReg { + const char *name; + int (*command)(lua_State*); + }; + struct DFHACK_EXPORT FunctionReg { + const char *name; + function_identity_base *identity; }; struct DFHACK_EXPORT PluginCommand { @@ -102,6 +115,7 @@ namespace DFHack { struct RefLock; struct RefAutolock; + struct RefAutoinc; enum plugin_state { PS_UNLOADED, @@ -138,6 +152,9 @@ namespace DFHack { return name; } + + void open_lua(lua_State *state, int table); + private: RefLock * access; std::vector commands; @@ -147,6 +164,26 @@ namespace DFHack DFLibrary * plugin_lib; PluginManager * parent; plugin_state state; + + struct LuaCommand { + Plugin *owner; + std::string name; + int (*command)(lua_State *state); + }; + std::map lua_commands; + static int lua_cmd_wrapper(lua_State *state); + + struct LuaFunction { + Plugin *owner; + std::string name; + function_identity_base *identity; + }; + std::map lua_functions; + static int lua_fun_wrapper(lua_State *state); + + void index_lua(DFLibrary *lib); + void reset_lua(); + command_result (*plugin_init)(color_ostream &, std::vector &); command_result (*plugin_status)(color_ostream &, std::string &); command_result (*plugin_shutdown)(color_ostream &); @@ -199,5 +236,15 @@ namespace DFHack }; /// You have to have this in every plugin you write - just once. Ideally on top of the main file. -#define DFHACK_PLUGIN(plugin_name) DFhackDataExport const char * version = DFHACK_VERSION;\ -DFhackDataExport const char * name = plugin_name; +#define DFHACK_PLUGIN(plugin_name) \ + DFhackDataExport const char * version = DFHACK_VERSION;\ + DFhackDataExport const char * name = plugin_name; + +#define DFHACK_PLUGIN_LUA_COMMANDS \ + DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = +#define DFHACK_PLUGIN_LUA_FUNCTIONS \ + DFhackCExport const DFHack::FunctionReg plugin_lua_functions[] = + +#define DFHACK_LUA_COMMAND(name) { #name, name } +#define DFHACK_LUA_FUNCTION(name) { #name, df::wrap_function(name) } +#define DFHACK_LUA_END { NULL, NULL } diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 626f60bec..7230a12aa 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -55,6 +55,10 @@ function mkmodule(module,env) error("Not a table in package.loaded["..module.."]") end end + local plugname = string.match(module,'^plugins%.(%w+)$') + if plugname then + dfhack.open_plugin(pkg,plugname) + end setmetatable(pkg, { __index = (env or _G) }) return pkg end diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index d30d25697..9d236fbe6 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -35,6 +35,10 @@ if (BUILD_DWARFEXPORT) add_subdirectory (dwarfexport) endif() +install(DIRECTORY lua/ + DESTINATION ${DFHACK_LUA_DESTINATION}/plugins + FILES_MATCHING PATTERN "*.lua") + # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index c973405ee..74cb57c45 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -2,6 +2,9 @@ #include "Console.h" #include "Export.h" #include "PluginManager.h" +#include "Error.h" + +#include "DataFuncs.h" #include "modules/Gui.h" #include "modules/Job.h" @@ -420,6 +423,9 @@ static df::burrow *findByName(color_ostream &out, std::string name, bool silent static void copyUnits(df::burrow *target, df::burrow *source, bool enable) { + CHECK_NULL_POINTER(target); + CHECK_NULL_POINTER(source); + if (source == target) { if (!enable) @@ -439,6 +445,9 @@ static void copyUnits(df::burrow *target, df::burrow *source, bool enable) static void copyTiles(df::burrow *target, df::burrow *source, bool enable) { + CHECK_NULL_POINTER(target); + CHECK_NULL_POINTER(source); + if (source == target) { if (!enable) @@ -480,6 +489,8 @@ static void copyTiles(df::burrow *target, df::burrow *source, bool enable) static void setTilesByDesignation(df::burrow *target, df::tile_designation d_mask, df::tile_designation d_value, bool enable) { + CHECK_NULL_POINTER(target); + auto &blocks = world->map.map_blocks; for (size_t i = 0; i < blocks.size(); i++) @@ -512,6 +523,8 @@ static void setTilesByDesignation(df::burrow *target, df::tile_designation d_mas static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) { + CHECK_NULL_POINTER(target); + df::tile_designation mask(0); df::tile_designation value(0); @@ -538,6 +551,14 @@ static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) return true; } +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(findByName), + DFHACK_LUA_FUNCTION(copyUnits), + DFHACK_LUA_FUNCTION(copyTiles), + DFHACK_LUA_FUNCTION(setTilesByKeyword), + DFHACK_LUA_END +}; + static command_result burrow(color_ostream &out, vector ¶meters) { CoreSuspender suspend; diff --git a/plugins/lua/burrows.lua b/plugins/lua/burrows.lua new file mode 100644 index 000000000..b1d51f6a6 --- /dev/null +++ b/plugins/lua/burrows.lua @@ -0,0 +1,33 @@ +local _ENV = mkmodule('plugins.burrows') + +--[[ + + Native functions: + + * findByName(name) -> burrow + * copyUnits(dest,src,enable) + * copyTiles(dest,src,enable) + * setTilesByKeyword(dest,kwd,enable) -> success + + 'enable' selects between add and remove modes + +--]] + +clearUnits = dfhack.units.clearBurrowMembers + +function isBurrowUnit(burrow,unit) + return dfhack.units.isInBurrow(unit,burrow) +end +function setBurrowUnit(burrow,unit,enable) + return dfhack.units.setInBurrow(unit,burrow,enable) +end + +clearTiles = dfhack.maps.clearBurrowTiles +listBlocks = dfhack.maps.listBurrowBlocks + +isBurrowTile = dfhack.maps.isBurrowTile +setBurrowTile = dfhack.maps.setBurrowTile +isBlockBurrowTile = dfhack.maps.isBlockBurrowTile +setBlockBurrowTile = dfhack.maps.setBlockBurrowTile + +return _ENV \ No newline at end of file From 9f95e67c7575cca9495062eb275ee99a590e41cb Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sat, 14 Apr 2012 19:06:03 +0200 Subject: [PATCH 072/108] tweak makeown: converts MERCHANT to TRADER (otherwise you can't assign jobs to kidnapped merchants. zone: fixed a bug which could lead to units being assigned to more than one cage and/or accidentally marked for slaughter --- plugins/tweak.cpp | 4 ++++ plugins/zone.cpp | 12 +++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 4c100cc48..31969bde7 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -227,6 +227,10 @@ static command_result tweak(color_ostream &out, vector ¶meters) unit->flags1.bits.forest = 0; if(unit->civ_id != df::global::ui->civ_id) unit->civ_id = df::global::ui->civ_id; + if(unit->profession == df::profession::MERCHANT) + unit->profession = df::profession::TRADER; + if(unit->profession2 == df::profession::MERCHANT) + unit->profession2 = df::profession::TRADER; return fix_clothing_ownership(out, unit); } else diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 1e90aa1cf..2f2b64588 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -905,7 +905,7 @@ bool isAssigned(df::unit* unit) if( rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED || rtype == df::general_ref_type::BUILDING_CAGED || rtype == df::general_ref_type::BUILDING_CHAIN - || (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isBuiltCageAtPos(unit->pos)) + || (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isInBuiltCage(unit)) ) { assigned = true; @@ -958,10 +958,10 @@ bool isInBuiltCage(df::unit* unit) df::building* building = world->buildings.all[b]; if( building->getType() == building_type::Cage) { - df::building_cagest* oldcage = (df::building_cagest*) building; - for(size_t oc=0; ocassigned_creature.size(); oc++) + df::building_cagest* cage = (df::building_cagest*) building; + for(size_t c=0; cassigned_creature.size(); c++) { - if(oldcage->assigned_creature[oc] == unit->id) + if(cage->assigned_creature[c] == unit->id) { caged = true; break; @@ -2769,7 +2769,9 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) || !isTame(unit) || isWar(unit) // ignore war dogs etc || isHunter(unit) // ignore hunting dogs etc - || (isContainedInItem(unit) && hasValidMapPos(unit) && isBuiltCageAtPos(unit->pos)) + // ignore creatures in built cages to leave zoos alone + // (TODO: allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCage(unit)) || unit->name.has_name ) continue; From 451f07ecb45ac7ddd84d4e586ea6681ff9153c41 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 15 Apr 2012 00:03:12 +0300 Subject: [PATCH 073/108] breakfast build bat... --- build/generate-MSVC-all-breakfast.bat | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 build/generate-MSVC-all-breakfast.bat diff --git a/build/generate-MSVC-all-breakfast.bat b/build/generate-MSVC-all-breakfast.bat new file mode 100644 index 000000000..08c5b03ff --- /dev/null +++ b/build/generate-MSVC-all-breakfast.bat @@ -0,0 +1,8 @@ +@echo off +IF EXIST DF_PATH.txt SET /P _DF_PATH= Date: Sat, 14 Apr 2012 23:36:45 +0200 Subject: [PATCH 074/108] Make release string tweakable. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cbbe6a1f..8c9ace59b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ set(DF_VERSION_MINOR "34") set(DF_VERSION_PATCH "07") set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}") -set(DFHACK_RELEASE "1") +SET(DFHACK_RELEASE "1" CACHE STRING "Current release revision.") set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-r${DFHACK_RELEASE}") add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}") From c6c6c517667563a07a7dc29761656f73c2da9656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 14 Apr 2012 23:38:29 +0200 Subject: [PATCH 075/108] The 'r' in release string is now also tweakable. --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c9ace59b..107626b13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,9 +58,9 @@ set(DF_VERSION_MINOR "34") set(DF_VERSION_PATCH "07") set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}") -SET(DFHACK_RELEASE "1" CACHE STRING "Current release revision.") +SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.") -set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-r${DFHACK_RELEASE}") +set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}") add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}") ## where to install things (after the build is done, classic 'make install' or package structure) From fa063e293610bae49be81f62b869ec3f5cfdf547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 15 Apr 2012 00:35:59 +0200 Subject: [PATCH 076/108] Track structures, fix trivial problem in zone plugin --- library/xml | 2 +- plugins/zone.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 27fec9797..a54516705 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 27fec9797d7d1215bd3f5530b44a55a9d394fe75 +Subproject commit a545167050fee9eedd5e319b518d961f933154a3 diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 2f2b64588..9f71b85d8 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -890,6 +890,8 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef() return newref; } +bool isInBuiltCage(df::unit* unit); + // check if assigned to pen, pit, (built) cage or chain // note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence) // animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead From e686b1c8e280051e20ba187bc4ea66ebedc21f97 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 15 Apr 2012 01:20:59 +0300 Subject: [PATCH 077/108] Orbital nuking of C4819 (code-page warning) --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cbbe6a1f..e39784adc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,9 @@ endif(CMAKE_CONFIGURATION_TYPES) cmake_minimum_required(VERSION 2.8 FATAL_ERROR) project(dfhack) +# disable C4819 code-page warning +add_definitions( "/wd4819" ) + # set up folder structures for IDE solutions # MSVC Express won't load solutions that use this. It also doesn't include MFC supported # Check for MFC! From f235d8f371082e4bc9bb17651eb7d3cc6fb5e54e Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 15 Apr 2012 01:27:02 +0300 Subject: [PATCH 078/108] Orbital nuking was bit excessive... --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e39784adc..01b7c5804 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,10 @@ endif(CMAKE_CONFIGURATION_TYPES) cmake_minimum_required(VERSION 2.8 FATAL_ERROR) project(dfhack) +if(MSVC) # disable C4819 code-page warning add_definitions( "/wd4819" ) +endif() # set up folder structures for IDE solutions # MSVC Express won't load solutions that use this. It also doesn't include MFC supported From 15248aa46540f455d62a9c2cdc6efea9218dd6c1 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 15 Apr 2012 01:49:37 +0300 Subject: [PATCH 079/108] Propogate errors from bat files. --- build/package-debug.bat | 4 +++- build/package-release.bat | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build/package-debug.bat b/build/package-debug.bat index e6149c56d..b6833f552 100644 --- a/build/package-debug.bat +++ b/build/package-debug.bat @@ -1,4 +1,6 @@ +@echo off call "%VS100COMNTOOLS%vsvars32.bat" cd VC2010 msbuild /m /p:Platform=Win32 /p:Configuration=RelWithDebInfo PACKAGE.vcxproj -cd .. \ No newline at end of file +cd .. +exit %ERRORLEVEL% \ No newline at end of file diff --git a/build/package-release.bat b/build/package-release.bat index 3cc3d4af7..99e94b0e5 100644 --- a/build/package-release.bat +++ b/build/package-release.bat @@ -1,4 +1,6 @@ +@echo off call "%VS100COMNTOOLS%vsvars32.bat" cd VC2010 msbuild /m /p:Platform=Win32 /p:Configuration=Release PACKAGE.vcxproj -cd .. \ No newline at end of file +cd .. +exit %ERRORLEVEL% \ No newline at end of file From fc38371b29ff20b959a401222a74e6a31f1b95c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 15 Apr 2012 01:15:15 +0200 Subject: [PATCH 080/108] Clean up some chaos in old code. --- library/PlugLoad-linux.cpp | 2 +- library/PlugLoad-windows.cpp | 2 +- library/include/Core.h | 6 ------ library/include/PluginManager.h | 11 ++++++++++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/library/PlugLoad-linux.cpp b/library/PlugLoad-linux.cpp index aaf7b2c33..69945c6f4 100644 --- a/library/PlugLoad-linux.cpp +++ b/library/PlugLoad-linux.cpp @@ -14,7 +14,7 @@ #include #include "DFHack.h" -#include "Core.h" +#include "PluginManager.h" #include "Hooks.h" #include diff --git a/library/PlugLoad-windows.cpp b/library/PlugLoad-windows.cpp index eadc9343d..f9a75df97 100644 --- a/library/PlugLoad-windows.cpp +++ b/library/PlugLoad-windows.cpp @@ -28,7 +28,7 @@ distribution. #include #include #include -#include "Core.h" +#include "PluginManager.h" #include "Hooks.h" #include diff --git a/library/include/Core.h b/library/include/Core.h index 8c2a284df..f8ac8e783 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -65,12 +65,6 @@ namespace DFHack { class df_window; } - // anon type, pretty much - struct DFLibrary; - - DFLibrary * OpenPlugin (const char * filename); - void * LookupPlugin (DFLibrary * plugin ,const char * function); - void ClosePlugin (DFLibrary * plugin); // Core is a singleton. Why? Because it is closely tied to SDL calls. It tracks the global state of DF. // There should never be more than one instance diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 554860dee..080e66e66 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -35,7 +35,6 @@ distribution. typedef struct lua_State lua_State; -struct DFLibrary; namespace tthread { class mutex; @@ -53,6 +52,16 @@ namespace DFHack class RPCService; class function_identity_base; + // anon type, pretty much + struct DFLibrary; + + // Open a plugin library + DFLibrary * OpenPlugin (const char * filename); + // find a symbol inside plugin + void * LookupPlugin (DFLibrary * plugin ,const char * function); + // Close a plugin library + void ClosePlugin (DFLibrary * plugin); + enum state_change_event { SC_WORLD_LOADED, From c69af6ab9eff10fc59f68d462656caa2dacd266c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 15 Apr 2012 01:58:02 +0200 Subject: [PATCH 081/108] Fix missing lua linkage in burrows plugin. --- plugins/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 5fe21f66e..b520c5f19 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -102,7 +102,8 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(feature feature.cpp) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(zone zone.cpp) - DFHACK_PLUGIN(burrows burrows.cpp) + # this one exports functions to lua + DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() From cb27a1d83916b333d1a0d1b5aa24a7f371e120af Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 15 Apr 2012 11:31:05 +0400 Subject: [PATCH 082/108] Fix typo in the 1MB constant. --- library/include/RemoteClient.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index 9eb8fb427..37aaea9b1 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -66,7 +66,7 @@ namespace DFHack }; struct RPCMessageHeader { - static const int MAX_MESSAGE_SIZE = 8*1048756; + static const int MAX_MESSAGE_SIZE = 8*1048576; int16_t id; int32_t size; From cf0d45be785fad65457ae2ebaa8d890afad79953 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sun, 15 Apr 2012 12:32:25 +0200 Subject: [PATCH 083/108] merged stuff from peterix --- CMakeLists.txt | 9 +- LUA_API.rst | 36 + Lua API.html | 27 + build/generate-MSVC-all-breakfast.bat | 8 + library/Core.cpp | 20 +- library/DataDefs.cpp | 396 ---------- library/LuaApi.cpp | 88 ++- library/LuaTools.cpp | 17 + library/LuaTypes.cpp | 6 + library/PlugLoad-linux.cpp | 2 +- library/PlugLoad-windows.cpp | 2 +- library/PluginManager.cpp | 112 ++- library/RemoteTools.cpp | 30 + library/VersionInfoFactory.cpp | 9 +- library/include/Core.h | 8 +- library/include/DataFuncs.h | 6 +- library/include/DataIdentity.h | 48 +- library/include/LuaWrapper.h | 11 +- library/include/PluginManager.h | 64 +- library/include/TileTypes.h | 12 + library/include/VersionInfo.h | 32 +- .../df/custom/block_burrow.methods.inc | 9 +- .../block_square_event_mineralst.methods.inc | 9 +- library/include/modules/Items.h | 18 +- library/include/modules/Maps.h | 16 +- library/include/modules/Units.h | 9 + library/lua/dfhack.lua | 19 + library/modules/Items.cpp | 187 ++++- library/modules/Maps.cpp | 73 +- library/modules/Units.cpp | 74 ++ library/proto/Basic.proto | 15 + library/proto/BasicApi.proto | 7 +- plugins/CMakeLists.txt | 15 +- plugins/Dfusion/luafiles/init.lua | 15 +- plugins/Dfusion/luafiles/tools/init.lua | 43 +- plugins/autodump.cpp | 10 +- plugins/burrows.cpp | 678 ++++++++++++++++++ plugins/lua/burrows.lua | 33 + plugins/zone.cpp | 2 + 39 files changed, 1651 insertions(+), 524 deletions(-) create mode 100644 build/generate-MSVC-all-breakfast.bat delete mode 100644 library/DataDefs.cpp create mode 100644 plugins/burrows.cpp create mode 100644 plugins/lua/burrows.lua diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cbbe6a1f..1cbf9ed24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,11 @@ endif(CMAKE_CONFIGURATION_TYPES) cmake_minimum_required(VERSION 2.8 FATAL_ERROR) project(dfhack) +if(MSVC) +# disable C4819 code-page warning +add_definitions( "/wd4819" ) +endif() + # set up folder structures for IDE solutions # MSVC Express won't load solutions that use this. It also doesn't include MFC supported # Check for MFC! @@ -58,9 +63,9 @@ set(DF_VERSION_MINOR "34") set(DF_VERSION_PATCH "07") set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}") -set(DFHACK_RELEASE "1") +SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.") -set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-r${DFHACK_RELEASE}") +set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}") add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}") ## where to install things (after the build is done, classic 'make install' or package structure) diff --git a/LUA_API.rst b/LUA_API.rst index fc86070c3..d5af5e7a4 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -663,6 +663,14 @@ Job module Units module ------------ +* ``dfhack.units.getPosition(unit)`` + + Returns true *x,y,z* of the unit; may be not equal to unit.pos if caged. + +* ``dfhack.units.getContainer(unit)`` + + Returns the container (cage) item or *nil*. + * ``dfhack.units.setNickname(unit,nick)`` Sets the unit's nickname properly. @@ -687,6 +695,10 @@ Units module The unit is capable of rational action, i.e. not dead, insane or zombie. +* ``dfhack.units.clearBurrowMembers(burrow)`` + + Removes all units from the burrow. + * ``dfhack.units.isInBurrow(unit,burrow)`` Checks if the unit is in the burrow. @@ -699,6 +711,10 @@ Units module Items module ------------ +* ``dfhack.items.getPosition(item)`` + + Returns true *x,y,z* of the item; may be not equal to item.pos if in inventory. + * ``dfhack.items.getOwner(item)`` Returns the owner unit or *nil*. @@ -708,6 +724,22 @@ Items module Replaces the owner of the item. If unit is *nil*, removes ownership. Returns *false* in case of error. +* ``dfhack.items.getContainer(item)`` + + Returns the container item or *nil*. + +* ``dfhack.items.getContainedItems(item)`` + + Returns a list of items contained in this one. + +* ``dfhack.items.moveToGround(item,pos)`` + + Move the item to the ground at position. Returns *false* if impossible. + +* ``dfhack.items.moveToContainer(item,container)`` + + Move the item to the container. Returns *false* if impossible. + Maps module ----------- @@ -748,6 +780,10 @@ Maps module Returns a table of map block pointers. +* ``dfhack.maps.clearBurrowTiles(burrow)`` + + Removes all tiles from the burrow. + * ``dfhack.maps.isBurrowTile(burrow,tile_coord)`` Checks if the tile is in burrow. diff --git a/Lua API.html b/Lua API.html index 7c3eeddb9..66385840b 100644 --- a/Lua API.html +++ b/Lua API.html @@ -900,6 +900,12 @@ a lua list containing them.

          Units module

            +
          • dfhack.units.getPosition(unit)

            +

            Returns true x,y,z of the unit; may be not equal to unit.pos if caged.

            +
          • +
          • dfhack.units.getContainer(unit)

            +

            Returns the container (cage) item or nil.

            +
          • dfhack.units.setNickname(unit,nick)

            Sets the unit's nickname properly.

          • @@ -918,6 +924,9 @@ a lua list containing them.

          • dfhack.units.isSane(unit)

            The unit is capable of rational action, i.e. not dead, insane or zombie.

          • +
          • dfhack.units.clearBurrowMembers(burrow)

            +

            Removes all units from the burrow.

            +
          • dfhack.units.isInBurrow(unit,burrow)

            Checks if the unit is in the burrow.

          • @@ -929,6 +938,9 @@ a lua list containing them.

            Items module

              +
            • dfhack.items.getPosition(item)

              +

              Returns true x,y,z of the item; may be not equal to item.pos if in inventory.

              +
            • dfhack.items.getOwner(item)

              Returns the owner unit or nil.

            • @@ -936,6 +948,18 @@ a lua list containing them.

              Replaces the owner of the item. If unit is nil, removes ownership. Returns false in case of error.

              +
            • dfhack.items.getContainer(item)

              +

              Returns the container item or nil.

              +
            • +
            • dfhack.items.getContainedItems(item)

              +

              Returns a list of items contained in this one.

              +
            • +
            • dfhack.items.moveToGround(item,pos)

              +

              Move the item to the ground at position. Returns false if impossible.

              +
            • +
            • dfhack.items.moveToContainer(item,container)

              +

              Move the item to the container. Returns false if impossible.

              +
            @@ -968,6 +992,9 @@ Returns false in case of error.

          • dfhack.maps.listBurrowBlocks(burrow)

            Returns a table of map block pointers.

          • +
          • dfhack.maps.clearBurrowTiles(burrow)

            +

            Removes all tiles from the burrow.

            +
          • dfhack.maps.isBurrowTile(burrow,tile_coord)

            Checks if the tile is in burrow.

          • diff --git a/build/generate-MSVC-all-breakfast.bat b/build/generate-MSVC-all-breakfast.bat new file mode 100644 index 000000000..08c5b03ff --- /dev/null +++ b/build/generate-MSVC-all-breakfast.bat @@ -0,0 +1,8 @@ +@echo off +IF EXIST DF_PATH.txt SET /P _DF_PATH= lock(d->AccessMutex); @@ -877,6 +873,22 @@ int Core::Update() d->df_suspend_depth = 1000; } + // Initialize the core + bool first_update = false; + + if(!started) + { + first_update = true; + Init(); + if(errorstate) + return -1; + } + + color_ostream_proxy out(con); + + if (first_update) + plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED); + // detect if the game was loaded or unloaded in the meantime void *new_wdata = NULL; void *new_mapdata = NULL; diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp deleted file mode 100644 index cf8a32fcc..000000000 --- a/library/DataDefs.cpp +++ /dev/null @@ -1,396 +0,0 @@ -/* -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" - -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); -}; - -bool type_identity::do_destroy_pod(void *obj) { - free(obj); - return true; -} - -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); - return true; - } - else - return false; -} - -bool type_identity::destroy(void *obj) { - if (can_allocate() && obj) - return do_destroy(obj); - 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. - */ -compound_identity *compound_identity::list = NULL; -std::vector compound_identity::top_scope; - -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) -{ - 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(); -} - -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, - const void *attrs, struct_identity *attr_type) - : 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), attrs(attrs), attr_type(attr_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) -{ - if (!has_children && actual != this) - return false; - - 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) -{ - if (!instance_ptr) return NULL; - - // Actually, a reader/writer lock would be sufficient, - // since the table is only written once per class. - tthread::lock_guard lock(*known_mutex); - - void *vtable = get_vtable(instance_ptr); - std::map::iterator it = known.find(vtable); - - if (it != known.end()) - return it->second; - - // If using a reader/writer lock, re-grab as write here, and recheck - Core &core = Core::getInstance(); - std::string name = core.p->doReadClassName(vtable); - - virtual_identity *actual = NULL; - - 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() - << "': found 0x" << std::hex << unsigned(vtable) - << ", previous 0x" << unsigned(p->vtable_ptr) << std::dec << std::endl; - abort(); - } else if (!p->vtable_ptr) { - std::cerr << "class '" << p->getName() << "': vtable = 0x" - << std::hex << unsigned(vtable) << std::dec << std::endl; - } - - known[vtable] = p; - p->vtable_ptr = vtable; - return p; - } - - std::cerr << "UNKNOWN CLASS '" << name << "': vtable = 0x" - << std::hex << unsigned(vtable) << std::dec << std::endl; - - known[vtable] = NULL; - return NULL; -} - -void virtual_identity::adjust_vtable(virtual_ptr obj, virtual_identity *main) -{ - if (vtable_ptr) { - *(void**)obj = vtable_ptr; - return; - } - - if (main && main != this && is_subclass(main)) - return; - - std::cerr << "Attempt to create class '" << getName() << "' without known vtable." << std::endl; - abort(); -} - -virtual_ptr virtual_identity::clone(virtual_ptr obj) -{ - virtual_identity *id = get(obj); - if (!id) return NULL; - virtual_ptr copy = id->instantiate(); - if (!copy) return NULL; - id->do_copy(copy, obj); - return copy; -} - -bool DFHack::findBitfieldField(unsigned *idx, const std::string &name, - unsigned size, const bitfield_item_info *items) -{ - for (unsigned i = 0; i < size; i++) { - if (items[i].name && items[i].name == name) - { - *idx = i; - return true; - } - } - - return false; -} - -void DFHack::setBitfieldField(void *p, unsigned idx, unsigned size, int value) -{ - uint8_t *data = ((uint8_t*)p) + (idx/8); - unsigned shift = idx%8; - uint32_t mask = ((1<> shift) - - if (!(mask & ~0xFFU)) ACCESS(uint8_t); - else if (!(mask & ~0xFFFFU)) ACCESS(uint16_t); - else ACCESS(uint32_t); - -#undef ACCESS -} - -void DFHack::bitfieldToString(std::vector *pvec, const void *p, - unsigned size, const bitfield_item_info *items) -{ - for (unsigned i = 0; i < size; i++) { - int value = getBitfieldField(p, i, std::min(1,items[i].size)); - - if (value) { - std::string name = format_key(items[i].name, i); - - if (items[i].size > 1) - name += stl_sprintf("=%u", value); - - pvec->push_back(name); - } - - if (items[i].size > 1) - i += items[i].size-1; - } -} - -int DFHack::findEnumItem(const std::string &name, int size, const char *const *items) -{ - for (int i = 0; i < size; i++) { - if (items[i] && items[i] == name) - return i; - } - - return -1; -} - -void DFHack::flagarrayToString(std::vector *pvec, const void *p, - int bytes, int base, int size, const char *const *items) -{ - for (unsigned i = 0; i < bytes*8; i++) { - int value = getBitfieldField(p, i, 1); - - if (value) - { - int ridx = int(i) - base; - const char *name = (ridx >= 0 && ridx < size) ? items[ridx] : NULL; - pvec->push_back(format_key(name, i)); - } - } -} diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index febc59026..917e44d38 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -45,6 +45,7 @@ distribution. #include "modules/Items.h" #include "modules/Materials.h" #include "modules/Maps.h" +#include "modules/MapCache.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -85,6 +86,33 @@ void push_pointer_vector(lua_State *state, const std::vector &pvec) } } +template +T *get_checked_arg(lua_State *state, int arg) +{ + luaL_checkany(state, arg); + + auto ptr = Lua::GetDFObject(state, arg); + if (!ptr) + luaL_argerror(state, arg, "invalid type"); + return ptr; +} + +int push_pos(lua_State *state, df::coord pos) +{ + if (!pos.isValid()) + { + lua_pushnil(state); + return 1; + } + else + { + lua_pushinteger(state, pos.x); + lua_pushinteger(state, pos.y); + lua_pushinteger(state, pos.z); + return 3; + } +} + /************************************************** * Per-world persistent configuration storage API * **************************************************/ @@ -599,23 +627,70 @@ static const luaL_Reg dfhack_job_funcs[] = { static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, getContainer), WRAPM(Units, setNickname), WRAPM(Units, getVisibleName), WRAPM(Units, getNemesis), WRAPM(Units, isDead), WRAPM(Units, isAlive), WRAPM(Units, isSane), + WRAPM(Units, clearBurrowMembers), WRAPM(Units, isInBurrow), WRAPM(Units, setInBurrow), { NULL, NULL } }; +static int units_getPosition(lua_State *state) +{ + return push_pos(state, Units::getPosition(get_checked_arg(state,1))); +} + +static const luaL_Reg dfhack_units_funcs[] = { + { "getPosition", units_getPosition }, + { NULL, NULL } +}; + +static bool items_moveToGround(df::item *item, df::coord pos) +{ + MapExtras::MapCache mc; + return Items::moveToGround(mc, item, pos); +} + +static bool items_moveToContainer(df::item *item, df::item *container) +{ + MapExtras::MapCache mc; + return Items::moveToContainer(mc, item, container); +} + static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, getOwner), WRAPM(Items, setOwner), + WRAPM(Items, getContainer), + WRAPN(moveToGround, items_moveToGround), + WRAPN(moveToContainer, items_moveToContainer), + { NULL, NULL } +}; + +static int items_getPosition(lua_State *state) +{ + return push_pos(state, Items::getPosition(get_checked_arg(state,1))); +} + +static int items_getContainedItems(lua_State *state) +{ + std::vector pvec; + Items::getContainedItems(get_checked_arg(state,1),&pvec); + push_pointer_vector(state, pvec); + return 1; +} + +static const luaL_Reg dfhack_items_funcs[] = { + { "getPosition", items_getPosition }, + { "getContainedItems", items_getContainedItems }, { NULL, NULL } }; + static bool maps_isBlockBurrowTile(df::burrow *burrow, df::map_block *block, int x, int y) { return Maps::isBlockBurrowTile(burrow, block, df::coord2d(x,y)); @@ -633,6 +708,7 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPM(Maps, getGlobalInitFeature), WRAPM(Maps, getLocalInitFeature), WRAPM(Maps, findBurrowByName), + WRAPM(Maps, clearBurrowTiles), WRAPN(isBlockBurrowTile, maps_isBlockBurrowTile), WRAPN(setBlockBurrowTile, maps_setBlockBurrowTile), WRAPM(Maps, isBurrowTile), @@ -642,14 +718,8 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { static int maps_listBurrowBlocks(lua_State *state) { - luaL_checkany(state, 1); - - auto ptr = Lua::GetDFObject(state, 1); - if (!ptr) - luaL_argerror(state, 1, "invalid burrow type"); - std::vector pvec; - Maps::listBurrowBlocks(&pvec, ptr); + Maps::listBurrowBlocks(&pvec, get_checked_arg(state,1)); push_pointer_vector(state, pvec); return 1; } @@ -672,7 +742,7 @@ void OpenDFHackApi(lua_State *state) LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); - OpenModule(state, "units", dfhack_units_module); - OpenModule(state, "items", dfhack_items_module); + OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); + OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs); OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs); } diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 471ffd550..5129ba648 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -745,6 +745,22 @@ static int lua_dfhack_with_suspend(lua_State *L) return lua_gettop(L); } +static int dfhack_open_plugin(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checktype(L, 2, LUA_TSTRING); + const char *name = lua_tostring(L, 2); + + PluginManager *pmgr = Core::getInstance().getPluginManager(); + Plugin *plugin = pmgr->getPluginByName(name); + + if (!plugin) + luaL_error(L, "plugin not found: '%s'", name); + + plugin->open_lua(L, 1); + return 0; +} + static const luaL_Reg dfhack_funcs[] = { { "print", lua_dfhack_print }, { "println", lua_dfhack_println }, @@ -757,6 +773,7 @@ static const luaL_Reg dfhack_funcs[] = { { "onerror", dfhack_onerror }, { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, + { "open_plugin", dfhack_open_plugin }, { NULL, NULL } }; diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 40269da80..f5ba565d5 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1030,6 +1030,12 @@ static int meta_global_newindex(lua_State *state) static int meta_call_function(lua_State *state) { auto id = (function_identity_base*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + return method_wrapper_core(state, id); +} + +int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id) +{ if (lua_gettop(state) != id->getNumArgs()) field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke"); diff --git a/library/PlugLoad-linux.cpp b/library/PlugLoad-linux.cpp index aaf7b2c33..69945c6f4 100644 --- a/library/PlugLoad-linux.cpp +++ b/library/PlugLoad-linux.cpp @@ -14,7 +14,7 @@ #include #include "DFHack.h" -#include "Core.h" +#include "PluginManager.h" #include "Hooks.h" #include diff --git a/library/PlugLoad-windows.cpp b/library/PlugLoad-windows.cpp index eadc9343d..f9a75df97 100644 --- a/library/PlugLoad-windows.cpp +++ b/library/PlugLoad-windows.cpp @@ -28,7 +28,7 @@ distribution. #include #include #include -#include "Core.h" +#include "PluginManager.h" #include "Hooks.h" #include diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index a7faabeec..4d0c06ddf 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -32,6 +32,8 @@ distribution. #include "DataDefs.h" #include "MiscUtils.h" +#include "LuaWrapper.h" + using namespace DFHack; #include @@ -107,8 +109,8 @@ struct Plugin::RefLock void lock_sub() { mut->lock(); - refcount --; - wakeup->notify_one(); + if (--refcount == 0) + wakeup->notify_one(); mut->unlock(); } void wait() @@ -130,6 +132,13 @@ struct Plugin::RefAutolock ~RefAutolock(){ lock->unlock(); }; }; +struct Plugin::RefAutoinc +{ + RefLock * lock; + RefAutoinc(RefLock * lck):lock(lck){ lock->lock_add(); }; + ~RefAutoinc(){ lock->lock_sub(); }; +}; + Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * pm) { filename = filepath; @@ -210,6 +219,7 @@ bool Plugin::load(color_ostream &con) plugin_shutdown = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_shutdown"); plugin_onstatechange = (command_result (*)(color_ostream &, state_change_event)) LookupPlugin(plug, "plugin_onstatechange"); plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect"); + index_lua(plug); this->name = *plug_name; plugin_lib = plug; commands.clear(); @@ -222,6 +232,7 @@ bool Plugin::load(color_ostream &con) else { con.printerr("Plugin %s has failed to initialize properly.\n", filename.c_str()); + reset_lua(); ClosePlugin(plugin_lib); state = PS_BROKEN; return false; @@ -235,13 +246,22 @@ bool Plugin::unload(color_ostream &con) // if we are actually loaded if(state == PS_LOADED) { + // notify the plugin about an attempt to shutdown + if (plugin_onstatechange && + plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) + { + con.printerr("Plugin %s has refused to be unloaded.\n", name.c_str()); + access->unlock(); + return false; + } + // wait for all calls to finish + access->wait(); // notify plugin about shutdown, if it has a shutdown function command_result cr = CR_OK; if(plugin_shutdown) cr = plugin_shutdown(con); - // wait for all calls to finish - access->wait(); // cleanup... + reset_lua(); parent->unregisterCommands(this); commands.clear(); if(cr == CR_OK) @@ -418,6 +438,90 @@ Plugin::plugin_state Plugin::getState() const return state; } +void Plugin::index_lua(DFLibrary *lib) +{ + if (auto cmdlist = (CommandReg*)LookupPlugin(lib, "plugin_lua_commands")) + { + for (; cmdlist->name; ++cmdlist) + { + auto &cmd = lua_commands[cmdlist->name]; + if (!cmd) cmd = new LuaCommand; + cmd->owner = this; + cmd->name = cmdlist->name; + cmd->command = cmdlist->command; + } + } + if (auto funlist = (FunctionReg*)LookupPlugin(lib, "plugin_lua_functions")) + { + for (; funlist->name; ++funlist) + { + auto &cmd = lua_functions[funlist->name]; + if (!cmd) cmd = new LuaFunction; + cmd->owner = this; + cmd->name = funlist->name; + cmd->identity = funlist->identity; + } + } +} + +void Plugin::reset_lua() +{ + for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it) + it->second->command = NULL; + for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) + it->second->identity = NULL; +} + +int Plugin::lua_cmd_wrapper(lua_State *state) +{ + auto cmd = (LuaCommand*)lua_touserdata(state, lua_upvalueindex(1)); + + RefAutoinc lock(cmd->owner->access); + + if (!cmd->command) + luaL_error(state, "plugin command %s() has been unloaded", + (cmd->owner->name+"."+cmd->name).c_str()); + + return cmd->command(state); +} + +int Plugin::lua_fun_wrapper(lua_State *state) +{ + auto cmd = (LuaFunction*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + RefAutoinc lock(cmd->owner->access); + + if (!cmd->identity) + luaL_error(state, "plugin function %s() has been unloaded", + (cmd->owner->name+"."+cmd->name).c_str()); + + return LuaWrapper::method_wrapper_core(state, cmd->identity); +} + +void Plugin::open_lua(lua_State *state, int table) +{ + table = lua_absindex(state, table); + + RefAutolock lock(access); + + for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it) + { + lua_pushlightuserdata(state, it->second); + lua_pushcclosure(state, lua_cmd_wrapper, 1); + lua_setfield(state, table, it->first.c_str()); + } + + for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) + { + lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN); + lua_pushlightuserdata(state, NULL); + lua_pushfstring(state, "%s.%s()", name.c_str(), it->second->name.c_str()); + lua_pushlightuserdata(state, it->second); + lua_pushcclosure(state, lua_fun_wrapper, 4); + lua_setfield(state, table, it->first.c_str()); + } +} + PluginManager::PluginManager(Core * core) { #ifdef LINUX_BUILD diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 00344d6a2..c8af46cbf 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -61,6 +61,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "df/world.h" #include "df/world_data.h" #include "df/unit.h" +#include "df/unit_misc_trait.h" #include "df/unit_soul.h" #include "df/unit_skill.h" #include "df/material.h" @@ -316,6 +317,19 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, } } + if (mask && mask->misc_traits()) + { + auto &vec = unit -> status.misc_traits; + + for (size_t i = 0; i < vec.size(); i++) + { + auto trait = vec[i]; + auto item = info->add_misc_traits(); + item->set_id(trait->id); + item->set_value(trait->value); + } + } + if (unit->curse.add_tags1.whole || unit->curse.add_tags2.whole || unit->curse.rem_tags1.whole || @@ -614,6 +628,20 @@ static command_result ListSquads(color_ostream &stream, return CR_OK; } +static command_result SetUnitLabors(color_ostream &stream, const SetUnitLaborsIn *in) +{ + for (size_t i = 0; i < in->change_size(); i++) + { + auto change = in->change(i); + auto unit = df::unit::find(change.unit_id()); + + if (unit) + unit->status.labors[change.labor()] = change.value(); + } + + return CR_OK; +} + CoreService::CoreService() { suspend_depth = 0; @@ -637,6 +665,8 @@ CoreService::CoreService() { addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE); addFunction("ListUnits", ListUnits); addFunction("ListSquads", ListSquads); + + addFunction("SetUnitLabors", SetUnitLabors); } CoreService::~CoreService() diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index cf63e3b1a..36dbd9aae 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -118,7 +118,8 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) string type, name, value; const char *cstr_type = pMemEntry->Value(); type = cstr_type; - if(type == "global-address") + bool is_vtable = (type == "vtable-address"); + if(is_vtable || type == "global-address") { const char *cstr_key = pMemEntry->Attribute("name"); if(!cstr_key) @@ -129,7 +130,11 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) cerr << "Dummy symbol table entry: " << cstr_key << endl; continue; } - mem->setAddress(cstr_key, strtol(cstr_value, 0, 0)); + uint32_t addr = strtol(cstr_value, 0, 0); + if (is_vtable) + mem->setVTable(cstr_key, addr); + else + mem->setAddress(cstr_key, addr); } else if (type == "md5-hash") { diff --git a/library/include/Core.h b/library/include/Core.h index 290a324b9..f8ac8e783 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -65,12 +65,6 @@ namespace DFHack { class df_window; } - // anon type, pretty much - struct DFLibrary; - - DFLibrary * OpenPlugin (const char * filename); - void * LookupPlugin (DFLibrary * plugin ,const char * function); - void ClosePlugin (DFLibrary * plugin); // Core is a singleton. Why? Because it is closely tied to SDL calls. It tracks the global state of DF. // There should never be more than one instance @@ -139,6 +133,8 @@ namespace DFHack static void print(const char *format, ...); static void printerr(const char *format, ...); + PluginManager *getPluginManager() { return plug_mgr; } + private: DFHack::Console con; diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index d78fe9e27..a949e784b 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -32,10 +32,6 @@ distribution. #include "DataIdentity.h" #include "LuaWrapper.h" -#ifndef BUILD_DFHACK_LIB -#error Due to export issues this header is internal to the main library. -#endif - namespace df { // A very simple and stupid implementation of some stuff from boost template struct is_same_type { static const bool value = false; }; @@ -50,7 +46,7 @@ namespace df { template::type,void>::value> struct function_wrapper {}; - class cur_lua_ostream_argument { + class DFHACK_EXPORT cur_lua_ostream_argument { DFHack::color_ostream *out; public: cur_lua_ostream_argument(lua_State *state); diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index bab87e1bb..279400f74 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -160,8 +160,6 @@ namespace DFHack }; } -// Due to export issues, this stuff can only work in the main dll -#ifdef BUILD_DFHACK_LIB namespace df { using DFHack::function_identity_base; @@ -171,7 +169,7 @@ namespace df using DFHack::ptr_container_identity; using DFHack::bit_container_identity; - class number_identity_base : public primitive_identity { + class DFHACK_EXPORT number_identity_base : public primitive_identity { const char *name; public: @@ -197,7 +195,7 @@ namespace df virtual void write(void *ptr, double val) { *(T*)ptr = T(val); } }; - class bool_identity : public primitive_identity { + class DFHACK_EXPORT bool_identity : public primitive_identity { public: bool_identity() : primitive_identity(sizeof(bool)) {}; @@ -207,7 +205,7 @@ namespace df virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); }; - class ptr_string_identity : public primitive_identity { + class DFHACK_EXPORT ptr_string_identity : public primitive_identity { public: ptr_string_identity() : primitive_identity(sizeof(char*)) {}; @@ -217,7 +215,7 @@ namespace df virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); }; - class stl_string_identity : public DFHack::constructed_identity { + class DFHACK_EXPORT stl_string_identity : public DFHack::constructed_identity { public: stl_string_identity() : constructed_identity(sizeof(std::string), &allocator_fn) @@ -233,7 +231,7 @@ namespace df virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); }; - class stl_ptr_vector_identity : public ptr_container_identity { + class DFHACK_EXPORT stl_ptr_vector_identity : public ptr_container_identity { public: typedef std::vector container; @@ -276,6 +274,8 @@ namespace df } }; +// Due to export issues, this stuff can only work in the main dll +#ifdef BUILD_DFHACK_LIB class buffer_container_identity : public container_identity { int size; @@ -370,8 +370,9 @@ namespace df ((container*)ptr)->set(idx, val); } }; +#endif - class stl_bit_vector_identity : public bit_container_identity { + class DFHACK_EXPORT stl_bit_vector_identity : public bit_container_identity { public: typedef std::vector container; @@ -400,6 +401,7 @@ namespace df } }; +#ifdef BUILD_DFHACK_LIB template class enum_list_attr_identity : public container_identity { public: @@ -421,9 +423,10 @@ namespace df return (void*)&((container*)ptr)->items[idx]; } }; +#endif #define NUMBER_IDENTITY_TRAITS(type) \ - template<> struct identity_traits { \ + template<> struct DFHACK_EXPORT identity_traits { \ static number_identity identity; \ static number_identity_base *get() { return &identity; } \ }; @@ -439,37 +442,37 @@ namespace df NUMBER_IDENTITY_TRAITS(uint64_t); NUMBER_IDENTITY_TRAITS(float); - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static bool_identity identity; static bool_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static stl_string_identity identity; static stl_string_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static ptr_string_identity identity; static ptr_string_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static ptr_string_identity identity; static ptr_string_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static pointer_identity identity; static pointer_identity *get() { return &identity; } }; - template<> struct identity_traits > { + template<> struct DFHACK_EXPORT identity_traits > { static stl_ptr_vector_identity identity; static stl_ptr_vector_identity *get() { return &identity; } }; - template<> struct identity_traits > { + template<> struct DFHACK_EXPORT identity_traits > { static stl_bit_vector_identity identity; static stl_bit_vector_identity *get() { return &identity; } }; @@ -478,14 +481,17 @@ namespace df // Container declarations +#ifdef BUILD_DFHACK_LIB template struct identity_traits > { static primitive_identity *get(); }; +#endif template struct identity_traits { static pointer_identity *get(); }; +#ifdef BUILD_DFHACK_LIB template struct identity_traits { static container_identity *get(); }; @@ -493,11 +499,13 @@ namespace df template struct identity_traits > { static container_identity *get(); }; +#endif template struct identity_traits > { static stl_ptr_vector_identity *get(); }; +#ifdef BUILD_DFHACK_LIB template struct identity_traits > { static container_identity *get(); }; @@ -518,13 +526,16 @@ namespace df template struct identity_traits > { static container_identity *get(); }; +#endif // Container definitions +#ifdef BUILD_DFHACK_LIB template inline primitive_identity *identity_traits >::get() { return identity_traits::get(); } +#endif template inline pointer_identity *identity_traits::get() { @@ -532,6 +543,7 @@ namespace df return &identity; } +#ifdef BUILD_DFHACK_LIB template inline container_identity *identity_traits::get() { static buffer_container_identity identity(sz, identity_traits::get()); @@ -544,6 +556,7 @@ namespace df static stl_container_identity identity("vector", identity_traits::get()); return &identity; } +#endif template inline stl_ptr_vector_identity *identity_traits >::get() { @@ -551,6 +564,7 @@ namespace df return &identity; } +#ifdef BUILD_DFHACK_LIB template inline container_identity *identity_traits >::get() { typedef std::deque container; @@ -576,5 +590,5 @@ namespace df static enum_list_attr_identity identity(identity_traits::get()); return &identity; } -} #endif +} diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 479ca0d67..2304b940d 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -30,6 +30,7 @@ distribution. #include #include "DataDefs.h" +#include "PluginManager.h" #include #include @@ -156,7 +157,7 @@ namespace DFHack { namespace LuaWrapper { * Verify that the object is a DF ref with UPVAL_METATABLE. * If everything ok, extract the address. */ - uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode); + DFHACK_EXPORT uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode); bool is_type_compatible(lua_State *state, type_identity *type1, int meta1, type_identity *type2, int meta2, bool exact_equal); @@ -221,16 +222,14 @@ namespace DFHack { namespace LuaWrapper { */ void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); - struct FunctionReg { - const char *name; - function_identity_base *identity; - }; - /** * Wrap functions and add them to the table on the top of the stack. */ + using DFHack::FunctionReg; void SetFunctionWrappers(lua_State *state, const FunctionReg *reg); + int method_wrapper_core(lua_State *state, function_identity_base *id); + void IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct); void AttachDFGlobals(lua_State *state); diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 8f02099be..080e66e66 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -33,7 +33,8 @@ distribution. #include "RemoteClient.h" -struct DFLibrary; +typedef struct lua_State lua_State; + namespace tthread { class mutex; @@ -49,6 +50,17 @@ namespace DFHack class PluginManager; class virtual_identity; class RPCService; + class function_identity_base; + + // anon type, pretty much + struct DFLibrary; + + // Open a plugin library + DFLibrary * OpenPlugin (const char * filename); + // find a symbol inside plugin + void * LookupPlugin (DFLibrary * plugin ,const char * function); + // Close a plugin library + void ClosePlugin (DFLibrary * plugin); enum state_change_event { @@ -56,7 +68,17 @@ namespace DFHack SC_WORLD_UNLOADED, SC_MAP_LOADED, SC_MAP_UNLOADED, - SC_VIEWSCREEN_CHANGED + SC_VIEWSCREEN_CHANGED, + SC_CORE_INITIALIZED, + SC_BEGIN_UNLOAD + }; + struct DFHACK_EXPORT CommandReg { + const char *name; + int (*command)(lua_State*); + }; + struct DFHACK_EXPORT FunctionReg { + const char *name; + function_identity_base *identity; }; struct DFHACK_EXPORT PluginCommand { @@ -102,6 +124,7 @@ namespace DFHack { struct RefLock; struct RefAutolock; + struct RefAutoinc; enum plugin_state { PS_UNLOADED, @@ -138,6 +161,9 @@ namespace DFHack { return name; } + + void open_lua(lua_State *state, int table); + private: RefLock * access; std::vector commands; @@ -147,6 +173,26 @@ namespace DFHack DFLibrary * plugin_lib; PluginManager * parent; plugin_state state; + + struct LuaCommand { + Plugin *owner; + std::string name; + int (*command)(lua_State *state); + }; + std::map lua_commands; + static int lua_cmd_wrapper(lua_State *state); + + struct LuaFunction { + Plugin *owner; + std::string name; + function_identity_base *identity; + }; + std::map lua_functions; + static int lua_fun_wrapper(lua_State *state); + + void index_lua(DFLibrary *lib); + void reset_lua(); + command_result (*plugin_init)(color_ostream &, std::vector &); command_result (*plugin_status)(color_ostream &, std::string &); command_result (*plugin_shutdown)(color_ostream &); @@ -199,5 +245,15 @@ namespace DFHack }; /// You have to have this in every plugin you write - just once. Ideally on top of the main file. -#define DFHACK_PLUGIN(plugin_name) DFhackDataExport const char * version = DFHACK_VERSION;\ -DFhackDataExport const char * name = plugin_name; +#define DFHACK_PLUGIN(plugin_name) \ + DFhackDataExport const char * version = DFHACK_VERSION;\ + DFhackDataExport const char * name = plugin_name; + +#define DFHACK_PLUGIN_LUA_COMMANDS \ + DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = +#define DFHACK_PLUGIN_LUA_FUNCTIONS \ + DFhackCExport const DFHack::FunctionReg plugin_lua_functions[] = + +#define DFHACK_LUA_COMMAND(name) { #name, name } +#define DFHACK_LUA_FUNCTION(name) { #name, df::wrap_function(name) } +#define DFHACK_LUA_END { NULL, NULL } diff --git a/library/include/TileTypes.h b/library/include/TileTypes.h index 26df7c3cd..55628b3df 100644 --- a/library/include/TileTypes.h +++ b/library/include/TileTypes.h @@ -203,6 +203,18 @@ namespace DFHack return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype)); } + inline + bool isWalkable(df::tiletype tiletype) + { + return ENUM_ATTR(tiletype_shape, walkable, tileShape(tiletype)); + } + + inline + bool isWalkableUp(df::tiletype tiletype) + { + return ENUM_ATTR(tiletype_shape, walkable_up, tileShape(tiletype)); + } + inline bool isWallTerrain(df::tiletype tiletype) { diff --git a/library/include/VersionInfo.h b/library/include/VersionInfo.h index d996722c0..5ff20516e 100644 --- a/library/include/VersionInfo.h +++ b/library/include/VersionInfo.h @@ -51,13 +51,15 @@ namespace DFHack std::vector md5_list; std::vector PE_list; std::map Addresses; + std::map VTables; uint32_t base; + int rebase_delta; std::string version; OSType OS; public: VersionInfo() { - base = 0; + base = 0; rebase_delta = 0; version = "invalid"; OS = OS_BAD; }; @@ -66,12 +68,15 @@ namespace DFHack md5_list = rhs.md5_list; PE_list = rhs.PE_list; Addresses = rhs.Addresses; + VTables = rhs.VTables; base = rhs.base; + rebase_delta = rhs.rebase_delta; version = rhs.version; OS = rhs.OS; }; uint32_t getBase () const { return base; }; + int getRebaseDelta() const { return rebase_delta; } void setBase (const uint32_t _base) { base = _base; }; void rebaseTo(const uint32_t new_base) { @@ -79,13 +84,11 @@ namespace DFHack int64_t newx = new_base; int64_t rebase = newx - old; base = new_base; - auto iter = Addresses.begin(); - while (iter != Addresses.end()) - { - uint32_t & ref = (*iter).second; - ref += rebase; - iter ++; - } + rebase_delta += rebase; + for (auto iter = Addresses.begin(); iter != Addresses.end(); ++iter) + iter->second += rebase; + for (auto iter = VTables.begin(); iter != VTables.end(); ++iter) + iter->second += rebase; }; void addMD5 (const std::string & _md5) @@ -125,6 +128,7 @@ namespace DFHack value = (T) (*i).second; return true; }; + uint32_t getAddress (const std::string& key) const { auto i = Addresses.find(key); @@ -133,6 +137,18 @@ namespace DFHack return (*i).second; } + void setVTable (const std::string& key, const uint32_t value) + { + VTables[key] = value; + }; + void *getVTable (const std::string& key) const + { + auto i = VTables.find(key); + if(i == VTables.end()) + return 0; + return (void*)i->second; + } + void setOS(const OSType os) { OS = os; diff --git a/library/include/df/custom/block_burrow.methods.inc b/library/include/df/custom/block_burrow.methods.inc index 7754ab0db..216a58662 100644 --- a/library/include/df/custom/block_burrow.methods.inc +++ b/library/include/df/custom/block_burrow.methods.inc @@ -16,4 +16,11 @@ inline void setassignment( int x, int y, bool bit ) tile_bitmask[y] |= (1 << x); else tile_bitmask[y] &= ~(1 << x); -} \ No newline at end of file +} +bool has_assignments() +{ + for (int i = 0; i < 16; i++) + if (tile_bitmask[i]) + return true; + return false; +} diff --git a/library/include/df/custom/block_square_event_mineralst.methods.inc b/library/include/df/custom/block_square_event_mineralst.methods.inc index 7754ab0db..216a58662 100644 --- a/library/include/df/custom/block_square_event_mineralst.methods.inc +++ b/library/include/df/custom/block_square_event_mineralst.methods.inc @@ -16,4 +16,11 @@ inline void setassignment( int x, int y, bool bit ) tile_bitmask[y] |= (1 << x); else tile_bitmask[y] &= ~(1 << x); -} \ No newline at end of file +} +bool has_assignments() +{ + for (int i = 0; i < 16; i++) + if (tile_bitmask[i]) + return true; + return false; +} diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 9bab02cc8..e7720fa91 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -42,6 +42,10 @@ namespace df struct itemdef; } +namespace MapExtras { + class MapCache; +} + /** * \defgroup grp_items Items module and its types * @ingroup grp_modules @@ -128,13 +132,15 @@ DFHACK_EXPORT df::unit *getOwner(df::item *item); DFHACK_EXPORT bool setOwner(df::item *item, df::unit *unit); /// which item is it contained in? -DFHACK_EXPORT int32_t getItemContainerID(const df::item * item); -DFHACK_EXPORT df::item *getItemContainer(const df::item * item); +DFHACK_EXPORT df::item *getContainer(df::item *item); /// which items does it contain? -DFHACK_EXPORT bool getContainedItems(const df::item * item, /*output*/ std::vector &items); +DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector *items); + +/// Returns the true position of the item. +DFHACK_EXPORT df::coord getPosition(df::item *item); + +DFHACK_EXPORT bool moveToGround(MapExtras::MapCache &mc, df::item *item, df::coord pos); +DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container); -/// read item references, filtered by class -DFHACK_EXPORT bool readItemRefs(const df::item * item, const df::general_ref_type type, - /*output*/ std::vector &values); } } \ No newline at end of file diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 0de262264..b7341f87c 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -262,12 +262,20 @@ extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, d DFHACK_EXPORT df::burrow *findBurrowByName(std::string name); -void listBurrowBlocks(std::vector *pvec, df::burrow *burrow); +DFHACK_EXPORT void listBurrowBlocks(std::vector *pvec, df::burrow *burrow); +DFHACK_EXPORT void clearBurrowTiles(df::burrow *burrow); -df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false); +DFHACK_EXPORT df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false); +DFHACK_EXPORT bool deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block, df::block_burrow *mask); -bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile); -bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable); +inline bool deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block) +{ + return deleteBlockBurrowMask(burrow, block, getBlockBurrowMask(burrow, block)); +} + + +DFHACK_EXPORT bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile); +DFHACK_EXPORT bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable); inline bool isBurrowTile(df::burrow *burrow, df::coord tile) { return isBlockBurrowTile(burrow, getTileBlock(tile), tile); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 12e687112..e093ed1ef 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -193,6 +193,11 @@ DFHACK_EXPORT int32_t GetDwarfCivId ( void ); DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); +/// Returns the true position of the unit (non-trivial in case of caged). +DFHACK_EXPORT df::coord getPosition(df::unit *unit); + +DFHACK_EXPORT df::item *getContainer(df::unit *unit); + DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); @@ -201,6 +206,10 @@ DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit); +DFHACK_EXPORT bool isCitizen(df::unit *unit); +DFHACK_EXPORT bool isDwarf(df::unit *unit); + +DFHACK_EXPORT void clearBurrowMembers(df::burrow *burrow); DFHACK_EXPORT bool isInBurrow(df::unit *unit, df::burrow *burrow); DFHACK_EXPORT void setInBurrow(df::unit *unit, df::burrow *burrow, bool enable); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index a6606d152..7230a12aa 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -55,6 +55,10 @@ function mkmodule(module,env) error("Not a table in package.loaded["..module.."]") end end + local plugname = string.match(module,'^plugins%.(%w+)$') + if plugname then + dfhack.open_plugin(pkg,plugname) + end setmetatable(pkg, { __index = (env or _G) }) return pkg end @@ -86,6 +90,21 @@ function copyall(table) return rv end +function pos2xyz(pos) + local x = pos.x + if x and x ~= -30000 then + return x, pos.y, pos.z + end +end + +function xyz2pos(x,y,z) + if x then + return {x=x,y=y,z=z} + else + return {x=-30000,y=-30000,z=-30000} + end +end + function dfhack.persistent:__tostring() return "" diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index d884a734e..9d0657e19 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -39,6 +39,7 @@ using namespace std; #include "modules/Materials.h" #include "modules/Items.h" #include "modules/Units.h" +#include "modules/MapCache.h" #include "ModuleFactory.h" #include "Core.h" #include "Error.h" @@ -46,6 +47,7 @@ using namespace std; #include "df/world.h" #include "df/item.h" +#include "df/building.h" #include "df/tool_uses.h" #include "df/itemdef_weaponst.h" #include "df/itemdef_trapcompst.h" @@ -65,6 +67,8 @@ using namespace std; #include "df/job_item.h" #include "df/general_ref.h" #include "df/general_ref_unit_itemownerst.h" +#include "df/general_ref_contains_itemst.h" +#include "df/general_ref_contained_in_itemst.h" using namespace DFHack; using namespace df::enums; @@ -454,6 +458,8 @@ bool Items::setOwner(df::item *item, df::unit *unit) vector_erase_at(item->itemrefs, i); } + item->flags.bits.owned = false; + if (unit) { auto ref = df::allocate(); @@ -466,50 +472,193 @@ bool Items::setOwner(df::item *item, df::unit *unit) insert_into_vector(unit->owned_items, item->id); item->itemrefs.push_back(ref); } - else - item->flags.bits.owned = false; return true; } - -int32_t Items::getItemContainerID(const df::item * item) +df::item *Items::getContainer(df::item * item) { + CHECK_NULL_POINTER(item); + for (size_t i = 0; i < item->itemrefs.size(); i++) { df::general_ref *ref = item->itemrefs[i]; if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM) - return ref->getID(); + return ref->getItem(); } - return -1; + + return NULL; } -df::item *Items::getItemContainer(const df::item * item) +void Items::getContainedItems(df::item *item, std::vector *items) { + CHECK_NULL_POINTER(item); + + items->clear(); + for (size_t i = 0; i < item->itemrefs.size(); i++) { df::general_ref *ref = item->itemrefs[i]; - if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM) - return ref->getItem(); + if (ref->getType() != general_ref_type::CONTAINS_ITEM) + continue; + + auto child = ref->getItem(); + if (child) + items->push_back(child); } - return NULL; } -bool Items::getContainedItems(const df::item * item, std::vector &items) +df::coord Items::getPosition(df::item *item) +{ + CHECK_NULL_POINTER(item); + + if (item->flags.bits.in_inventory || + item->flags.bits.in_chest || + item->flags.bits.in_building) + { + for (size_t i = 0; i < item->itemrefs.size(); i++) + { + df::general_ref *ref = item->itemrefs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + if (auto item2 = ref->getItem()) + return getPosition(item2); + break; + + case general_ref_type::UNIT_HOLDER: + if (auto unit = ref->getUnit()) + return Units::getPosition(unit); + break; + + case general_ref_type::BUILDING_HOLDER: + if (auto bld = ref->getBuilding()) + return df::coord(bld->centerx, bld->centery, bld->z); + break; + } + } + } + + return item->pos; +} + +static void removeRef(std::vector &vec, df::general_ref_type type, int id) { - return readItemRefs(item, general_ref_type::CONTAINS_ITEM, items); + for (int i = vec.size()-1; i >= 0; i--) + { + df::general_ref *ref = vec[i]; + if (ref->getType() != type || ref->getID() != id) + continue; + + vector_erase_at(vec, i); + delete ref; + } } -bool Items::readItemRefs(const df::item * item, df::general_ref_type type, std::vector &values) +static bool detachItem(MapExtras::MapCache &mc, df::item *item) { - values.clear(); + if (item->flags.bits.on_ground) + { + if (!mc.removeItemOnGround(item)) + Core::printerr("Item was marked on_ground, but not in block: %d (%d,%d,%d)\n", + item->id, item->pos.x, item->pos.y, item->pos.z); - for (size_t i = 0; i < item->itemrefs.size(); i++) + item->flags.bits.on_ground = false; + return true; + } + else if (item->flags.bits.in_inventory) { - df::general_ref *ref = item->itemrefs[i]; - if (ref->getType() == type) - values.push_back(ref->getID()); + bool found = false; + + for (int i = item->itemrefs.size()-1; i >= 0; i--) + { + df::general_ref *ref = item->itemrefs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + if (auto item2 = ref->getItem()) + { + item2->flags.bits.weight_computed = false; + + removeRef(item2->itemrefs, general_ref_type::CONTAINS_ITEM, item->id); + } + break; + + case general_ref_type::UNIT_HOLDER: + case general_ref_type::BUILDING_HOLDER: + return false; + + default: + continue; + } + + found = true; + vector_erase_at(item->itemrefs, i); + delete ref; + } + + if (!found) + return false; + + item->flags.bits.in_inventory = false; + return true; + } + else + return false; +} + +static void putOnGround(MapExtras::MapCache &mc, df::item *item, df::coord pos) +{ + item->pos = pos; + item->flags.bits.on_ground = true; + + if (!mc.addItemOnGround(item)) + Core::printerr("Could not add item %d to ground at (%d,%d,%d)\n", + item->id, pos.x, pos.y, pos.z); +} + +bool DFHack::Items::moveToGround(MapExtras::MapCache &mc, df::item *item, df::coord pos) +{ + CHECK_NULL_POINTER(item); + + if (!detachItem(mc, item)) + return false; + + putOnGround(mc, item, pos); + return true; +} + +bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container) +{ + CHECK_NULL_POINTER(item); + CHECK_NULL_POINTER(container); + + if (!detachItem(mc, item)) + return false; + + auto ref1 = df::allocate(); + auto ref2 = df::allocate(); + + if (!ref1 || !ref2) + { + delete ref1; delete ref2; + Core::printerr("Could not allocate container refs.\n"); + putOnGround(mc, item, getPosition(container)); + return false; } - return !values.empty(); + item->pos = container->pos; + item->flags.bits.in_inventory = true; + container->flags.bits.container = true; + + container->flags.bits.weight_computed = false; + + ref1->item_id = item->id; + container->itemrefs.push_back(ref1); + ref2->item_id = container->id; + item->itemrefs.push_back(ref2); + + return true; } diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index c0aa9b634..327e26986 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -596,7 +596,7 @@ void MapExtras::Block::init_item_counts() if (item_counts) return; item_counts = new T_item_counts[16]; - memset(item_counts, 0, sizeof(T_item_counts)); + memset(item_counts, 0, sizeof(T_item_counts)*16); if (!block) return; @@ -715,6 +715,42 @@ void Maps::listBurrowBlocks(std::vector *pvec, df::burrow *burro } } +static void destroyBurrowMask(df::block_burrow *mask) +{ + if (!mask) return; + + auto link = mask->link; + + link->prev->next = link->next; + if (link->next) + link->next->prev = link->prev; + delete link; + + delete mask; +} + +void Maps::clearBurrowTiles(df::burrow *burrow) +{ + CHECK_NULL_POINTER(burrow); + + df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); + + for (size_t i = 0; i < burrow->block_x.size(); i++) + { + df::coord pos(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); + + auto block = getBlock(pos - base); + if (!block) + continue; + + destroyBurrowMask(getBlockBurrowMask(burrow, block)); + } + + burrow->block_x.clear(); + burrow->block_y.clear(); + burrow->block_z.clear(); +} + df::block_burrow *Maps::getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create) { CHECK_NULL_POINTER(burrow); @@ -754,6 +790,36 @@ df::block_burrow *Maps::getBlockBurrowMask(df::burrow *burrow, df::map_block *bl return NULL; } +bool Maps::deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block, df::block_burrow *mask) +{ + CHECK_NULL_POINTER(burrow); + CHECK_NULL_POINTER(block); + + if (!mask) + return false; + + df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); + df::coord pos = base + block->map_pos/16; + + destroyBurrowMask(mask); + + for (size_t i = 0; i < burrow->block_x.size(); i++) + { + df::coord cur(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); + + if (cur == pos) + { + vector_erase_at(burrow->block_x, i); + vector_erase_at(burrow->block_y, i); + vector_erase_at(burrow->block_z, i); + + break; + } + } + + return true; +} + bool Maps::isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile) { CHECK_NULL_POINTER(burrow); @@ -774,8 +840,13 @@ bool Maps::setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coor auto mask = getBlockBurrowMask(burrow, block, enable); if (mask) + { mask->setassignment(tile & 15, enable); + if (!enable && !mask->has_assignments()) + deleteBlockBurrowMask(burrow, block, mask); + } + return true; } diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 3e58e0d5c..8b44d4cf9 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -40,6 +40,7 @@ using namespace std; // we connect to those #include "modules/Units.h" +#include "modules/Items.h" #include "modules/Materials.h" #include "modules/Translation.h" #include "ModuleFactory.h" @@ -500,6 +501,34 @@ void Units::CopyNameTo(df::unit * creature, df::language_name * target) Translation::copyName(&creature->name, target); } +df::coord Units::getPosition(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + if (unit->flags1.bits.caged) + { + auto cage = getContainer(unit); + if (cage) + return Items::getPosition(cage); + } + + return unit->pos; +} + +df::item *Units::getContainer(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + for (size_t i = 0; i < unit->refs.size(); i++) + { + df::general_ref *ref = unit->refs[i]; + if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM) + return ref->getItem(); + } + + return NULL; +} + void Units::setNickname(df::unit *unit, std::string nick) { CHECK_NULL_POINTER(unit); @@ -624,6 +653,51 @@ bool DFHack::Units::isSane(df::unit *unit) return true; } +bool DFHack::Units::isCitizen(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + return unit->civ_id == ui->civ_id && + !unit->flags1.bits.merchant && + !unit->flags1.bits.diplomat && + !unit->flags2.bits.resident && + !unit->flags1.bits.dead && + !unit->flags3.bits.ghostly; +} + +bool DFHack::Units::isDwarf(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + return unit->race == ui->race_id; +} + +void DFHack::Units::clearBurrowMembers(df::burrow *burrow) +{ + CHECK_NULL_POINTER(burrow); + + for (size_t i = 0; i < burrow->units.size(); i++) + { + auto unit = df::unit::find(burrow->units[i]); + + if (unit) + erase_from_vector(unit->burrows, burrow->id); + } + + burrow->units.clear(); + + // Sync ui if active + if (ui && ui->main.mode == ui_sidebar_mode::Burrows && + ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id) + { + auto &sel = ui->burrows.sel_units; + + for (size_t i = 0; i < sel.size(); i++) + sel[i] = false; + } +} + + bool DFHack::Units::isInBurrow(df::unit *unit, df::burrow *burrow) { CHECK_NULL_POINTER(unit); diff --git a/library/proto/Basic.proto b/library/proto/Basic.proto index 246fba22e..c387bef01 100755 --- a/library/proto/Basic.proto +++ b/library/proto/Basic.proto @@ -130,6 +130,11 @@ message SkillInfo { required int32 experience = 3; }; +message UnitMiscTrait { + required int32 id = 1; + required int32 value = 2; +}; + message BasicUnitInfo { required int32 unit_id = 1; @@ -166,6 +171,9 @@ message BasicUnitInfo { // IF mask.skills: repeated SkillInfo skills = 12; + // IF mask.misc_traits: + repeated UnitMiscTrait misc_traits = 24; + optional UnitCurseInfo curse = 16; repeated int32 burrows = 21; @@ -175,6 +183,7 @@ message BasicUnitInfoMask { optional bool labors = 1 [default = false]; optional bool skills = 2 [default = false]; optional bool profession = 3 [default = false]; + optional bool misc_traits = 4 [default = false]; }; message BasicSquadInfo { @@ -188,3 +197,9 @@ message BasicSquadInfo { // Member histfig ids: repeated sint32 members = 4; }; + +message UnitLaborState { + required int32 unit_id = 1; + required int32 labor = 2; + required bool value = 3; +}; diff --git a/library/proto/BasicApi.proto b/library/proto/BasicApi.proto index 3072f9cad..a5a07aa1c 100755 --- a/library/proto/BasicApi.proto +++ b/library/proto/BasicApi.proto @@ -99,4 +99,9 @@ message ListUnitsOut { message ListSquadsIn {} message ListSquadsOut { repeated BasicSquadInfo value = 1; -} +}; + +// RPC SetUnitLabors : SetUnitLaborsIn -> EmptyMessage +message SetUnitLaborsIn { + repeated UnitLaborState change = 1; +}; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6c2a537d1..b520c5f19 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -20,10 +20,11 @@ if(BUILD_DEV_PLUGINS) add_subdirectory (devel) endif() -OPTION(BUILD_DF2MC "Build DF2MC (needs a checkout first)." OFF) -if(BUILD_DF2MC) - add_subdirectory (df2mc) -endif() +#It's dead :< +#OPTION(BUILD_DF2MC "Build DF2MC (needs a checkout first)." OFF) +#if(BUILD_DF2MC) +# add_subdirectory (df2mc) +#endif() OPTION(BUILD_MAPEXPORT "Build map exporter." ON) if (BUILD_MAPEXPORT) @@ -35,6 +36,10 @@ if (BUILD_DWARFEXPORT) add_subdirectory (dwarfexport) endif() +install(DIRECTORY lua/ + DESTINATION ${DFHACK_LUA_DESTINATION}/plugins + FILES_MATCHING PATTERN "*.lua") + # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) @@ -97,6 +102,8 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(feature feature.cpp) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(zone zone.cpp) + # this one exports functions to lua + DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() diff --git a/plugins/Dfusion/luafiles/init.lua b/plugins/Dfusion/luafiles/init.lua index 358f93d00..e68684bf7 100644 --- a/plugins/Dfusion/luafiles/init.lua +++ b/plugins/Dfusion/luafiles/init.lua @@ -86,14 +86,15 @@ loadall(plugins) dofile_silent("dfusion/initcustom.lua") local args={...} -for k,v in pairs(args) do - local f,err=load(v) - if f then - f() - else - Console.printerr(err) - end + + +local f,err=load(table.concat(args,' ')) +if f then + f() +else + Console.printerr(err) end + if not INIT then mainmenu(plugins) end diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index e690e9412..b01157c87 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -274,19 +274,50 @@ function tools.changesite(names) print(string.format("%x->%d",off,n2)) engine.poke(off,ptr_site.type,n2) end -function tools.project(unit) +function tools.project(unit,trg) if unit==nil then - unit=getSelectedUnit() - end - - if unit==nil then - unit=getCreatureAtPos(getxyz()) + unit=getCreatureAtPointer() end if unit==nil then error("Failed to project unit. Unit not selected/valid") end -- todo: add projectile to world, point to unit, add flag to unit, add gen-ref to projectile. + local p=df.proj_unitst:new() + local startpos={x=unit.pos.x,y=unit.pos.y,z=unit.pos.z} + p.origin_pos=startpos + p.target_pos=trg + p.cur_pos=startpos + p.prev_pos=startpos + p.unit=unit + --- wtf stuff + p.unk14=100 + p.unk16=-1 + p.unk23=-1 + p.fall_delay=5 + p.fall_counter=5 + p.collided=true + -- end wtf + local citem=df.global.world.proj_list + local maxid=1 + local newlink=df.proj_list_link:new() + newlink.item=p + while citem.item~= nil do + if citem.item.id>maxid then maxid=citem.item.id end + if citem.next ~= nil then + citem=citem.next + else + break + end + end + p.id=maxid+1 + newlink.prev=citem + citem.next=newlink + + local proj_ref=df.general_ref_projectile:new() + proj_ref.projectile_id=p.id + unit.refs:insert(#unit.refs,proj_ref) + unit.flags1.projectile=true end function tools.empregnate(unit) if unit==nil then diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index a71555b7a..2620d9dea 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -182,15 +182,7 @@ static command_result autodump_main(color_ostream &out, vector & parame // Don't move items if they're already at the cursor if (pos_cursor != pos_item) - { - if (!MC.removeItemOnGround(itm)) - out.printerr("Item %d wasn't in the source block.\n", itm->id); - - itm->pos = pos_cursor; - - if (!MC.addItemOnGround(itm)) - out.printerr("Could not add item %d to destination block.\n", itm->id); - } + Items::moveToGround(MC, itm, pos_cursor); } else // destroy { diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp new file mode 100644 index 000000000..74cb57c45 --- /dev/null +++ b/plugins/burrows.cpp @@ -0,0 +1,678 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "Error.h" + +#include "DataFuncs.h" + +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/Maps.h" +#include "modules/MapCache.h" +#include "modules/World.h" +#include "modules/Units.h" +#include "TileTypes.h" + +#include "DataDefs.h" +#include "df/ui.h" +#include "df/world.h" +#include "df/unit.h" +#include "df/burrow.h" +#include "df/map_block.h" +#include "df/block_burrow.h" +#include "df/job.h" +#include "df/job_list_link.h" + +#include "MiscUtils.h" + +#include + +using std::vector; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; +using namespace dfproto; + +using df::global::ui; +using df::global::world; + +/* + * Initialization. + */ + +static command_result burrow(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("burrows"); + +static void init_map(color_ostream &out); +static void deinit_map(color_ostream &out); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "burrow", "Miscellaneous burrow control.", burrow, false, + " burrow enable options...\n" + " burrow disable options...\n" + " Enable or disable features of the plugin.\n" + " See below for a list and explanation.\n" + " burrow clear-units burrow burrow...\n" + " burrow clear-tiles burrow burrow...\n" + " Removes all units or tiles from the burrows.\n" + " burrow set-units target-burrow src-burrow...\n" + " burrow add-units target-burrow src-burrow...\n" + " burrow remove-units target-burrow src-burrow...\n" + " Adds or removes units in source burrows to/from the target\n" + " burrow. Set is equivalent to clear and add.\n" + " burrow set-tiles target-burrow src-burrow...\n" + " burrow add-tiles target-burrow src-burrow...\n" + " burrow remove-tiles target-burrow src-burrow...\n" + " Adds or removes tiles in source burrows to/from the target\n" + " burrow. In place of a source burrow it is possible to use\n" + " one of the following keywords:\n" + " ABOVE_GROUND, SUBTERRANEAN, INSIDE, OUTSIDE,\n" + " LIGHT, DARK, HIDDEN, REVEALED\n" + "Implemented features:\n" + " auto-grow\n" + " When a wall inside a burrow with a name ending in '+' is dug\n" + " out, the burrow is extended to newly-revealed adjacent walls.\n" + " This final '+' may be omitted in burrow name args of commands above.\n" + " Note: Digging 1-wide corridors with the miner inside the burrow is SLOW.\n" + )); + + if (Core::getInstance().isMapLoaded()) + init_map(out); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + deinit_map(out); + + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + deinit_map(out); + if (df::global::game_mode && + *df::global::game_mode == GAMEMODE_DWARF) + init_map(out); + break; + case SC_MAP_UNLOADED: + deinit_map(out); + break; + default: + break; + } + + return CR_OK; +} + +/* + * State change tracking. + */ + +static int name_burrow_id = -1; + +static void handle_burrow_rename(color_ostream &out, df::burrow *burrow); + +static void detect_burrow_renames(color_ostream &out) +{ + if (ui->main.mode == ui_sidebar_mode::Burrows && + ui->burrows.in_edit_name_mode && + ui->burrows.sel_id >= 0) + { + name_burrow_id = ui->burrows.sel_id; + } + else if (name_burrow_id >= 0) + { + auto burrow = df::burrow::find(name_burrow_id); + name_burrow_id = -1; + if (burrow) + handle_burrow_rename(out, burrow); + } +} + +struct DigJob { + int id; + df::job_type job; + df::coord pos; + df::tiletype old_tile; +}; + +static int next_job_id_save = 0; +static std::map diggers; + +static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos, + df::tiletype old_tile, df::tiletype new_tile); + +static void detect_digging(color_ostream &out) +{ + for (auto it = diggers.begin(); it != diggers.end();) + { + auto worker = df::unit::find(it->first); + + if (!worker || !worker->job.current_job || + worker->job.current_job->id != it->second.id) + { + //out.print("Dig job %d expired.\n", it->second.id); + + df::coord pos = it->second.pos; + + if (auto block = Maps::getTileBlock(pos)) + { + df::tiletype new_tile = block->tiletype[pos.x&15][pos.y&15]; + + //out.print("Tile %d -> %d\n", it->second.old_tile, new_tile); + + if (new_tile != it->second.old_tile) + { + handle_dig_complete(out, it->second.job, pos, it->second.old_tile, new_tile); + + //if (worker && !worker->job.current_job) + // worker->counters.think_counter = worker->counters.job_counter = 0; + } + } + + auto cur = it; ++it; diggers.erase(cur); + } + else + ++it; + } + + std::vector jvec; + + if (Job::listNewlyCreated(&jvec, &next_job_id_save)) + { + for (size_t i = 0; i < jvec.size(); i++) + { + auto job = jvec[i]; + auto type = ENUM_ATTR(job_type, type, job->job_type); + if (type != job_type_class::Digging) + continue; + + auto worker = Job::getWorker(job); + if (!worker) + continue; + + df::coord pos = job->pos; + auto block = Maps::getTileBlock(pos); + if (!block) + continue; + + auto &info = diggers[worker->id]; + + //out.print("New dig job %d.\n", job->id); + + info.id = job->id; + info.job = job->job_type; + info.pos = pos; + info.old_tile = block->tiletype[pos.x&15][pos.y&15]; + } + } +} + +static bool active = false; +static bool auto_grow = false; +static std::vector grow_burrows; + +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + if (!active) + return CR_OK; + + detect_burrow_renames(out); + + if (auto_grow) + detect_digging(out); + + return CR_OK; +} + +/* + * Config and processing + */ + +static std::map name_lookup; + +static void parse_names() +{ + auto &list = ui->burrows.list; + + grow_burrows.clear(); + name_lookup.clear(); + + for (size_t i = 0; i < list.size(); i++) + { + auto burrow = list[i]; + + std::string name = burrow->name; + + if (!name.empty()) + { + name_lookup[name] = burrow->id; + + if (name[name.size()-1] == '+') + { + grow_burrows.push_back(burrow->id); + name.resize(name.size()-1); + } + + if (!name.empty()) + name_lookup[name] = burrow->id; + } + } +} + +static void reset_tracking() +{ + diggers.clear(); + next_job_id_save = 0; +} + +static void init_map(color_ostream &out) +{ + auto config = Core::getInstance().getWorld()->GetPersistentData("burrows/config"); + if (config.isValid()) + { + auto_grow = !!(config.ival(0) & 1); + } + + parse_names(); + name_burrow_id = -1; + + reset_tracking(); + active = true; + + if (auto_grow && !grow_burrows.empty()) + out.print("Auto-growing %d burrows.\n", grow_burrows.size()); +} + +static void deinit_map(color_ostream &out) +{ + active = false; + auto_grow = false; + reset_tracking(); +} + +static PersistentDataItem create_config(color_ostream &out) +{ + bool created; + auto rv = Core::getInstance().getWorld()->GetPersistentData("burrows/config", &created); + if (created && rv.isValid()) + rv.ival(0) = 0; + if (!rv.isValid()) + out.printerr("Could not write configuration."); + return rv; +} + +static void enable_auto_grow(color_ostream &out, bool enable) +{ + if (enable == auto_grow) + return; + + auto config = create_config(out); + if (!config.isValid()) + return; + + if (enable) + config.ival(0) |= 1; + else + config.ival(0) &= ~1; + + auto_grow = enable; + + if (enable) + reset_tracking(); +} + +static void handle_burrow_rename(color_ostream &out, df::burrow *burrow) +{ + parse_names(); +} + +static void add_to_burrows(std::vector &burrows, df::coord pos) +{ + for (size_t i = 0; i < burrows.size(); i++) + Maps::setBurrowTile(burrows[i], pos, true); +} + +static void add_walls_to_burrows(color_ostream &out, std::vector &burrows, + MapExtras::MapCache &mc, df::coord pos1, df::coord pos2) +{ + for (int x = pos1.x; x <= pos2.x; x++) + { + for (int y = pos1.y; y <= pos2.y; y++) + { + for (int z = pos1.z; z <= pos2.z; z++) + { + df::coord pos(x,y,z); + + auto tile = mc.tiletypeAt(pos); + + if (isWallTerrain(tile)) + add_to_burrows(burrows, pos); + } + } + } +} + +static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos, + df::tiletype old_tile, df::tiletype new_tile) +{ + if (!isWalkable(new_tile)) + return; + + std::vector to_grow; + + for (size_t i = 0; i < grow_burrows.size(); i++) + { + auto b = df::burrow::find(grow_burrows[i]); + if (b && Maps::isBurrowTile(b, pos)) + to_grow.push_back(b); + } + + //out.print("%d to grow.\n", to_grow.size()); + + if (to_grow.empty()) + return; + + MapExtras::MapCache mc; + + if (!isWalkable(old_tile)) + { + add_walls_to_burrows(out, to_grow, mc, pos+df::coord(-1,-1,0), pos+df::coord(1,1,0)); + + if (isWalkableUp(new_tile)) + add_to_burrows(to_grow, pos+df::coord(0,0,1)); + + if (tileShape(new_tile) == tiletype_shape::RAMP) + { + add_walls_to_burrows(out, to_grow, mc, + pos+df::coord(-1,-1,1), pos+df::coord(1,1,1)); + } + } + + if (LowPassable(new_tile) && !LowPassable(old_tile)) + { + add_to_burrows(to_grow, pos-df::coord(0,0,1)); + + if (tileShape(new_tile) == tiletype_shape::RAMP_TOP) + { + add_walls_to_burrows(out, to_grow, mc, + pos+df::coord(-1,-1,-1), pos+df::coord(1,1,-1)); + } + } +} + +static df::burrow *findByName(color_ostream &out, std::string name, bool silent = false) +{ + int id = -1; + if (name_lookup.count(name)) + id = name_lookup[name]; + auto rv = df::burrow::find(id); + if (!rv && !silent) + out.printerr("Burrow not found: '%s'\n", name.c_str()); + return rv; +} + +static void copyUnits(df::burrow *target, df::burrow *source, bool enable) +{ + CHECK_NULL_POINTER(target); + CHECK_NULL_POINTER(source); + + if (source == target) + { + if (!enable) + Units::clearBurrowMembers(target); + + return; + } + + for (size_t i = 0; i < source->units.size(); i++) + { + auto unit = df::unit::find(source->units[i]); + + if (unit) + Units::setInBurrow(unit, target, enable); + } +} + +static void copyTiles(df::burrow *target, df::burrow *source, bool enable) +{ + CHECK_NULL_POINTER(target); + CHECK_NULL_POINTER(source); + + if (source == target) + { + if (!enable) + Maps::clearBurrowTiles(target); + + return; + } + + std::vector pvec; + Maps::listBurrowBlocks(&pvec, source); + + for (size_t i = 0; i < pvec.size(); i++) + { + auto block = pvec[i]; + auto smask = Maps::getBlockBurrowMask(source, block); + if (!smask) + continue; + + auto tmask = Maps::getBlockBurrowMask(target, block, enable); + if (!tmask) + continue; + + if (enable) + { + for (int j = 0; j < 16; j++) + tmask->tile_bitmask[j] |= smask->tile_bitmask[j]; + } + else + { + for (int j = 0; j < 16; j++) + tmask->tile_bitmask[j] &= ~smask->tile_bitmask[j]; + + if (!tmask->has_assignments()) + Maps::deleteBlockBurrowMask(target, block, tmask); + } + } +} + +static void setTilesByDesignation(df::burrow *target, df::tile_designation d_mask, + df::tile_designation d_value, bool enable) +{ + CHECK_NULL_POINTER(target); + + auto &blocks = world->map.map_blocks; + + for (size_t i = 0; i < blocks.size(); i++) + { + auto block = blocks[i]; + df::block_burrow *mask = NULL; + + for (int x = 0; x < 16; x++) + { + for (int y = 0; y < 16; y++) + { + if ((block->designation[x][y].whole & d_mask.whole) != d_value.whole) + continue; + + if (!mask) + mask = Maps::getBlockBurrowMask(target, block, enable); + if (!mask) + goto next_block; + + mask->setassignment(x, y, enable); + } + } + + if (mask && !enable && !mask->has_assignments()) + Maps::deleteBlockBurrowMask(target, block, mask); + + next_block:; + } +} + +static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) +{ + CHECK_NULL_POINTER(target); + + df::tile_designation mask(0); + df::tile_designation value(0); + + if (name == "ABOVE_GROUND") + mask.bits.subterranean = true; + else if (name == "SUBTERRANEAN") + mask.bits.subterranean = value.bits.subterranean = true; + else if (name == "LIGHT") + mask.bits.light = value.bits.light = true; + else if (name == "DARK") + mask.bits.light = true; + else if (name == "OUTSIDE") + mask.bits.outside = value.bits.outside = true; + else if (name == "INSIDE") + mask.bits.outside = true; + else if (name == "HIDDEN") + mask.bits.hidden = value.bits.hidden = true; + else if (name == "REVEALED") + mask.bits.hidden = true; + else + return false; + + setTilesByDesignation(target, mask, value, enable); + return true; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(findByName), + DFHACK_LUA_FUNCTION(copyUnits), + DFHACK_LUA_FUNCTION(copyTiles), + DFHACK_LUA_FUNCTION(setTilesByKeyword), + DFHACK_LUA_END +}; + +static command_result burrow(color_ostream &out, vector ¶meters) +{ + CoreSuspender suspend; + + if (!active) + { + out.printerr("The plugin cannot be used without map.\n"); + return CR_FAILURE; + } + + string cmd; + if (!parameters.empty()) + cmd = parameters[0]; + + if (cmd == "enable" || cmd == "disable") + { + if (parameters.size() < 2) + return CR_WRONG_USAGE; + + bool state = (cmd == "enable"); + + for (int i = 1; i < parameters.size(); i++) + { + string &option = parameters[i]; + + if (option == "auto-grow") + enable_auto_grow(out, state); + else + return CR_WRONG_USAGE; + } + } + else if (cmd == "clear-units") + { + if (parameters.size() < 2) + return CR_WRONG_USAGE; + + for (int i = 1; i < parameters.size(); i++) + { + auto target = findByName(out, parameters[i]); + if (!target) + return CR_WRONG_USAGE; + + Units::clearBurrowMembers(target); + } + } + else if (cmd == "set-units" || cmd == "add-units" || cmd == "remove-units") + { + if (parameters.size() < 3) + return CR_WRONG_USAGE; + + auto target = findByName(out, parameters[1]); + if (!target) + return CR_WRONG_USAGE; + + if (cmd == "set-units") + Units::clearBurrowMembers(target); + + bool enable = (cmd != "remove-units"); + + for (int i = 2; i < parameters.size(); i++) + { + auto source = findByName(out, parameters[i]); + if (!source) + return CR_WRONG_USAGE; + + copyUnits(target, source, enable); + } + } + else if (cmd == "clear-tiles") + { + if (parameters.size() < 2) + return CR_WRONG_USAGE; + + for (int i = 1; i < parameters.size(); i++) + { + auto target = findByName(out, parameters[i]); + if (!target) + return CR_WRONG_USAGE; + + Maps::clearBurrowTiles(target); + } + } + else if (cmd == "set-tiles" || cmd == "add-tiles" || cmd == "remove-tiles") + { + if (parameters.size() < 3) + return CR_WRONG_USAGE; + + auto target = findByName(out, parameters[1]); + if (!target) + return CR_WRONG_USAGE; + + if (cmd == "set-tiles") + Maps::clearBurrowTiles(target); + + bool enable = (cmd != "remove-tiles"); + + for (int i = 2; i < parameters.size(); i++) + { + if (setTilesByKeyword(target, parameters[i], enable)) + continue; + + auto source = findByName(out, parameters[i]); + if (!source) + return CR_WRONG_USAGE; + + copyTiles(target, source, enable); + } + } + else + { + if (!parameters.empty() && cmd != "?") + out.printerr("Invalid command: %s\n", cmd.c_str()); + return CR_WRONG_USAGE; + } + + return CR_OK; +} diff --git a/plugins/lua/burrows.lua b/plugins/lua/burrows.lua new file mode 100644 index 000000000..b1d51f6a6 --- /dev/null +++ b/plugins/lua/burrows.lua @@ -0,0 +1,33 @@ +local _ENV = mkmodule('plugins.burrows') + +--[[ + + Native functions: + + * findByName(name) -> burrow + * copyUnits(dest,src,enable) + * copyTiles(dest,src,enable) + * setTilesByKeyword(dest,kwd,enable) -> success + + 'enable' selects between add and remove modes + +--]] + +clearUnits = dfhack.units.clearBurrowMembers + +function isBurrowUnit(burrow,unit) + return dfhack.units.isInBurrow(unit,burrow) +end +function setBurrowUnit(burrow,unit,enable) + return dfhack.units.setInBurrow(unit,burrow,enable) +end + +clearTiles = dfhack.maps.clearBurrowTiles +listBlocks = dfhack.maps.listBurrowBlocks + +isBurrowTile = dfhack.maps.isBurrowTile +setBurrowTile = dfhack.maps.setBurrowTile +isBlockBurrowTile = dfhack.maps.isBlockBurrowTile +setBlockBurrowTile = dfhack.maps.setBlockBurrowTile + +return _ENV \ No newline at end of file diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 2f2b64588..9f71b85d8 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -890,6 +890,8 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef() return newref; } +bool isInBuiltCage(df::unit* unit); + // check if assigned to pen, pit, (built) cage or chain // note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence) // animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead From 98df7d99d4aedccd7ad8a90a7b7c7c94bf555d9e Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sun, 15 Apr 2012 15:26:08 +0200 Subject: [PATCH 084/108] fixed library/xml pointing to wrong commit (I hope) --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 27fec9797..a54516705 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 27fec9797d7d1215bd3f5530b44a55a9d394fe75 +Subproject commit a545167050fee9eedd5e319b518d961f933154a3 From 005658a9cd667c5fa04f379fd96ff5f68de6af4a Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 15 Apr 2012 09:04:19 -0500 Subject: [PATCH 085/108] Add noegglayer zone filter --- plugins/zone.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 9f71b85d8..7694ad00d 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -119,6 +119,7 @@ const string zone_help_filters = " male - obvious\n" " female - obvious\n" " egglayer - race lays eggs (use together with 'female')\n" + " noegglayer - race does not lay eggs\n" " grazer - is a grazer\n" " nograzer - not a grazer\n" " milkable - race is milkable (use together with 'female')\n" @@ -1466,6 +1467,7 @@ command_result df_zone (color_ostream &out, vector & parameters) bool find_male = false; bool find_female = false; bool find_egglayer = false; + bool find_noegglayer = false; bool find_grazer = false; bool find_nograzer = false; bool find_milkable = false; @@ -1699,6 +1701,10 @@ command_result df_zone (color_ostream &out, vector & parameters) { find_egglayer = true; } + else if(p == "noegglayer") + { + find_noegglayer = true; + } else if(p == "grazer") { find_grazer = true; @@ -1885,6 +1891,7 @@ command_result df_zone (color_ostream &out, vector & parameters) || (find_grazer && !isGrazer(unit)) || (find_nograzer && isGrazer(unit)) || (find_egglayer && !isEggLayer(unit)) + || (find_noegglayer && isEggLayer(unit)) || (find_milkable && !isMilkable(unit)) || (find_male && !isMale(unit)) || (find_female && !isFemale(unit)) From f3c7a685f5b6d84bff6193ad289df3cda634a4ee Mon Sep 17 00:00:00 2001 From: Jared Adams Date: Sun, 15 Apr 2012 08:40:19 -0600 Subject: [PATCH 086/108] Make tiletypes more useful * Paint, filter, and brush state is now saved between calls. * Added 'all' paint option to set material, shape, special, and variant at the same time. * Added tiletypes-here (like liquids here, except is uses the saved brush settings) * Added tiletypes-here-point (like liquids here, always only the tile under the cursor) * Added tiletypes-command: runs tiletypes commands seperated by ';' tokens (affects saved state) * Make the internal workings match liquids a bit more * Give brush objects a descriptor string * Make Core::cheap_tokenise available --- library/Core.cpp | 6 +- library/include/Core.h | 2 + plugins/Brushes.h | 23 + plugins/tiletypes.cpp | 937 ++++++++++++++++++++++++----------------- 4 files changed, 582 insertions(+), 386 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index e6b9c45ff..111a88019 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -118,7 +118,7 @@ struct Core::Private } }; -void cheap_tokenise(string const& input, vector &output) +void Core::cheap_tokenise(string const& input, vector &output) { string *cur = NULL; @@ -177,7 +177,7 @@ void fHKthread(void * iodata) color_ostream_proxy out(core->getConsole()); vector args; - cheap_tokenise(stuff, args); + Core::cheap_tokenise(stuff, args); if (args.empty()) { out.printerr("Empty hotkey command.\n"); continue; @@ -218,7 +218,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue { // cut the input into parts vector parts; - cheap_tokenise(command,parts); + Core::cheap_tokenise(command,parts); if(parts.size() == 0) { clueless_counter ++; diff --git a/library/include/Core.h b/library/include/Core.h index f8ac8e783..a97574692 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -135,6 +135,8 @@ namespace DFHack PluginManager *getPluginManager() { return plug_mgr; } + static void cheap_tokenise(std::string const& input, std::vector &output); + private: DFHack::Console con; diff --git a/plugins/Brushes.h b/plugins/Brushes.h index 73c3a05a9..97ddc44c0 100644 --- a/plugins/Brushes.h +++ b/plugins/Brushes.h @@ -6,6 +6,9 @@ class Brush public: virtual ~Brush(){}; virtual coord_vec points(MapExtras::MapCache & mc,DFHack::DFCoord start) = 0; + virtual std::string str() const { + return "unknown"; + } }; /** * generic 3D rectangle brush. you can specify the dimensions of @@ -56,6 +59,13 @@ public: return v; }; ~RectangleBrush(){}; + std::string str() const { + if (x_ == 1 && y_ == 1 && z_ == 1) { + return "point"; + } else { + return "rectangle"; + } + } private: int x_, y_, z_; int cx_, cy_, cz_; @@ -89,6 +99,9 @@ public: } return v; }; + std::string str() const { + return "block"; + } }; /** @@ -117,6 +130,9 @@ public: } return v; }; + std::string str() const { + return "column"; + } }; /** @@ -168,6 +184,9 @@ public: return v; } + std::string str() const { + return "flood"; + } private: void maybeFlood(DFCoord c, std::stack &to_flood, MapExtras::MapCache &mc) { if (mc.testCoord(c)) { @@ -176,3 +195,7 @@ private: } Core *c_; }; + +std::ostream &operator<<(std::ostream &stream, const Brush& brush) { + stream << brush.str(); +} diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 61b4ec8e7..41627a852 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -1,4 +1,24 @@ +// Plugin tiletypes // +// This plugin allows fine editing of individual game tiles (expect for +// changing material subtypes). +// +// Commands: +// tiletypes - runs the interractive interpreter +// tiletypes-command - run the given command +// (intended to be mapped to a hotkey or used from dfhack-run) +// tiletypes-here - runs the execute method with the last settings from +// tiletypes(-command), including brush! +// (intended to be mapped to a hotkey) +// tiletypes-here-point - runs the execute method with the last settings from +// tiletypes(-command), except with a point brush! +// (intended to be mapped to a hotkey) +// Options (everything but tiletypes-command): +// ?, help - print some help +// +// Options (tiletypes-command): +// (anything) - run the given command + #include #include #include @@ -25,39 +45,120 @@ using namespace MapExtras; using namespace DFHack; using namespace df::enums; -//zilpin: These two functions were giving me compile errors in VS2008, so I cheated with the C style loop below, just to get it to build. -//Original code is commented out. -void tolower(std::string &str) -{ - //The C++ way... - //std::transform(str.begin(), str.end(), str.begin(), std::bind2nd(std::ptr_fun(&std::tolower ), std::locale(""))); +CommandHistory tiletypes_hist; - //The C way... - for(char *c=(char *)str.c_str(); *c; ++c) - { - *c = tolower(*c); - } +command_result df_tiletypes (color_ostream &out, vector & parameters); +command_result df_tiletypes_command (color_ostream &out, vector & parameters); +command_result df_tiletypes_here (color_ostream &out, vector & parameters); +command_result df_tiletypes_here_point (color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("tiletypes"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + tiletypes_hist.load("tiletypes.history"); + commands.push_back(PluginCommand("tiletypes", "Paint map tiles freely, similar to liquids.", df_tiletypes, true)); + commands.push_back(PluginCommand("tiletypes-command", "Run the given commands (seperated by ' ; '; an empty command is the same as run).", df_tiletypes_command)); + commands.push_back(PluginCommand("tiletypes-here", "Use the last settings from tiletypes, including brush, at cursor location.", df_tiletypes_here)); + commands.push_back(PluginCommand("tiletypes-here-point", "Use the last settings from tiletypes, not including brush, at cursor location.", df_tiletypes_here_point)); + return CR_OK; } -void toupper(std::string &str) +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { - //std::transform(str.begin(), str.end(), str.begin(), std::bind2nd(std::ptr_fun(&std::toupper), std::locale(""))); - for(char *c=(char *)str.c_str(); *c; ++c) - { - *c = toupper(*c); - } + tiletypes_hist.save("tiletypes.history"); + return CR_OK; } -int toint(const std::string &str, int failValue = 0) +void help( color_ostream & out, std::vector &commands, int start, int end) { - std::istringstream ss(str); - int valInt; - ss >> valInt; - if (ss.fail()) + std::string option = commands.size() > start ? commands[start] : ""; + if (option.empty()) { - return failValue; + out << "Commands:" << std::endl + << " quit / q : quit" << std::endl + << " filter / f [options] : change filter options" << std::endl + << " paint / p [options] : change paint options" << std::endl + << " point / p : set point brush" << std::endl + << " range / r [w] [h] [z] : set range brush" << std::endl + << " block : set block brush" << std::endl + << " column : set column brush" << std::endl + << " run / (empty) : paint!" << std::endl + << std::endl + << "Filter/paint options:" << std::endl + << " Shape / sh / s: set tile shape information" << std::endl + << " Material / mat / m: set tile material information" << std::endl + << " Special / sp: set special tile information" << std::endl + << " Variant / var / v: set variant tile information" << std::endl + << " All / a: set the four above at the same time (no ANY support)" << std::endl + << " Designated / d: set designated flag" << std::endl + << " Hidden / h: set hidden flag" << std::endl + << " Light / l: set light flag" << std::endl + << " Subterranean / st: set subterranean flag" << std::endl + << " Skyview / sv: set skyview flag" << std::endl + << "See help [option] for more information" << std::endl; + } + else if (option == "shape" || option == "s" ||option == "sh") + { + out << "Available shapes:" << std::endl + << " ANY" << std::endl; + FOR_ENUM_ITEMS(tiletype_shape,i) + { + out << " " << ENUM_KEY_STR(tiletype_shape,i) << std::endl; + } + } + else if (option == "material"|| option == "mat" ||option == "m") + { + out << "Available materials:" << std::endl + << " ANY" << std::endl; + FOR_ENUM_ITEMS(tiletype_material,i) + { + out << " " << ENUM_KEY_STR(tiletype_material,i) << std::endl; + } + } + else if (option == "special" || option == "sp") + { + out << "Available specials:" << std::endl + << " ANY" << std::endl; + FOR_ENUM_ITEMS(tiletype_special,i) + { + out << " " << ENUM_KEY_STR(tiletype_special,i) << std::endl; + } + } + else if (option == "variant" || option == "var" || option == "v") + { + out << "Available variants:" << std::endl + << " ANY" << std::endl; + FOR_ENUM_ITEMS(tiletype_variant,i) + { + out << " " << ENUM_KEY_STR(tiletype_variant,i) << std::endl; + } + } + else if (option == "designated" || option == "d") + { + out << "Available designated flags:" << std::endl + << " ANY, 0, 1" << std::endl; + } + else if (option == "hidden" || option == "h") + { + out << "Available hidden flags:" << std::endl + << " ANY, 0, 1" << std::endl; + } + else if (option == "light" || option == "l") + { + out << "Available light flags:" << std::endl + << " ANY, 0, 1" << std::endl; + } + else if (option == "subterranean" || option == "st") + { + out << "Available subterranean flags:" << std::endl + << " ANY, 0, 1" << std::endl; + } + else if (option == "skyview" || option == "sv") + { + out << "Available skyview flags:" << std::endl + << " ANY, 0, 1" << std::endl; } - return valInt; } struct TileType @@ -217,12 +318,116 @@ std::ostream &operator<<(std::ostream &stream, const TileType &paint) return stream; } -bool processTileType(TileType &paint, const std::string &option, const std::string &value) +static TileType filter, paint; +static Brush *brush = new RectangleBrush(1,1); + +void printState(color_ostream &out) { - std::string val = value; - toupper(val); + out << "Filter: " << filter << std::endl + << "Paint: " << paint << std::endl + << "Brush: " << brush->str() << std::endl; +} + +//zilpin: These two functions were giving me compile errors in VS2008, so I cheated with the C style loop below, just to get it to build. +//Original code is commented out. +void tolower(std::string &str) +{ + //The C++ way... + //std::transform(str.begin(), str.end(), str.begin(), std::bind2nd(std::ptr_fun(&std::tolower ), std::locale(""))); + + //The C way... + for(char *c=(char *)str.c_str(); *c; ++c) + { + *c = tolower(*c); + } +} + +void toupper(std::string &str) +{ + //std::transform(str.begin(), str.end(), str.begin(), std::bind2nd(std::ptr_fun(&std::toupper), std::locale(""))); + for(char *c=(char *)str.c_str(); *c; ++c) + { + *c = toupper(*c); + } +} + +int toint(const std::string &str, int failValue = 0) +{ + std::istringstream ss(str); int valInt; - if (val == "ANY") + ss >> valInt; + if (ss.fail()) + { + return failValue; + } + return valInt; +} + +bool tryShape(std::string value, TileType &paint) +{ + FOR_ENUM_ITEMS(tiletype_shape,i) + { + if (value == ENUM_KEY_STR(tiletype_shape,i)) + { + paint.shape = i; + return true; + } + } + return false; +} + +bool tryMaterial(std::string value, TileType &paint) +{ + FOR_ENUM_ITEMS(tiletype_material, i) + { + if (value == ENUM_KEY_STR(tiletype_material,i)) + { + paint.material = i; + return true; + } + } + return false; +} + +bool trySpecial(std::string value, TileType &paint) +{ + FOR_ENUM_ITEMS(tiletype_special, i) + { + if (value == ENUM_KEY_STR(tiletype_special,i)) + { + paint.special = i; + return true; + } + } + return false; +} + +bool tryVariant(std::string value, TileType &paint) +{ + FOR_ENUM_ITEMS(tiletype_variant, i) + { + if (value == ENUM_KEY_STR(tiletype_variant,i)) + { + paint.variant = i; + return true; + } + } + return false; +} + +bool processTileType(color_ostream & out, TileType &paint, std::vector ¶ms, int start, int end) +{ + if (params.size() < start + 2) + { + return false; + } + + int loc = start; + std::string option = params[loc++]; + std::string value = params[loc++]; + toupper(value); + int valInt; + if (value == "ANY") { valInt = -1; } @@ -241,19 +446,9 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri } else { - FOR_ENUM_ITEMS(tiletype_shape,i) + if (!tryShape(value, paint)) { - if (val == ENUM_KEY_STR(tiletype_shape,i)) - { - paint.shape = i; - found = true; - break; - } - } - - if (!found) - { - std::cout << "Unknown tile shape: " << value << std::endl; + out << "Unknown tile shape: " << value << std::endl; } } } @@ -266,19 +461,9 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri } else { - FOR_ENUM_ITEMS(tiletype_material, i) - { - if (val == ENUM_KEY_STR(tiletype_material,i)) - { - paint.material = i; - found = true; - break; - } - } - - if (!found) + if (!tryMaterial(value, paint)) { - std::cout << "Unknown tile material: " << value << std::endl; + out << "Unknown tile material: " << value << std::endl; } } } @@ -291,19 +476,9 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri } else { - FOR_ENUM_ITEMS(tiletype_special, i) + if (!trySpecial(value, paint)) { - if (val == ENUM_KEY_STR(tiletype_special,i)) - { - paint.special = i; - found = true; - break; - } - } - - if (!found) - { - std::cout << "Unknown tile special: " << value << std::endl; + out << "Unknown tile special: " << value << std::endl; } } } @@ -316,19 +491,9 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri } else { - FOR_ENUM_ITEMS(tiletype_variant, i) + if (!tryVariant(value, paint)) { - if (val == ENUM_KEY_STR(tiletype_variant,i)) - { - paint.variant = i; - found = true; - break; - } - } - - if (!found) - { - std::cout << "Unknown tile variant: " << value << std::endl; + out << "Unknown tile variant: " << value << std::endl; } } } @@ -341,7 +506,7 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri } else { - std::cout << "Unknown designation flag: " << value << std::endl; + out << "Unknown designation flag: " << value << std::endl; } } else if (option == "hidden" || option == "h") @@ -353,7 +518,7 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri } else { - std::cout << "Unknown hidden flag: " << value << std::endl; + out << "Unknown hidden flag: " << value << std::endl; } } else if (option == "light" || option == "l") @@ -365,7 +530,7 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri } else { - std::cout << "Unknown light flag: " << value << std::endl; + out << "Unknown light flag: " << value << std::endl; } } else if (option == "subterranean" || option == "st") @@ -377,7 +542,7 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri } else { - std::cout << "Unknown subterranean flag: " << value << std::endl; + out << "Unknown subterranean flag: " << value << std::endl; } } else if (option == "skyview" || option == "sv") @@ -389,380 +554,386 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri } else { - std::cout << "Unknown skyview flag: " << value << std::endl; + out << "Unknown skyview flag: " << value << std::endl; + } + } + else if (option == "all" || option == "a") + { + for (; loc < end; loc++) + { + std::string param = params[loc]; + toupper(param); + + if (!(tryShape(param, paint) || tryMaterial(param, paint) || + trySpecial(param, paint) || tryVariant(param, paint))) + { + out << "Unknown description: '" << param << "'" << std::endl; + break; + } } + + found = true; } else { - std::cout << "Unknown option: '" << option << "'" << std::endl; + out << "Unknown option: '" << option << "'" << std::endl; } return found; } -void help( std::ostream & out, const std::string &option) +command_result executePaintJob(color_ostream &out) { - if (option.empty()) + if (paint.empty()) { - out << "Commands:" << std::endl - << " quit / q : quit" << std::endl - << " filter / f [options] : change filter options" << std::endl - << " paint / p [options] : change paint options" << std::endl - << " point / p : set point brush" << std::endl - << " range / r : set range brush" << std::endl - << " block : set block brush" << std::endl - << " column : set column brush" << std::endl - << std::endl - << "Filter/paint options:" << std::endl - << " Shape / sh / s: set tile shape information" << std::endl - << " Material / mat / m: set tile material information" << std::endl - << " Special / sp: set special tile information" << std::endl - << " Variant / var / v: set variant tile information" << std::endl - << " Designated / d: set designated flag" << std::endl - << " Hidden / h: set hidden flag" << std::endl - << " Light / l: set light flag" << std::endl - << " Subterranean / st: set subterranean flag" << std::endl - << " Skyview / sv: set skyview flag" << std::endl - << "See help [option] for more information" << std::endl; + out.printerr("Set the paint first.\n"); + return CR_OK; } - else if (option == "shape" || option == "s" ||option == "sh") + + CoreSuspender suspend; + uint32_t x_max = 0, y_max = 0, z_max = 0; + int32_t x = 0, y = 0, z = 0; + + if (!Maps::IsValid()) { - out << "Available shapes:" << std::endl - << " ANY" << std::endl; - FOR_ENUM_ITEMS(tiletype_shape,i) - { - out << " " << ENUM_KEY_STR(tiletype_shape,i) << std::endl; - } + out.printerr("Map is not available!\n"); + return CR_FAILURE; } - else if (option == "material"|| option == "mat" ||option == "m") + Maps::getSize(x_max, y_max, z_max); + + if (!Gui::getCursorCoords(x,y,z)) { - out << "Available materials:" << std::endl - << " ANY" << std::endl; - FOR_ENUM_ITEMS(tiletype_material,i) - { - out << " " << ENUM_KEY_STR(tiletype_material,i) << std::endl; - } + out.printerr("Can't get cursor coords! Make sure you have a cursor active in DF.\n"); + return CR_FAILURE; } - else if (option == "special" || option == "sp") + out.print("Cursor coords: (%d, %d, %d)\n", x, y, z); + + DFHack::DFCoord cursor(x,y,z); + MapExtras::MapCache map; + coord_vec all_tiles = brush->points(map, cursor); + out.print("working...\n"); + + for (coord_vec::iterator iter = all_tiles.begin(); iter != all_tiles.end(); ++iter) { - out << "Available specials:" << std::endl - << " ANY" << std::endl; - FOR_ENUM_ITEMS(tiletype_special,i) + const df::tiletype source = map.tiletypeAt(*iter); + df::tile_designation des = map.designationAt(*iter); + + if ((filter.shape > -1 && filter.shape != tileShape(source)) + || (filter.material > -1 && filter.material != tileMaterial(source)) + || (filter.special > -1 && filter.special != tileSpecial(source)) + || (filter.variant > -1 && filter.variant != tileVariant(source)) + || (filter.dig > -1 && (filter.dig != 0) != (des.bits.dig != tile_dig_designation::No)) + ) { - out << " " << ENUM_KEY_STR(tiletype_special,i) << std::endl; + return CR_OK; } - } - else if (option == "variant" || option == "var" || option == "v") - { - out << "Available variants:" << std::endl - << " ANY" << std::endl; - FOR_ENUM_ITEMS(tiletype_variant,i) + + df::tiletype_shape shape = paint.shape; + if (shape == tiletype_shape::NONE) { - out << " " << ENUM_KEY_STR(tiletype_variant,i) << std::endl; + shape = tileShape(source); + } + + df::tiletype_material material = paint.material; + if (material == tiletype_material::NONE) + { + material = tileMaterial(source); + } + + df::tiletype_special special = paint.special; + if (special == tiletype_special::NONE) + { + special = tileSpecial(source); + } + df::tiletype_variant variant = paint.variant; + /* + * FIXME: variant should be: + * 1. If user variant: + * 2. If user variant \belongs target variants + * 3. use user variant + * 4. Else + * 5. use variant 0 + * 6. If the source variant \belongs target variants + * 7 use source variant + * 8 ElseIf num target shape/material variants > 1 + * 9. pick one randomly + * 10.Else + * 11. use variant 0 + * + * The following variant check has been disabled because it's severely limiting + * the usefullness of the tool. + */ + /* + if (variant == tiletype_variant::NONE) + { + variant = tileVariant(source); + } + */ + // Remove direction from directionless tiles + DFHack::TileDirection direction = tileDirection(source); + if (!(material == tiletype_material::RIVER || shape == tiletype_shape::BROOK_BED || shape == tiletype_shape::WALL && (material == tiletype_material::CONSTRUCTION || special == tiletype_special::SMOOTH))) + { + direction.whole = 0; + } + + df::tiletype type = DFHack::findTileType(shape, material, variant, special, direction); + // hack for empty space + if (shape == tiletype_shape::EMPTY && material == tiletype_material::AIR && variant == tiletype_variant::VAR_1 && special == tiletype_special::NORMAL && direction.whole == 0) + { + type = tiletype::OpenSpace; + } + // make sure it's not invalid + if(type != tiletype::Void) + map.setTiletypeAt(*iter, type); + + if (paint.hidden > -1) + { + des.bits.hidden = paint.hidden; + } + + if (paint.light > -1) + { + des.bits.light = paint.light; + } + + if (paint.subterranean > -1) + { + des.bits.subterranean = paint.subterranean; } + + if (paint.skyview > -1) + { + des.bits.outside = paint.skyview; + } + + // Remove liquid from walls, etc + if (type != -1 && !DFHack::FlowPassable(type)) + { + des.bits.flow_size = 0; + //des.bits.liquid_type = DFHack::liquid_water; + //des.bits.water_table = 0; + des.bits.flow_forbid = 0; + //des.bits.liquid_static = 0; + //des.bits.water_stagnant = 0; + //des.bits.water_salt = 0; + } + + map.setDesignationAt(*iter, des); } - else if (option == "designated" || option == "d") + + if (map.WriteAll()) { - out << "Available designated flags:" << std::endl - << " ANY, 0, 1" << std::endl; + out.print("OK\n"); } - else if (option == "hidden" || option == "h") + else { - out << "Available hidden flags:" << std::endl - << " ANY, 0, 1" << std::endl; + out.printerr("Something failed horribly! RUN!\n"); + return CR_FAILURE; } - else if (option == "light" || option == "l") +} + +command_result processCommand(color_ostream &out, std::vector &commands, int start, int end, bool & endLoop, bool hasConsole = false) +{ + if (commands.size() == start) { - out << "Available light flags:" << std::endl - << " ANY, 0, 1" << std::endl; + return executePaintJob(out); } - else if (option == "subterranean" || option == "st") + + std::ostringstream ss_o; + int loc = start; + + std::string command = commands[loc++]; + tolower(command); + + if (command == "help" || command == "?") { - out << "Available subterranean flags:" << std::endl - << " ANY, 0, 1" << std::endl; + help(out, commands, loc, end); } - else if (option == "skyview" || option == "sv") + else if (command == "quit" || command == "q") { - out << "Available skyview flags:" << std::endl - << " ANY, 0, 1" << std::endl; + endLoop = true; } -} + else if (command == "filter" || command == "f") + { + processTileType(out, filter, commands, loc, end); + } + else if (command == "paint" || (command == "p" && commands.size() > 1)) + { + processTileType(out, paint, commands, loc, end); + } + else if (command == "point" || command == "p") + { + delete brush; + brush = new RectangleBrush(1,1); + } + else if (command == "range" || command == "r") + { + int width = 0, height = 0, z_levels = 0; -CommandHistory tiletypes_hist; + if (commands.size() > loc + 1) + { + width = toint(commands[loc++]); + height = toint(commands[loc++]); -command_result df_tiletypes (color_ostream &out, vector & parameters); + if (commands.size() > loc) { + z_levels = toint(commands[loc++]); + } + } -DFHACK_PLUGIN("tiletypes"); + if (width < 1 || height < 1) { + if (hasConsole) { + Console &con = static_cast(out); + CommandHistory hist; -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - tiletypes_hist.load("tiletypes.history"); - commands.push_back(PluginCommand("tiletypes", "Paint map tiles freely, similar to liquids.", df_tiletypes, true)); - return CR_OK; -} + ss_o << "Set range width <" << width << "> "; + con.lineedit(ss_o.str(),command,hist); + width = command == "" ? width : toint(command); + + ss_o.str(""); + ss_o << "Set range height <" << height << "> "; + con.lineedit(ss_o.str(),command,hist); + height = command == "" ? height : toint(command); + + ss_o.str(""); + ss_o << "Set range z-levels <" << z_levels << "> "; + con.lineedit(ss_o.str(),command,hist); + z_levels = command == "" ? z_levels : toint(command); + } else { + return CR_WRONG_USAGE; + } + } + + if (width < 1) width = 1; + if (height < 1) height = 1; + if (z_levels < 1) z_levels = 1; + + delete brush; + brush = new RectangleBrush(width, height, z_levels, 0, 0, 0); + } + else if (command == "block") + { + delete brush; + brush = new BlockBrush(); + } + else if (command == "column") + { + delete brush; + brush = new ColumnBrush(); + } + else if (command == "run" || command.empty()) + { + executePaintJob(out); + } -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - tiletypes_hist.save("tiletypes.history"); return CR_OK; } -command_result df_tiletypes (color_ostream &out, vector & parameters) +command_result df_tiletypes (color_ostream &out_, vector & parameters) { - uint32_t x_max = 0, y_max = 0, z_max = 0; - int32_t x = 0, y = 0, z = 0; - for(size_t i = 0; i < parameters.size();i++) { if(parameters[i] == "help" || parameters[i] == "?") { - out.print("This tool allows painting tiles types with a brush, using an optional filter.\n" - "The tool is interactive, similarly to the liquids tool.\n" - "Further help is available inside.\n" + out_.print("This tool allows painting tiles types with a brush, using an optional filter.\n" + "The tool is interactive, similarly to the liquids tool.\n" + "Further help is available inside.\n" ); return CR_OK; } } - if(!out.is_console()) + if(!out_.is_console()) return CR_FAILURE; - Console &con = static_cast(out); + Console &out = static_cast(out_); - TileType filter, paint; - Brush *brush = new RectangleBrush(1,1); + std::vector commands; bool end = false; - std::string brushname = "point"; - int width = 1, height = 1, z_levels = 1; - con << "Welcome to the tiletype tool.\nType 'help' or '?' for a list of available commands, 'q' to quit.\nPress return after a command to confirm." << std::endl; - con.printerr("THIS TOOL CAN BE DANGEROUS. YOU'VE BEEN WARNED.\n"); + out << "Welcome to the tiletype tool.\nType 'help' or '?' for a list of available commands, 'q' to quit.\nPress return after a command to confirm." << std::endl; + out.printerr("THIS TOOL CAN BE DANGEROUS. YOU'VE BEEN WARNED.\n"); while (!end) { - con << "Filter: " << filter << std::endl - << "Paint: " << paint << std::endl - << "Brush: " << brushname << std::endl; + printState(out); std::string input = ""; - std::string command = ""; - std::string option = ""; - std::string value = ""; - con.lineedit("tiletypes> ",input,tiletypes_hist); - tiletypes_hist.add(input); - std::istringstream ss(input); - ss >> command >> option >> value; - tolower(command); - tolower(option); + if (out.lineedit("tiletypes> ",input,tiletypes_hist) == -1) + return CR_FAILURE; - if (command == "help" || command == "?") - { - help(con,option); - } - else if (command == "quit" || command == "q") - { - end = true; - } - else if (command == "filter" || command == "f") - { - processTileType(filter, option, value); - } - else if (command == "paint" || (command == "p" && !option.empty())) - { - processTileType(paint, option, value); - } - else if (command == "point" || command == "p") + commands.clear(); + Core::cheap_tokenise(input, commands); + + command_result ret = processCommand(out, commands, 0, commands.size(), end, true); + + if (ret != CR_OK) { - delete brush; - brushname = "point"; - brush = new RectangleBrush(1,1); + return ret; } - else if (command == "range" || command == "r") - { - std::stringstream ss; - CommandHistory hist; - ss << "Set range width <" << width << "> "; - con.lineedit(ss.str(),command,hist); - width = command == "" ? width : toint(command); - if (width < 1) width = 1; - - ss.str(""); - ss << "Set range height <" << height << "> "; - con.lineedit(ss.str(),command,hist); - height = command == "" ? height : toint(command); - if (height < 1) height = 1; + } + return CR_OK; +} - ss.str(""); - ss << "Set range z-levels <" << z_levels << "> "; - con.lineedit(ss.str(),command,hist); - z_levels = command == "" ? z_levels : toint(command); - if (z_levels < 1) z_levels = 1; +command_result df_tiletypes_command (color_ostream &out, vector & parameters) +{ + bool dummy; + int start = 0, end = 0; - delete brush; - if (width == 1 && height == 1 && z_levels == 1) - { - brushname = "point"; - } - else - { - brushname = "range"; + parameters.push_back(";"); + for (size_t i = 0; i < parameters.size();i++) + { + if (parameters[i] == ";") { + command_result rv = processCommand(out, parameters, start, end, dummy); + if (rv != CR_OK) { + return rv; } - brush = new RectangleBrush(width, height, z_levels, 0, 0, 0); + end++; + start = end; + } else { + end++; } - else if (command == "block") - { - delete brush; - brushname = "block"; - brush = new BlockBrush(); - } - else if (command == "column") + } + + return CR_OK; +} + +command_result df_tiletypes_here (color_ostream &out, vector & parameters) +{ + for(size_t i = 0; i < parameters.size();i++) + { + if(parameters[i] == "help" || parameters[i] == "?") { - delete brush; - brushname = "column"; - brush = new ColumnBrush(); + out << "This command is supposed to be mapped to a hotkey." << endl; + out << "It will use the current/last parameters set in tiletypes (including brush settings!)." << endl; + return CR_OK; } - else if (command.empty()) - { - if (paint.empty()) - { - con.printerr("Set the paint first.\n"); - continue; - } + } - CoreSuspender suspend; + out.print("Run tiletypes-here with these parameters: "); + printState(out); - if (!Maps::IsValid()) - { - con.printerr("Map is not available!\n"); - return CR_FAILURE; - } - Maps::getSize(x_max, y_max, z_max); + return executePaintJob(out); +} - if (!Gui::getCursorCoords(x,y,z)) - { - con.printerr("Can't get cursor coords! Make sure you have a cursor active in DF.\n"); - return CR_FAILURE; - } - con.print("Cursor coords: (%d, %d, %d)\n",x,y,z); +command_result df_tiletypes_here_point (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 tiletypes (except with a point brush)." << endl; + return CR_OK; + } + } - DFHack::DFCoord cursor(x,y,z); - MapExtras::MapCache map; - coord_vec all_tiles = brush->points(map, cursor); - con.print("working...\n"); + Brush *old = brush; + brush = new RectangleBrush(1, 1); - for (coord_vec::iterator iter = all_tiles.begin(); iter != all_tiles.end(); ++iter) - { - const df::tiletype source = map.tiletypeAt(*iter); - df::tile_designation des = map.designationAt(*iter); - - if ((filter.shape > -1 && filter.shape != tileShape(source)) - || (filter.material > -1 && filter.material != tileMaterial(source)) - || (filter.special > -1 && filter.special != tileSpecial(source)) - || (filter.variant > -1 && filter.variant != tileVariant(source)) - || (filter.dig > -1 && (filter.dig != 0) != (des.bits.dig != tile_dig_designation::No)) - ) - { - continue; - } - - df::tiletype_shape shape = paint.shape; - if (shape == tiletype_shape::NONE) - { - shape = tileShape(source); - } - - df::tiletype_material material = paint.material; - if (material == tiletype_material::NONE) - { - material = tileMaterial(source); - } - - df::tiletype_special special = paint.special; - if (special == tiletype_special::NONE) - { - special = tileSpecial(source); - } - df::tiletype_variant variant = paint.variant; - /* - * FIXME: variant should be: - * 1. If user variant: - * 2. If user variant \belongs target variants - * 3. use user variant - * 4. Else - * 5. use variant 0 - * 6. If the source variant \belongs target variants - * 7 use source variant - * 8 ElseIf num target shape/material variants > 1 - * 9. pick one randomly - * 10.Else - * 11. use variant 0 - * - * The following variant check has been disabled because it's severely limiting - * the usefullness of the tool. - */ - /* - if (variant == tiletype_variant::NONE) - { - variant = tileVariant(source); - } - */ - // Remove direction from directionless tiles - DFHack::TileDirection direction = tileDirection(source); - if (!(material == tiletype_material::RIVER || shape == tiletype_shape::BROOK_BED || shape == tiletype_shape::WALL && (material == tiletype_material::CONSTRUCTION || special == tiletype_special::SMOOTH))) { - direction.whole = 0; - } - - df::tiletype type = DFHack::findTileType(shape, material, variant, special, direction); - // hack for empty space - if (shape == tiletype_shape::EMPTY && material == tiletype_material::AIR && variant == tiletype_variant::VAR_1 && special == tiletype_special::NORMAL && direction.whole == 0) { - type = tiletype::OpenSpace; - } - // make sure it's not invalid - if(type != tiletype::Void) - map.setTiletypeAt(*iter, type); - - if (paint.hidden > -1) - { - des.bits.hidden = paint.hidden; - } - - if (paint.light > -1) - { - des.bits.light = paint.light; - } - - if (paint.subterranean > -1) - { - des.bits.subterranean = paint.subterranean; - } - - if (paint.skyview > -1) - { - des.bits.outside = paint.skyview; - } - - // Remove liquid from walls, etc - if (type != -1 && !DFHack::FlowPassable(type)) - { - des.bits.flow_size = 0; - //des.bits.liquid_type = DFHack::liquid_water; - //des.bits.water_table = 0; - des.bits.flow_forbid = 0; - //des.bits.liquid_static = 0; - //des.bits.water_stagnant = 0; - //des.bits.water_salt = 0; - } - - map.setDesignationAt(*iter, des); - } + out.print("Run tiletypes-here with these parameters: "); + printState(out); - if (map.WriteAll()) - { - con.print("OK\n"); - } - else - { - con.printerr("Something failed horribly! RUN!\n"); - } - } - } - return CR_OK; + command_result rv = executePaintJob(out); + + delete brush; + brush = old; + return rv; } From 14709e5d4598e11ddce6f9d2cce734528d842ca5 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 15 Apr 2012 19:09:25 +0400 Subject: [PATCH 087/108] Add an official core lua context, and allow plugins to send events to it. - This context requires core suspend lock and asserts it in a few places. - Special 'event' objects are introduced. They can be invoked as functions, in which case they iterate all their fields and call them as functions. Errors are printed and consumed. - When a plugin is opened by the core context, events registered in a special array are linked to it. The system is organized so as to avoid even trying to pass the event to lua if the module isn't loaded. --- LUA_API.rst | 14 ++ Lua API.html | 13 ++ library/Core.cpp | 17 ++ library/LuaApi.cpp | 45 ++---- library/LuaTools.cpp | 270 +++++++++++++++++++++++++++++++- library/LuaTypes.cpp | 18 ++- library/PluginManager.cpp | 60 +++++-- library/include/Core.h | 2 + library/include/DataFuncs.h | 8 +- library/include/DataIdentity.h | 6 +- library/include/LuaTools.h | 174 +++++++++++++++++++- library/include/LuaWrapper.h | 6 + library/include/PluginManager.h | 23 ++- library/lua/dfhack.lua | 4 + plugins/Dfusion/dfusion.cpp | 7 + plugins/burrows.cpp | 28 +++- 16 files changed, 632 insertions(+), 63 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index d5af5e7a4..e68a87a66 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -799,3 +799,17 @@ Maps module * ``dfhack.maps.setBlockBurrowTile(burrow,block,x,y,enable)`` Adds or removes the tile from the burrow. Returns *false* if invalid coords. + + +Core interpreter context +======================== + +While plugins can create any number of interpreter instances, +there is one special context managed by dfhack core. It is the +only context that can receive events from DF and plugins. + +Core context specific functions: + +* ``dfhack.is_core_context`` + + Boolean value; *true* in the core context. diff --git a/Lua API.html b/Lua API.html index 66385840b..38a375d86 100644 --- a/Lua API.html +++ b/Lua API.html @@ -344,6 +344,7 @@ ul.auto-toc {
          • Maps module
          +
        • Core interpreter context
      @@ -1010,6 +1011,18 @@ Returns false in case of error.

    +
    +

    Core interpreter context

    +

    While plugins can create any number of interpreter instances, +there is one special context managed by dfhack core. It is the +only context that can receive events from DF and plugins.

    +

    Core context specific functions:

    +
      +
    • dfhack.is_core_context

      +

      Boolean value; true in the core context.

      +
    • +
    +
    diff --git a/library/Core.cpp b/library/Core.cpp index e6b9c45ff..16d90bfb1 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -49,6 +49,8 @@ using namespace std; #include "modules/Graphic.h" #include "modules/Windows.h" #include "RemoteServer.h" +#include "LuaTools.h" + using namespace DFHack; #include "df/ui.h" @@ -701,6 +703,9 @@ bool Core::Init() virtual_identity::Init(this); df::global::InitGlobals(); + // initialize common lua context + Lua::Core::Init(con); + // create mutex for syncing with interactive tasks misc_data_mutex=new mutex(); cerr << "Initializing Plugins.\n"; @@ -803,6 +808,13 @@ void *Core::GetData( std::string key ) } } +bool Core::isSuspended(void) +{ + lock_guard lock(d->AccessMutex); + + return (d->df_suspend_depth > 0 && d->df_suspend_thread == this_thread::get_id()); +} + void Core::Suspend() { auto tid = this_thread::get_id(); @@ -882,10 +894,13 @@ int Core::Update() Init(); if(errorstate) return -1; + Lua::Core::Reset(con, "core init"); } color_ostream_proxy out(con); + Lua::Core::Reset(out, "DF code execution"); + if (first_update) plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED); @@ -976,6 +991,8 @@ int Core::Update() assert(d->df_suspend_depth == 0); // destroy condition delete nc; + // check lua stack depth + Lua::Core::Reset(con, "suspend"); } return 0; diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 917e44d38..5f2195760 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -74,30 +74,7 @@ distribution. using namespace DFHack; using namespace DFHack::LuaWrapper; -template -void push_pointer_vector(lua_State *state, const std::vector &pvec) -{ - lua_createtable(state,pvec.size(),0); - - for (size_t i = 0; i < pvec.size(); i++) - { - Lua::PushDFObject(state, pvec[i]); - lua_rawseti(state, -2, i+1); - } -} - -template -T *get_checked_arg(lua_State *state, int arg) -{ - luaL_checkany(state, arg); - - auto ptr = Lua::GetDFObject(state, arg); - if (!ptr) - luaL_argerror(state, arg, "invalid type"); - return ptr; -} - -int push_pos(lua_State *state, df::coord pos) +int Lua::PushPosXYZ(lua_State *state, df::coord pos) { if (!pos.isValid()) { @@ -570,9 +547,9 @@ static void OpenModule(lua_State *state, const char *mname, lua_pop(state, 1); } -#define WRAPM(module, function) { #function, df::wrap_function(module::function) } -#define WRAP(function) { #function, df::wrap_function(function) } -#define WRAPN(name, function) { #name, df::wrap_function(function) } +#define WRAPM(module, function) { #function, df::wrap_function(module::function,true) } +#define WRAP(function) { #function, df::wrap_function(function,true) } +#define WRAPN(name, function) { #name, df::wrap_function(function,true) } static const LuaWrapper::FunctionReg dfhack_module[] = { WRAPM(Translation, TranslateName), @@ -613,7 +590,7 @@ static int job_listNewlyCreated(lua_State *state) if (Job::listNewlyCreated(&pvec, &nxid)) { lua_pushinteger(state, nxid); - push_pointer_vector(state, pvec); + Lua::PushVector(state, pvec); return 2; } else @@ -642,7 +619,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { static int units_getPosition(lua_State *state) { - return push_pos(state, Units::getPosition(get_checked_arg(state,1))); + return Lua::PushPosXYZ(state, Units::getPosition(Lua::CheckDFObject(state,1))); } static const luaL_Reg dfhack_units_funcs[] = { @@ -673,14 +650,14 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { static int items_getPosition(lua_State *state) { - return push_pos(state, Items::getPosition(get_checked_arg(state,1))); + return Lua::PushPosXYZ(state, Items::getPosition(Lua::CheckDFObject(state,1))); } static int items_getContainedItems(lua_State *state) { std::vector pvec; - Items::getContainedItems(get_checked_arg(state,1),&pvec); - push_pointer_vector(state, pvec); + Items::getContainedItems(Lua::CheckDFObject(state,1),&pvec); + Lua::PushVector(state, pvec); return 1; } @@ -719,8 +696,8 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { static int maps_listBurrowBlocks(lua_State *state) { std::vector pvec; - Maps::listBurrowBlocks(&pvec, get_checked_arg(state,1)); - push_pointer_vector(state, pvec); + Maps::listBurrowBlocks(&pvec, Lua::CheckDFObject(state,1)); + Lua::PushVector(state, pvec); return 1; } diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 5129ba648..304d79bcc 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -58,9 +58,18 @@ distribution. #include #include +#include + using namespace DFHack; using namespace DFHack::LuaWrapper; +lua_State *DFHack::Lua::Core::State = NULL; + +inline void AssertCoreSuspend(lua_State *state) +{ + assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended()); +} + void DFHack::Lua::PushDFObject(lua_State *state, type_identity *type, void *ptr) { push_object_internal(state, type, ptr, false); @@ -71,6 +80,36 @@ void *DFHack::Lua::GetDFObject(lua_State *state, type_identity *type, int val_in return get_object_internal(state, type, val_index, exact_type, false); } +void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type) +{ + if (lua_type(state, val_index) == LUA_TNONE) + { + if (val_index > 0) + luaL_argerror(state, val_index, "pointer expected"); + else + luaL_error(state, "at index %d: pointer expected", val_index); + } + + if (lua_isnil(state, val_index)) + return NULL; + + void *rv = get_object_internal(state, type, val_index, exact_type, false); + + if (!rv) + { + std::string error = "invalid pointer type"; + if (type) + error += "; expected: " + type->getFullName(); + + if (val_index > 0) + luaL_argerror(state, val_index, error.c_str()); + else + luaL_error(state, "at index %d: %s", val_index, error.c_str()); + } + + return rv; +} + static int DFHACK_OSTREAM_TOKEN = 0; color_ostream *DFHack::Lua::GetOutput(lua_State *L) @@ -418,6 +457,8 @@ static int lua_dfhack_safecall (lua_State *L) bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres, bool perr) { + AssertCoreSuspend(L); + int base = lua_gettop(L) - nargs; color_ostream *cur_out = Lua::GetOutput(L); @@ -440,13 +481,52 @@ bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres return ok; } -bool DFHack::Lua::Require(color_ostream &out, lua_State *state, - const std::string &module, bool setglobal) +static int DFHACK_LOADED_TOKEN = 0; + +bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *module) { + AssertCoreSuspend(state); + + // Check if it is already loaded + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_LOADED_TOKEN); + lua_pushstring(state, module); + lua_rawget(state, -2); + + if (lua_toboolean(state, -1)) + { + lua_remove(state, -2); + return true; + } + + lua_pop(state, 2); lua_getglobal(state, "require"); - lua_pushstring(state, module.c_str()); + lua_pushstring(state, module); + + return Lua::SafeCall(out, state, 1, 1); +} - if (!Lua::SafeCall(out, state, 1, 1)) +bool DFHack::Lua::PushModulePublic(color_ostream &out, lua_State *state, + const char *module, const char *name) +{ + if (!PushModule(out, state, module)) + return false; + + if (!lua_istable(state, -1)) + { + lua_pop(state, 1); + return false; + } + + lua_pushstring(state, name); + lua_rawget(state, -2); + lua_remove(state, -2); + return true; +} + +bool DFHack::Lua::Require(color_ostream &out, lua_State *state, + const std::string &module, bool setglobal) +{ + if (!PushModule(out, state, module.c_str())) return false; if (setglobal) @@ -471,6 +551,8 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std int nargs, int nres, bool perr, const char *debug_tag, int env_idx) { + AssertCoreSuspend(state); + if (!debug_tag) debug_tag = code.c_str(); if (env_idx) @@ -507,6 +589,8 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, const char *prompt, int env, const char *hfile) { + AssertCoreSuspend(state); + if (!out.is_console()) return false; if (!lua_checkstack(state, 20)) @@ -761,6 +845,15 @@ static int dfhack_open_plugin(lua_State *L) return 0; } +bool Lua::IsCoreContext(lua_State *state) +{ + // This uses a private field of the lua state to + // evaluate the condition without accessing the lua + // stack, and thus requiring a lock on the core state. + return state && Lua::Core::State && + state->l_G == Lua::Core::State->l_G; +} + static const luaL_Reg dfhack_funcs[] = { { "print", lua_dfhack_print }, { "println", lua_dfhack_println }, @@ -777,6 +870,132 @@ static const luaL_Reg dfhack_funcs[] = { { NULL, NULL } }; +/************ + * Events * + ************/ + +static int DFHACK_EVENT_META_TOKEN = 0; + +int DFHack::Lua::NewEvent(lua_State *state) +{ + lua_newtable(state); + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN); + lua_setmetatable(state, -2); + return 1; +} + +static void dfhack_event_invoke(lua_State *L, int base, bool from_c) +{ + int event = base+1; + int num_args = lua_gettop(L)-event; + + int errorfun = base+2; + lua_pushcfunction(L, dfhack_onerror); + lua_insert(L, errorfun); + + int argbase = base+3; + lua_pushnil(L); + + // stack: |base| event errorfun (args) key cb (args) + + while (lua_next(L, event)) + { + if (from_c && lua_islightuserdata(L, -1) && !lua_touserdata(L, -1)) + continue; + + for (int i = 0; i < num_args; i++) + lua_pushvalue(L, argbase+i); + + if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK) + { + report_error(L); + lua_pop(L, 1); + } + } + + lua_settop(L, base); +} + +static int dfhack_event_call(lua_State *state) +{ + luaL_checktype(state, 1, LUA_TTABLE); + luaL_checkstack(state, lua_gettop(state)+2, "stack overflow in event dispatch"); + + dfhack_event_invoke(state, 0, false); + return 0; +} + +void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args) +{ + AssertCoreSuspend(state); + + int base = lua_gettop(state) - num_args; + + if (!lua_checkstack(state, num_args+4)) + { + out.printerr("Stack overflow in Lua::InvokeEvent"); + lua_settop(state, base); + return; + } + + lua_rawgetp(state, LUA_REGISTRYINDEX, key); + + if (!lua_istable(state, -1)) + { + if (!lua_isnil(state, -1)) + out.printerr("Invalid event object in Lua::InvokeEvent"); + lua_settop(state, base); + return; + } + + lua_insert(state, base+1); + + color_ostream *cur_out = Lua::GetOutput(state); + set_dfhack_output(state, &out); + dfhack_event_invoke(state, base, true); + set_dfhack_output(state, cur_out); +} + +void DFHack::Lua::CreateEvent(lua_State *state, void *key) +{ + lua_rawgetp(state, LUA_REGISTRYINDEX, key); + + if (lua_isnil(state, -1)) + { + lua_pop(state, 1); + NewEvent(state); + } + + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, key); +} + +void DFHack::Lua::Notification::invoke(color_ostream &out, int nargs) +{ + assert(state); + InvokeEvent(out, state, key, nargs); +} + +void DFHack::Lua::Notification::bind(lua_State *state, void *key) +{ + this->state = state; + this->key = key; +} + +void DFHack::Lua::Notification::bind(lua_State *state, const char *name) +{ + CreateEvent(state, this); + + if (handler) + { + PushFunctionWrapper(state, 0, name, handler); + lua_rawsetp(state, -2, NULL); + } + + this->state = state; + this->key = this; +} + /************************ * Main Open function * ************************/ @@ -798,6 +1017,9 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) // Create the dfhack global lua_newtable(state); + lua_pushboolean(state, IsCoreContext(state)); + lua_setfield(state, -2, "is_core_context"); + // Create the metatable for exceptions lua_newtable(state); lua_pushcfunction(state, dfhack_exception_tostring); @@ -806,6 +1028,15 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN); lua_setfield(state, -2, "exception"); + lua_newtable(state); + lua_pushcfunction(state, dfhack_event_call); + lua_setfield(state, -2, "__call"); + lua_pushcfunction(state, Lua::NewEvent); + lua_setfield(state, -2, "new"); + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN); + lua_setfield(state, -2, "event"); + // Initialize the dfhack global luaL_setfuncs(state, dfhack_funcs, 0); @@ -813,9 +1044,40 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_setglobal(state, "dfhack"); + // stash the loaded module table into our own registry key + lua_getglobal(state, "package"); + assert(lua_istable(state, -1)); + lua_getfield(state, -1, "loaded"); + assert(lua_istable(state, -1)); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_LOADED_TOKEN); + lua_pop(state, 1); + // load dfhack.lua Require(out, state, "dfhack"); + lua_settop(state, 0); + if (!lua_checkstack(state, 64)) + out.printerr("Could not extend initial lua stack size to 64 items.\n"); + return state; } +void DFHack::Lua::Core::Init(color_ostream &out) +{ + if (State) + return; + + State = luaL_newstate(); + Lua::Open(out, State); +} + +void DFHack::Lua::Core::Reset(color_ostream &out, const char *where) +{ + int top = lua_gettop(State); + + if (top != 0) + { + out.printerr("Common lua context stack top left at %d after %s.\n", top, where); + lua_settop(State, 0); + } +} diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index f5ba565d5..c5ffe0f6b 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1036,7 +1036,9 @@ static int meta_call_function(lua_State *state) int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id) { - if (lua_gettop(state) != id->getNumArgs()) + if (id->adjustArgs()) + lua_settop(state, id->getNumArgs()); + else if (lua_gettop(state) != id->getNumArgs()) field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke"); try { @@ -1056,10 +1058,10 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id } /** - * Create a closure invoking the given function, and add it to the field table. + * Push a closure invoking the given function. */ -static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, - const char *name, function_identity_base *fun) +void LuaWrapper::PushFunctionWrapper(lua_State *state, int meta_idx, + const char *name, function_identity_base *fun) { lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN); if (meta_idx) @@ -1069,7 +1071,15 @@ static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, lua_pushfstring(state, "%s()", name); lua_pushlightuserdata(state, fun); lua_pushcclosure(state, meta_call_function, 4); +} +/** + * Create a closure invoking the given function, and add it to the field table. + */ +static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, + const char *name, function_identity_base *fun) +{ + PushFunctionWrapper(state, meta_idx, name, fun); lua_setfield(state, field_idx, name); } diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 4d0c06ddf..edc7fb9f8 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -33,6 +33,7 @@ distribution. #include "MiscUtils.h" #include "LuaWrapper.h" +#include "LuaTools.h" using namespace DFHack; @@ -380,6 +381,7 @@ command_result Plugin::on_update(color_ostream &out) if(state == PS_LOADED && plugin_onupdate) { cr = plugin_onupdate(out); + Lua::Core::Reset(out, "plugin_onupdate"); } access->lock_sub(); return cr; @@ -392,6 +394,7 @@ command_result Plugin::on_state_change(color_ostream &out, state_change_event ev if(state == PS_LOADED && plugin_onstatechange) { cr = plugin_onstatechange(out, event); + Lua::Core::Reset(out, "plugin_onstatechange"); } access->lock_sub(); return cr; @@ -445,9 +448,7 @@ void Plugin::index_lua(DFLibrary *lib) for (; cmdlist->name; ++cmdlist) { auto &cmd = lua_commands[cmdlist->name]; - if (!cmd) cmd = new LuaCommand; - cmd->owner = this; - cmd->name = cmdlist->name; + if (!cmd) cmd = new LuaCommand(this,cmdlist->name); cmd->command = cmdlist->command; } } @@ -456,12 +457,22 @@ void Plugin::index_lua(DFLibrary *lib) for (; funlist->name; ++funlist) { auto &cmd = lua_functions[funlist->name]; - if (!cmd) cmd = new LuaFunction; - cmd->owner = this; - cmd->name = funlist->name; + if (!cmd) cmd = new LuaFunction(this,funlist->name); cmd->identity = funlist->identity; } } + if (auto evlist = (EventReg*)LookupPlugin(lib, "plugin_lua_events")) + { + for (; evlist->name; ++evlist) + { + auto &cmd = lua_events[evlist->name]; + if (!cmd) cmd = new LuaEvent(this,evlist->name); + cmd->handler.identity = evlist->event->get_handler(); + cmd->event = evlist->event; + if (cmd->active) + cmd->event->bind(Lua::Core::State, cmd); + } + } } void Plugin::reset_lua() @@ -470,6 +481,11 @@ void Plugin::reset_lua() it->second->command = NULL; for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) it->second->identity = NULL; + for (auto it = lua_events.begin(); it != lua_events.end(); ++it) + { + it->second->handler.identity = NULL; + it->second->event = NULL; + } } int Plugin::lua_cmd_wrapper(lua_State *state) @@ -513,13 +529,35 @@ void Plugin::open_lua(lua_State *state, int table) for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) { - lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN); - lua_pushlightuserdata(state, NULL); - lua_pushfstring(state, "%s.%s()", name.c_str(), it->second->name.c_str()); - lua_pushlightuserdata(state, it->second); - lua_pushcclosure(state, lua_fun_wrapper, 4); + push_function(state, it->second); lua_setfield(state, table, it->first.c_str()); } + + if (Lua::IsCoreContext(state)) + { + for (auto it = lua_events.begin(); it != lua_events.end(); ++it) + { + Lua::CreateEvent(state, it->second); + + push_function(state, &it->second->handler); + lua_rawsetp(state, -2, NULL); + + it->second->active = true; + if (it->second->event) + it->second->event->bind(state, it->second); + + lua_setfield(state, table, it->first.c_str()); + } + } +} + +void Plugin::push_function(lua_State *state, LuaFunction *fn) +{ + lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN); + lua_pushlightuserdata(state, NULL); + lua_pushfstring(state, "%s.%s()", name.c_str(), fn->name.c_str()); + lua_pushlightuserdata(state, fn); + lua_pushcclosure(state, lua_fun_wrapper, 4); } PluginManager::PluginManager(Core * core) diff --git a/library/include/Core.h b/library/include/Core.h index f8ac8e783..d18ebfa71 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -90,6 +90,8 @@ namespace DFHack static Core instance; return instance; } + /// check if the activity lock is owned by this thread + bool isSuspended(void); /// try to acquire the activity lock void Suspend(void); /// return activity lock diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index a949e784b..8b917ad70 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -161,15 +161,15 @@ INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (vA1,vA2,vA3,vA4,vA5), public: typedef function_wrapper wrapper; - function_identity(T ptr) - : function_identity_base(wrapper::num_args), ptr(ptr) {}; + function_identity(T ptr, bool vararg) + : function_identity_base(wrapper::num_args, vararg), ptr(ptr) {}; virtual void invoke(lua_State *state, int base) { wrapper::execute(state, base, ptr); } }; template - inline function_identity_base *wrap_function(T ptr) { + inline function_identity_base *wrap_function(T ptr, bool vararg = false) { // bah, but didn't have any idea how to allocate statically - return new function_identity(ptr); + return new function_identity(ptr, vararg); } } \ No newline at end of file diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index 279400f74..a775c85b2 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -39,13 +39,17 @@ namespace DFHack { class DFHACK_EXPORT function_identity_base : public type_identity { int num_args; + bool vararg; public: - function_identity_base(int num_args) : type_identity(0), num_args(num_args) {}; + function_identity_base(int num_args, bool vararg = false) + : type_identity(0), num_args(num_args), vararg(vararg) {}; virtual identity_type type() { return IDTYPE_FUNCTION; } int getNumArgs() { return num_args; } + bool adjustArgs() { return vararg; } + std::string getFullName() { return "function"; } virtual void invoke(lua_State *state, int base) = 0; diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 7e3ed1683..70a2e5fca 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -34,18 +34,33 @@ distribution. #include #include -namespace DFHack { namespace Lua { +namespace DFHack { + class function_identity_base; +} + +namespace DFHack {namespace Lua { /** * Create or initialize a lua interpreter with access to DFHack tools. */ DFHACK_EXPORT lua_State *Open(color_ostream &out, lua_State *state = NULL); /** - * Load a module using require(). + * Load a module using require(). Leaves the stack as is. */ DFHACK_EXPORT bool Require(color_ostream &out, lua_State *state, const std::string &module, bool setglobal = false); + /** + * Push the module table, loading it using require() if necessary. + */ + DFHACK_EXPORT bool PushModule(color_ostream &out, lua_State *state, const char *module); + + /** + * Push the public object name exported by the module. Uses PushModule. + */ + DFHACK_EXPORT bool PushModulePublic(color_ostream &out, lua_State *state, + const char *module, const char *name); + /** * Check if the object at the given index is NIL or NULL. */ @@ -79,6 +94,12 @@ namespace DFHack { namespace Lua { */ DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false); + /** + * Check that the value is a wrapped DF object of the given type, and if so return the pointer. + * Otherwise throw an argument type error. + */ + DFHACK_EXPORT void *CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false); + /** * Assign the value at val_index to the target of given identity using df.assign(). * Return behavior is of SafeCall below. @@ -102,6 +123,14 @@ namespace DFHack { namespace Lua { return (T*)GetDFObject(state, df::identity_traits::get(), val_index, exact_type); } + /** + * Check that the value is a wrapped DF object of the correct type, and if so return the pointer. Otherwise throw an argument type error. + */ + template + T *CheckDFObject(lua_State *state, int val_index, bool exact_type = false) { + return (T*)CheckDFObject(state, df::identity_traits::get(), val_index, exact_type); + } + /** * Assign the value at val_index to the target using df.assign(). */ @@ -134,5 +163,146 @@ namespace DFHack { namespace Lua { */ DFHACK_EXPORT bool InterpreterLoop(color_ostream &out, lua_State *state, const char *prompt = NULL, int env = 0, const char *hfile = NULL); + + /** + * Push utility functions + */ +#define NUMBER_PUSH(type) inline void Push(lua_State *state, type value) { lua_pushnumber(state, value); } + NUMBER_PUSH(char) + NUMBER_PUSH(int8_t) NUMBER_PUSH(uint8_t) + NUMBER_PUSH(int16_t) NUMBER_PUSH(uint16_t) + NUMBER_PUSH(int32_t) NUMBER_PUSH(uint32_t) + NUMBER_PUSH(int64_t) NUMBER_PUSH(uint64_t) + NUMBER_PUSH(float) NUMBER_PUSH(double) +#undef NUMBER_PUSH + inline void Push(lua_State *state, bool value) { + lua_pushboolean(state, value); + } + inline void Push(lua_State *state, const std::string &str) { + lua_pushlstring(state, str.data(), str.size()); + } + inline void Push(lua_State *state, df::coord &obj) { PushDFObject(state, &obj); } + inline void Push(lua_State *state, df::coord2d &obj) { PushDFObject(state, &obj); } + template inline void Push(lua_State *state, T *ptr) { + PushDFObject(state, ptr); + } + + template + void PushVector(lua_State *state, const T &pvec) + { + lua_createtable(state,pvec.size(),0); + + for (size_t i = 0; i < pvec.size(); i++) + { + Push(state, pvec[i]); + lua_rawseti(state, -2, i+1); + } + } + + DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos); + + DFHACK_EXPORT bool IsCoreContext(lua_State *state); + + DFHACK_EXPORT int NewEvent(lua_State *state); + DFHACK_EXPORT void CreateEvent(lua_State *state, void *key); + DFHACK_EXPORT void InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args); + + /** + * Namespace for the common lua interpreter state. + * All accesses must be done under CoreSuspender. + */ + namespace Core { + DFHACK_EXPORT extern lua_State *State; + + // Not exported; for use by the Core class + void Init(color_ostream &out); + void Reset(color_ostream &out, const char *where); + + template inline void Push(T &arg) { Lua::Push(State, arg); } + template inline void Push(const T &arg) { Lua::Push(State, arg); } + template inline void PushVector(const T &arg) { Lua::PushVector(State, arg); } + + inline bool SafeCall(color_ostream &out, int nargs, int nres, bool perr = true) { + return Lua::SafeCall(out, State, nargs, nres, perr); + } + inline bool PushModule(color_ostream &out, const char *module) { + return Lua::PushModule(out, State, module); + } + inline bool PushModulePublic(color_ostream &out, const char *module, const char *name) { + return Lua::PushModulePublic(out, State, module, name); + } + } + + class DFHACK_EXPORT Notification { + lua_State *state; + void *key; + function_identity_base *handler; + + public: + Notification(function_identity_base *handler = NULL) + : state(NULL), key(NULL), handler(handler) {} + + lua_State *get_state() { return state; } + function_identity_base *get_handler() { return handler; } + + void invoke(color_ostream &out, int nargs); + + void bind(lua_State *state, const char *name); + void bind(lua_State *state, void *key); + }; }} +#define DEFINE_LUA_EVENT_0(name, handler) \ + static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ + void name(color_ostream &out) { \ + handler(out); \ + if (name##_event.get_state()) { \ + name##_event.invoke(out, 0); \ + } \ + } + +#define DEFINE_LUA_EVENT_1(name, handler, arg_type1) \ + static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ + void name(color_ostream &out, arg_type1 arg1) { \ + handler(out, arg1); \ + if (auto state = name##_event.get_state()) { \ + DFHack::Lua::Push(state, arg1); \ + name##_event.invoke(out, 1); \ + } \ + } + +#define DEFINE_LUA_EVENT_2(name, handler, arg_type1, arg_type2) \ + static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ + void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2) { \ + handler(out, arg1, arg2); \ + if (auto state = name##_event.get_state()) { \ + DFHack::Lua::Push(state, arg1); \ + DFHack::Lua::Push(state, arg2); \ + name##_event.invoke(out, 2); \ + } \ + } + +#define DEFINE_LUA_EVENT_3(name, handler, arg_type1, arg_type2, arg_type3) \ + static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ + void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3) { \ + handler(out, arg1, arg2, arg3); \ + if (auto state = name##_event.get_state()) { \ + DFHack::Lua::Push(state, arg1); \ + DFHack::Lua::Push(state, arg2); \ + DFHack::Lua::Push(state, arg3); \ + name##_event.invoke(out, 3); \ + } \ + } + +#define DEFINE_LUA_EVENT_4(name, handler, arg_type1, arg_type2, arg_type3, arg_type4) \ + static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ + void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4) { \ + handler(out, arg1, arg2, arg3, arg4); \ + if (auto state = name##_event.get_state()) { \ + DFHack::Lua::Push(state, arg1); \ + DFHack::Lua::Push(state, arg2); \ + DFHack::Lua::Push(state, arg3); \ + DFHack::Lua::Push(state, arg4); \ + name##_event.invoke(out, 4); \ + } \ + } diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 2304b940d..97b2e6980 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -222,6 +222,12 @@ namespace DFHack { namespace LuaWrapper { */ void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); + /** + * Push a closure invoking the given function. + */ + void PushFunctionWrapper(lua_State *state, int meta_idx, + const char *name, function_identity_base *fun); + /** * Wrap functions and add them to the table on the top of the stack. */ diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 080e66e66..a51d90747 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -51,6 +51,9 @@ namespace DFHack class virtual_identity; class RPCService; class function_identity_base; + namespace Lua { + class Notification; + } // anon type, pretty much struct DFLibrary; @@ -80,6 +83,10 @@ namespace DFHack const char *name; function_identity_base *identity; }; + struct DFHACK_EXPORT EventReg { + const char *name; + Lua::Notification *event; + }; struct DFHACK_EXPORT PluginCommand { typedef command_result (*command_function)(color_ostream &out, std::vector &); @@ -178,6 +185,7 @@ namespace DFHack Plugin *owner; std::string name; int (*command)(lua_State *state); + LuaCommand(Plugin *owner, std::string name) : owner(owner), name(name) {} }; std::map lua_commands; static int lua_cmd_wrapper(lua_State *state); @@ -186,9 +194,19 @@ namespace DFHack Plugin *owner; std::string name; function_identity_base *identity; + LuaFunction(Plugin *owner, std::string name) : owner(owner), name(name) {} }; std::map lua_functions; static int lua_fun_wrapper(lua_State *state); + void push_function(lua_State *state, LuaFunction *fn); + + struct LuaEvent { + LuaFunction handler; + Lua::Notification *event; + bool active; + LuaEvent(Plugin *owner, std::string name) : handler(owner,name), active(false) {} + }; + std::map lua_events; void index_lua(DFLibrary *lib); void reset_lua(); @@ -253,7 +271,10 @@ namespace DFHack DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = #define DFHACK_PLUGIN_LUA_FUNCTIONS \ DFhackCExport const DFHack::FunctionReg plugin_lua_functions[] = +#define DFHACK_PLUGIN_LUA_EVENTS \ + DFhackCExport const DFHack::EventReg plugin_lua_events[] = #define DFHACK_LUA_COMMAND(name) { #name, name } -#define DFHACK_LUA_FUNCTION(name) { #name, df::wrap_function(name) } +#define DFHACK_LUA_FUNCTION(name) { #name, df::wrap_function(name,true) } +#define DFHACK_LUA_EVENT(name) { #name, &name##_event } #define DFHACK_LUA_END { NULL, NULL } diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 7230a12aa..63de69864 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -105,6 +105,10 @@ function xyz2pos(x,y,z) end end +function dfhack.event:__tostring() + return "" +end + function dfhack.persistent:__tostring() return "" diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index 0886d2a34..cfd2e27cd 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -121,6 +121,13 @@ command_result lua_run_file (color_ostream &out, std::vector ¶ } command_result lua_run (color_ostream &out, std::vector ¶meters) { + if (!parameters.empty() && parameters[0] == "--core-context") + { + CoreSuspender suspend; + Lua::InterpreterLoop(out, Lua::Core::State); + return CR_OK; + } + mymutex->lock(); lua::state s=lua::glua::Get(); diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index 74cb57c45..a629e30c1 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -5,6 +5,7 @@ #include "Error.h" #include "DataFuncs.h" +#include "LuaTools.h" #include "modules/Gui.h" #include "modules/Job.h" @@ -121,6 +122,8 @@ static int name_burrow_id = -1; static void handle_burrow_rename(color_ostream &out, df::burrow *burrow); +DEFINE_LUA_EVENT_1(onBurrowRename, handle_burrow_rename, df::burrow*); + static void detect_burrow_renames(color_ostream &out) { if (ui->main.mode == ui_sidebar_mode::Burrows && @@ -134,7 +137,7 @@ static void detect_burrow_renames(color_ostream &out) auto burrow = df::burrow::find(name_burrow_id); name_burrow_id = -1; if (burrow) - handle_burrow_rename(out, burrow); + onBurrowRename(out, burrow); } } @@ -151,6 +154,9 @@ static std::map diggers; static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos, df::tiletype old_tile, df::tiletype new_tile); +DEFINE_LUA_EVENT_4(onDigComplete, handle_dig_complete, + df::job_type, df::coord, df::tiletype, df::tiletype); + static void detect_digging(color_ostream &out) { for (auto it = diggers.begin(); it != diggers.end();) @@ -172,7 +178,7 @@ static void detect_digging(color_ostream &out) if (new_tile != it->second.old_tile) { - handle_dig_complete(out, it->second.job, pos, it->second.old_tile, new_tile); + onDigComplete(out, it->second.job, pos, it->second.old_tile, new_tile); //if (worker && !worker->job.current_job) // worker->counters.think_counter = worker->counters.job_counter = 0; @@ -410,6 +416,17 @@ static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord } } +static void renameBurrow(color_ostream &out, df::burrow *burrow, std::string name) +{ + CHECK_NULL_POINTER(burrow); + + // The event makes this absolutely necessary + CoreSuspender suspend; + + burrow->name = name; + onBurrowRename(out, burrow); +} + static df::burrow *findByName(color_ostream &out, std::string name, bool silent = false) { int id = -1; @@ -552,6 +569,7 @@ static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) } DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(renameBurrow), DFHACK_LUA_FUNCTION(findByName), DFHACK_LUA_FUNCTION(copyUnits), DFHACK_LUA_FUNCTION(copyTiles), @@ -559,6 +577,12 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_END }; +DFHACK_PLUGIN_LUA_EVENTS { + DFHACK_LUA_EVENT(onBurrowRename), + DFHACK_LUA_EVENT(onDigComplete), + DFHACK_LUA_END +}; + static command_result burrow(color_ostream &out, vector ¶meters) { CoreSuspender suspend; From 92bc28bb81658e07b09bf1a0f60c4ca82cc9623b Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 15 Apr 2012 18:16:45 +0300 Subject: [PATCH 088/108] Autobuild script update... Edit if you want different zip names. --- build/generate-MSVC-all-breakfast.bat | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/generate-MSVC-all-breakfast.bat b/build/generate-MSVC-all-breakfast.bat index 08c5b03ff..1921fba6c 100644 --- a/build/generate-MSVC-all-breakfast.bat +++ b/build/generate-MSVC-all-breakfast.bat @@ -4,5 +4,6 @@ IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF mkdir VC2010 cd VC2010 echo generating a build folder -for /f "delims=" %%a in ('DATE /T') do @set myvar=breakfast-%%a +rem for /f "delims=" %%a in ('DATE /T') do @set myvar=breakfast-%BUILD_NUMBER% +set myvar=breakfast-%BUILD_NUMBER% cmake ..\.. -G"Visual Studio 10" -DDFHACK_RELEASE="%myvar%" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 From 8bbd43f2c955ac812000f75443997bcf3d65e36f Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sun, 15 Apr 2012 17:40:39 +0200 Subject: [PATCH 089/108] zone: added filters to search for creatures who can be trained for war/hunting --- README.rst | 44 +++++++++++++++++++++++--------------------- plugins/zone.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/README.rst b/README.rst index a563f7bb8..d8f8398c0 100644 --- a/README.rst +++ b/README.rst @@ -817,27 +817,29 @@ Options: Filters: -------- -:all: Process all units (to be used with additional filters). -:count: Must be followed by a number. Process only n units (to be used with additional filters). -:race: Must be followed by a race raw id (e.g. BIRD_TURKEY, ALPACA etc). -:unassigned: Not assigned to zone, chain or built cage. -:caged: In a built cage. -:uncaged: Not in a cage (in case you want your stockpiles to be left alone). -:foreign: Not of own civilization. -:own: From own civilization. -:merchant: Is a merchant / belongs to a merchant. Should only be used for pitting, not for stealing animals (slaughter should work). -:war: Trained war creature. -:tamed: Creature is tame. -:trained: Creature is trained. -:untrained: Creature is untrained. -:male: Creature is male. -:female: Creature is female. -:egglayer: Race lays eggs. -:grazer: Race is a grazer. -:nograzer: Race is not a grazer. -:milkable: Race is milkable. -:minage: Minimum age. Must be followed by number. -:maxage: Maximum age. Must be followed by number. +:all: Process all units (to be used with additional filters). +:count: Must be followed by a number. Process only n units (to be used with additional filters). +:race: Must be followed by a race raw id (e.g. BIRD_TURKEY, ALPACA etc). +:unassigned: Not assigned to zone, chain or built cage. +:caged: In a built cage. +:uncaged: Not in a cage (in case you want your stockpiles to be left alone). +:foreign: Not of own civilization. +:own: From own civilization. +:merchant: Is a merchant / belongs to a merchant. Should only be used for pitting, not for stealing animals (slaughter should work). +:war: Trained war creature. +:tamed: Creature is tame. +:trained: Creature is trained. +:untrained: Creature is untrained. +:trainablewar: Creature can be trained for war (and is not already trained for war/hunt). +:trainablehunt: Creature can be trained for hunting (and is not already trained for war/hunt). +:male: Creature is male. +:female: Creature is female. +:egglayer: Race lays eggs. +:grazer: Race is a grazer. +:nograzer: Race is not a grazer. +:milkable: Race is milkable. +:minage: Minimum age. Must be followed by number. +:maxage: Maximum age. Must be followed by number. Usage with single units ----------------------- diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 7694ad00d..55a150691 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -116,6 +116,8 @@ const string zone_help_filters = " (could have weird effects during trading, be careful)\n" " trained - obvious\n" " untrained - obvious\n" + " trainablewar - can be trained for war (and is not already trained)\n" + " trainablehunt- can be trained for hunting (and is not already trained)\n" " male - obvious\n" " female - obvious\n" " egglayer - race lays eggs (use together with 'female')\n" @@ -563,6 +565,32 @@ bool isMilkable(df::unit* unit) return false; } +bool isTrainableWar(df::unit* unit) +{ + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + size_t sizecas = raw->caste.size(); + for (size_t j = 0; j < sizecas;j++) + { + df::caste_raw *caste = raw->caste[j]; + if(caste->flags.is_set(caste_raw_flags::TRAINABLE_WAR)) + return true; + } + return false; +} + +bool isTrainableHunting(df::unit* unit) +{ + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + size_t sizecas = raw->caste.size(); + for (size_t j = 0; j < sizecas;j++) + { + df::caste_raw *caste = raw->caste[j]; + if(caste->flags.is_set(caste_raw_flags::TRAINABLE_HUNTING)) + return true; + } + return false; +} + bool isMale(df::unit* unit) { return unit->sex == 1; @@ -1459,6 +1487,8 @@ command_result df_zone (color_ostream &out, vector & parameters) bool find_uncaged = false; bool find_foreign = false; bool find_untrained = false; + bool find_trainable_war = false; + bool find_trainable_hunting = false; //bool find_trained = false; bool find_war = false; bool find_own = false; @@ -1593,6 +1623,16 @@ command_result df_zone (color_ostream &out, vector & parameters) out << "Filter by 'untrained'." << endl; find_untrained = true; } + else if(p == "trainablewar") + { + out << "Filter by 'trainable for war'." << endl; + find_trainable_war = true; + } + else if(p == "trainablehunt") + { + out << "Filter by 'trainable for hunting'." << endl; + find_trainable_hunting = true; + } else if(p == "war") { out << "Filter by 'trained war creature'." << endl; @@ -1896,6 +1936,8 @@ command_result df_zone (color_ostream &out, vector & parameters) || (find_male && !isMale(unit)) || (find_female && !isFemale(unit)) || (find_named && !unit->name.has_name) + || (find_trainable_war && (isWar(unit) || isHunter(unit) || !isTrainableWar(unit))) + || (find_trainable_hunting && (isWar(unit) || isHunter(unit) || !isTrainableHunting(unit))) ) continue; From 6197c4e7397d8cd67f8486ce2470280251f9edd7 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sun, 15 Apr 2012 19:49:50 +0200 Subject: [PATCH 090/108] zone: added keyword 'not' which negates the filter followed by it (e.g. 'not grazer') --- README.rst | 41 ++++---- plugins/zone.cpp | 244 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 215 insertions(+), 70 deletions(-) diff --git a/README.rst b/README.rst index d8f8398c0..cae931cc5 100644 --- a/README.rst +++ b/README.rst @@ -802,11 +802,11 @@ Export dwarves to RuneSmith-compatible XML. zone ==== -Helps a bit with managing activity zones (pens, pastures and pits). +Helps a bit with managing activity zones (pens, pastures and pits) and cages. Options: -------- -:set: Set zone under cursor as default for future assigns. +:set: Set zone or cage under cursor as default for future assigns. :assign: Assign unit(s) to the pen or pit marked with the 'set' command. If no filters are set a unit must be selected in the in-game ui. Can also be followed by a valid zone id which will be set instead. :unassign: Unassign selected creature from it's zone. :uinfo: Print info about unit(s). If no filters are set a unit must be selected in the in-game ui. @@ -814,32 +814,29 @@ Options: :verbose: Print some more info. :filters: Print list of valid filter options. :examples: Print some usage examples. +:not: Negates the next filter keyword. Filters: -------- :all: Process all units (to be used with additional filters). :count: Must be followed by a number. Process only n units (to be used with additional filters). -:race: Must be followed by a race raw id (e.g. BIRD_TURKEY, ALPACA etc). :unassigned: Not assigned to zone, chain or built cage. -:caged: In a built cage. -:uncaged: Not in a cage (in case you want your stockpiles to be left alone). -:foreign: Not of own civilization. -:own: From own civilization. -:merchant: Is a merchant / belongs to a merchant. Should only be used for pitting, not for stealing animals (slaughter should work). -:war: Trained war creature. -:tamed: Creature is tame. -:trained: Creature is trained. -:untrained: Creature is untrained. -:trainablewar: Creature can be trained for war (and is not already trained for war/hunt). -:trainablehunt: Creature can be trained for hunting (and is not already trained for war/hunt). -:male: Creature is male. -:female: Creature is female. -:egglayer: Race lays eggs. -:grazer: Race is a grazer. -:nograzer: Race is not a grazer. -:milkable: Race is milkable. :minage: Minimum age. Must be followed by number. :maxage: Maximum age. Must be followed by number. +:race: Must be followed by a race raw id (e.g. BIRD_TURKEY, ALPACA etc). Negatable. +:caged: In a built cage. Negatable. +:own: From own civilization. Negatable. +:merchant: Is a merchant / belongs to a merchant. Should only be used for pitting, not for stealing animals (slaughter should work). +:war: Trained war creature. Negatable. +:tamed: Creature is tame. Negatable. +:trained: Creature is trained. Negatable. +:trainablewar: Creature can be trained for war (and is not already trained for war/hunt). Negatable. +:trainablehunt: Creature can be trained for hunting (and is not already trained for war/hunt). Negatable. +:male: Creature is male. Negatable. +:female: Creature is female. Negatable. +:egglayer: Race lays eggs. Negatable. +:grazer: Race is a grazer. Negatable. +:milkable: Race is milkable. Negatable. Usage with single units ----------------------- @@ -847,12 +844,14 @@ One convenient way to use the zone tool is to bind the command 'zone assign' to Usage with filters ------------------ -All filters can be used together with the 'assign' command. The only restriction is that it's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. Usually you should always use the filter 'own' (which implies tame) unless you want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you specify 'race DWARF' (so it's safe to use 'assign all own' to one big pasture if you want to have all your animals at the same place). 'egglayer' and 'milkable' should be used together with 'female' unless you have a mod with egg-laying male elves who give milk or whatever. Merchants and their animals are ignored unless you specify 'merchant' (pitting them should be no problem, but stealing and pasturing their animals is not a good idea since currently they are not properly added to your own stocks; slaughtering them should work). +All filters can be used together with the 'assign' command. The only restriction is that it's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. Usually you should always use the filter 'own' (which implies tame) unless you want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you specify 'race DWARF' (so it's safe to use 'assign all own' to one big pasture if you want to have all your animals at the same place). 'egglayer' and 'milkable' should be used together with 'female' unless you have a mod with egg-laying male elves who give milk or whatever. Merchants and their animals are ignored unless you specify 'merchant' (pitting them should be no problem, but stealing and pasturing their animals is not a good idea since currently they are not properly added to your own stocks; slaughtering them should work). Most filters can be negated (e.g. 'not grazer' -> race is not a grazer). ``zone assign all own ALPACA minage 3 maxage 10`` Assign all own alpacas who are between 3 and 10 years old to the selected pasture. ``zone assign all own caged grazer`` Assign all own grazers who are sitting in cages on stockpiles (e.g. after buying them from merchants) to the selected pasture. +``zone assign all own not grazer not race CAT`` + Assign all own animals who are not grazers, excluding cats. ``zone assign count 5 own female milkable`` Assign up to 5 own female milkable creatures to the selected pasture. ``zone assign all own race DWARF maxage 2`` diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 55a150691..c3f065e45 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -96,17 +96,20 @@ const string zone_help = const string zone_help_filters = "Filters (to be used in combination with 'all' or 'count'):\n" + "These filters can not be used with the prefix 'not':" " all - in combinations with zinfo/uinfo: print all zones/units\n" " in combination with assign: process all units\n" " should be used in combination with further filters\n" " count - must be followed by number. process X units\n" " should be used in combination with further filters\n" - " race - must be followed by a race raw id (e.g. BIRD_TURKEY)\n" " unassigned - not assigned to zone, chain or built cage\n" + " age - exact age. must be followed by number\n" + " minage - minimum age. must be followed by number\n" + " maxage - maximum age. must be followed by number\n" + "These filters can be used with the prefix 'not' (e.g. 'not own'):" + " race - must be followed by a race raw id (e.g. BIRD_TURKEY)\n" " caged - in a built cage\n" - " uncaged - not in cage\n" " own - from own civilization\n" - " foreign - not of own civilization\n" " war - trained war creature\n" " tamed - tamed\n" " named - has name or nickname\n" @@ -115,18 +118,14 @@ const string zone_help_filters = " can be used to pit merchants and slaughter their animals\n" " (could have weird effects during trading, be careful)\n" " trained - obvious\n" - " untrained - obvious\n" " trainablewar - can be trained for war (and is not already trained)\n" " trainablehunt- can be trained for hunting (and is not already trained)\n" " male - obvious\n" " female - obvious\n" " egglayer - race lays eggs (use together with 'female')\n" - " noegglayer - race does not lay eggs\n" " grazer - is a grazer\n" - " nograzer - not a grazer\n" " milkable - race is milkable (use together with 'female')\n" - " minage - minimum age. must be followed by number\n" - " maxage - maximum age. must be followed by number\n"; + ; const string zone_help_examples = "Example for assigning single units:\n" @@ -138,6 +137,7 @@ const string zone_help_examples = "Examples for assigning with filters:\n" " (this assumes you have already set up a target zone)\n" " zone assign all own grazer maxage 10\n" + " zone assign all own milkable not grazer\n" " zone assign count 5 own female milkable\n" " zone assign all own race DWARF maxage 2\n" " throw all useless kids into a pit :)\n" @@ -1482,26 +1482,38 @@ command_result df_zone (color_ostream &out, vector & parameters) //bool cage_info = false; //bool chain_info = false; + bool invert_filter = false; bool find_unassigned = false; bool find_caged = false; - bool find_uncaged = false; - bool find_foreign = false; - bool find_untrained = false; + bool find_not_caged = false; bool find_trainable_war = false; + bool find_not_trainable_war = false; bool find_trainable_hunting = false; - //bool find_trained = false; + bool find_not_trainable_hunting = false; + bool find_trained = false; + bool find_not_trained = false; bool find_war = false; + bool find_not_war = false; + bool find_hunter = false; + bool find_not_hunter = false; bool find_own = false; + bool find_not_own = false; bool find_tame = false; + bool find_not_tame = false; bool find_merchant = false; + bool find_not_merchant = false; bool find_male = false; + bool find_not_male = false; bool find_female = false; + bool find_not_female = false; bool find_egglayer = false; - bool find_noegglayer = false; + bool find_not_egglayer = false; bool find_grazer = false; - bool find_nograzer = false; + bool find_not_grazer = false; bool find_milkable = false; + bool find_not_milkable = false; bool find_named = false; + bool find_not_named = false; bool find_agemin = false; bool find_agemax = false; @@ -1512,6 +1524,7 @@ command_result df_zone (color_ostream &out, vector & parameters) size_t target_count = 0; bool find_race = false; + bool find_not_race = false; string target_race = ""; bool building_assign = false; @@ -1544,21 +1557,39 @@ command_result df_zone (color_ostream &out, vector & parameters) else if(p == "zinfo") { zone_info = true; + invert_filter=false; } else if(p == "uinfo") { unit_info = true; + invert_filter=false; } else if(p == "verbose") { verbose = true; + if(invert_filter) + { + verbose = false; + invert_filter=false; + } } else if(p == "unassign") { + if(invert_filter) + { + out << "'not unassign' makes no sense." << endl; + return CR_WRONG_USAGE; + } building_unassign = true; } else if(p == "assign") { + if(invert_filter) + { + out << "'not assign' makes no sense. (did you want to use unassign?)" << endl; + return CR_WRONG_USAGE; + } + // if followed by another parameter, check if it's numeric if(i < parameters.size()-1) { @@ -1583,7 +1614,7 @@ command_result df_zone (color_ostream &out, vector & parameters) building_assign = true; } } - else if(p == "race") + else if(p == "race" && !invert_filter) { if(i == parameters.size()-1) { @@ -1598,68 +1629,132 @@ command_result df_zone (color_ostream &out, vector & parameters) find_race = true; } } - else if(p == "foreign") + else if(p == "race" && invert_filter) { - out << "Filter by 'foreign' (i.e. not from the fortress civ, can be a dwarf)." << endl; - find_foreign = true; + if(i == parameters.size()-1) + { + out.printerr("No race id specified!"); + return CR_WRONG_USAGE; + } + else + { + target_race = parameters[i+1]; + i++; + out << "Excluding race: " << target_race << endl; + find_not_race = true; + } + invert_filter = false; + } + else if(p == "not") + { + invert_filter = true; } else if(p == "unassigned") { + if(invert_filter) + return CR_WRONG_USAGE; out << "Filter by 'unassigned'." << endl; find_unassigned = true; } - else if(p == "caged") + else if(p == "caged" && !invert_filter) { out << "Filter by 'caged' (ignores built cages)." << endl; find_caged = true; } - else if(p == "uncaged") + else if(p == "caged" && invert_filter) + { + out << "Filter by 'not caged'." << endl; + find_not_caged = true; + invert_filter = false; + } + else if(p == "trained" && !invert_filter) { - out << "Filter by 'uncaged'." << endl; - find_uncaged = true; + out << "Filter by 'trained'." << endl; + find_trained = true; } - else if(p == "untrained") + else if(p == "trained" && invert_filter) { out << "Filter by 'untrained'." << endl; - find_untrained = true; + find_not_trained = true; + invert_filter = false; } - else if(p == "trainablewar") + else if(p == "trainablewar" && !invert_filter) { out << "Filter by 'trainable for war'." << endl; find_trainable_war = true; } - else if(p == "trainablehunt") + else if(p == "trainablewar" && invert_filter) + { + out << "Filter by 'not trainable for war'." << endl; + find_not_trainable_war = true; + invert_filter = false; + } + else if(p == "trainablehunt"&& !invert_filter) { out << "Filter by 'trainable for hunting'." << endl; find_trainable_hunting = true; } - else if(p == "war") + else if(p == "trainablehunt"&& invert_filter) + { + out << "Filter by 'not trainable for hunting'." << endl; + find_not_trainable_hunting = true; + invert_filter = false; + } + else if(p == "war" && !invert_filter) { out << "Filter by 'trained war creature'." << endl; find_war = true; } - else if(p == "own") + else if(p == "war" && invert_filter) + { + out << "Filter by 'not a trained war creature'." << endl; + find_not_war = true; + invert_filter = false; + } + else if(p == "own"&& !invert_filter) { out << "Filter by 'own civilization'." << endl; find_own = true; } - else if(p == "tame") + else if(p == "own" && invert_filter) + { + out << "Filter by 'not own' (i.e. not from the fortress civ, can be a dwarf)." << endl; + find_not_own = true; + invert_filter = false; + } + else if(p == "tame" && !invert_filter) { out << "Filter by 'tame'." << endl; find_tame = true; } - else if(p == "named") + else if(p == "tame" && invert_filter) + { + out << "Filter by 'not tame'." << endl; + find_not_tame = true; + invert_filter=false; + } + else if(p == "named" && !invert_filter) { out << "Filter by 'has name or nickname'." << endl; find_named = true; } + else if(p == "named" && invert_filter) + { + out << "Filter by 'has no name or nickname'." << endl; + find_not_named = true; + invert_filter=false; + } else if(p == "slaughter") { + if(invert_filter) + return CR_WRONG_USAGE; out << "Assign animals for slaughter." << endl; unit_slaughter = true; } else if(p == "count") { + if(invert_filter) + return CR_WRONG_USAGE; if(i == parameters.size()-1) { out.printerr("No count specified!"); @@ -1681,6 +1776,8 @@ command_result df_zone (color_ostream &out, vector & parameters) } else if(p == "age") { + if(invert_filter) + return CR_WRONG_USAGE; if(i == parameters.size()-1) { out.printerr("No age specified!"); @@ -1699,6 +1796,8 @@ command_result df_zone (color_ostream &out, vector & parameters) } else if(p == "minage") { + if(invert_filter) + return CR_WRONG_USAGE; if(i == parameters.size()-1) { out.printerr("No age specified!"); @@ -1715,6 +1814,8 @@ command_result df_zone (color_ostream &out, vector & parameters) } else if(p == "maxage") { + if(invert_filter) + return CR_WRONG_USAGE; if(i == parameters.size()-1) { out.printerr("No age specified!"); @@ -1729,44 +1830,61 @@ command_result df_zone (color_ostream &out, vector & parameters) out << "Filter by maximum age of " << target_agemax << endl; } } - else if(p == "male") + else if(p == "male" && !invert_filter) { find_male = true; } - else if(p == "female") + else if(p == "female" && !invert_filter) { find_female = true; } - else if(p == "egglayer") + else if(p == "egglayer" && !invert_filter) { find_egglayer = true; } - else if(p == "noegglayer") - { - find_noegglayer = true; - } - else if(p == "grazer") + else if(p == "egglayer" && invert_filter) + { + find_not_egglayer = true; + invert_filter=false; + } + else if(p == "grazer" && !invert_filter) { find_grazer = true; } - else if(p == "nograzer") + else if(p == "grazer" && invert_filter) { - find_nograzer = true; + find_not_grazer = true; + invert_filter=false; } - else if(p == "merchant") + else if(p == "merchant" && !invert_filter) { find_merchant = true; } - else if(p == "milkable") + else if(p == "merchant" && invert_filter) + { + // actually 'not merchant' is pointless since merchant units are ignored by default + find_not_merchant = true; + invert_filter=false; + } + else if(p == "milkable" && !invert_filter) { find_milkable = true; } + else if(p == "milkable" && invert_filter) + { + find_not_milkable = true; + invert_filter=false; + } else if(p == "set") { + if(invert_filter) + return CR_WRONG_USAGE; building_set = true; } else if(p == "all") { + if(invert_filter) + return CR_WRONG_USAGE; out << "Filter: all" << endl; all = true; } @@ -1792,6 +1910,20 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_FAILURE; } + // search for male and not male is exclusive, so drop the flags if both are specified + if(find_male && find_not_male) + { + find_male=false; + find_not_male=false; + } + + // search for female and not female is exclusive, so drop the flags if both are specified + if(find_female && find_not_female) + { + find_female=false; + find_not_female=false; + } + // search for male and female is exclusive, so drop the flags if both are specified if(find_male && find_female) { @@ -1808,10 +1940,10 @@ command_result df_zone (color_ostream &out, vector & parameters) //} // search for grazer and nograzer is exclusive, so drop the flags if both are specified - if(find_grazer && find_nograzer) + if(find_grazer && find_not_grazer) { find_grazer=false; - find_nograzer=false; + find_not_grazer=false; } // try to cope with user dumbness @@ -1907,6 +2039,8 @@ command_result df_zone (color_ostream &out, vector & parameters) if(find_race && getRaceName(unit) != target_race) continue; + if(find_not_race && getRaceName(unit) == target_race) + continue; // ignore own dwarves by default if(isOwnCiv(unit) && isOwnRace(unit)) { @@ -1918,26 +2052,38 @@ command_result df_zone (color_ostream &out, vector & parameters) if( (find_unassigned && isAssigned(unit)) // avoid tampering with creatures who are currently being hauled to a built cage - || (isContainedInItem(unit) && (find_uncaged || isInBuiltCage(unit))) + || (isContainedInItem(unit) && (find_not_caged || isInBuiltCage(unit))) || (isChained(unit)) || (find_caged && !isContainedInItem(unit)) + || (find_not_caged && isContainedInItem(unit)) || (find_own && !isOwnCiv(unit)) - || (find_foreign && isOwnCiv(unit)) + || (find_not_own && isOwnCiv(unit)) || (find_tame && !isTame(unit)) - || (find_untrained && isTrained(unit)) + || (find_not_tame && isTame(unit)) + || (find_trained && !isTrained(unit)) + || (find_not_trained && isTrained(unit)) || (find_war && !isWar(unit)) + || (find_not_war && isWar(unit)) + || (find_hunter && !isHunter(unit)) + || (find_not_hunter && isHunter(unit)) || (find_agemin && getUnitAge(unit)target_agemax) || (find_grazer && !isGrazer(unit)) - || (find_nograzer && isGrazer(unit)) + || (find_not_grazer && isGrazer(unit)) || (find_egglayer && !isEggLayer(unit)) - || (find_noegglayer && isEggLayer(unit)) + || (find_not_egglayer && isEggLayer(unit)) || (find_milkable && !isMilkable(unit)) + || (find_not_milkable && isMilkable(unit)) || (find_male && !isMale(unit)) + || (find_not_male && isMale(unit)) || (find_female && !isFemale(unit)) + || (find_not_female && isFemale(unit)) || (find_named && !unit->name.has_name) + || (find_not_named && unit->name.has_name) || (find_trainable_war && (isWar(unit) || isHunter(unit) || !isTrainableWar(unit))) + || (find_not_trainable_war && isTrainableWar(unit)) // hm, is this check enough? || (find_trainable_hunting && (isWar(unit) || isHunter(unit) || !isTrainableHunting(unit))) + || (find_not_trainable_hunting && isTrainableHunting(unit)) // hm, is this check enough? ) continue; From a1756a864cc7c7a6f498c5cfe96e0a9d0ae57828 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 15 Apr 2012 21:50:22 +0400 Subject: [PATCH 091/108] Implement a way to do prompts from core context. The trick obviously is doing it without forcing DF to wait suspended. Fortunately, lua has built-in coroutine support, so the interactive prompt can simply yield and rely on the external loop to do the job. To use this however the REPL had to be replaced with lua code. --- library/LuaTools.cpp | 199 ++++++++++++++++++++---------------- library/include/LuaTools.h | 10 +- library/lua/dfhack.lua | 75 ++++++++++++++ plugins/Dfusion/dfusion.cpp | 3 +- 4 files changed, 197 insertions(+), 90 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 304d79bcc..bf40e4041 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -426,7 +426,7 @@ static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space { lua_pushboolean(L, success); lua_replace(L, base); /* put first result in first slot */ - return true; + return success; } } @@ -586,131 +586,157 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std return Lua::SafeCall(out, state, nargs, nres, perr); } -bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, - const char *prompt, int env, const char *hfile) +static int resume_query_loop(color_ostream &out, + lua_State *thread, lua_State *state, bool start, + std::string &prompt, std::string &histfile) { - AssertCoreSuspend(state); + color_ostream *cur_out = Lua::GetOutput(state); + set_dfhack_output(state, &out); + + int rv = lua_resume(thread, state, lua_gettop(thread)-(start?1:0)); + + set_dfhack_output(state, cur_out); + + if (rv == LUA_YIELD || rv == LUA_OK) + { + lua_settop(thread, 2); + prompt = ifnull(lua_tostring(thread, 1), ""); + histfile = ifnull(lua_tostring(thread, 2), ""); + } + + return rv; +} +bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, + bool (*init)(color_ostream&, lua_State*, lua_State*, void*), + void *arg) +{ if (!out.is_console()) return false; if (!lua_checkstack(state, 20)) return false; - if (!hfile) - hfile = "lua.history"; - if (!prompt) - prompt = "lua"; + Console &con = static_cast(out); + + lua_State *thread; + int rv; + std::string prompt; + std::string histfile; DFHack::CommandHistory hist; - hist.load(hfile); + std::string histname; - out.print("Type quit to exit interactive lua interpreter.\n"); + { + CoreSuspender suspend; - static bool print_banner = true; - if (print_banner) { - out.print("Shortcuts:\n" - " '= foo' => '_1,_2,... = foo'\n" - " '! foo' => 'print(foo)'\n" - "Both save the first result as '_'.\n"); - print_banner = false; - } + int base = lua_gettop(state); + thread = lua_newthread(state); - Console &con = static_cast(out); + if (!init(out, state, thread, arg)) + { + lua_settop(state, base); + return false; + } - // Make a proxy global environment. - lua_newtable(state); - int base = lua_gettop(state); + lua_settop(state, base+1); + lua_rawsetp(state, LUA_REGISTRYINDEX, thread); - lua_newtable(state); - if (env) - lua_pushvalue(state, env); - else - lua_rawgeti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); - lua_setfield(state, -2, "__index"); - lua_setmetatable(state, -2); + rv = resume_query_loop(out, thread, state, true, prompt, histfile); + } - // Main interactive loop - int vcnt = 1; - string curline; - string prompt_str = "[" + string(prompt) + "]# "; + while (rv == LUA_YIELD) + { + if (histfile != histname) + { + if (!histname.empty()) + hist.save(histname.c_str()); - for (;;) { - lua_settop(state, base); + hist.clear(); + histname = histfile; - con.lineedit(prompt_str,curline,hist); + if (!histname.empty()) + hist.load(histname.c_str()); + } - if (curline.empty()) - continue; - if (curline == "quit") - break; + if (prompt.empty()) + prompt = ">> "; + std::string curline; + con.lineedit(prompt,curline,hist); hist.add(curline); - char pfix = curline[0]; - - if (pfix == '=' || pfix == '!') { - curline = "return " + curline.substr(1); + CoreSuspender suspend; - if (!Lua::SafeCallString(out, state, curline, 0, LUA_MULTRET, true, "=(interactive)", base)) - continue; + lua_settop(thread, 0); + lua_pushlstring(thread, curline.data(), curline.size()); - int numret = lua_gettop(state) - base; + rv = resume_query_loop(out, thread, state, false, prompt, histfile); + } + } - if (numret >= 1) - { - lua_pushvalue(state, base+1); - lua_setfield(state, base, "_"); - - if (pfix == '!') - { - lua_pushcfunction(state, lua_dfhack_println); - lua_insert(state, base+1); - SafeCall(out, state, numret, 0); - continue; - } - } + if (!histname.empty()) + hist.save(histname.c_str()); - for (int i = 1; i <= numret; i++) - { - std::string name = stl_sprintf("_%d", vcnt++); - lua_pushvalue(state, base + i); - lua_setfield(state, base, name.c_str()); + { + CoreSuspender suspend; - out.print("%s = ", name.c_str()); + if (rv != LUA_OK) + { + lua_xmove(thread, state, 1); - lua_pushcfunction(state, lua_dfhack_println); - lua_pushvalue(state, base + i); - SafeCall(out, state, 1, 0); + if (convert_to_exception(state)) + { + luaL_traceback(state, thread, NULL, 1); + lua_setfield(state, -2, "stacktrace"); } + + report_error(state, &out); + lua_pop(state, 1); } - else - { - if (!Lua::SafeCallString(out, state, curline, 0, LUA_MULTRET, true, "=(interactive)", base)) - continue; - } + + lua_pushnil(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, thread); } - lua_settop(state, base-1); + return (rv == LUA_OK); +} + +namespace { + struct InterpreterArgs { + const char *prompt; + const char *hfile; + }; +} - hist.save(hfile); +static bool init_interpreter(color_ostream &out, lua_State *state, lua_State *thread, void *info) +{ + auto args = (InterpreterArgs*)info; + lua_getglobal(state, "dfhack"); + lua_getfield(state, -1, "interpreter"); + lua_pushstring(state, args->prompt); + lua_pushstring(state, args->hfile); + lua_xmove(state, thread, 3); + lua_pop(state, 1); return true; } -static int lua_dfhack_interpreter(lua_State *state) +bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, + const char *prompt, const char *hfile) { - Console *pstream = get_console(state); - if (!pstream) - return 2; + if (!out.is_console()) + return false; - int argc = lua_gettop(state); + if (!hfile) + hfile = "lua.history"; + if (!prompt) + prompt = "lua"; - const char *prompt = (argc >= 1 ? lua_tostring(state, 1) : NULL); - int env = (argc >= 2 && !lua_isnil(state,2) ? 2 : 0); - const char *hfile = (argc >= 3 ? lua_tostring(state, 3) : NULL); + InterpreterArgs args; + args.prompt = prompt; + args.hfile = hfile; - lua_pushboolean(state, Lua::InterpreterLoop(*pstream, state, prompt, env, hfile)); - return 1; + return RunCoreQueryLoop(out, state, init_interpreter, &args); } static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool success) @@ -861,7 +887,6 @@ static const luaL_Reg dfhack_funcs[] = { { "color", lua_dfhack_color }, { "is_interactive", lua_dfhack_is_interactive }, { "lineedit", lua_dfhack_lineedit }, - { "interpreter", lua_dfhack_interpreter }, { "safecall", lua_dfhack_safecall }, { "onerror", dfhack_onerror }, { "call_with_finalizer", dfhack_call_with_finalizer }, diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 70a2e5fca..549bdaaf0 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -160,9 +160,17 @@ namespace DFHack {namespace Lua { /** * Run an interactive interpreter loop if possible, or return false. + * Uses RunCoreQueryLoop internally. */ DFHACK_EXPORT bool InterpreterLoop(color_ostream &out, lua_State *state, - const char *prompt = NULL, int env = 0, const char *hfile = NULL); + const char *prompt = NULL, const char *hfile = NULL); + + /** + * Run an interactive prompt loop. All access to lua is done inside CoreSuspender. + */ + DFHACK_EXPORT bool RunCoreQueryLoop(color_ostream &out, lua_State *state, + bool (*init)(color_ostream&, lua_State*, lua_State*, void*), + void *arg); /** * Push utility functions diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 63de69864..595363da8 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -128,5 +128,80 @@ function dfhack.maps.getTileSize() return map.x_count, map.y_count, map.z_count end +-- Interactive + +function dfhack.query(prompt,hfile) + local _,main = coroutine.running() + if main then + return dfhack.lineedit(prompt,hfile) + else + return coroutine.yield(prompt,hfile) + end +end + +local print_banner = true + +function dfhack.interpreter(prompt,hfile,env) + if not dfhack.is_interactive() then + return nil, 'not interactive' + end + + print("Type quit to exit interactive lua interpreter.") + + if print_banner then + print("Shortcuts:\n".. + " '= foo' => '_1,_2,... = foo'\n".. + " '! foo' => 'print(foo)'\n".. + "Both save the first result as '_'.") + print_banner = false + end + + local prompt_str = "["..(prompt or 'lua').."]# " + local prompt_env = {} + local vcnt = 1 + + setmetatable(prompt_env, { __index = env or _G }) + + while true do + local cmdline = dfhack.query(prompt_str, hfile) + + if cmdline == nil or cmdline == 'quit' then + break + elseif cmdline ~= '' then + local pfix = string.sub(cmdline,1,1) + + if pfix == '!' or pfix == '=' then + cmdline = 'return '..string.sub(cmdline,2) + end + + local code,err = load(cmdline, '=(interactive)', 't', prompt_env) + + if code == nil then + dfhack.printerr(err) + else + local data = table.pack(safecall(code)) + + if data[1] and data.n > 1 then + prompt_env._ = data[2] + + if pfix == '!' then + safecall(print, table.unpack(data,2,data.n)) + else + for i=2,data.n do + local varname = '_'..vcnt + prompt_env[varname] = data[i] + dfhack.print(varname..' = ') + safecall(print, data[i]) + vcnt = vcnt + 1 + end + end + end + end + end + end + + return true +end + -- Feed the table back to the require() mechanism. return dfhack diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index cfd2e27cd..d8710c2db 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -123,8 +123,7 @@ command_result lua_run (color_ostream &out, std::vector ¶meter { if (!parameters.empty() && parameters[0] == "--core-context") { - CoreSuspender suspend; - Lua::InterpreterLoop(out, Lua::Core::State); + Lua::InterpreterLoop(out, Lua::Core::State, "core lua"); return CR_OK; } From 7aefae17d1b0173df99e4c788e58b5f347ebfd39 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sun, 15 Apr 2012 21:45:15 +0200 Subject: [PATCH 092/108] autonestbox: don't assign to nestboxes which are already claimed or contain eggs. bprobe: look a bit closer at nestboxes --- plugins/probe.cpp | 7 +++++++ plugins/zone.cpp | 25 ++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 37df180da..cb627c8ad 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -16,6 +16,7 @@ using namespace std; #include "PluginManager.h" #include "modules/Units.h" #include "df/unit_inventory_item.h" +#include "df/building_nest_boxst.h" #include "modules/Maps.h" #include "modules/Gui.h" #include "modules/Materials.h" @@ -370,6 +371,12 @@ command_result df_bprobe (color_ostream &out, vector & parameters) ENUM_KEY_STR(trap_type, building.trap_type).c_str(), building.trap_type); break; + case building_type::NestBox: + { + df::building_nest_boxst* nestbox = (df::building_nest_boxst*) building.origin; + out.print(", claimed:(%i), items:%i", nestbox->claimed_by, nestbox->contained_items.size()); + break; + } default: if (building.subtype != -1) out.print(", subtype %i", building.subtype); diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c3f065e45..f17f29460 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -56,6 +56,7 @@ using namespace std; #include "df/building_civzonest.h" #include "df/building_cagest.h" #include "df/building_chainst.h" +#include "df/building_nest_boxst.h" #include "df/general_ref_building_civzone_assignedst.h" #include #include @@ -1063,6 +1064,28 @@ bool isNestboxAtPos(int32_t x, int32_t y, int32_t z) return found; } +bool isFreeNestboxAtPos(int32_t x, int32_t y, int32_t z) +{ + bool found = false; + for (size_t b=0; b < world->buildings.all.size(); b++) + { + df::building* building = world->buildings.all[b]; + if( building->getType() == building_type::NestBox + && building->x1 == x + && building->y1 == y + && building->z == z ) + { + df::building_nest_boxst* nestbox = (df::building_nest_boxst*) building; + if(nestbox->claimed_by == -1 && nestbox->contained_items.size() == 1) + { + found = true; + break; + } + } + } + return found; +} + bool isEmptyPasture(df::building* building) { if(!isPenPasture(building)) @@ -1083,7 +1106,7 @@ df::building* findFreeNestboxZone() df::building* building = world->buildings.all[b]; if( isEmptyPasture(building) && isActive(building) && - isNestboxAtPos(building->x1, building->y1, building->z)) + isFreeNestboxAtPos(building->x1, building->y1, building->z)) { free_building = building; break; From 4b3c8d46c1315683e6d80605c130c64d65fd26cc Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sun, 15 Apr 2012 23:21:36 +0200 Subject: [PATCH 093/108] zone: don't assign owned pets to a cage. the owner will release them, resulting into infinite hauling (df bug) --- plugins/changeitem.cpp | 3 +++ plugins/zone.cpp | 27 +++++++++++---------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/plugins/changeitem.cpp b/plugins/changeitem.cpp index 5b02dc12e..9c0a55ad9 100644 --- a/plugins/changeitem.cpp +++ b/plugins/changeitem.cpp @@ -347,6 +347,9 @@ command_result changeitem_execute( // subtype and mode should match to avoid doing dumb stuff like changing boulders into meat whatever // changing a stone cabinet to wood is fine, though. as well as lots of other combinations. // still, it's better to make the user activate 'force' if he really wants to. + + // fixme: changing material of cloth items needs more work... + // <_Q> cloth items have a "CLOTH" improvement which tells you about the cloth that was used to make it if(force||(mat_old.subtype == mat_new.subtype && mat_old.mode==mat_new.mode)) { diff --git a/plugins/zone.cpp b/plugins/zone.cpp index f17f29460..1e1171235 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -1309,15 +1309,9 @@ command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building return CR_WRONG_USAGE; } - // try to get a fresh civzone ref - //df::general_ref_building_civzone_assignedst * ref = createCivzoneRef(); - //if(!ref) - //{ - // out << "Could not find a clonable activity zone reference" << endl - // << "You need to pen/pasture/pit at least one creature" << endl - // << "before using 'assign' for the first time." << endl; - // return CR_WRONG_USAGE; - //} + // don't assign owned pets to a cage. the owner will release them, resulting into infinite hauling (df bug) + if(unit->relations.pet_owner_id != -1) + return CR_OK; // check if unit is already pastured or caged, remove refs where necessary bool cleared_old = unassignUnitFromBuilding(unit); @@ -1955,12 +1949,11 @@ command_result df_zone (color_ostream &out, vector & parameters) } // search for trained and untrained is exclusive, so drop the flags if both are specified - // (there is no trained filter, since it doesn't make much sense to throw war and hunting pets together) - //if(find_trained && find_untrained) - //{ - // find_trained=false; - // find_female=false; - //} + if(find_trained && find_not_trained) + { + find_trained=false; + find_not_trained=false; + } // search for grazer and nograzer is exclusive, so drop the flags if both are specified if(find_grazer && find_not_grazer) @@ -1969,6 +1962,8 @@ command_result df_zone (color_ostream &out, vector & parameters) find_not_grazer=false; } + // todo: maybe add this type of sanity check for all remaining bools, maybe not (lots of code just to avoid parsing dumb input) + // try to cope with user dumbness if(target_agemin > target_agemax) { @@ -2129,7 +2124,7 @@ command_result df_zone (color_ostream &out, vector & parameters) unitInfo(out, unit, verbose); } else if(building_assign) - { + { command_result result = assignUnitToBuilding(out, unit, building, verbose); if(result != CR_OK) return result; From 6287ee59d8a7796640dbf030be8425452c339892 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 16 Apr 2012 00:52:25 +0200 Subject: [PATCH 094/108] updated readme.rst --- README.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index cae931cc5..0849cacf5 100644 --- a/README.rst +++ b/README.rst @@ -615,7 +615,8 @@ Options ------- :tweak clear-missing: Remove the missing status from the selected unit. This allows engraving slabs for ghostly, but not yet found, creatures. :tweak clear-ghostly: Remove the ghostly status from the selected unit and mark it as dead. This allows getting rid of bugged ghosts which do not show up in the engraving slab menu at all, even after using clear-missing. It works, but is potentially very dangerous - so use with care. Probably (almost certainly) it does not have the same effects like a proper burial. You've been warned. -:tweak fixmigrant: Remove the resident/merchant flag from the selected unit. Intended to fix bugged migrants/traders who stay at the map edge and don't enter your fort. Only works for dwarves (or generally the player's race in modded games). Can be abused to grab caravan merchants, but that might result into weirdness during trading. +:tweak fixmigrant: Remove the resident/merchant flag from the selected unit. Intended to fix bugged migrants/traders who stay at the map edge and don't enter your fort. Only works for dwarves (or generally the player's race in modded games). Do NOT abuse this for 'real' caravan merchants (if you really want to kidnap them, use 'tweak makeown' instead, otherwise they will have their clothes set to forbidden etc). +:tweak makeown: Force selected unit to become a member of your fort. Can be abused to grab caravan merchants and escorts, even if they don't belong to the player's race. Foreign sentients (humans, elves) can be put to work, but you can't assign rooms to them and they don't show up in DwarfTherapist because the game treats them like pets. Grabbing draft animals from a caravan can result in weirdness (animals go insane or berserk and are not flagged as tame), but you are allowed to mark them for slaughter. Grabbing wagons results in some funny spam, then they are scuttled. tubefill ======== @@ -844,7 +845,7 @@ One convenient way to use the zone tool is to bind the command 'zone assign' to Usage with filters ------------------ -All filters can be used together with the 'assign' command. The only restriction is that it's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. Usually you should always use the filter 'own' (which implies tame) unless you want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you specify 'race DWARF' (so it's safe to use 'assign all own' to one big pasture if you want to have all your animals at the same place). 'egglayer' and 'milkable' should be used together with 'female' unless you have a mod with egg-laying male elves who give milk or whatever. Merchants and their animals are ignored unless you specify 'merchant' (pitting them should be no problem, but stealing and pasturing their animals is not a good idea since currently they are not properly added to your own stocks; slaughtering them should work). Most filters can be negated (e.g. 'not grazer' -> race is not a grazer). +All filters can be used together with the 'assign' command. Restrictions: It's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. It's not possible to cage owned pets because in that case the owner uncages them after a while which results in infinite hauling back and forth. Usually you should always use the filter 'own' (which implies tame) unless you want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you specify 'race DWARF' (so it's safe to use 'assign all own' to one big pasture if you want to have all your animals at the same place). 'egglayer' and 'milkable' should be used together with 'female' unless you have a mod with egg-laying male elves who give milk or whatever. Merchants and their animals are ignored unless you specify 'merchant' (pitting them should be no problem, but stealing and pasturing their animals is not a good idea since currently they are not properly added to your own stocks; slaughtering them should work). Most filters can be negated (e.g. 'not grazer' -> race is not a grazer). ``zone assign all own ALPACA minage 3 maxage 10`` Assign all own alpacas who are between 3 and 10 years old to the selected pasture. From e58a46b42b99c935a4c200a557efa479165d45b4 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 16 Apr 2012 03:31:49 +0200 Subject: [PATCH 095/108] zone: fixed filter 'trained' so it now finds war/hunting creatures (who strangely don't have a training level) --- README.rst | 3 +- plugins/zone.cpp | 79 +++++++++++++++++++++++++++--------------------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/README.rst b/README.rst index 0849cacf5..7cd6de124 100644 --- a/README.rst +++ b/README.rst @@ -829,8 +829,9 @@ Filters: :own: From own civilization. Negatable. :merchant: Is a merchant / belongs to a merchant. Should only be used for pitting, not for stealing animals (slaughter should work). :war: Trained war creature. Negatable. +:hunting: Trained hunting creature. Negatable. :tamed: Creature is tame. Negatable. -:trained: Creature is trained. Negatable. +:trained: Creature is trained. Finds war/hunting creatures as well as creatures who have a training level greater than 'domesticated'. If you want to specifically search for war/hunting creatures use 'war' or 'hunting' Negatable. :trainablewar: Creature can be trained for war (and is not already trained for war/hunt). Negatable. :trainablehunt: Creature can be trained for hunting (and is not already trained for war/hunt). Negatable. :male: Creature is male. Negatable. diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 1e1171235..ca9213128 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -336,12 +336,11 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) int32_t getUnitAge(df::unit* unit); bool isTame(df::unit* unit); bool isTrained(df::unit* unit); -bool isTrained(df::unit* creature); -bool isWar(df::unit* creature); -bool isHunter(df::unit* creature); -bool isOwnCiv(df::unit* creature); -bool isMerchant(df::unit* creature); -bool isForest(df::unit* creature); +bool isWar(df::unit* unit); +bool isHunter(df::unit* unit); +bool isOwnCiv(df::unit* unit); +bool isMerchant(df::unit* unit); +bool isForest(df::unit* unit); bool isActivityZone(df::building * building); bool isPenPasture(df::building * building); @@ -433,44 +432,46 @@ bool isTame(df::unit* creature) } // check if trained (might be useful if pasturing war dogs etc) -bool isTrained(df::unit* creature) +bool isTrained(df::unit* unit) { + // case a: trained for war/hunting (those don't have a training level, strangely) + if(isWar(unit) || isHunter(unit)) + return true; + + // case b: tamed and trained wild creature, gets a training level bool trained = false; - if(creature->flags1.bits.tame) - { - switch (creature->training_level) - { - case df::animal_training_level::Trained: - case df::animal_training_level::WellTrained: - case df::animal_training_level::SkilfullyTrained: - case df::animal_training_level::ExpertlyTrained: - case df::animal_training_level::ExceptionallyTrained: - case df::animal_training_level::MasterfullyTrained: - //case df::animal_training_level::Domesticated: - trained = true; - break; - default: - break; - } + switch (unit->training_level) + { + case df::animal_training_level::Trained: + case df::animal_training_level::WellTrained: + case df::animal_training_level::SkilfullyTrained: + case df::animal_training_level::ExpertlyTrained: + case df::animal_training_level::ExceptionallyTrained: + case df::animal_training_level::MasterfullyTrained: + //case df::animal_training_level::Domesticated: + trained = true; + break; + default: + break; } return trained; } // check for profession "war creature" -bool isWar(df::unit* creature) +bool isWar(df::unit* unit) { - if( creature->profession == df::profession::TRAINED_WAR - || creature->profession2 == df::profession::TRAINED_WAR) + if( unit->profession == df::profession::TRAINED_WAR + || unit->profession2 == df::profession::TRAINED_WAR) return true; else return false; } // check for profession "hunting creature" -bool isHunter(df::unit* creature) +bool isHunter(df::unit* unit) { - if( creature->profession == df::profession::TRAINED_HUNTER - || creature->profession2 == df::profession::TRAINED_HUNTER) + if( unit->profession == df::profession::TRAINED_HUNTER + || unit->profession2 == df::profession::TRAINED_HUNTER) return true; else return false; @@ -478,16 +479,16 @@ bool isHunter(df::unit* creature) // check if creature belongs to the player's civilization // (don't try to pasture/slaughter random untame animals) -bool isOwnCiv(df::unit* creature) +bool isOwnCiv(df::unit* unit) { - return creature->civ_id == ui->civ_id; + return unit->civ_id == ui->civ_id; } // check if creature belongs to the player's race // (in combination with check for civ helps to filter out own dwarves) -bool isOwnRace(df::unit* creature) +bool isOwnRace(df::unit* unit) { - return creature->race == ui->race_id; + return unit->race == ui->race_id; } string getRaceName(int32_t id) @@ -1728,7 +1729,17 @@ command_result df_zone (color_ostream &out, vector & parameters) find_not_war = true; invert_filter = false; } - else if(p == "own"&& !invert_filter) + else if(p == "hunting" && !invert_filter) + { + out << "Filter by 'trained hunting creature'." << endl; + find_hunter = true; + } + else if(p == "hunting" && invert_filter) + { + out << "Filter by 'not a trained hunting creature'." << endl; + find_not_hunter = true; + invert_filter = false; + }else if(p == "own"&& !invert_filter) { out << "Filter by 'own civilization'." << endl; find_own = true; From 9c67250729dbbd2e1dc66e753bcbc52abd133a9e Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 16 Apr 2012 07:03:27 +0200 Subject: [PATCH 096/108] regrass: pick random grass event if tile is soil instead of always using the first one found --- plugins/devel/regrass.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/devel/regrass.cpp b/plugins/devel/regrass.cpp index b4106ba5a..dec091a92 100644 --- a/plugins/devel/regrass.cpp +++ b/plugins/devel/regrass.cpp @@ -127,9 +127,23 @@ command_result df_regrass (color_ostream &out, vector & parameters) } } // if original could not be found (meaning it was already depleted): - // refresh the first grass event in the map block + // refresh random grass event in the map block if(!regrew) - grev->amount[x][y]=100; + { + vector gr_evs; + for(size_t e=0; eblock_events.size(); e++) + { + df::block_square_event * blev = cur->block_events[e]; + df::block_square_event_type blevtype = blev->getType(); + if(blevtype == df::block_square_event_type::grass) + { + df::block_square_event_grassst * gr_ev = (df::block_square_event_grassst *)blev; + gr_evs.push_back(gr_ev); + } + } + int r = rand() % gr_evs.size(); + gr_evs[r]->amount[x][y]=100; + } } cur->tiletype[x][y] = findRandomVariant((rand() & 1) ? tiletype::GrassLightFloor1 : tiletype::GrassDarkFloor1); count++; From 48e4717dd2cf6c0e2a6ae88ae7bde20e01c0b610 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 16 Apr 2012 10:59:55 +0400 Subject: [PATCH 097/108] Try working around some msvc problems. --- library/LuaTools.cpp | 4 ++-- library/PluginManager.cpp | 2 +- library/include/LuaTools.h | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index bf40e4041..b4f959446 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -981,7 +981,7 @@ void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, i set_dfhack_output(state, cur_out); } -void DFHack::Lua::CreateEvent(lua_State *state, void *key) +void DFHack::Lua::MakeEvent(lua_State *state, void *key) { lua_rawgetp(state, LUA_REGISTRYINDEX, key); @@ -1009,7 +1009,7 @@ void DFHack::Lua::Notification::bind(lua_State *state, void *key) void DFHack::Lua::Notification::bind(lua_State *state, const char *name) { - CreateEvent(state, this); + MakeEvent(state, this); if (handler) { diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index edc7fb9f8..1821e18d7 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -537,7 +537,7 @@ void Plugin::open_lua(lua_State *state, int table) { for (auto it = lua_events.begin(); it != lua_events.end(); ++it) { - Lua::CreateEvent(state, it->second); + Lua::MakeEvent(state, it->second); push_function(state, &it->second->handler); lua_rawsetp(state, -2, NULL); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 549bdaaf0..7e38f5bb3 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -175,6 +175,7 @@ namespace DFHack {namespace Lua { /** * Push utility functions */ +#if 0 #define NUMBER_PUSH(type) inline void Push(lua_State *state, type value) { lua_pushnumber(state, value); } NUMBER_PUSH(char) NUMBER_PUSH(int8_t) NUMBER_PUSH(uint8_t) @@ -183,6 +184,11 @@ namespace DFHack {namespace Lua { NUMBER_PUSH(int64_t) NUMBER_PUSH(uint64_t) NUMBER_PUSH(float) NUMBER_PUSH(double) #undef NUMBER_PUSH +#else + template inline void Push(lua_State *state, T value) { + lua_pushnumber(state, lua_Number(value)); + } +#endif inline void Push(lua_State *state, bool value) { lua_pushboolean(state, value); } @@ -212,7 +218,7 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT bool IsCoreContext(lua_State *state); DFHACK_EXPORT int NewEvent(lua_State *state); - DFHACK_EXPORT void CreateEvent(lua_State *state, void *key); + DFHACK_EXPORT void MakeEvent(lua_State *state, void *key); DFHACK_EXPORT void InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args); /** From e6ed81508d2826b2fd4eae8c9c4ba873daeff100 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 16 Apr 2012 10:15:37 +0200 Subject: [PATCH 098/108] autobutcher: only ignore built cages which are defined as rooms/zoos. zone: allow mass-assigning of nicknames (to protect a pasture/cage from autobutcher) --- README.rst | 14 +++- plugins/zone.cpp | 163 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 165 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index 7cd6de124..69fed9e83 100644 --- a/README.rst +++ b/README.rst @@ -810,6 +810,8 @@ Options: :set: Set zone or cage under cursor as default for future assigns. :assign: Assign unit(s) to the pen or pit marked with the 'set' command. If no filters are set a unit must be selected in the in-game ui. Can also be followed by a valid zone id which will be set instead. :unassign: Unassign selected creature from it's zone. +:nick: Mass-assign nicknames, must be followed by the name you want to set. +:remnick: Mass-remove nicknames. :uinfo: Print info about unit(s). If no filters are set a unit must be selected in the in-game ui. :zinfo: Print info about zone(s). If no filters are set zones under the cursor are listed. :verbose: Print some more info. @@ -848,16 +850,22 @@ Usage with filters ------------------ All filters can be used together with the 'assign' command. Restrictions: It's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. It's not possible to cage owned pets because in that case the owner uncages them after a while which results in infinite hauling back and forth. Usually you should always use the filter 'own' (which implies tame) unless you want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless you specify 'race DWARF' (so it's safe to use 'assign all own' to one big pasture if you want to have all your animals at the same place). 'egglayer' and 'milkable' should be used together with 'female' unless you have a mod with egg-laying male elves who give milk or whatever. Merchants and their animals are ignored unless you specify 'merchant' (pitting them should be no problem, but stealing and pasturing their animals is not a good idea since currently they are not properly added to your own stocks; slaughtering them should work). Most filters can be negated (e.g. 'not grazer' -> race is not a grazer). +Mass-renaming +------------- +Using the 'nick' command you can set the same nickname for multiple units. If used without 'assign', 'all' or 'count' it will rename all units in the current default target zone. Combined with 'assign', 'all' or 'count' (and further optional filters) it will rename units matching the filter conditions. + ``zone assign all own ALPACA minage 3 maxage 10`` Assign all own alpacas who are between 3 and 10 years old to the selected pasture. -``zone assign all own caged grazer`` - Assign all own grazers who are sitting in cages on stockpiles (e.g. after buying them from merchants) to the selected pasture. +``zone assign all own caged grazer nick ineedgrass`` + Assign all own grazers who are sitting in cages on stockpiles (e.g. after buying them from merchants) to the selected pasture and give them the nickname 'ineedgrass'. ``zone assign all own not grazer not race CAT`` Assign all own animals who are not grazers, excluding cats. ``zone assign count 5 own female milkable`` Assign up to 5 own female milkable creatures to the selected pasture. ``zone assign all own race DWARF maxage 2`` Throw all useless kids into a pit :) +``zone nick donttouchme`` + Nicknames all units in the current default zone or cage to 'donttouchme'. Mostly intended to be used for special pastures or cages which are not marked as rooms you want to protect from autobutcher. autonestbox =========== @@ -871,7 +879,7 @@ Options: autobutcher =========== -Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed. Named units will be completely ignored (you can give animals nicknames with the tool 'rename unit' to protect them from autobutcher). Creatures trained for war or hunting will be ignored as well. Once you have too much adults, the oldest will be butchered first. Once you have too much kids, the youngest will be butchered first. If you don't set any target count the following default will be used: 1 male kid, 5 female kids, 1 male adult, 5 female adults. +Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed. Named units will be completely ignored (to protect specific animals fro autobutcher you can give them nicknames with the tool 'rename unit' for single units or with 'zone nick' to mass-rename units in pastures and cages). Creatures trained for war or hunting will be ignored as well. Creatures assigned to cages will be ignored if the cage is defined as a room (to avoid butchering unnamed zoo animals). Once you have too much adults, the oldest will be butchered first. Once you have too much kids, the youngest will be butchered first. If you don't set any target count the following default will be used: 1 male kid, 5 female kids, 1 male adult, 5 female adults. Options: -------- diff --git a/plugins/zone.cpp b/plugins/zone.cpp index ca9213128..4ee991549 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -803,6 +803,25 @@ int32_t findBuildingIndexById(int32_t id) return -1; } +int32_t findUnitIndexById(int32_t id) +{ + for (size_t i = 0; i < world->units.all.size(); i++) + { + if(world->units.all.at(i)->id == id) + return i; + } + return -1; +} + +df::unit* findUnitById(int32_t id) +{ + int32_t index = findUnitIndexById(id); + if(index != -1) + return world->units.all[index]; + else + return NULL; +} + // returns id of pen/pit at cursor position (-1 if nothing found) int32_t findPenPitAtCursor() { @@ -1007,6 +1026,31 @@ bool isInBuiltCage(df::unit* unit) return caged; } +// built cage defined as room (supposed to detect zoo cages) +bool isInBuiltCageRoom(df::unit* unit) +{ + bool caged_room = false; + for (size_t b=0; b < world->buildings.all.size(); b++) + { + df::building* building = world->buildings.all[b]; + if(building->isRoom() && building->getType() == building_type::Cage) + { + df::building_cagest* cage = (df::building_cagest*) building; + for(size_t c=0; cassigned_creature.size(); c++) + { + if(cage->assigned_creature[c] == unit->id) + { + caged_room = true; + break; + } + } + } + if(caged_room) + break; + } + return caged_room; +} + // check a map position for a built cage // animals in cages are CONTAINED_IN_ITEM, no matter if they are on a stockpile or inside a built cage // if they are on animal stockpiles they should count as unassigned to allow pasturing them @@ -1360,6 +1404,68 @@ command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::buil return result; } +command_result nickUnitsInZone(color_ostream& out, df::building* building, string nick) +{ + // building must be a pen/pasture or pit + if(!isPenPasture(building) && !isPitPond(building)) + { + out << "Invalid building type. This is not a pen/pasture or pit/pond." << endl; + return CR_WRONG_USAGE; + } + + df::building_civzonest * civz = (df::building_civzonest *) building; + for(size_t i = 0; i < civz->assigned_creature.size(); i++) + { + df::unit* unit = findUnitById(civz->assigned_creature[i]); + if(unit) + Units::setNickname(unit, nick); + } + + return CR_OK; +} + +command_result nickUnitsInCage(color_ostream& out, df::building* building, string nick) +{ + // building must be a pen/pasture or pit + if(!isCage(building)) + { + out << "Invalid building type. This is not a cage." << endl; + return CR_WRONG_USAGE; + } + + df::building_cagest* cage = (df::building_cagest*) building; + for(size_t i=0; iassigned_creature.size(); i++) + { + df::unit* unit = findUnitById(cage->assigned_creature[i]); + if(unit) + Units::setNickname(unit, nick); + } + + return CR_OK; +} + +command_result nickUnitsInChain(color_ostream& out, df::building* building, string nick) +{ + out << "sorry. nicknaming chained units is not possible yet." << endl; + return CR_WRONG_USAGE; +} + +command_result nickUnitsInBuilding(color_ostream& out, df::building* building, string nick) +{ + command_result result = CR_WRONG_USAGE; + + if(isActivityZone(building)) + result = nickUnitsInZone(out, building, nick); + else if(isCage(building)) + result = nickUnitsInCage(out, building, nick); + else if(isChain(building)) + result = nickUnitsInChain(out, building, nick); + else + out << "Cannot nickname units in this type of building!" << endl; + + return result; +} + // dump some zone info void zoneInfo(color_ostream & out, df::building* building, bool verbose) { @@ -1552,6 +1658,8 @@ command_result df_zone (color_ostream &out, vector & parameters) bool all = false; bool unit_slaughter = false; static int target_building = -1; + bool nick_set = false; + string target_nick = ""; for (size_t i = 0; i < parameters.size(); i++) { @@ -1909,6 +2017,31 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_WRONG_USAGE; building_set = true; } + else if(p == "nick") + { + if(invert_filter) + return CR_WRONG_USAGE; + + if(i == parameters.size()-1) + { + out.printerr("No nickname specified! Use 'remnick' to remove nicknames!"); + return CR_WRONG_USAGE; + } + nick_set = true; + target_nick = parameters[i+1]; + i++; + out << "Set nickname to: " << target_nick << endl; + } + else if(p == "remnick") + { + if(invert_filter) + return CR_WRONG_USAGE; + + nick_set = true; + target_nick = ""; + i++; + out << "Remove nickname." << endl; + } else if(p == "all") { if(invert_filter) @@ -2031,11 +2164,10 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } - // assign to pen or pit - if(building_assign || unit_info || unit_slaughter) + if(building_assign || unit_info || unit_slaughter || nick_set) { df::building * building; - if(building_assign) + if(building_assign || (nick_set && !all && !find_count)) { // try to get building index from the id int32_t index = findBuildingIndexById(target_building); @@ -2046,6 +2178,12 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_WRONG_USAGE; } building = world->buildings.all.at(index); + + if(nick_set && !building_assign) + { + out << "Renaming all units in target building." << endl; + return nickUnitsInBuilding(out, building, target_nick); + } } if(all || find_count) @@ -2133,14 +2271,22 @@ command_result df_zone (color_ostream &out, vector & parameters) if(unit_info) { unitInfo(out, unit, verbose); + continue; + } + + if(nick_set) + { + Units::setNickname(unit, target_nick); } - else if(building_assign) + + if(building_assign) { command_result result = assignUnitToBuilding(out, unit, building, verbose); if(result != CR_OK) return result; } - else if(unit_slaughter) + + if(unit_slaughter) { // don't slaughter named creatures unless told to do so if(!find_named && unit->name.has_name) @@ -2185,7 +2331,6 @@ command_result df_zone (color_ostream &out, vector & parameters) doMarkForSlaughter(unit); return CR_OK; } - } } @@ -2995,9 +3140,9 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) || !isTame(unit) || isWar(unit) // ignore war dogs etc || isHunter(unit) // ignore hunting dogs etc - // ignore creatures in built cages to leave zoos alone - // (TODO: allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCage(unit)) + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) || unit->name.has_name ) continue; From 3e4863bc80de2844e795a086599c68b6506ca57f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 16 Apr 2012 14:45:04 +0400 Subject: [PATCH 099/108] Integrate coroutines with table-based error handling. Properly attach stack traces to errors passing the resume boundary. Replaces coroutine.resume and coroutine.wrap with appropriately modified versions, and adds a Lua::SafeResume function for C++. --- library/LuaTools.cpp | 268 +++++++++++++++++++++++++++++-------- library/include/LuaTools.h | 24 +++- 2 files changed, 236 insertions(+), 56 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index b4f959446..564384ef3 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -70,6 +70,10 @@ inline void AssertCoreSuspend(lua_State *state) assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended()); } +/* + * Public DF object reference handling API + */ + void DFHack::Lua::PushDFObject(lua_State *state, type_identity *type, void *ptr) { push_object_internal(state, type, ptr, false); @@ -110,6 +114,10 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_ return rv; } +/* + * Console I/O wrappers + */ + static int DFHACK_OSTREAM_TOKEN = 0; color_ostream *DFHack::Lua::GetOutput(lua_State *L) @@ -263,6 +271,10 @@ static int lua_dfhack_lineedit(lua_State *S) } } +/* + * Exception handling + */ + static int DFHACK_EXCEPTION_META_TOKEN = 0; static void error_tostring(lua_State *L, bool keep_old = false) @@ -304,8 +316,21 @@ static void report_error(lua_State *L, color_ostream *out = NULL) lua_pop(L, 1); } -static bool convert_to_exception(lua_State *L) +static bool convert_to_exception(lua_State *L, int slevel, lua_State *thread = NULL) { + if (!thread) + thread = L; + + if (thread == L) + lua_pushthread(L); + else + { + lua_pushthread(thread); + lua_xmove(thread, L, 1); + } + + lua_swap(L); + int base = lua_gettop(L); bool force_unknown = false; @@ -316,13 +341,33 @@ static bool convert_to_exception(lua_State *L) bool is_exception = lua_rawequal(L, -1, -2); lua_settop(L, base); - // If it is an exception, return as is if (is_exception) - return false; + { + // If it is an exception from the same thread, return as is + lua_getfield(L, base, "thread"); + bool same_thread = lua_rawequal(L, -1, base-1); + lua_settop(L, base); + + if (same_thread) + { + lua_remove(L, base-1); + return false; + } - force_unknown = true; + // Create a new exception for this thread + lua_newtable(L); + luaL_where(L, 1); + lua_pushstring(L, "coroutine resume failed"); + lua_concat(L, 2); + lua_setfield(L, -2, "message"); + lua_swap(L); + lua_setfield(L, -2, "cause"); + } + else + force_unknown = true; } + // Promote non-table to table, and do some sanity checks if (!lua_istable(L, base) || force_unknown) { lua_newtable(L); @@ -352,6 +397,10 @@ static bool convert_to_exception(lua_State *L) lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN); lua_setmetatable(L, base); + lua_swap(L); + lua_setfield(L, -2, "thread"); + luaL_traceback(L, thread, NULL, slevel); + lua_setfield(L, -2, "stacktrace"); return true; } @@ -360,13 +409,7 @@ static int dfhack_onerror(lua_State *L) luaL_checkany(L, 1); lua_settop(L, 1); - bool changed = convert_to_exception(L); - if (!changed) - return 1; - - luaL_traceback(L, L, NULL, 1); - lua_setfield(L, 1, "stacktrace"); - + convert_to_exception(L, 1); return 1; } @@ -403,14 +446,8 @@ static void push_simple_error(lua_State *L, const char *str) { lua_pushstring(L, str); - if (lua_checkstack(L, 5)) - convert_to_exception(L); - if (lua_checkstack(L, LUA_MINSTACK)) - { - luaL_traceback(L, L, NULL, 1); - lua_setfield(L, -2, "stacktrace"); - } + convert_to_exception(L, 0); } static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space = 2) @@ -481,6 +518,134 @@ bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres return ok; } +// Copied from lcorolib.c, with error handling modifications +static int resume_helper(lua_State *L, lua_State *co, int narg, int nres) +{ + if (!co) { + lua_pop(L, narg); + push_simple_error(L, "coroutine expected in resume"); + return LUA_ERRRUN; + } + if (!lua_checkstack(co, narg)) { + lua_pop(L, narg); + push_simple_error(L, "too many arguments to resume"); + return LUA_ERRRUN; + } + if (lua_status(co) == LUA_OK && lua_gettop(co) == 0) { + lua_pop(L, narg); + push_simple_error(L, "cannot resume dead coroutine"); + return LUA_ERRRUN; + } + lua_xmove(L, co, narg); + int status = lua_resume(co, L, narg); + if (status == LUA_OK || status == LUA_YIELD) + { + int nact = lua_gettop(co); + if (nres == LUA_MULTRET) + nres = nact; + else if (nres < nact) + lua_settop(co, nact = nres); + if (!lua_checkstack(L, nres + 1)) { + lua_settop(co, 0); + push_simple_error(L, "too many results to resume"); + return LUA_ERRRUN; + } + int ttop = lua_gettop(L) + nres; + lua_xmove(co, L, nact); + lua_settop(L, ttop); + } + else + { + lua_xmove(co, L, 1); + + // A cross-thread version of dfhack_onerror + if (lua_checkstack(L, LUA_MINSTACK)) + convert_to_exception(L, 0, co); + } + return status; +} + +static int dfhack_coresume (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + luaL_argcheck(L, !!co, 1, "coroutine expected"); + int r = resume_helper(L, co, lua_gettop(L) - 1, LUA_MULTRET); + bool ok = (r == LUA_OK || r == LUA_YIELD); + lua_pushboolean(L, ok); + lua_insert(L, 2); + return lua_gettop(L) - 1; +} + +static int dfhack_saferesume (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + luaL_argcheck(L, !!co, 1, "coroutine expected"); + int r = resume_helper(L, co, lua_gettop(L) - 1, LUA_MULTRET); + bool ok = (r == LUA_OK || r == LUA_YIELD); + lua_pushboolean(L, ok); + lua_insert(L, 2); + if (!ok) + report_error(L); + return lua_gettop(L) - 1; +} + +static int dfhack_coauxwrap (lua_State *L) { + lua_State *co = lua_tothread(L, lua_upvalueindex(1)); + int r = resume_helper(L, co, lua_gettop(L), LUA_MULTRET); + if (r == LUA_OK || r == LUA_YIELD) + return lua_gettop(L); + else + return lua_error(L); +} + +static int dfhack_cowrap (lua_State *L) { + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_settop(L, 1); + Lua::NewCoroutine(L); + lua_pushcclosure(L, dfhack_coauxwrap, 1); + return 1; +} + +lua_State *DFHack::Lua::NewCoroutine(lua_State *L) { + lua_State *NL = lua_newthread(L); + lua_swap(L); + lua_xmove(L, NL, 1); /* move function from L to NL */ + return NL; +} + +int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, lua_State *thread, int nargs, int nres, bool perr) +{ + AssertCoreSuspend(from); + + color_ostream *cur_out = Lua::GetOutput(from); + set_dfhack_output(from, &out); + + int rv = resume_helper(from, thread, nargs, nres); + + if (rv != LUA_OK && rv != LUA_YIELD && perr) + { + report_error(from, &out); + lua_pop(from, 1); + } + + set_dfhack_output(from, cur_out); + + return rv; +} + +int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, int nargs, int nres, bool perr) +{ + int base = lua_gettop(from) - nargs; + lua_State *thread = lua_tothread(from, base); + + int rv = SafeResume(out, from, thread, nargs, nres, perr); + + lua_remove(from, base); + return rv; +} + +/* + * Module loading + */ + static int DFHACK_LOADED_TOKEN = 0; bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *module) @@ -586,29 +751,28 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std return Lua::SafeCall(out, state, nargs, nres, perr); } +/* + * Coroutine interactive query loop + */ + static int resume_query_loop(color_ostream &out, - lua_State *thread, lua_State *state, bool start, + lua_State *thread, lua_State *state, int nargs, std::string &prompt, std::string &histfile) { - color_ostream *cur_out = Lua::GetOutput(state); - set_dfhack_output(state, &out); - - int rv = lua_resume(thread, state, lua_gettop(thread)-(start?1:0)); - - set_dfhack_output(state, cur_out); + int rv = Lua::SafeResume(out, state, thread, nargs, 2); if (rv == LUA_YIELD || rv == LUA_OK) { - lua_settop(thread, 2); - prompt = ifnull(lua_tostring(thread, 1), ""); - histfile = ifnull(lua_tostring(thread, 2), ""); + prompt = ifnull(lua_tostring(state, -2), ""); + histfile = ifnull(lua_tostring(state, -1), ""); + lua_pop(state, 2); } return rv; } bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, - bool (*init)(color_ostream&, lua_State*, lua_State*, void*), + bool (*init)(color_ostream&, lua_State*, void*), void *arg) { if (!out.is_console()) @@ -630,18 +794,19 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, CoreSuspender suspend; int base = lua_gettop(state); - thread = lua_newthread(state); - if (!init(out, state, thread, arg)) + if (!init(out, state, arg)) { lua_settop(state, base); return false; } - lua_settop(state, base+1); + lua_pushvalue(state, base+1); + lua_remove(state, base+1); + thread = Lua::NewCoroutine(state); lua_rawsetp(state, LUA_REGISTRYINDEX, thread); - rv = resume_query_loop(out, thread, state, true, prompt, histfile); + rv = resume_query_loop(out, thread, state, lua_gettop(state)-base, prompt, histfile); } while (rv == LUA_YIELD) @@ -668,10 +833,8 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, { CoreSuspender suspend; - lua_settop(thread, 0); - lua_pushlstring(thread, curline.data(), curline.size()); - - rv = resume_query_loop(out, thread, state, false, prompt, histfile); + lua_pushlstring(state, curline.data(), curline.size()); + rv = resume_query_loop(out, thread, state, 1, prompt, histfile); } } @@ -681,20 +844,6 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, { CoreSuspender suspend; - if (rv != LUA_OK) - { - lua_xmove(thread, state, 1); - - if (convert_to_exception(state)) - { - luaL_traceback(state, thread, NULL, 1); - lua_setfield(state, -2, "stacktrace"); - } - - report_error(state, &out); - lua_pop(state, 1); - } - lua_pushnil(state); lua_rawsetp(state, LUA_REGISTRYINDEX, thread); } @@ -709,15 +858,14 @@ namespace { }; } -static bool init_interpreter(color_ostream &out, lua_State *state, lua_State *thread, void *info) +static bool init_interpreter(color_ostream &out, lua_State *state, void *info) { auto args = (InterpreterArgs*)info; lua_getglobal(state, "dfhack"); lua_getfield(state, -1, "interpreter"); + lua_remove(state, -2); lua_pushstring(state, args->prompt); lua_pushstring(state, args->hfile); - lua_xmove(state, thread, 3); - lua_pop(state, 1); return true; } @@ -888,6 +1036,7 @@ static const luaL_Reg dfhack_funcs[] = { { "is_interactive", lua_dfhack_is_interactive }, { "lineedit", lua_dfhack_lineedit }, { "safecall", lua_dfhack_safecall }, + { "saferesume", dfhack_saferesume }, { "onerror", dfhack_onerror }, { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, @@ -895,6 +1044,12 @@ static const luaL_Reg dfhack_funcs[] = { { NULL, NULL } }; +static const luaL_Reg dfhack_coro_funcs[] = { + { "resume", dfhack_coresume }, + { "wrap", dfhack_cowrap }, + { NULL, NULL } +}; + /************ * Events * ************/ @@ -1077,6 +1232,11 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_LOADED_TOKEN); lua_pop(state, 1); + // replace some coroutine functions + lua_getglobal(state, "coroutine"); + luaL_setfuncs(state, dfhack_coro_funcs, 0); + lua_pop(state, 1); + // load dfhack.lua Require(out, state, "dfhack"); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 7e38f5bb3..5e1638251 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -145,6 +145,24 @@ namespace DFHack {namespace Lua { */ DFHACK_EXPORT bool SafeCall(color_ostream &out, lua_State *state, int nargs, int nres, bool perr = true); + /** + * Pops a function from the top of the stack, and pushes a new coroutine. + */ + DFHACK_EXPORT lua_State *NewCoroutine(lua_State *state); + + /** + * Resume the coroutine using nargs values from state from. Results or the error are moved back. + * If an error is signalled, and perr is true, it is printed and popped from the stack. + * Returns the lua_resume return value. + */ + DFHACK_EXPORT int SafeResume(color_ostream &out, lua_State *from, lua_State *thread, int nargs, int nres, bool perr = true); + + /** + * Works just like SafeCall, only expects a coroutine on the stack + * instead of a function. Returns the lua_resume return value. + */ + DFHACK_EXPORT int SafeResume(color_ostream &out, lua_State *from, int nargs, int nres, bool perr = true); + /** * Parse code from string with debug_tag and env_idx, then call it using SafeCall. * In case of error, it is either left on the stack, or printed like SafeCall does. @@ -166,10 +184,12 @@ namespace DFHack {namespace Lua { const char *prompt = NULL, const char *hfile = NULL); /** - * Run an interactive prompt loop. All access to lua is done inside CoreSuspender. + * Run an interactive prompt loop. All access to the lua state + * is done inside CoreSuspender, while waiting for input happens + * without the suspend lock. */ DFHACK_EXPORT bool RunCoreQueryLoop(color_ostream &out, lua_State *state, - bool (*init)(color_ostream&, lua_State*, lua_State*, void*), + bool (*init)(color_ostream&, lua_State*, void*), void *arg); /** From 9c253512818adc0b36f5a32b8be499dcf65f34a2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 16 Apr 2012 18:01:21 +0400 Subject: [PATCH 100/108] Add a template to make using lua_pcallk a bit more convenient. --- library/LuaTools.cpp | 54 ++++++++++++++++---------------------- library/include/LuaTools.h | 26 ++++++++++++++++++ 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 564384ef3..993efa747 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -467,29 +467,24 @@ static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space } } -static int finish_dfhack_safecall (lua_State *L, bool success) -{ - success = do_finish_pcall(L, success); - - if (!success) - report_error(L); +namespace { + int safecall_cont(lua_State *L, int status, int) + { + bool success = do_finish_pcall(L, Lua::IsSuccess(status)); - return lua_gettop(L); -} + if (!success) + report_error(L); -static int safecall_cont (lua_State *L) -{ - int status = lua_getctx(L, NULL); - return finish_dfhack_safecall(L, (status == LUA_YIELD)); + return lua_gettop(L); + } } -static int lua_dfhack_safecall (lua_State *L) +static int dfhack_safecall (lua_State *L) { luaL_checkany(L, 1); lua_pushcfunction(L, dfhack_onerror); lua_insert(L, 1); - int status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 1, 0, safecall_cont); - return finish_dfhack_safecall(L, (status == LUA_OK)); + return Lua::TailPCallK(L, lua_gettop(L) - 2, LUA_MULTRET, 1, 0); } bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres, bool perr) @@ -538,7 +533,7 @@ static int resume_helper(lua_State *L, lua_State *co, int narg, int nres) } lua_xmove(L, co, narg); int status = lua_resume(co, L, narg); - if (status == LUA_OK || status == LUA_YIELD) + if (Lua::IsSuccess(status)) { int nact = lua_gettop(co); if (nres == LUA_MULTRET) @@ -569,7 +564,7 @@ static int dfhack_coresume (lua_State *L) { lua_State *co = lua_tothread(L, 1); luaL_argcheck(L, !!co, 1, "coroutine expected"); int r = resume_helper(L, co, lua_gettop(L) - 1, LUA_MULTRET); - bool ok = (r == LUA_OK || r == LUA_YIELD); + bool ok = Lua::IsSuccess(r); lua_pushboolean(L, ok); lua_insert(L, 2); return lua_gettop(L) - 1; @@ -579,7 +574,7 @@ static int dfhack_saferesume (lua_State *L) { lua_State *co = lua_tothread(L, 1); luaL_argcheck(L, !!co, 1, "coroutine expected"); int r = resume_helper(L, co, lua_gettop(L) - 1, LUA_MULTRET); - bool ok = (r == LUA_OK || r == LUA_YIELD); + bool ok = Lua::IsSuccess(r); lua_pushboolean(L, ok); lua_insert(L, 2); if (!ok) @@ -590,7 +585,7 @@ static int dfhack_saferesume (lua_State *L) { static int dfhack_coauxwrap (lua_State *L) { lua_State *co = lua_tothread(L, lua_upvalueindex(1)); int r = resume_helper(L, co, lua_gettop(L), LUA_MULTRET); - if (r == LUA_OK || r == LUA_YIELD) + if (Lua::IsSuccess(r)) return lua_gettop(L); else return lua_error(L); @@ -620,7 +615,7 @@ int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, lua_State *thre int rv = resume_helper(from, thread, nargs, nres); - if (rv != LUA_OK && rv != LUA_YIELD && perr) + if (!Lua::IsSuccess(rv) && perr) { report_error(from, &out); lua_pop(from, 1); @@ -761,7 +756,7 @@ static int resume_query_loop(color_ostream &out, { int rv = Lua::SafeResume(out, state, thread, nargs, 2); - if (rv == LUA_YIELD || rv == LUA_OK) + if (Lua::IsSuccess(rv)) { prompt = ifnull(lua_tostring(state, -2), ""); histfile = ifnull(lua_tostring(state, -1), ""); @@ -906,8 +901,10 @@ static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool succes return success; } -static int finish_dfhack_cleanup (lua_State *L, bool success) +int dfhack_cleanup_cont(lua_State *L, int status, int) { + bool success = Lua::IsSuccess(status); + int nargs = lua_tointeger(L, 1); bool always = lua_toboolean(L, 2); int rvbase = 4+nargs; @@ -948,12 +945,6 @@ static int finish_dfhack_cleanup (lua_State *L, bool success) return numret; } -static int dfhack_cleanup_cont (lua_State *L) -{ - int status = lua_getctx(L, NULL); - return finish_dfhack_cleanup(L, (status == LUA_YIELD)); -} - static int dfhack_call_with_finalizer (lua_State *L) { int nargs = luaL_checkint(L, 1); @@ -988,8 +979,7 @@ static int dfhack_call_with_finalizer (lua_State *L) // Actually invoke // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...] - int status = lua_pcallk(L, lua_gettop(L)-rvbase-1, LUA_MULTRET, 3, 0, dfhack_cleanup_cont); - return finish_dfhack_cleanup(L, (status == LUA_OK)); + return Lua::TailPCallK(L, lua_gettop(L)-rvbase-1, LUA_MULTRET, 3, 0); } static int lua_dfhack_with_suspend(lua_State *L) @@ -1034,8 +1024,8 @@ static const luaL_Reg dfhack_funcs[] = { { "printerr", lua_dfhack_printerr }, { "color", lua_dfhack_color }, { "is_interactive", lua_dfhack_is_interactive }, - { "lineedit", lua_dfhack_lineedit }, - { "safecall", lua_dfhack_safecall }, + { "lineedit", dfhack_lineedit }, + { "safecall", dfhack_safecall }, { "saferesume", dfhack_saferesume }, { "onerror", dfhack_onerror }, { "call_with_finalizer", dfhack_call_with_finalizer }, diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 5e1638251..54aa6dcc7 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -139,6 +139,32 @@ namespace DFHack {namespace Lua { return AssignDFObject(out, state, df::identity_traits::get(), target, val_index, perr); } + /** + * Check if the status is a success, i.e. LUA_OK or LUA_YIELD. + */ + inline bool IsSuccess(int status) { + return (status == LUA_OK || status == LUA_YIELD); + } + + // Internal helper + template + int TailPCallK_Thunk(lua_State *state) { + int tmp; + int rv = lua_getctx(state, &tmp); + return cb(state, rv, tmp); + } + + /** + * A utility for using the restartable pcall feature more conveniently; + * specifically, the callback is called with the same kind of arguments + * in both yield and non-yield case. + */ + template + int TailPCallK(lua_State *state, int narg, int nret, int errfun, int ctx) { + int rv = lua_pcallk(state, narg, nret, errfun, ctx, &TailPCallK_Thunk); + return cb(state, rv, ctx); + } + /** * Invoke lua function via pcall. Returns true if success. * If an error is signalled, and perr is true, it is printed and popped from the stack. From 1e64a6a2f623a6f6a3ce1b0b10ccfe40acbcdde4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 16 Apr 2012 18:05:42 +0400 Subject: [PATCH 101/108] Make dfhack.lineedit automatically interact with RunCoreQueryLoop. It still falls back to the original waiting mode if yield fails. --- library/LuaTools.cpp | 62 +++++++++++++++++++++++++++++++++++++----- library/lua/dfhack.lua | 11 +------- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 993efa747..be4fa864e 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -240,15 +240,14 @@ static int lua_dfhack_is_interactive(lua_State *S) return 1; } -static int lua_dfhack_lineedit(lua_State *S) +static int dfhack_lineedit_sync(lua_State *S, Console *pstream) { - const char *prompt = luaL_optstring(S, 1, ">> "); - const char *hfile = luaL_optstring(S, 2, NULL); - - Console *pstream = get_console(S); if (!pstream) return 2; + const char *prompt = luaL_optstring(S, 1, ">> "); + const char *hfile = luaL_optstring(S, 2, NULL); + DFHack::CommandHistory hist; if (hfile) hist.load(hfile); @@ -271,6 +270,47 @@ static int lua_dfhack_lineedit(lua_State *S) } } +static int DFHACK_QUERY_COROTABLE_TOKEN = 0; + +static int yield_helper(lua_State *S) +{ + return lua_yield(S, lua_gettop(S)); +} + +namespace { + int dfhack_lineedit_cont(lua_State *L, int status, int) + { + if (Lua::IsSuccess(status)) + return lua_gettop(L) - 2; + else + return dfhack_lineedit_sync(L, get_console(L)); + } +} + +static int dfhack_lineedit(lua_State *S) +{ + lua_settop(S, 2); + + Console *pstream = get_console(S); + if (!pstream) + return 2; + + lua_rawgetp(S, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); + lua_rawgetp(S, -1, S); + bool in_coroutine = !lua_isnil(S, -1); + lua_settop(S, 2); + + if (in_coroutine) + { + lua_pushcfunction(S, yield_helper); + lua_pushvalue(S, 1); + lua_pushvalue(S, 2); + return Lua::TailPCallK(S, 2, LUA_MULTRET, 0, 0); + } + + return dfhack_lineedit_sync(S, pstream); +} + /* * Exception handling */ @@ -796,10 +836,12 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, return false; } + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); lua_pushvalue(state, base+1); lua_remove(state, base+1); thread = Lua::NewCoroutine(state); - lua_rawsetp(state, LUA_REGISTRYINDEX, thread); + lua_rawsetp(state, -2, thread); + lua_pop(state, 1); rv = resume_query_loop(out, thread, state, lua_gettop(state)-base, prompt, histfile); } @@ -839,8 +881,10 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, { CoreSuspender suspend; + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); lua_pushnil(state); - lua_rawsetp(state, LUA_REGISTRYINDEX, thread); + lua_rawsetp(state, -2, thread); + lua_pop(state, 1); } return (rv == LUA_OK); @@ -1180,6 +1224,10 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) luaL_openlibs(state); AttachDFGlobals(state); + // Table of query coroutines + lua_newtable(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); + // Replace the print function of the standard library lua_pushcfunction(state, lua_dfhack_println); lua_setglobal(state, "print"); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 595363da8..b5985f0c1 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -130,15 +130,6 @@ end -- Interactive -function dfhack.query(prompt,hfile) - local _,main = coroutine.running() - if main then - return dfhack.lineedit(prompt,hfile) - else - return coroutine.yield(prompt,hfile) - end -end - local print_banner = true function dfhack.interpreter(prompt,hfile,env) @@ -163,7 +154,7 @@ function dfhack.interpreter(prompt,hfile,env) setmetatable(prompt_env, { __index = env or _G }) while true do - local cmdline = dfhack.query(prompt_str, hfile) + local cmdline = dfhack.lineedit(prompt_str, hfile) if cmdline == nil or cmdline == 'quit' then break From 9560fc641c05f28ea0e374b8cce610794c926808 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 16 Apr 2012 16:31:12 +0200 Subject: [PATCH 102/108] zone: mass-assign nicknames, assign units to multiple cages inside a pasture. autobutcher: some fixes regarding autowatch and butchering caged units. --- plugins/zone.cpp | 192 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 173 insertions(+), 19 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 4ee991549..43c259723 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -89,11 +89,16 @@ const string zone_help = " if no filters are used, a single unit must be selected.\n" " with filters named units are ignored unless specified.\n" " unassign - unassign selected creature(s) from it's zone or cage\n" + " nick - give unit(s) nicknames (e.g. all units in a cage)\n" + " remnick - remove nicknames\n" + " tocages - assign to (multiple) built cages inside a pen/pasture\n" + " spreads creatures evenly among cages for faster hauling.\n" " uinfo - print info about selected unit\n" " zinfo - print info about zone(s) under cursor\n" " verbose - print some more info, mostly useless debug stuff\n" " filters - print list of supported filters\n" - " examples - print some usage examples\n"; + " examples - print some usage examples\n" + ; const string zone_help_filters = "Filters (to be used in combination with 'all' or 'count'):\n" @@ -360,6 +365,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose); void cageInfo(color_ostream & out, df::building* building, bool verbose); void chainInfo(color_ostream & out, df::building* building, bool verbose); bool isBuiltCageAtPos(df::coord pos); +bool isInBuiltCageRoom(df::unit*); int32_t getUnitAge(df::unit* unit) { @@ -734,6 +740,10 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) } out << endl; } + if(isInBuiltCageRoom(unit)) + { + out << "in a room." << endl; + } } bool isActivityZone(df::building * building) @@ -1033,7 +1043,14 @@ bool isInBuiltCageRoom(df::unit* unit) for (size_t b=0; b < world->buildings.all.size(); b++) { df::building* building = world->buildings.all[b]; - if(building->isRoom() && building->getType() == building_type::Cage) + + // !!! for whatever reason isRoom() returns true if a cage is not a room + // !!! and false if it was defined as a room/zoo ingame + // !!! (seems not general behaviour, activity zones return false, for example) + if(building->isRoom()) + continue; + + if(building->getType() == building_type::Cage) { df::building_cagest* cage = (df::building_cagest*) building; for(size_t c=0; cassigned_creature.size(); c++) @@ -1404,6 +1421,71 @@ command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::buil return result; } +command_result assignUnitsToCagezone(color_ostream& out, vector units, df::building* building, bool verbose) +{ + command_result result = CR_WRONG_USAGE; + + if(!isPenPasture(building)) + { + out << "A cage zone needs to be a pen/pasture containing at least one cage!" << endl; + return CR_WRONG_USAGE; + } + + int32_t x1 = building->x1; + int32_t x2 = building->x2; + int32_t y1 = building->y1; + int32_t y2 = building->y2; + int32_t z = building->z; + //out << " x1:"< cages; + for (int32_t x = x1; x<=x2; x++) + { + for (int32_t y = y1; y<=y2; y++) + { + df::building* cage = getBuiltCageAtPos(df::coord(x,y,z)); + if(cage) + { + df::building_cagest* cagest = (df::building_cagest*) cage; + cages.push_back(cagest); + } + } + } + if(!cages.size()) + { + out << "No cages found in this zone!" << endl; + return CR_WRONG_USAGE; + } + else + { + out << "Number of cages: " << cages.size() << endl; + } + + while(units.size()) + { + // hrm, better use sort() instead? + df::building_cagest * bestcage = cages[0]; + size_t lowest = cages[0]->assigned_creature.size(); + for(size_t i=1; iassigned_creature.size()assigned_creature.size(); + bestcage = cages[i]; + } + } + df::unit* unit = units.back(); + command_result result = assignUnitToCage(out, unit, (df::building*) bestcage, verbose); + if(result!=CR_OK) + return result; + units.pop_back(); + } + + return CR_OK; +} + command_result nickUnitsInZone(color_ostream& out, df::building* building, string nick) { // building must be a pen/pasture or pit @@ -1450,6 +1532,8 @@ command_result nickUnitsInChain(color_ostream& out, df::building* building, stri return CR_WRONG_USAGE; } +// give all units inside a pasture or cage the same nickname +// (usage example: protect them from being autobutchered) command_result nickUnitsInBuilding(color_ostream& out, df::building* building, string nick) { command_result result = CR_WRONG_USAGE; @@ -1493,6 +1577,11 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) else out << "not active"; + //if(building->isRoom()) + // out <<", room"; + //else + // out << ", not a room"; + if(civ->zone_flags.bits.pen_pasture) out << ", pen/pasture"; else if (civ->zone_flags.bits.pit_pond) @@ -1549,7 +1638,17 @@ void cageInfo(color_ostream & out, df::building* building, bool verbose) << " z:" << building->z << endl; + //if(building->isRoom()) + // out <<", bldg room"; + //else + // out << ", bldg not a room"; + df::building_cagest * cage = (df::building_cagest*) building; + //if(cage->isRoom()) + // out <<", cage is room"; + //else + // out << ", cage is not a room"; + int32_t creaturecount = cage->assigned_creature.size(); out << "Creatures in this cage: " << creaturecount << endl; for(size_t c = 0; c < creaturecount; c++) @@ -1654,6 +1753,7 @@ command_result df_zone (color_ostream &out, vector & parameters) bool building_assign = false; bool building_unassign = false; bool building_set = false; + bool cagezone_assign = false; bool verbose = false; bool all = false; bool unit_slaughter = false; @@ -1740,6 +1840,38 @@ command_result df_zone (color_ostream &out, vector & parameters) building_assign = true; } } + else if(p == "tocages") + { + if(invert_filter) + { + out << "'not tocages' makes no sense." << endl; + return CR_WRONG_USAGE; + } + + // if followed by another parameter, check if it's numeric + if(i < parameters.size()-1) + { + stringstream ss(parameters[i+1]); + int new_building = -1; + ss >> new_building; + if(new_building != -1) + { + i++; + target_building = new_building; + out << "Assign selected unit(s) to cagezone #" << target_building < & parameters) // set building at cursor position to be new target building if(building_set) { - target_building = findCageAtCursor(); + // cagezone wants a pen/pit as starting point + if(!cagezone_assign) + target_building = findCageAtCursor(); if(target_building != -1) { out << "Target building type: cage." << endl; @@ -2164,10 +2298,10 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } - if(building_assign || unit_info || unit_slaughter || nick_set) + if(building_assign || cagezone_assign || unit_info || unit_slaughter || nick_set) { df::building * building; - if(building_assign || (nick_set && !all && !find_count)) + if(building_assign || cagezone_assign || (nick_set && !all && !find_count)) { // try to get building index from the id int32_t index = findBuildingIndexById(target_building); @@ -2188,6 +2322,7 @@ command_result df_zone (color_ostream &out, vector & parameters) if(all || find_count) { + vector units_for_cagezone; size_t count = 0; for(size_t c = 0; c < world->units.all.size(); c++) { @@ -2278,8 +2413,18 @@ command_result df_zone (color_ostream &out, vector & parameters) { Units::setNickname(unit, target_nick); } - - if(building_assign) + + if(cagezone_assign) + { + // !!! optimize me: collect a vector of unit pointers which match the search and pass it to + // a method assignUnitsToCagezone(out, units, building) which only builds the vector of cages once + //command_result result = assignUnitToCagezone(out, unit, building, verbose); + //if(result != CR_OK) + // return result; + //continue; + units_for_cagezone.push_back(unit); + } + else if(building_assign) { command_result result = assignUnitToBuilding(out, unit, building, verbose); if(result != CR_OK) @@ -2289,15 +2434,21 @@ command_result df_zone (color_ostream &out, vector & parameters) if(unit_slaughter) { // don't slaughter named creatures unless told to do so - if(!find_named && unit->name.has_name) - continue; - doMarkForSlaughter(unit); + if(!unit->name.has_name || find_named) + doMarkForSlaughter(unit); } count++; if(find_count && count >= target_count) break; } + if(cagezone_assign) + { + command_result result = assignUnitsToCagezone(out, units_for_cagezone, building, verbose); + if(result != CR_OK) + return result; + } + out << "Processed creatures: " << count << endl; } else @@ -3115,18 +3266,21 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) return CR_OK; // check if there is anything to watch before walking through units vector - bool watching = false; - for(size_t i=0; iisWatched) + bool watching = false; + for(size_t i=0; iisWatched) + { + watching = true; + break; + } } + if(!watching) + return CR_OK; } - if(!watching) - return CR_OK; for(size_t i=0; iunits.all.size(); i++) { @@ -3142,7 +3296,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) || isHunter(unit) // ignore hunting dogs etc // ignore creatures in built cages which are defined as rooms to leave zoos alone // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() || unit->name.has_name ) continue; From ee7100216e7ac43ad51f1cdd5e51d89daa545b91 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 16 Apr 2012 18:32:12 +0400 Subject: [PATCH 103/108] Fix lua interpreter bug: the C call counter is already unwound by yield. Decrementing it causes underflow and subsequent spurious stack overflow. --- depends/lua/CMakeLists.txt | 2 +- depends/lua/src/ldo.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index 3a56aa639..b77bce6f7 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -1,7 +1,7 @@ PROJECT ( lua CXX ) CMAKE_MINIMUM_REQUIRED(VERSION 2.8) -SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-DLUA_USE_APICHECK") +SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DLUA_USE_APICHECK") IF(WIN32) ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE ) diff --git a/depends/lua/src/ldo.c b/depends/lua/src/ldo.c index 26f9a6747..e0f13c44e 100644 --- a/depends/lua/src/ldo.c +++ b/depends/lua/src/ldo.c @@ -403,7 +403,7 @@ static void finishCcall (lua_State *L) { lua_assert(ci->u.c.k != NULL); /* must have a continuation */ lua_assert(L->nny == 0); /* finish 'luaD_call' */ - L->nCcalls--; + //L->nCcalls--; /* finish 'lua_callk' */ adjustresults(L, ci->nresults); /* call continuation function */ @@ -513,7 +513,7 @@ static void resume (lua_State *L, void *ud) { api_checknelems(L, n); firstArg = L->top - n; /* yield results come from continuation */ } - L->nCcalls--; /* finish 'luaD_call' */ + //L->nCcalls--; /* finish 'luaD_call' */ luaD_poscall(L, firstArg); /* finish 'luaD_precall' */ } unroll(L, NULL); From 3b68faa1bee67b3422ddce967ea86c4be9387847 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 16 Apr 2012 17:14:02 +0200 Subject: [PATCH 104/108] updated readme.rst --- README.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 69fed9e83..0b189e40a 100644 --- a/README.rst +++ b/README.rst @@ -812,6 +812,7 @@ Options: :unassign: Unassign selected creature from it's zone. :nick: Mass-assign nicknames, must be followed by the name you want to set. :remnick: Mass-remove nicknames. +:tocages: Assign unit(s) to cages inside a pasture. :uinfo: Print info about unit(s). If no filters are set a unit must be selected in the in-game ui. :zinfo: Print info about zone(s). If no filters are set zones under the cursor are listed. :verbose: Print some more info. @@ -854,6 +855,12 @@ Mass-renaming ------------- Using the 'nick' command you can set the same nickname for multiple units. If used without 'assign', 'all' or 'count' it will rename all units in the current default target zone. Combined with 'assign', 'all' or 'count' (and further optional filters) it will rename units matching the filter conditions. +Cage zones +---------- +Using the 'tocages' command you can assign units to a set of cages, for example a room next to your butcher shop(s). They will be spread evenly among available cages to optimize hauling to and butchering from them. For this to work you need to build cages and then place one pen/pasture activity zone above them, covering all cages you want to use. Then use 'zone set' (like with 'assign') and use 'zone tocages filter1 filter2 ...'. 'tocages' overwrites 'assign' because it would make no sense, but can be used together with 'nick' or 'remnick' and all the usual filters. + +Examples +-------- ``zone assign all own ALPACA minage 3 maxage 10`` Assign all own alpacas who are between 3 and 10 years old to the selected pasture. ``zone assign all own caged grazer nick ineedgrass`` @@ -866,7 +873,9 @@ Using the 'nick' command you can set the same nickname for multiple units. If us Throw all useless kids into a pit :) ``zone nick donttouchme`` Nicknames all units in the current default zone or cage to 'donttouchme'. Mostly intended to be used for special pastures or cages which are not marked as rooms you want to protect from autobutcher. - +``zone tocages count 50 own tame male not grazer`` + Stuff up to 50 owned tame male animals who are not grazers into cages built on the current default zone. + autonestbox =========== Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless of the size. The age of the units is currently not checked, most birds grow up quite fast. Egglayers who are also grazers will be ignored, since confining them to a 1x1 pasture is not a good idea. When called without options autonestbox will instantly run once. From 43e48141472506db142a67b53b752c53c85db6d9 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 16 Apr 2012 10:34:12 -0500 Subject: [PATCH 105/108] Mod workflow to properly handle custom reactions that produce CRAFTS --- plugins/workflow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 480379261..aa3c94a55 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -808,7 +808,7 @@ static void compute_custom_job(ProtectedJob *pj, df::job *job) using namespace df::enums::reaction_product_item_flags; VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]); - if (!prod || prod->item_type < 0) + if (!prod || (prod->item_type < 0 && !prod->flags.is_set(CRAFTS))) continue; MaterialInfo mat(prod); @@ -854,7 +854,7 @@ static void compute_custom_job(ProtectedJob *pj, df::job *job) } link_job_constraint(pj, prod->item_type, prod->item_subtype, - mat_mask, mat.type, mat.index); + mat_mask, mat.type, mat.index, prod->flags.is_set(CRAFTS)); } } From 17d5b2de04decb31be74ea196bb56875c66e226c Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 16 Apr 2012 18:46:20 +0300 Subject: [PATCH 106/108] Multiline interpreter. To test try writing for k,v in pairs(table) do print(k) end Also prompt could be changed (couldn't think of anything better). --- library/lua/dfhack.lua | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index b5985f0c1..4be58661b 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -149,12 +149,13 @@ function dfhack.interpreter(prompt,hfile,env) local prompt_str = "["..(prompt or 'lua').."]# " local prompt_env = {} + local t_prompt local vcnt = 1 setmetatable(prompt_env, { __index = env or _G }) - + local cmdlinelist={} while true do - local cmdline = dfhack.lineedit(prompt_str, hfile) + local cmdline = dfhack.lineedit(t_prompt or prompt_str, hfile) if cmdline == nil or cmdline == 'quit' then break @@ -164,12 +165,23 @@ function dfhack.interpreter(prompt,hfile,env) if pfix == '!' or pfix == '=' then cmdline = 'return '..string.sub(cmdline,2) end - - local code,err = load(cmdline, '=(interactive)', 't', prompt_env) + table.insert(cmdlinelist,cmdline) + local code,err = load(table.concat(cmdlinelist,'\n'), '=(interactive)', 't', prompt_env) if code == nil then - dfhack.printerr(err) + if err:sub(-5)=="" then + t_prompt="[cont]" + + else + dfhack.printerr(err) + cmdlinelist={} + t_prompt=nil + end else + + cmdlinelist={} + t_prompt=nil + local data = table.pack(safecall(code)) if data[1] and data.n > 1 then From 862fa9d67583b81e078ed0fd130046d2c64ab379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Mon, 16 Apr 2012 20:19:45 +0200 Subject: [PATCH 107/108] Track stonesense --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index c50af342c..cddc64777 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit c50af342c27542c3b2c51ddd9099798339b7aa62 +Subproject commit cddc64777b8368e15c1a820ccd1cf56e11267e52 From 6d180d61c483350d19f08c5cdb74edd4aaff21da Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 16 Apr 2012 21:37:48 +0200 Subject: [PATCH 108/108] autopasture: only process domesticated own units to avoid problems with wild animals forgetting their training and going on a rampage --- README.rst | 2 +- plugins/probe.cpp | 4 ++++ plugins/zone.cpp | 38 ++++++++++++++++++++++++-------------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index 0b189e40a..6e0031ef6 100644 --- a/README.rst +++ b/README.rst @@ -878,7 +878,7 @@ Examples autonestbox =========== -Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless of the size. The age of the units is currently not checked, most birds grow up quite fast. Egglayers who are also grazers will be ignored, since confining them to a 1x1 pasture is not a good idea. When called without options autonestbox will instantly run once. +Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless of the size. The age of the units is currently not checked, most birds grow up quite fast. Egglayers who are also grazers will be ignored, since confining them to a 1x1 pasture is not a good idea. Only tame and domesticated own units are processed since pasturing half-trained wild egglayers could destroy your neat nestbox zones when they revert to wild. When called without options autonestbox will instantly run once. Options: -------- diff --git a/plugins/probe.cpp b/plugins/probe.cpp index cb627c8ad..3f4a3b6fe 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -382,6 +382,10 @@ command_result df_bprobe (color_ostream &out, vector & parameters) out.print(", subtype %i", building.subtype); break; } + if(building.origin->isRoom()) + out << ", is room"; + else + out << ", not a room"; out.print("\n"); } diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 43c259723..968fda72f 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -410,6 +410,7 @@ void doMarkForSlaughter(df::unit* unit) unit->flags2.bits.slaughter = 1; } +// check if creature is tame bool isTame(df::unit* creature) { bool tame = false; @@ -437,6 +438,26 @@ bool isTame(df::unit* creature) return tame; } +// check if creature is domesticated +// seems to be the only way to really tell if it's completely safe to autonestbox it (training can revert) +bool isDomesticated(df::unit* creature) +{ + bool tame = false; + if(creature->flags1.bits.tame) + { + switch (creature->training_level) + { + case df::animal_training_level::Domesticated: + tame=true; + break; + default: + tame=false; + break; + } + } + return tame; +} + // check if trained (might be useful if pasturing war dogs etc) bool isTrained(df::unit* unit) { @@ -1181,7 +1202,7 @@ bool isFreeEgglayer(df::unit * unit) { if( !isDead(unit) && !isUndead(unit) && isFemale(unit) - && isTame(unit) + && isDomesticated(unit) // better strict than sorry (medium trained wild animals can revert into wild state) && isOwnCiv(unit) && isEggLayer(unit) && !isAssigned(unit) @@ -1436,10 +1457,6 @@ command_result assignUnitsToCagezone(color_ostream& out, vector units int32_t y1 = building->y1; int32_t y2 = building->y2; int32_t z = building->z; - //out << " x1:"< cages; for (int32_t x = x1; x<=x2; x++) { @@ -1477,10 +1494,10 @@ command_result assignUnitsToCagezone(color_ostream& out, vector units } } df::unit* unit = units.back(); + units.pop_back(); command_result result = assignUnitToCage(out, unit, (df::building*) bestcage, verbose); if(result!=CR_OK) return result; - units.pop_back(); } return CR_OK; @@ -2416,12 +2433,6 @@ command_result df_zone (color_ostream &out, vector & parameters) if(cagezone_assign) { - // !!! optimize me: collect a vector of unit pointers which match the search and pass it to - // a method assignUnitsToCagezone(out, units, building) which only builds the vector of cages once - //command_result result = assignUnitToCagezone(out, unit, building, verbose); - //if(result != CR_OK) - // return result; - //continue; units_for_cagezone.push_back(unit); } else if(building_assign) @@ -2472,7 +2483,7 @@ command_result df_zone (color_ostream &out, vector & parameters) } else if(unit_slaughter) { - // by default behave like the game: only allow slaughtering of named war/hunting pets + // by default behave like the game? only allow slaughtering of named war/hunting pets //if(unit->name.has_name && !find_named && !(isWar(unit)||isHunter(unit))) //{ // out << "Slaughter of named unit denied. Use the filter 'named' to override this check." << endl; @@ -3453,7 +3464,6 @@ command_result start_autonestbox(color_ostream &out) config_autonestbox = pworld->AddPersistentData("autonestbox/config"); config_autonestbox.ival(0) = enable_autonestbox; config_autonestbox.ival(1) = sleep_autonestbox; - //out << "autonestbox created persistent config object." << endl; } out << "Starting autonestbox." << endl; init_autonestbox(out);