From f4e8fa2f30894b6ad17d85c1b1e5783adcfb287b Mon Sep 17 00:00:00 2001 From: quipyowert Date: Mon, 1 Dec 2014 17:19:20 -0800 Subject: [PATCH 001/115] Define a static variable to fix debug mode crash. Fixes #404 --- library/modules/MapCache.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index 5fc48789f..404626e00 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -74,6 +74,8 @@ using df::global::world; extern bool GetLocalFeature(t_feature &feature, df::coord2d rgn_pos, int32_t index); +const unsigned MapExtras::BiomeInfo::MAX_LAYERS; + const BiomeInfo MapCache::biome_stub = { df::coord2d(), -1, -1, -1, -1, From e206c242c6b7011f424e0da975d7aeb2d0b450d5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 2 Dec 2014 21:29:13 -0500 Subject: [PATCH 002/115] Add a plugin_globals vector to aid in safety checks for plugins that require globals This allows "using df::global::foo" to be replaced by "REQUIRE_GLOBAL(foo)", and DFHack will refuse to load the plugin if df::global::foo is NULL --- library/PluginManager.cpp | 25 +++++++++++++++++++++++-- library/include/PluginManager.h | 10 +++++++++- plugins/3dveins.cpp | 7 +++---- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index ad390d507..713c442c3 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -30,6 +30,7 @@ distribution. #include "RemoteServer.h" #include "Console.h" #include "Types.h" +#include "VersionInfo.h" #include "DataDefs.h" #include "MiscUtils.h" @@ -167,6 +168,7 @@ Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _f } plugin_lib = 0; plugin_init = 0; + plugin_globals = 0; plugin_shutdown = 0; plugin_status = 0; plugin_onupdate = 0; @@ -235,13 +237,32 @@ bool Plugin::load(color_ostream &con) *plug_self = this; RefAutolock lock(access); plugin_init = (command_result (*)(color_ostream &, std::vector &)) LookupPlugin(plug, "plugin_init"); - if(!plugin_init) + std::vector** plugin_globals_ptr = (std::vector**) LookupPlugin(plug, "plugin_globals"); + if(!plugin_init || !plugin_globals_ptr) { - con.printerr("Plugin %s has no init function.\n", filename.c_str()); + con.printerr("Plugin %s has no init function or globals vector.\n", filename.c_str()); ClosePlugin(plug); state = PS_BROKEN; return false; } + plugin_globals = *plugin_globals_ptr; + if (plugin_globals->size()) + { + std::vector missing_globals; + for (auto it = plugin_globals->begin(); it != plugin_globals->end(); ++it) + { + if (!Core::getInstance().vinfo->getAddress(it->c_str())) + missing_globals.push_back(*it); + } + if (missing_globals.size()) + { + con.printerr("Plugin %s is missing required globals: %s\n", + filename.c_str(), join_strings(", ", missing_globals).c_str()); + ClosePlugin(plug); + state = PS_BROKEN; + return false; + } + } plugin_status = (command_result (*)(color_ostream &, std::string &)) LookupPlugin(plug, "plugin_status"); plugin_onupdate = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_onupdate"); plugin_shutdown = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_shutdown"); diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index b47863d48..ba5b76b62 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -205,6 +205,7 @@ namespace DFHack void reset_lua(); bool *plugin_is_enabled; + std::vector* plugin_globals; command_result (*plugin_init)(color_ostream &, std::vector &); command_result (*plugin_status)(color_ostream &, std::string &); command_result (*plugin_shutdown)(color_ostream &); @@ -264,7 +265,9 @@ namespace DFHack #define DFHACK_PLUGIN(plugin_name) \ DFhackDataExport const char * version = DFHACK_VERSION;\ DFhackDataExport const char * name = plugin_name;\ - DFhackDataExport Plugin *plugin_self = NULL; + DFhackDataExport Plugin *plugin_self = NULL;\ + std::vector _plugin_globals;\ + DFhackDataExport std::vector* plugin_globals = &_plugin_globals; #define DFHACK_PLUGIN_IS_ENABLED(varname) \ DFhackDataExport bool plugin_is_enabled = false; \ @@ -281,3 +284,8 @@ namespace DFHack #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 } + +#define REQUIRE_GLOBAL(global_name) \ + using df::global::global_name; \ + static int VARIABLE_IS_NOT_USED CONCAT_TOKENS(required_globals_, __LINE__) = \ + (plugin_globals->push_back(#global_name), 0); diff --git a/plugins/3dveins.cpp b/plugins/3dveins.cpp index 9802ff5eb..d57a80662 100644 --- a/plugins/3dveins.cpp +++ b/plugins/3dveins.cpp @@ -41,13 +41,12 @@ using namespace DFHack; using namespace MapExtras; using namespace DFHack::Random; -using df::global::world; -using df::global::gametype; +DFHACK_PLUGIN("3dveins"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(gametype); command_result cmd_3dveins(color_ostream &out, std::vector & parameters); -DFHACK_PLUGIN("3dveins"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( From cac22454388e266b9b6cf0d4ab9fcd88e968c7f8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 2 Dec 2014 21:44:20 -0500 Subject: [PATCH 003/115] Update some plugins to use REQUIRE_GLOBAL --- library/include/PluginManager.h | 1 + plugins/add-spatter.cpp | 11 +++++------ plugins/advtools.cpp | 9 ++++----- plugins/autochop.cpp | 6 ++---- plugins/autodump.cpp | 2 +- plugins/autolabor.cpp | 10 ++++------ plugins/automaterial.cpp | 6 +++--- plugins/automelt.cpp | 6 +++--- plugins/autotrade.cpp | 6 +++--- plugins/building-hacks.cpp | 3 ++- plugins/buildingplan.cpp | 7 +++---- plugins/burrows.cpp | 9 ++++----- plugins/catsplosion.cpp | 6 +++--- plugins/changeitem.cpp | 2 +- plugins/changelayer.cpp | 7 +++---- plugins/changevein.cpp | 7 +++---- plugins/cleanconst.cpp | 3 +-- plugins/cleaners.cpp | 5 ++--- plugins/cleanowned.cpp | 5 ++--- plugins/colonies.cpp | 1 + plugins/command-prompt.cpp | 9 +++++---- plugins/createitem.cpp | 7 +++---- plugins/cursecheck.cpp | 8 ++++---- plugins/deramp.cpp | 3 +-- plugins/dfstream.cpp | 8 ++++---- plugins/dig.cpp | 1 + plugins/digFlood.cpp | 7 ++----- plugins/diggingInvaders/diggingInvaders.cpp | 7 ++++--- plugins/drybuckets.cpp | 3 +-- plugins/dwarfmonitor.cpp | 11 +++++------ plugins/eventful.cpp | 9 ++++----- 31 files changed, 85 insertions(+), 100 deletions(-) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index ba5b76b62..663dde90a 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -27,6 +27,7 @@ distribution. #include "Export.h" #include "Hooks.h" #include "ColorText.h" +#include "MiscUtils.h" #include #include #include diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp index 52e95e88b..cb2858d06 100644 --- a/plugins/add-spatter.cpp +++ b/plugins/add-spatter.cpp @@ -41,14 +41,13 @@ using std::stack; using namespace DFHack; using namespace df::enums; -using df::global::gps; -using df::global::world; -using df::global::ui; - -typedef df::reaction_product_item_improvementst improvement_product; - DFHACK_PLUGIN("add-spatter"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); + +typedef df::reaction_product_item_improvementst improvement_product; struct ReagentSource { int idx; diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index ad50dbe5d..8f613d1cc 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -37,14 +37,15 @@ using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::ui_advmode; - using df::nemesis_record; using df::historical_figure; using namespace DFHack::Translation; +DFHACK_PLUGIN("advtools"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui_advmode); + /********************* * PLUGIN INTERFACE * *********************/ @@ -54,8 +55,6 @@ static bool bodyswap_hotkey(df::viewscreen *top); command_result adv_bodyswap (color_ostream &out, std::vector & parameters); command_result adv_tools (color_ostream &out, std::vector & parameters); -DFHACK_PLUGIN("advtools"); - DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (!ui_advmode) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 5dab8f69b..ee5f6c492 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -36,12 +36,10 @@ using std::set; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::ui; - #define PLUGIN_VERSION 0.3 DFHACK_PLUGIN("autochop"); - +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); static bool autochop_enabled = false; static int min_logs, max_logs; diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index dcbc7c083..efb58a10c 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -34,10 +34,10 @@ using namespace df::enums; using MapExtras::Block; using MapExtras::MapCache; -using df::global::world; using df::building_stockpilest; DFHACK_PLUGIN("autodump"); +REQUIRE_GLOBAL(world); // Stockpile interface START static const string PERSISTENCE_KEY = "autodump/stockpiles"; diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index ad7992038..c1dfc5d2e 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -44,8 +44,10 @@ using std::endl; using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::ui; -using df::global::world; + +DFHACK_PLUGIN("autolabor"); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(world); #define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) @@ -91,10 +93,6 @@ enum ConfigFlags { // mostly to allow having the mandatory stuff on top of the file and commands on the bottom command_result autolabor (color_ostream &out, std::vector & parameters); -// A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - autolabor.plug.so or autolabor.plug.dll in this case -DFHACK_PLUGIN("autolabor"); - static void generate_labor_to_skill_map(); enum labor_mode { diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index b795622d9..b49b04ee8 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -43,11 +43,11 @@ using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::gps; -using df::global::ui; -using df::global::ui_build_selector; DFHACK_PLUGIN("automaterial"); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_build_selector); struct MaterialDescriptor { diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index 042d8e082..ccbf3489c 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -14,13 +14,13 @@ #include "modules/World.h" #include "df/item_quality.h" -using df::global::world; -using df::global::cursor; -using df::global::ui; using df::building_stockpilest; DFHACK_PLUGIN("automelt"); #define PLUGIN_VERSION 0.3 +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(cursor); +REQUIRE_GLOBAL(ui); static const string PERSISTENCE_KEY = "automelt/stockpiles"; diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index e6156b0c3..e238cc514 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -19,12 +19,12 @@ #include "df/mandate.h" #include "modules/Maps.h" -using df::global::world; -using df::global::cursor; -using df::global::ui; using df::building_stockpilest; DFHACK_PLUGIN("autotrade"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(cursor); +REQUIRE_GLOBAL(ui); static const string PERSISTENCE_KEY = "autotrade/stockpiles"; diff --git a/plugins/building-hacks.cpp b/plugins/building-hacks.cpp index d56215fda..152e4568b 100644 --- a/plugins/building-hacks.cpp +++ b/plugins/building-hacks.cpp @@ -24,9 +24,10 @@ using namespace DFHack; using namespace df::enums; -using df::global::world; DFHACK_PLUGIN("building-hacks"); +REQUIRE_GLOBAL(world); + struct graphic_tile //could do just 31x31 and be done, but it's nicer to have flexible imho. { int16_t tile; //originally uint8_t but we need to indicate non-animated tiles diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index d916ba2e3..e9bb6eda4 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -35,12 +35,11 @@ #include "df/building.h" #include "df/building_doorst.h" -using df::global::ui; -using df::global::ui_build_selector; -using df::global::world; - DFHACK_PLUGIN("buildingplan"); #define PLUGIN_VERSION 0.14 +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_build_selector); +REQUIRE_GLOBAL(world); struct MaterialDescriptor { diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index 214fb0582..87adbe734 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -37,9 +37,10 @@ using namespace DFHack; using namespace df::enums; using namespace dfproto; -using df::global::ui; -using df::global::world; -using df::global::gamemode; +DFHACK_PLUGIN("burrows"); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(gamemode); /* * Initialization. @@ -47,8 +48,6 @@ using df::global::gamemode; 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); diff --git a/plugins/catsplosion.cpp b/plugins/catsplosion.cpp index 1c323de22..725aaf492 100644 --- a/plugins/catsplosion.cpp +++ b/plugins/catsplosion.cpp @@ -25,11 +25,11 @@ using namespace std; #include using namespace DFHack; -using df::global::world; - -command_result catsplosion (color_ostream &out, std::vector & parameters); DFHACK_PLUGIN("catsplosion"); +REQUIRE_GLOBAL(world); + +command_result catsplosion (color_ostream &out, std::vector & parameters); // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) diff --git a/plugins/changeitem.cpp b/plugins/changeitem.cpp index 9c0a55ad9..b73f24097 100644 --- a/plugins/changeitem.cpp +++ b/plugins/changeitem.cpp @@ -31,9 +31,9 @@ using namespace df::enums; using MapExtras::Block; using MapExtras::MapCache; -using df::global::world; DFHACK_PLUGIN("changeitem"); +REQUIRE_GLOBAL(world); command_result df_changeitem(color_ostream &out, vector & parameters); diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp index 3ab1899af..727fae56c 100644 --- a/plugins/changelayer.cpp +++ b/plugins/changelayer.cpp @@ -28,8 +28,9 @@ using namespace std; using std::vector; using std::string; -using df::global::world; -using df::global::cursor; +DFHACK_PLUGIN("changelayer"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(cursor); const string changelayer_help = " Allows to change the material of whole geology layers.\n" @@ -83,8 +84,6 @@ const string changelayer_trouble = command_result changelayer (color_ostream &out, std::vector & parameters); -DFHACK_PLUGIN("changelayer"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/changevein.cpp b/plugins/changevein.cpp index 1d08cfeaf..9c17af292 100644 --- a/plugins/changevein.cpp +++ b/plugins/changevein.cpp @@ -15,8 +15,9 @@ using std::string; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::cursor; +DFHACK_PLUGIN("changevein"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(cursor); command_result df_changevein (color_ostream &out, vector & parameters) { @@ -77,8 +78,6 @@ command_result df_changevein (color_ostream &out, vector & parameters) return CR_OK; } -DFHACK_PLUGIN("changevein"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("changevein", diff --git a/plugins/cleanconst.cpp b/plugins/cleanconst.cpp index 3efdadecb..2d5bf77f6 100644 --- a/plugins/cleanconst.cpp +++ b/plugins/cleanconst.cpp @@ -16,9 +16,8 @@ using namespace std; using namespace DFHack; -using df::global::world; - DFHACK_PLUGIN("cleanconst"); +REQUIRE_GLOBAL(world); command_result df_cleanconst(color_ostream &out, vector & parameters) { diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index ab1ccda2b..953ff5305 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -18,10 +18,9 @@ using std::string; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::cursor; - DFHACK_PLUGIN("cleaners"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(cursor); command_result cleanmap (color_ostream &out, bool snow, bool mud, bool item_spatter) { diff --git a/plugins/cleanowned.cpp b/plugins/cleanowned.cpp index 28314d3f0..4dd3fd212 100644 --- a/plugins/cleanowned.cpp +++ b/plugins/cleanowned.cpp @@ -23,12 +23,11 @@ using namespace std; using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("cleanowned"); +REQUIRE_GLOBAL(world); command_result df_cleanowned (color_ostream &out, vector & parameters); -DFHACK_PLUGIN("cleanowned"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/colonies.cpp b/plugins/colonies.cpp index 3ebfc324c..bea595bf9 100644 --- a/plugins/colonies.cpp +++ b/plugins/colonies.cpp @@ -14,6 +14,7 @@ using namespace DFHack; command_result colonies (color_ostream &out, vector & parameters); DFHACK_PLUGIN("colonies"); +REQUIRE_GLOBAL(world); // used by Materials DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index cf1e78763..bc3b89610 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -22,9 +22,10 @@ using namespace DFHack; using namespace df::enums; -using df::global::ui; -using df::global::gps; -using df::global::enabler; +DFHACK_PLUGIN("command-prompt"); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(enabler); std::vector command_history; @@ -303,7 +304,7 @@ void viewscreen_commandpromptst::feed(std::set *events) frame = 0; } -DFHACK_PLUGIN("command-prompt"); + command_result show_prompt(color_ostream &out, std::vector & parameters) { if (Gui::getCurFocus() == "dfhack/commandprompt") diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index 89c2ade36..c4c0c6ebb 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -31,11 +31,10 @@ using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::ui; -using df::global::gametype; - DFHACK_PLUGIN("createitem"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(gametype); int dest_container = -1, dest_building = -1; diff --git a/plugins/cursecheck.cpp b/plugins/cursecheck.cpp index f7c6d2fd9..dc7fd2f3c 100644 --- a/plugins/cursecheck.cpp +++ b/plugins/cursecheck.cpp @@ -48,12 +48,12 @@ using std::vector; using std::string; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::cursor; - -command_result cursecheck (color_ostream &out, vector & parameters); DFHACK_PLUGIN("cursecheck"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(cursor); + +command_result cursecheck (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { diff --git a/plugins/deramp.cpp b/plugins/deramp.cpp index e5d0331d9..dc2651db7 100644 --- a/plugins/deramp.cpp +++ b/plugins/deramp.cpp @@ -14,9 +14,8 @@ using std::string; using namespace DFHack; using namespace df::enums; -using df::global::world; - DFHACK_PLUGIN("deramp"); +REQUIRE_GLOBAL(world); command_result df_deramp (color_ostream &out, vector & parameters) { diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp index 19334abae..c8e99d678 100644 --- a/plugins/dfstream.cpp +++ b/plugins/dfstream.cpp @@ -18,8 +18,10 @@ using namespace df::enums; using std::string; using std::vector; -using df::global::gps; -using df::global::enabler; + +DFHACK_PLUGIN("dfstream"); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(enabler); // The error messages are taken from the clsocket source code const char * translate_socket_error(CSimpleSocket::CSocketError err) { @@ -283,8 +285,6 @@ public: } }; -DFHACK_PLUGIN("dfstream"); - inline df::renderer *& active_renderer() { return enabler->renderer; } diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 082e6a04a..922a47477 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -27,6 +27,7 @@ command_result digcircle (color_ostream &out, vector & parameters); command_result digtype (color_ostream &out, vector & parameters); DFHACK_PLUGIN("dig"); +REQUIRE_GLOBAL(world); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { diff --git a/plugins/digFlood.cpp b/plugins/digFlood.cpp index 1646f4deb..170427bf6 100644 --- a/plugins/digFlood.cpp +++ b/plugins/digFlood.cpp @@ -21,14 +21,11 @@ using namespace DFHack; using namespace std; -using df::global::world; -// using df::global::process_jobs; -// using df::global::process_dig; +DFHACK_PLUGIN("digFlood"); +REQUIRE_GLOBAL(world); command_result digFlood (color_ostream &out, std::vector & parameters); -DFHACK_PLUGIN("digFlood"); - void onDig(color_ostream& out, void* ptr); void maybeExplore(color_ostream& out, MapExtras::MapCache& cache, df::coord pt, set& jobLocations); EventManager::EventHandler digHandler(onDig, 0); diff --git a/plugins/diggingInvaders/diggingInvaders.cpp b/plugins/diggingInvaders/diggingInvaders.cpp index 48c34d5d4..cd171f527 100644 --- a/plugins/diggingInvaders/diggingInvaders.cpp +++ b/plugins/diggingInvaders/diggingInvaders.cpp @@ -77,6 +77,7 @@ void findAndAssignInvasionJob(color_ostream& out, void*); DFHACK_PLUGIN_IS_ENABLED(enabled); DFHACK_PLUGIN("diggingInvaders"); +REQUIRE_GLOBAL(world); //TODO: when world unloads static int32_t lastInvasionJob=-1; @@ -386,8 +387,8 @@ void findAndAssignInvasionJob(color_ostream& out, void* tickTime) { unordered_set localConnectivity; //find all locals and invaders - for ( size_t a = 0; a < df::global::world->units.all.size(); a++ ) { - df::unit* unit = df::global::world->units.all[a]; + for ( size_t a = 0; a < world->units.all.size(); a++ ) { + df::unit* unit = world->units.all[a]; if ( unit->flags1.bits.dead ) continue; if ( Units::isCitizen(unit) ) { @@ -597,7 +598,7 @@ void findAndAssignInvasionJob(color_ostream& out, void* tickTime) { lastInvasionDigger = firstInvader->id; lastInvasionJob = firstInvader->job.current_job ? firstInvader->job.current_job->id : -1; invaderJobs.erase(lastInvasionJob); - for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + for ( df::job_list_link* link = &world->job_list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; df::job* job = link->item; diff --git a/plugins/drybuckets.cpp b/plugins/drybuckets.cpp index e54d2b90c..711a05928 100644 --- a/plugins/drybuckets.cpp +++ b/plugins/drybuckets.cpp @@ -15,9 +15,8 @@ using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::world; - DFHACK_PLUGIN("drybuckets"); +REQUIRE_GLOBAL(world); command_result df_drybuckets (color_ostream &out, vector & parameters) { diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index cda764f54..8a90b3378 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -43,9 +43,11 @@ using std::deque; -using df::global::current_weather; -using df::global::world; -using df::global::ui; +DFHACK_PLUGIN("dwarfmonitor"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(current_weather); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); typedef int16_t activity_type; @@ -1775,9 +1777,6 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, render); -DFHACK_PLUGIN("dwarfmonitor"); -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - static bool set_monitoring_mode(const string &mode, const bool &state) { bool mode_recognized = false; diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 6a264ab95..8cf1d7832 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -34,14 +34,13 @@ using std::stack; using namespace DFHack; using namespace df::enums; -using df::global::gps; -using df::global::world; -using df::global::ui; +DFHACK_PLUGIN("eventful"); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); typedef df::reaction_product_itemst item_product; -DFHACK_PLUGIN("eventful"); - struct ReagentSource { int idx; df::reaction_reagent *reagent; From f1a863eb795ad4c9aca602112c8da7d84298212b Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 3 Dec 2014 23:27:52 -0500 Subject: [PATCH 004/115] Use short plugin name --- library/PluginManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 713c442c3..f108b9047 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -257,7 +257,7 @@ bool Plugin::load(color_ostream &con) if (missing_globals.size()) { con.printerr("Plugin %s is missing required globals: %s\n", - filename.c_str(), join_strings(", ", missing_globals).c_str()); + *plug_name, join_strings(", ", missing_globals).c_str()); ClosePlugin(plug); state = PS_BROKEN; return false; From 2e43ea8b384cdf41030402c96710e4e9fbc8b56e Mon Sep 17 00:00:00 2001 From: Casey Link Date: Tue, 2 Dec 2014 12:06:01 +0100 Subject: [PATCH 005/115] stockpiles: refactor code into separate files The file was approaching 3000 lines and contained multiple classes, making it a real PITA to navigate through. upcoming features would only add more LOC, so splitting was necessary. --- plugins/CMakeLists.txt | 2 +- plugins/stockpiles.cpp | 2835 ----------------- plugins/stockpiles/CMakeLists.txt | 44 + plugins/stockpiles/OrganicMatLookup.cpp | 155 + plugins/stockpiles/OrganicMatLookup.h | 47 + plugins/stockpiles/StockpileSerializer.cpp | 2195 +++++++++++++ plugins/stockpiles/StockpileSerializer.h | 349 ++ plugins/stockpiles/StockpileUtils.h | 53 + plugins/stockpiles/proto/.gitignore | 3 + .../{ => stockpiles}/proto/stockpiles.proto | 0 plugins/stockpiles/stockpiles.cpp | 303 ++ 11 files changed, 3150 insertions(+), 2836 deletions(-) delete mode 100644 plugins/stockpiles.cpp create mode 100644 plugins/stockpiles/CMakeLists.txt create mode 100644 plugins/stockpiles/OrganicMatLookup.cpp create mode 100644 plugins/stockpiles/OrganicMatLookup.h create mode 100644 plugins/stockpiles/StockpileSerializer.cpp create mode 100644 plugins/stockpiles/StockpileSerializer.h create mode 100644 plugins/stockpiles/StockpileUtils.h create mode 100644 plugins/stockpiles/proto/.gitignore rename plugins/{ => stockpiles}/proto/stockpiles.proto (100%) create mode 100644 plugins/stockpiles/stockpiles.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 3cec373e7..412631a3d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -152,7 +152,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(steam-engine steam-engine.cpp) DFHACK_PLUGIN(stockflow stockflow.cpp LINK_LIBRARIES lua) - DFHACK_PLUGIN(stockpiles stockpiles.cpp PROTOBUFS stockpiles) + add_subdirectory(stockpiles) DFHACK_PLUGIN(stocks stocks.cpp) DFHACK_PLUGIN(strangemood strangemood.cpp) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) diff --git a/plugins/stockpiles.cpp b/plugins/stockpiles.cpp deleted file mode 100644 index ebf4b0d40..000000000 --- a/plugins/stockpiles.cpp +++ /dev/null @@ -1,2835 +0,0 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" - -#include "MiscUtils.h" - -#include "modules/Materials.h" -#include "modules/Items.h" - -#include "DataDefs.h" -#include "df/world.h" -#include "df/world_data.h" -#include "df/inorganic_raw.h" -#include "df/organic_mat_category.h" -#include "df/furniture_type.h" -#include "df/item_quality.h" -#include "df/item_type.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" -#include "df/material.h" -#include "df/inorganic_raw.h" -#include "df/plant_raw.h" -#include "df/stockpile_group_set.h" - -#include "df/ui.h" -#include "df/building_stockpilest.h" -#include "df/stockpile_settings.h" -#include "df/global_objects.h" -#include "df/viewscreen_dwarfmodest.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// protobuf -#include "proto/stockpiles.pb.h" -#include - -// os -#include - -// stl -#include -#include - -using std::vector; -using std::string; -using std::endl; -using namespace DFHack; -using namespace df::enums; -using namespace google::protobuf; -using namespace dfstockpiles; - -using df::global::world; -using df::global::ui; -using df::global::selection_rect; - -using df::building_stockpilest; -using std::placeholders::_1; - -static command_result copystock ( color_ostream &out, vector & parameters ); -static bool copystock_guard ( df::viewscreen *top ); - -static command_result savestock ( color_ostream &out, vector & parameters ); -static bool savestock_guard ( df::viewscreen *top ); - -static command_result loadstock ( color_ostream &out, vector & parameters ); -static bool loadstock_guard ( df::viewscreen *top ); - -DFHACK_PLUGIN ( "stockpiles" ); - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands ) -{ - if ( world && ui ) - { - commands.push_back ( - PluginCommand ( - "copystock", "Copy stockpile under cursor.", - copystock, copystock_guard, - " - In 'q' or 't' mode: select a stockpile and invoke in order\n" - " to switch to the 'p' stockpile creation mode, and initialize\n" - " the custom settings from the selected stockpile.\n" - " - In 'p': invoke in order to switch back to 'q'.\n" - ) - ); - commands.push_back ( - PluginCommand ( - "savestock", "Save the active stockpile's settings to a file.", - savestock, savestock_guard, - "Must be in 'q' mode and have a stockpile selected.\n" - "example: 'savestock food.dfstock' will save the settings to 'food.dfstock'\n" - "in your stockpile folder.\n" - "Omitting the filename will result in text output directly to the console\n\n" - " -d, --debug: enable debug output\n" - " : filename to save stockpile settings to (will be overwritten!)\n" - ) - ); - commands.push_back ( - PluginCommand ( - "loadstock", "Load settings from a file and apply them to the active stockpile.", - loadstock, loadstock_guard, - "Must be in 'q' mode and have a stockpile selected.\n" - "example: 'loadstock food.dfstock' will load the settings from 'food.dfstock'\n" - "in your stockpile folder and apply them to the selected stockpile.\n" - " -d, --debug: enable debug output\n" - " : filename to load stockpile settings from\n" - ) - ); - } - std::cerr << "world: " << sizeof ( df::world ) << " ui: " << sizeof ( df::ui ) - << " b_stock: " << sizeof ( building_stockpilest ) << endl; - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -static bool copystock_guard ( df::viewscreen *top ) -{ - using namespace ui_sidebar_mode; - - if ( !Gui::dwarfmode_hotkey ( top ) ) - return false; - - switch ( ui->main.mode ) - { - case Stockpiles: - return true; - case BuildingItems: - case QueryBuilding: - return !!virtual_cast ( world->selected_building ); - default: - return false; - } -} - -static command_result copystock ( color_ostream &out, vector & parameters ) -{ - // HOTKEY COMMAND: CORE ALREADY SUSPENDED - - // For convenience: when used in the stockpiles mode, switch to 'q' - if ( ui->main.mode == ui_sidebar_mode::Stockpiles ) - { - world->selected_building = NULL; // just in case it contains some kind of garbage - ui->main.mode = ui_sidebar_mode::QueryBuilding; - selection_rect->start_x = -30000; - - out << "Switched back to query building." << endl; - return CR_OK; - } - - building_stockpilest *sp = virtual_cast ( world->selected_building ); - if ( !sp ) - { - out.printerr ( "Selected building isn't a stockpile.\n" ); - return CR_WRONG_USAGE; - } - - ui->stockpile.custom_settings = sp->settings; - ui->main.mode = ui_sidebar_mode::Stockpiles; - world->selected_stockpile_type = stockpile_category::Custom; - - out << "Stockpile options copied." << endl; - return CR_OK; -} - - -static bool savestock_guard ( df::viewscreen *top ) -{ - using namespace ui_sidebar_mode; - - if ( !Gui::dwarfmode_hotkey ( top ) ) - return false; - - switch ( ui->main.mode ) - { - case Stockpiles: - return true; - case BuildingItems: - case QueryBuilding: - return !!virtual_cast ( world->selected_building ); - default: - return false; - } -} - -static bool loadstock_guard ( df::viewscreen *top ) -{ - using namespace ui_sidebar_mode; - - if ( !Gui::dwarfmode_hotkey ( top ) ) - return false; - - switch ( ui->main.mode ) - { - case Stockpiles: - return true; - case BuildingItems: - case QueryBuilding: - return !!virtual_cast ( world->selected_building ); - default: - return false; - } -} - -// Utility Functions {{{ -// A set of convenience functions for doing common lookups - - -/** - * Retrieve creature raw from index - */ -static df::creature_raw* find_creature ( int32_t idx ) -{ - return world->raws.creatures.all[idx]; -} - -/** - * Retrieve creature index from id string - * @return -1 if not found - */ -static int16_t find_creature ( const std::string &creature_id ) -{ - return linear_index ( world->raws.creatures.all, &df::creature_raw::creature_id, creature_id ); -} - -/** - * Retrieve plant raw from index -*/ -static df::plant_raw* find_plant ( size_t idx ) -{ - return world->raws.plants.all[idx]; -} - -/** - * Retrieve plant index from id string - * @return -1 if not found - */ -static size_t find_plant ( const std::string &plant_id ) -{ - return linear_index ( world->raws.plants.all, &df::plant_raw::id, plant_id ); -} - -// }}} utility Functions - - - - -/** - * Helper class for mapping the various organic mats between their material indices - * and their index in the stockpile_settings structures. - */ -class OrganicMatLookup -{ - -// pair of material type and index - typedef std::pair FoodMatPair; -// map for using type,index pairs to find the food index - typedef std::map FoodMatMap; - -public: - struct FoodMat - { - MaterialInfo material; - df::creature_raw *creature; - df::caste_raw * caste; - FoodMat() : material ( -1 ), creature ( 0 ), caste ( 0 ) {} - }; - static void food_mat_by_idx ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, std::vector::size_type food_idx, FoodMat & food_mat ) - { - out << "food_lookup: food_idx(" << food_idx << ") "; - df::world_raws &raws = world->raws; - df::special_mat_table table = raws.mat_table; - int32_t main_idx = table.organic_indexes[mat_category][food_idx]; - int16_t type = table.organic_types[mat_category][food_idx]; - if ( mat_category == organic_mat_category::Fish || - mat_category == organic_mat_category::UnpreparedFish || - mat_category == organic_mat_category::Eggs ) - { - food_mat.creature = raws.creatures.all[type]; - food_mat.caste = food_mat.creature->caste[main_idx]; - out << " special creature type(" << type << ") caste("<< main_idx <<")" <::size_type idx ) - { - FoodMat food_mat; - food_mat_by_idx ( out, mat_category, idx, food_mat ); - if ( food_mat.material.isValid() ) - { - return food_mat.material.getToken(); - } - else if ( food_mat.creature ) - { - return food_mat.creature->creature_id + ":" + food_mat.caste->caste_id; - } - return std::string(); - } - - static size_t food_max_size ( organic_mat_category::organic_mat_category mat_category ) - { - return world->raws.mat_table.organic_types[mat_category].size(); - } - - static void food_build_map ( std::ostream &out ) - { - if ( index_built ) - return; - df::world_raws &raws = world->raws; - df::special_mat_table table = raws.mat_table; - using df::enums::organic_mat_category::organic_mat_category; - df::enum_traits traits; - for ( int32_t mat_category = traits.first_item_value; mat_category <= traits.last_item_value; ++mat_category ) - { - for ( size_t i = 0; i < table.organic_indexes[mat_category].size(); ++i ) - { - int16_t type = table.organic_types[mat_category].at ( i ); - int32_t index = table.organic_indexes[mat_category].at ( i ); - food_index[mat_category].insert ( std::make_pair ( std::make_pair ( type,index ), i ) ); // wtf.. only in c++ - } - } - index_built = true; - } - - static int16_t food_idx_by_token ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, const std::string & token ) - { - int16_t food_idx = -1; - df::world_raws &raws = world->raws; - df::special_mat_table table = raws.mat_table; - out << "food_idx_by_token: "; - if ( mat_category == organic_mat_category::Fish || - mat_category == organic_mat_category::UnpreparedFish || - mat_category == organic_mat_category::Eggs ) - { - std::vector tokens; - split_string ( &tokens, token, ":" ); - if ( tokens.size() != 2 ) - { - out << "creature " << "invalid CREATURE:CASTE token: " << token << endl; - } - else - { - int16_t creature_idx = find_creature ( tokens[0] ); - if ( creature_idx < 0 ) - { - out << " creature invalid token " << tokens[0]; - } - else - { - food_idx = linear_index ( table.organic_types[mat_category], creature_idx ); - if ( tokens[1] == "MALE" ) - food_idx += 1; - if (table.organic_types[mat_category][food_idx] == creature_idx ) - out << "creature " << token << " caste " << tokens[1] << " creature_idx(" << creature_idx << ") food_idx("<< food_idx << ")" << endl; - else { - out << "ERROR creature caste not found: " << token << " caste " << tokens[1] << " creature_idx(" << creature_idx << ") food_idx("<< food_idx << ")" << endl; - food_idx = -1; - } - } - } - } - else - { - if ( !index_built ) - food_build_map ( out ); - MaterialInfo mat_info = food_mat_by_token ( out, token ); - int16_t type = mat_info.type; - int32_t index = mat_info.index; - int16_t food_idx2 = -1; - auto it = food_index[mat_category].find ( std::make_pair ( type, index ) ); - if ( it != food_index[mat_category].end() ) - { - out << "matinfo: " << token << " type(" << mat_info.type << ") idx(" << mat_info.index << ") food_idx(" << it->second << ")" << endl; - food_idx = it->second; - } - else - { - out << "matinfo: " << token << " type(" << mat_info.type << ") idx(" << mat_info.index << ") food_idx not found :(" << endl; - } - } - return food_idx; - } - - static MaterialInfo food_mat_by_token ( std::ostream &out, const std::string & token ) - { - MaterialInfo mat_info; - mat_info.find ( token ); - return mat_info; - } - - static bool index_built; - static std::vector food_index; -private: - OrganicMatLookup() {} -}; - -bool OrganicMatLookup::index_built = false; -std::vector OrganicMatLookup::food_index = std::vector ( 37 ); - - -/** - * Null buffer that acts like /dev/null for when debug is disabled - */ -class NullBuffer : public std::streambuf -{ -public: - int overflow ( int c ) - { - return c; - } -}; - -class NullStream : public std::ostream -{ -public: - NullStream() : std::ostream ( &m_sb ) {} -private: - NullBuffer m_sb; -}; - -/** - * Class for serializing the stockpile_settings structure into a Google protobuf - */ -class StockpileSerializer -{ -public: - /** - * @param out for debugging - * @param stockpile stockpile to read or write settings to - */ - StockpileSerializer ( building_stockpilest * stockpile ) - : mDebug ( false ) - , mOut ( 0 ) - , mNull() - , mPile ( stockpile ) - { - - // build other_mats indices - furniture_setup_other_mats(); - bars_blocks_setup_other_mats(); - finished_goods_setup_other_mats(); - weapons_armor_setup_other_mats(); - } - - ~StockpileSerializer() {} - - void enable_debug ( color_ostream&out ) - { - mDebug = true; - mOut = &out; - } - - /** - * Since we depend on protobuf-lite, not the full lib, we copy this function from - * protobuf message.cc - */ - bool serialize_to_ostream(ostream* output) - { - mBuffer.Clear(); - write(); - { - io::OstreamOutputStream zero_copy_output(output); - if (!mBuffer.SerializeToZeroCopyStream(&zero_copy_output)) return false; - } - return output->good(); - } - - /** - * Will serialize stockpile settings to a file (overwrites existing files) - * @return success/failure - */ - bool serialize_to_file ( const std::string & file ) - { - std::fstream output ( file, std::ios::out | std::ios::binary | std::ios::trunc ); - return serialize_to_ostream(&output); - } - - /** - * Again, copied from message.cc - */ - bool parse_from_istream(istream* input) - { - mBuffer.Clear(); - io::IstreamInputStream zero_copy_input(input); - const bool res = mBuffer.ParseFromZeroCopyStream(&zero_copy_input) && input->eof(); - if( res ) read(); - return res; - } - - - /** - * Read stockpile settings from file - */ - bool unserialize_from_file ( const std::string & file ) - { - std::fstream input ( file, std::ios::in | std::ios::binary ); - return parse_from_istream(&input); - } - -private: - - bool mDebug; - std::ostream * mOut; - NullStream mNull; - building_stockpilest * mPile; - StockpileSettings mBuffer; - std::map mOtherMatsFurniture; - std::map mOtherMatsFinishedGoods; - std::map mOtherMatsBars; - std::map mOtherMatsBlocks; - std::map mOtherMatsWeaponsArmor; - - - std::ostream & debug() - { - if ( mDebug ) - return *mOut; - return mNull; - } - - /** - read memory structures and serialize to protobuf - */ - void write() - { -// debug() << "GROUP SET " << bitfield_to_string(mPile->settings.flags) << endl; - write_general(); - if ( mPile->settings.flags.bits.animals ) - write_animals(); - if ( mPile->settings.flags.bits.food ) - write_food(); - if ( mPile->settings.flags.bits.furniture ) - write_furniture(); - if ( mPile->settings.flags.bits.refuse ) - write_refuse(); - if ( mPile->settings.flags.bits.stone ) - write_stone(); - if ( mPile->settings.flags.bits.ammo ) - write_ammo(); - if ( mPile->settings.flags.bits.coins ) - write_coins(); - if ( mPile->settings.flags.bits.bars_blocks ) - write_bars_blocks(); - if ( mPile->settings.flags.bits.gems ) - write_gems(); - if ( mPile->settings.flags.bits.finished_goods ) - write_finished_goods(); - if ( mPile->settings.flags.bits.leather ) - write_leather(); - if ( mPile->settings.flags.bits.cloth ) - write_cloth(); - if ( mPile->settings.flags.bits.wood ) - write_wood(); - if ( mPile->settings.flags.bits.weapons ) - write_weapons(); - if ( mPile->settings.flags.bits.armor ) - write_armor(); - } - - // parse serialized data into ui indices - void read () - { - debug() << endl << "==READ==" << endl; - read_general(); - read_animals(); - read_food(); - read_furniture(); - read_refuse(); - read_stone(); - read_ammo(); - read_coins(); - read_bars_blocks(); - read_gems(); - read_finished_goods(); - read_leather(); - read_cloth(); - read_wood(); - read_weapons(); - read_armor(); - } - - /** - * Find an enum's value based off the string label. - * @param traits the enum's trait struct - * @param token the string value in key_table - * @return the enum's value, -1 if not found - */ - template - static typename df::enum_traits::base_type linear_index ( std::ostream & out, df::enum_traits traits, const std::string &token ) - { - auto j = traits.first_item_value; - auto limit = traits.last_item_value; - // sometimes enums start at -1, which is bad news for array indexing - if ( j < 0 ) - { - j += abs ( traits.first_item_value ); - limit += abs ( traits.first_item_value ); - } - for ( ; j <= limit; ++j ) - { -// out << " linear_index("<< token <<") = table["< FuncReadImport; - // add the token to the serialized list during export - typedef std::function FuncWriteExport; - // are item's of item_type allowed? - typedef std::function FuncItemAllowed; - // is this material allowed? - typedef std::function FuncMaterialAllowed; - - // convenient struct for parsing food stockpile items - struct food_pair - { - // exporting - FuncWriteExport set_value; - std::vector * stockpile_values; - // importing - FuncReadImport get_value; - size_t serialized_count; - bool valid; - - food_pair ( FuncWriteExport s, std::vector* sp_v, FuncReadImport g, size_t count ) - : set_value ( s ) - , stockpile_values ( sp_v ) - , get_value ( g ) - , serialized_count ( count ) - , valid ( true ) - {} - food_pair(): valid( false ) {} - }; - - /** - * There are many repeated (un)serialization cases throughout the stockpile_settings structure, - * so the most common cases have been generalized into generic functions using lambdas. - * - * The basic process to serialize a stockpile_settings structure is: - * 1. loop through the list - * 2. for every element that is TRUE: - * 3. map the specific stockpile_settings index into a general material, creature, etc index - * 4. verify that type is allowed in the list (e.g., no stone in gems stockpiles) - * 5. add it to the protobuf using FuncWriteExport - * - * The unserialization process is the same in reverse. - */ - void serialize_list_organic_mat ( FuncWriteExport add_value, const std::vector * list, organic_mat_category::organic_mat_category cat ) - { - if ( !list ) - { - debug() << "serialize_list_organic_mat: list null" << endl; - } - for ( size_t i = 0; i < list->size(); ++i ) - { - if ( list->at ( i ) ) - { - std::string token = OrganicMatLookup::food_token_by_idx ( debug(), cat, i ); - if ( !token.empty() ) - { - add_value ( token ); - debug() << " organic_material " << i << " is " << token << endl; - } - else - { - debug() << "food mat invalid :(" << endl; - } - } - } - } - - /** - * @see serialize_list_organic_mat - */ - void unserialize_list_organic_mat ( FuncReadImport get_value, size_t list_size, std::vector *pile_list, organic_mat_category::organic_mat_category cat ) - { - pile_list->clear(); - pile_list->resize ( OrganicMatLookup::food_max_size ( cat ), '\0' ); - for ( size_t i = 0; i < list_size; ++i ) - { - std::string token = get_value ( i ); - int16_t idx = OrganicMatLookup::food_idx_by_token ( debug(), cat, token ); - debug() << " organic_material " << idx << " is " << token << endl; - if ( idx >= pile_list->size() ) - { - debug() << "error organic mat index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; - continue; - } - pile_list->at ( idx ) = 1; - } - } - - /** - * @see serialize_list_organic_mat - */ - void serialize_list_item_type ( FuncItemAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ) - { - using df::enums::item_type::item_type; - df::enum_traits type_traits; - debug() << "item_type size = " << list.size() << " size limit = " << type_traits.last_item_value << " typecasted: " << ( size_t ) type_traits.last_item_value << endl; - for ( size_t i = 0; i <= ( size_t ) type_traits.last_item_value; ++i ) - { - if ( list.at ( i ) ) - { - const item_type type = ( item_type ) ( ( df::enum_traits::base_type ) i ); - std::string r_type ( type_traits.key_table[i+1] ); - if ( !is_allowed ( type ) ) continue; - add_value ( r_type ); - debug() << "item_type key_table[" << i+1 << "] type[" << ( int16_t ) type << "] is " << r_type < *pile_list ) - { - pile_list->clear(); - pile_list->resize ( 112, '\0' ); // TODO remove hardcoded list size value - for ( int i = 0; i < pile_list->size(); ++i ) - { - pile_list->at ( i ) = is_allowed ( ( item_type::item_type ) i ) ? 0 : 1; - } - using df::enums::item_type::item_type; - df::enum_traits type_traits; - for ( int32_t i = 0; i < list_size; ++i ) - { - const std::string token = read_value ( i ); - // subtract one because item_type starts at -1 - const df::enum_traits::base_type idx = linear_index ( debug(), type_traits, token ) - 1; - const item_type type = ( item_type ) idx; - if ( !is_allowed ( type ) ) continue; - debug() << " item_type " << idx << " is " << token << endl; - if ( idx >= pile_list->size() ) - { - debug() << "error item_type index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; - continue; - } - pile_list->at ( idx ) = 1; - } - } - - /** - * @see serialize_list_organic_mat - */ - void serialize_list_material ( FuncMaterialAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ) - { - MaterialInfo mi; - for ( size_t i = 0; i < list.size(); ++i ) - { - if ( list.at ( i ) ) - { - mi.decode ( 0, i ); - if ( !is_allowed ( mi ) ) continue; - debug() << " material " << i << " is " << mi.getToken() << endl; - add_value ( mi.getToken() ); - } - } - } - - /** - * @see serialize_list_organic_mat - */ - void unserialize_list_material ( FuncMaterialAllowed is_allowed, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ) - { - // we initialize all possible (allowed) values to 0, - // then all other not-allowed values to 1 - // why? because that's how the memory is in DF before - // we muck with it. - std::set idx_set; - pile_list->clear(); - pile_list->resize ( world->raws.inorganics.size(), 0 ); - for ( int i = 0; i < pile_list->size(); ++i ) - { - MaterialInfo mi ( 0, i ); - pile_list->at ( i ) = is_allowed ( mi ) ? 0 : 1; - } - for ( int i = 0; i < list_size; ++i ) - { - const std::string token = read_value ( i ); - MaterialInfo mi; - mi.find ( token ); - if ( !is_allowed ( mi ) ) continue; - debug() << " material " << mi.index << " is " << token << endl; - if ( mi.index >= pile_list->size() ) - { - debug() << "error material index too large! idx[" << mi.index << "] max_size[" << pile_list->size() << "]" << endl; - continue; - } - pile_list->at ( mi.index ) = 1; - } - } - - /** - * @see serialize_list_organic_mat - */ - void serialize_list_quality ( FuncWriteExport add_value, const bool ( &quality_list ) [7] ) - { - using df::enums::item_quality::item_quality; - df::enum_traits quality_traits; - for ( size_t i = 0; i < 7; ++i ) - { - if ( quality_list[i] ) - { - const std::string f_type ( quality_traits.key_table[i] ); - add_value ( f_type ); - debug() << " quality: " << i << " is " << f_type < 0 && list_size <= 7 ) - { - using df::enums::item_quality::item_quality; - df::enum_traits quality_traits; - for ( int i = 0; i < list_size; ++i ) - { - const std::string quality = read_value ( i ); - df::enum_traits::base_type idx = linear_index ( debug(), quality_traits, quality ); - if ( idx < 0 ) - { - debug() << " invalid quality token " << quality << endl; - continue; - } - debug() << " quality: " << idx << " is " << quality << endl; - pile_list[idx] = true; - } - } - else - { - quality_clear ( pile_list ); - } - } - - /** - * @see serialize_list_organic_mat - */ - void serialize_list_other_mats ( const std::map other_mats, FuncWriteExport add_value, std::vector list ) - { - for ( size_t i = 0; i < list.size(); ++i ) - { - if ( list.at ( i ) ) - { - const std::string token = other_mats_index ( other_mats, i ); - if ( token.empty() ) - { - debug() << " invalid other material with index " << i << endl; - continue; - } - add_value ( token ); - debug() << " other mats " << i << " is " << token << endl; - } - } - } - - /** - * @see serialize_list_organic_mat - */ - void unserialize_list_other_mats ( const std::map other_mats, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ) - { - pile_list->clear(); - pile_list->resize ( other_mats.size(), '\0' ); - for ( int i = 0; i < list_size; ++i ) - { - const std::string token = read_value ( i ); - size_t idx = other_mats_token ( other_mats, token ); - if ( idx < 0 ) - { - debug() << "invalid other mat with token " << token; - continue; - } - debug() << " other_mats " << idx << " is " << token << endl; - if ( idx >= pile_list->size() ) - { - debug() << "error other_mats index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; - continue; - } - pile_list->at ( idx ) = 1; - } - } - - - /** - * @see serialize_list_organic_mat - */ - void serialize_list_itemdef ( FuncWriteExport add_value, std::vector list, std::vector items, item_type::item_type type ) - { - for ( size_t i = 0; i < list.size(); ++i ) - { - if ( list.at ( i ) ) - { - const df::itemdef *a = items.at ( i ); - // skip procedurally generated items - if ( a->base_flags.is_set ( 0 ) ) continue; - ItemTypeInfo ii; - if ( !ii.decode ( type, i ) ) continue; - add_value ( ii.getToken() ); - debug() << " itemdef type" << i << " is " << ii.getToken() << endl; - } - } - } - - /** - * @see serialize_list_organic_mat - */ - void unserialize_list_itemdef ( FuncReadImport read_value, int32_t list_size, std::vector *pile_list, item_type::item_type type ) - { - pile_list->clear(); - pile_list->resize ( Items::getSubtypeCount ( type ), '\0' ); - for ( int i = 0; i < list_size; ++i ) - { - std::string token = read_value ( i ); - ItemTypeInfo ii; - if ( !ii.find ( token ) ) continue; - debug() << " itemdef " << ii.subtype << " is " << token << endl; - if ( ii.subtype >= pile_list->size() ) - { - debug() << "error itemdef index too large! idx[" << ii.subtype << "] max_size[" << pile_list->size() << "]" << endl; - continue; - } - pile_list->at ( ii.subtype ) = 1; - } - } - - /** - * Given a list of other_materials and an index, return its corresponding token - * @return empty string if not found - * @see other_mats_token - */ - std::string other_mats_index ( const std::map other_mats, int idx ) - { - auto it = other_mats.find ( idx ); - if ( it == other_mats.end() ) - return std::string(); - return it->second; - } - /** - * Given a list of other_materials and a token, return its corresponding index - * @return -1 if not found - * @see other_mats_index - */ - int other_mats_token ( const std::map other_mats, const std::string & token ) - { - for ( auto it = other_mats.begin(); it != other_mats.end(); ++it ) - { - if ( it->second == token ) - return it->first; - } - return -1; - } - - void write_general() - { - mBuffer.set_max_bins ( mPile->max_barrels ); - mBuffer.set_max_wheelbarrows ( mPile->max_wheelbarrows ); - mBuffer.set_use_links_only ( mPile->use_links_only ); - mBuffer.set_unknown1 ( mPile->settings.unk1 ); - mBuffer.set_allow_inorganic ( mPile->settings.allow_inorganic ); - mBuffer.set_allow_organic ( mPile->settings.allow_organic ); - mBuffer.set_corpses ( mPile->settings.flags.bits.corpses ); - } - - void read_general() - { - if ( mBuffer.has_max_bins() ) - mPile->max_bins = mBuffer.max_bins(); - if ( mBuffer.has_max_wheelbarrows() ) - mPile->max_wheelbarrows = mBuffer.max_wheelbarrows(); - if ( mBuffer.has_max_barrels() ) - mPile->max_barrels = mBuffer.max_barrels(); - if ( mBuffer.has_use_links_only() ) - mPile->use_links_only = mBuffer.use_links_only(); - if ( mBuffer.has_unknown1() ) - mPile->settings.unk1 = mBuffer.unknown1(); - if ( mBuffer.has_allow_inorganic() ) - mPile->settings.allow_inorganic = mBuffer.allow_inorganic(); - if ( mBuffer.has_allow_organic() ) - mPile->settings.allow_organic = mBuffer.allow_organic(); - if ( mBuffer.has_corpses() ) - mPile->settings.flags.bits.corpses = mBuffer.corpses(); - } - - void write_animals() - { - StockpileSettings::AnimalsSet *animals= mBuffer.mutable_animals(); - animals->set_empty_cages ( mPile->settings.animals.empty_cages ); - animals->set_empty_traps ( mPile->settings.animals.empty_traps ); - for ( size_t i = 0; i < mPile->settings.animals.enabled.size(); ++i ) - { - if ( mPile->settings.animals.enabled.at ( i ) == 1 ) - { - df::creature_raw* r = find_creature ( i ); - debug() << "creature "<< r->creature_id << " " << i << endl; - animals->add_enabled ( r->creature_id ); - } - } - } - - void read_animals() - { - if ( mBuffer.has_animals() ) - { - mPile->settings.flags.bits.animals = 1; - debug() << "animals:" << endl; - mPile->settings.animals.empty_cages = mBuffer.animals().empty_cages(); - mPile->settings.animals.empty_traps = mBuffer.animals().empty_traps(); - - mPile->settings.animals.enabled.clear(); - mPile->settings.animals.enabled.resize ( world->raws.creatures.all.size(), '\0' ); - debug() << " pile has " << mPile->settings.animals.enabled.size() << endl; - for ( auto i = 0; i < mBuffer.animals().enabled_size(); ++i ) - { - std::string id = mBuffer.animals().enabled ( i ); - int idx = find_creature ( id ); - debug() << id << " " << idx << endl; - if ( idx < 0 || idx >= mPile->settings.animals.enabled.size() ) - { - debug() << "WARNING: animal index invalid: " << idx << endl; - continue; - } - mPile->settings.animals.enabled.at ( idx ) = ( char ) 1; - } - } - else - { - mPile->settings.animals.enabled.clear(); - mPile->settings.flags.bits.animals = 0; - mPile->settings.animals.empty_cages = false; - mPile->settings.animals.empty_traps = false; - } - } - - food_pair food_map ( organic_mat_category::organic_mat_category cat ) - { - using df::enums::organic_mat_category::organic_mat_category; - using namespace std::placeholders; - switch ( cat ) - { - case organic_mat_category::Meat: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_meat ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().meat ( idx ); }; - return food_pair ( setter, &mPile->settings.food.meat, getter, mBuffer.food().meat_size() ); - } - case organic_mat_category::Fish: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_fish ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().fish ( idx ); }; - return food_pair ( setter, &mPile->settings.food.fish, getter, mBuffer.food().fish_size() ); - } - case organic_mat_category::UnpreparedFish: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_unprepared_fish ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().unprepared_fish ( idx ); }; - return food_pair ( setter, &mPile->settings.food.unprepared_fish, getter, mBuffer.food().unprepared_fish_size() ); - } - case organic_mat_category::Eggs: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_egg ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().egg ( idx ); }; - return food_pair ( setter, &mPile->settings.food.egg, getter, mBuffer.food().egg_size() ); - } - case organic_mat_category::Plants: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_plants ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().plants ( idx ); }; - return food_pair ( setter, &mPile->settings.food.plants, getter, mBuffer.food().plants_size() ); - } - case organic_mat_category::PlantDrink: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_drink_plant ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().drink_plant ( idx ); }; - return food_pair ( setter, &mPile->settings.food.drink_plant, getter, mBuffer.food().drink_plant_size() ); - } - case organic_mat_category::CreatureDrink: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_drink_animal ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().drink_animal ( idx ); }; - return food_pair ( setter, &mPile->settings.food.drink_animal, getter, mBuffer.food().drink_animal_size() ); - } - case organic_mat_category::PlantCheese: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_cheese_plant ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().cheese_plant ( idx ); }; - return food_pair ( setter, &mPile->settings.food.cheese_plant, getter, mBuffer.food().cheese_plant_size() ); - } - case organic_mat_category::CreatureCheese: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_cheese_animal ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().cheese_animal ( idx ); }; - return food_pair ( setter, &mPile->settings.food.cheese_animal, getter, mBuffer.food().cheese_animal_size() ); - } - case organic_mat_category::Seed: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_seeds ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().seeds ( idx ); }; - return food_pair ( setter, &mPile->settings.food.seeds, getter, mBuffer.food().seeds_size() ); - } - case organic_mat_category::Leaf: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_leaves ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().leaves ( idx ); }; - return food_pair ( setter, &mPile->settings.food.leaves, getter, mBuffer.food().leaves_size() ); - } - case organic_mat_category::PlantPowder: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_powder_plant ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().powder_plant ( idx ); }; - return food_pair ( setter, &mPile->settings.food.powder_plant, getter, mBuffer.food().powder_plant_size() ); - } - case organic_mat_category::CreaturePowder: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_powder_creature ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().powder_creature ( idx ); }; - return food_pair ( setter, &mPile->settings.food.powder_creature, getter, mBuffer.food().powder_creature_size() ); - } - case organic_mat_category::Glob: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_glob ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().glob ( idx ); }; - return food_pair ( setter, &mPile->settings.food.glob, getter, mBuffer.food().glob_size() ); - } - case organic_mat_category::PlantLiquid: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_liquid_plant ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().liquid_plant ( idx ); }; - return food_pair ( setter, &mPile->settings.food.liquid_plant, getter, mBuffer.food().liquid_plant_size() ); - } - case organic_mat_category::CreatureLiquid: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_liquid_animal ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().liquid_animal ( idx ); }; - return food_pair ( setter, &mPile->settings.food.liquid_animal, getter, mBuffer.food().liquid_animal_size() ); - } - case organic_mat_category::MiscLiquid: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_liquid_misc ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().liquid_misc ( idx ); }; - return food_pair ( setter, &mPile->settings.food.liquid_misc, getter, mBuffer.food().liquid_misc_size() ); - } - - case organic_mat_category::Paste: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_glob_paste ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().glob_paste ( idx ); }; - return food_pair ( setter, &mPile->settings.food.glob_paste, getter, mBuffer.food().glob_paste_size() ); - } - case organic_mat_category::Pressed: - { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_glob_pressed ( id ); - }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().glob_pressed ( idx ); }; - return food_pair ( setter, &mPile->settings.food.glob_pressed, getter, mBuffer.food().glob_pressed_size() ); - } - case organic_mat_category::Leather: - case organic_mat_category::Silk: - case organic_mat_category::PlantFiber: - case organic_mat_category::Bone: - case organic_mat_category::Shell: - case organic_mat_category::Wood: - case organic_mat_category::Horn: - case organic_mat_category::Pearl: - case organic_mat_category::Tooth: - case organic_mat_category::EdibleCheese: - case organic_mat_category::AnyDrink: - case organic_mat_category::EdiblePlant: - case organic_mat_category::CookableLiquid: - case organic_mat_category::CookablePowder: - case organic_mat_category::CookableSeed: - case organic_mat_category::CookableLeaf: - case organic_mat_category::Yarn: - case organic_mat_category::MetalThread: - default: - // not used in stockpile food menu - break; - } - return food_pair(); - } - - - void write_food() - { - StockpileSettings::FoodSet *food = mBuffer.mutable_food(); - debug() << " food: " << endl; - food->set_prepared_meals ( mPile->settings.food.prepared_meals ); - - using df::enums::organic_mat_category::organic_mat_category; - df::enum_traits traits; - for ( int32_t mat_category = traits.first_item_value; mat_category traits; - if ( mBuffer.has_food() ) - { - mPile->settings.flags.bits.food = 1; - const StockpileSettings::FoodSet food = mBuffer.food(); - debug() << "food:" <settings.food.prepared_meals = food.prepared_meals(); - else - mPile->settings.food.prepared_meals = true; - - debug() << " prepared_meals: " << mPile->settings.food.prepared_meals << endl; - - for ( int32_t mat_category = traits.first_item_value; mat_category clear(); - } - mPile->settings.flags.bits.food = 0; - mPile->settings.food.prepared_meals = false; - } - } - - void furniture_setup_other_mats() - { - mOtherMatsFurniture.insert ( std::make_pair ( 0,"WOOD" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 1,"PLANT_CLOTH" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 2,"BONE" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 3,"TOOTH" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 4,"HORN" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 5,"PEARL" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 6,"SHELL" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 7,"LEATHER" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 8,"SILK" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 9,"AMBER" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 10,"CORAL" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 11,"GREEN_GLASS" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 12,"CLEAR_GLASS" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 13,"CRYSTAL_GLASS" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 14,"YARN" ) ); - } - - void write_furniture() - { - StockpileSettings::FurnitureSet *furniture= mBuffer.mutable_furniture(); - furniture->set_sand_bags ( mPile->settings.furniture.sand_bags ); - - // FURNITURE type - using df::enums::furniture_type::furniture_type; - df::enum_traits type_traits; - for ( size_t i = 0; i < mPile->settings.furniture.type.size(); ++i ) - { - if ( mPile->settings.furniture.type.at ( i ) ) - { - std::string f_type ( type_traits.key_table[i] ); - furniture->add_type ( f_type ); - debug() << "furniture_type " << i << " is " << f_type <add_mats ( token ); - }, mPile->settings.furniture.mats ); - - // other mats - serialize_list_other_mats ( mOtherMatsFurniture, [=] ( const std::string &token ) - { - furniture->add_other_mats ( token ); - }, mPile->settings.furniture.other_mats ); - - serialize_list_quality ( [=] ( const std::string &token ) - { - furniture->add_quality_core ( token ); - }, mPile->settings.furniture.quality_core ); - serialize_list_quality ( [=] ( const std::string &token ) - { - furniture->add_quality_total ( token ); - }, mPile->settings.furniture.quality_total ); - } - - bool furniture_mat_is_allowed ( const MaterialInfo &mi ) - { - return mi.isValid() && mi.material - && ( mi.material->flags.is_set ( material_flags::IS_METAL) - || mi.material->flags.is_set ( material_flags::IS_STONE )); - } - - void read_furniture() - { - if ( mBuffer.has_furniture() ) - { - mPile->settings.flags.bits.furniture = 1; - const StockpileSettings::FurnitureSet furniture = mBuffer.furniture(); - debug() << "furniture:" <settings.furniture.sand_bags = mBuffer.furniture().sand_bags(); - else - mPile->settings.furniture.sand_bags = false; - - // type - using df::enums::furniture_type::furniture_type; - df::enum_traits type_traits; - mPile->settings.furniture.type.clear(); - mPile->settings.furniture.type.resize ( type_traits.last_item_value+1, '\0' ); - if ( furniture.type_size() > 0 ) - { - for ( int i = 0; i < furniture.type_size(); ++i ) - { - const std::string type = furniture.type ( i ); - df::enum_traits::base_type idx = linear_index ( debug(), type_traits, type ); - debug() << " type " << idx << " is " << type << endl; - if ( idx < 0 || idx >= mPile->settings.furniture.type.size() ) - { - debug() << "WARNING: furniture type index invalid " << type << ", idx=" << idx << endl; - continue; - } - mPile->settings.furniture.type.at ( idx ) = 1; - } - } - - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::furniture_mat_is_allowed, this, _1 ); - unserialize_list_material ( filter, [=] ( const size_t & idx ) -> const std::string& - { - return furniture.mats ( idx ); - }, furniture.mats_size(), &mPile->settings.furniture.mats ); - - // other materials - unserialize_list_other_mats ( mOtherMatsFurniture, [=] ( const size_t & idx ) -> const std::string& - { - return furniture.other_mats ( idx ); - }, furniture.other_mats_size(), &mPile->settings.furniture.other_mats ); - - // core quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return furniture.quality_core ( idx ); - }, furniture.quality_core_size(), mPile->settings.furniture.quality_core ); - - // total quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return furniture.quality_total ( idx ); - }, furniture.quality_total_size(), mPile->settings.furniture.quality_total ); - - } - else - { - mPile->settings.flags.bits.furniture = 0; - mPile->settings.furniture.type.clear(); - mPile->settings.furniture.other_mats.clear(); - mPile->settings.furniture.mats.clear(); - quality_clear ( mPile->settings.furniture.quality_core ); - quality_clear ( mPile->settings.furniture.quality_total ); - } - } - - bool refuse_creature_is_allowed ( const df::creature_raw *raw ) - { - if ( !raw ) return false; - // wagon and generated creatures not allowed, except angels - const bool is_wagon = raw->creature_id == "EQUIPMENT_WAGON"; - const bool is_generated = raw->flags.is_set ( creature_raw_flags::GENERATED ); - const bool is_angel = is_generated && raw->creature_id.find ( "DIVINE_" ) != std::string::npos; - return !is_wagon && ! ( is_generated && !is_angel ); - } - - void refuse_write_helper ( std::function add_value, const vector & list ) - { - for ( size_t i = 0; i < list.size(); ++i ) - { - if ( list.at ( i ) == 1 ) - { - df::creature_raw* r = find_creature ( i ); - // skip forgotten beasts, titans, demons, and night creatures - if ( !refuse_creature_is_allowed ( r ) ) continue; - debug() << "creature "<< r->creature_id << " " << i << endl; - add_value ( r->creature_id ); - } - } - } - - bool refuse_type_is_allowed ( item_type::item_type type ) - { - if ( type == item_type::NONE - || type == item_type::BAR - || type == item_type::SMALLGEM - || type == item_type::BLOCKS - || type == item_type::ROUGH - || type == item_type::BOULDER - || type == item_type::CORPSE - || type == item_type::CORPSEPIECE - || type == item_type::ROCK - || type == item_type::ORTHOPEDIC_CAST - ) return false; - return true; - } - - - void write_refuse() - { - StockpileSettings::RefuseSet *refuse = mBuffer.mutable_refuse(); - refuse->set_fresh_raw_hide ( mPile->settings.refuse.fresh_raw_hide ); - refuse->set_rotten_raw_hide ( mPile->settings.refuse.rotten_raw_hide ); - - // type - FuncItemAllowed filter = std::bind ( &StockpileSerializer::refuse_type_is_allowed, this, _1 ); - serialize_list_item_type ( filter, [=] ( const std::string &token ) - { - refuse->add_type ( token ); - }, mPile->settings.refuse.type ); - - // corpses - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_corpses ( id ); - }, mPile->settings.refuse.corpses ); - // body_parts - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_body_parts ( id ); - }, mPile->settings.refuse.body_parts ); - // skulls - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_skulls ( id ); - }, mPile->settings.refuse.skulls ); - // bones - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_bones ( id ); - }, mPile->settings.refuse.bones ); - // hair - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_hair ( id ); - }, mPile->settings.refuse.hair ); - // shells - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_shells ( id ); - }, mPile->settings.refuse.shells ); - // teeth - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_teeth ( id ); - }, mPile->settings.refuse.teeth ); - // horns - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_horns ( id ); - }, mPile->settings.refuse.horns ); - } - - void refuse_read_helper ( std::function get_value, size_t list_size, std::vector* pile_list ) - { - pile_list->clear(); - pile_list->resize ( world->raws.creatures.all.size(), '\0' ); - if ( list_size > 0 ) - { - for ( size_t i = 0; i < list_size; ++i ) - { - const std::string creature_id = get_value ( i ); - const int idx = find_creature ( creature_id ); - const df::creature_raw* creature = find_creature ( idx ); - if ( idx < 0 || !refuse_creature_is_allowed ( creature ) || idx >= pile_list->size() ) - { - debug() << "WARNING invalid refuse creature " << creature_id << ", idx=" << idx << endl; - continue; - } - debug() << " creature " << idx << " is " << creature_id << endl; - pile_list->at ( idx ) = 1; - } - } - } - - - - void read_refuse() - { - if ( mBuffer.has_refuse() ) - { - mPile->settings.flags.bits.refuse = 1; - const StockpileSettings::RefuseSet refuse = mBuffer.refuse(); - debug() << "refuse: " <settings.refuse.fresh_raw_hide = refuse.fresh_raw_hide(); - mPile->settings.refuse.rotten_raw_hide = refuse.rotten_raw_hide(); - - // type - FuncItemAllowed filter = std::bind ( &StockpileSerializer::refuse_type_is_allowed, this, _1 ); - unserialize_list_item_type ( filter, [=] ( const size_t & idx ) -> const std::string& - { - return refuse.type ( idx ); - }, refuse.type_size(), &mPile->settings.refuse.type ); - - // corpses - debug() << " corpses" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.corpses ( idx ); - }, refuse.corpses_size(), &mPile->settings.refuse.corpses ); - // body_parts - debug() << " body_parts" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.body_parts ( idx ); - }, refuse.body_parts_size(), &mPile->settings.refuse.body_parts ); - // skulls - debug() << " skulls" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.skulls ( idx ); - }, refuse.skulls_size(), &mPile->settings.refuse.skulls ); - // bones - debug() << " bones" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.bones ( idx ); - }, refuse.bones_size(), &mPile->settings.refuse.bones ); - // hair - debug() << " hair" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.hair ( idx ); - }, refuse.hair_size(), &mPile->settings.refuse.hair ); - // shells - debug() << " shells" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.shells ( idx ); - }, refuse.shells_size(), &mPile->settings.refuse.shells ); - // teeth - debug() << " teeth" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.teeth ( idx ); - }, refuse.teeth_size(), &mPile->settings.refuse.teeth ); - // horns - debug() << " horns" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.horns ( idx ); - }, refuse.horns_size(), &mPile->settings.refuse.horns ); - } - else - { - mPile->settings.flags.bits.refuse = 0; - mPile->settings.refuse.type.clear(); - mPile->settings.refuse.corpses.clear(); - mPile->settings.refuse.body_parts.clear(); - mPile->settings.refuse.skulls.clear(); - mPile->settings.refuse.bones.clear(); - mPile->settings.refuse.hair.clear(); - mPile->settings.refuse.shells.clear(); - mPile->settings.refuse.teeth.clear(); - mPile->settings.refuse.horns.clear(); - mPile->settings.refuse.fresh_raw_hide = false; - mPile->settings.refuse.rotten_raw_hide = false; - } - } - - bool stone_is_allowed ( const MaterialInfo &mi ) - { - if ( !mi.isValid() ) return false; - const bool is_allowed_soil = mi.inorganic->flags.is_set ( inorganic_flags::SOIL ) && !mi.inorganic->flags.is_set ( inorganic_flags::AQUIFER ); - const bool is_allowed_stone = mi.material->flags.is_set ( material_flags::IS_STONE ) && !mi.material->flags.is_set ( material_flags::NO_STONE_STOCKPILE ); - return is_allowed_soil || is_allowed_stone; - } - - void write_stone() - { - StockpileSettings::StoneSet *stone= mBuffer.mutable_stone(); - - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::stone_is_allowed, this, _1 ); - serialize_list_material ( filter, [=] ( const std::string &token ) - { - stone->add_mats ( token ); - }, mPile->settings.stone.mats ); - } - - void read_stone() - { - if ( mBuffer.has_stone() ) - { - mPile->settings.flags.bits.stone = 1; - const StockpileSettings::StoneSet stone = mBuffer.stone(); - debug() << "stone: " < const std::string& - { - return stone.mats ( idx ); - }, stone.mats_size(), &mPile->settings.stone.mats ); - } - else - { - mPile->settings.flags.bits.stone = 0; - mPile->settings.stone.mats.clear(); - } - } - - bool ammo_mat_is_allowed ( const MaterialInfo &mi ) - { - return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_METAL ); - } - - void write_ammo() - { - StockpileSettings::AmmoSet *ammo = mBuffer.mutable_ammo(); - - // ammo type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - ammo->add_type ( token ); - }, mPile->settings.ammo.type, - std::vector ( world->raws.itemdefs.ammo.begin(),world->raws.itemdefs.ammo.end() ), - item_type::AMMO ); - - // metal - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::ammo_mat_is_allowed, this, _1 ); - serialize_list_material ( filter, [=] ( const std::string &token ) - { - ammo->add_mats ( token ); - }, mPile->settings.ammo.mats ); - - // other mats - only wood and bone - if ( mPile->settings.ammo.other_mats.size() > 2 ) - { - debug() << "WARNING: ammo other materials > 2! " << mPile->settings.ammo.other_mats.size() << endl; - } - - for ( size_t i = 0; i < std::min ( size_t ( 2 ), mPile->settings.ammo.other_mats.size() ); ++i ) - { - if ( !mPile->settings.ammo.other_mats.at ( i ) ) - continue; - const std::string token = i == 0 ? "WOOD" : "BONE"; - ammo->add_other_mats ( token ); - debug() << " other mats " << i << " is " << token << endl; - } - - // quality core - serialize_list_quality ( [=] ( const std::string &token ) - { - ammo->add_quality_core ( token ); - }, mPile->settings.ammo.quality_core ); - - // quality total - serialize_list_quality ( [=] ( const std::string &token ) - { - ammo->add_quality_total ( token ); - }, mPile->settings.ammo.quality_total ); - } - - void read_ammo() - { - if ( mBuffer.has_ammo() ) - { - mPile->settings.flags.bits.ammo = 1; - const StockpileSettings::AmmoSet ammo = mBuffer.ammo(); - debug() << "ammo: " < const std::string& - { - return ammo.type ( idx ); - }, ammo.type_size(), &mPile->settings.ammo.type, item_type::AMMO ); - - // materials metals - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::ammo_mat_is_allowed, this, _1 ); - unserialize_list_material ( filter, [=] ( const size_t & idx ) -> const std::string& - { - return ammo.mats ( idx ); - }, ammo.mats_size(), &mPile->settings.ammo.mats ); - - // others - mPile->settings.ammo.other_mats.clear(); - mPile->settings.ammo.other_mats.resize ( 2, '\0' ); - if ( ammo.other_mats_size() > 0 ) - { - // TODO remove hardcoded value - for ( int i = 0; i < ammo.other_mats_size(); ++i ) - { - const std::string token = ammo.other_mats ( i ); - const int32_t idx = token == "WOOD" ? 0 : token == "BONE" ? 1 : -1; - debug() << " other mats " << idx << " is " << token << endl; - if ( idx != -1 ) - mPile->settings.ammo.other_mats.at ( idx ) = 1; - } - } - - // core quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return ammo.quality_core ( idx ); - }, ammo.quality_core_size(), mPile->settings.ammo.quality_core ); - - // total quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return ammo.quality_total ( idx ); - }, ammo.quality_total_size(), mPile->settings.ammo.quality_total ); - } - else - { - mPile->settings.flags.bits.ammo = 0; - mPile->settings.ammo.type.clear(); - mPile->settings.ammo.mats.clear(); - mPile->settings.ammo.other_mats.clear(); - quality_clear ( mPile->settings.ammo.quality_core ); - quality_clear ( mPile->settings.ammo.quality_total ); - } - } - - bool coins_mat_is_allowed ( const MaterialInfo &mi ) - { - return mi.isValid(); - } - - void write_coins() - { - StockpileSettings::CoinSet *coins = mBuffer.mutable_coin(); - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::coins_mat_is_allowed, this, _1 ); - serialize_list_material ( filter, [=] ( const std::string &token ) - { - coins->add_mats ( token ); - }, mPile->settings.coins.mats ); - } - - void read_coins() - { - if ( mBuffer.has_coin() ) - { - mPile->settings.flags.bits.coins = 1; - const StockpileSettings::CoinSet coins = mBuffer.coin(); - debug() << "coins: " < const std::string& - { - return coins.mats ( idx ); - }, coins.mats_size(), &mPile->settings.coins.mats ); - } - else - { - mPile->settings.flags.bits.coins = 0; - mPile->settings.coins.mats.clear(); - } - } - - void bars_blocks_setup_other_mats() - { - mOtherMatsBars.insert ( std::make_pair ( 0,"COAL" ) ); - mOtherMatsBars.insert ( std::make_pair ( 1,"POTASH" ) ); - mOtherMatsBars.insert ( std::make_pair ( 2,"ASH" ) ); - mOtherMatsBars.insert ( std::make_pair ( 3,"PEARLASH" ) ); - mOtherMatsBars.insert ( std::make_pair ( 4,"SOAP" ) ); - - mOtherMatsBlocks.insert ( std::make_pair ( 0,"GREEN_GLASS" ) ); - mOtherMatsBlocks.insert ( std::make_pair ( 1,"CLEAR_GLASS" ) ); - mOtherMatsBlocks.insert ( std::make_pair ( 2,"CRYSTAL_GLASS" ) ); - mOtherMatsBlocks.insert ( std::make_pair ( 3,"WOOD" ) ); - } - - bool bars_mat_is_allowed ( const MaterialInfo &mi ) - { - return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_METAL ); - } - - bool blocks_mat_is_allowed ( const MaterialInfo &mi ) - { - return mi.isValid() && mi.material - && ( mi.material->flags.is_set ( material_flags::IS_METAL) - || mi.material->flags.is_set ( material_flags::IS_STONE )); - } - - - void write_bars_blocks() - { - StockpileSettings::BarsBlocksSet *bars_blocks = mBuffer.mutable_barsblocks(); - MaterialInfo mi; - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::bars_mat_is_allowed, this, _1 ); - serialize_list_material ( filter, [=] ( const std::string &token ) - { - bars_blocks->add_bars_mats ( token ); - }, mPile->settings.bars_blocks.bars_mats ); - - // blocks mats - filter = std::bind ( &StockpileSerializer::blocks_mat_is_allowed, this, _1 ); - serialize_list_material ( filter, [=] ( const std::string &token ) - { - bars_blocks->add_blocks_mats ( token ); - }, mPile->settings.bars_blocks.blocks_mats ); - - // bars other mats - serialize_list_other_mats ( mOtherMatsBars, [=] ( const std::string &token ) - { - bars_blocks->add_bars_other_mats ( token ); - }, mPile->settings.bars_blocks.bars_other_mats ); - - // blocks other mats - serialize_list_other_mats ( mOtherMatsBlocks, [=] ( const std::string &token ) - { - bars_blocks->add_blocks_other_mats ( token ); - }, mPile->settings.bars_blocks.blocks_other_mats ); - } - - void read_bars_blocks() - { - if ( mBuffer.has_barsblocks() ) - { - mPile->settings.flags.bits.bars_blocks = 1; - const StockpileSettings::BarsBlocksSet bars_blocks = mBuffer.barsblocks(); - debug() << "bars_blocks: " < const std::string& - { - return bars_blocks.bars_mats ( idx ); - }, bars_blocks.bars_mats_size(), &mPile->settings.bars_blocks.bars_mats ); - - // blocks - filter = std::bind ( &StockpileSerializer::blocks_mat_is_allowed, this, _1 ); - unserialize_list_material ( filter, [=] ( const size_t & idx ) -> const std::string& - { - return bars_blocks.blocks_mats ( idx ); - }, bars_blocks.blocks_mats_size(), &mPile->settings.bars_blocks.blocks_mats ); - // bars other mats - unserialize_list_other_mats ( mOtherMatsBars, [=] ( const size_t & idx ) -> const std::string& - { - return bars_blocks.bars_other_mats ( idx ); - }, bars_blocks.bars_other_mats_size(), &mPile->settings.bars_blocks.bars_other_mats ); - - - // blocks other mats - unserialize_list_other_mats ( mOtherMatsBlocks, [=] ( const size_t & idx ) -> const std::string& - { - return bars_blocks.blocks_other_mats ( idx ); - }, bars_blocks.blocks_other_mats_size(), &mPile->settings.bars_blocks.blocks_other_mats ); - - } - else - { - mPile->settings.flags.bits.bars_blocks = 0; - mPile->settings.bars_blocks.bars_other_mats.clear(); - mPile->settings.bars_blocks.bars_mats.clear(); - mPile->settings.bars_blocks.blocks_other_mats.clear(); - mPile->settings.bars_blocks.blocks_mats.clear(); - } - } - - bool gem_mat_is_allowed ( const MaterialInfo &mi ) - { - return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_GEM ); - } - bool gem_cut_mat_is_allowed ( const MaterialInfo &mi ) - { - return mi.isValid() && mi.material && ( mi.material->flags.is_set ( material_flags::IS_GEM ) || mi.material->flags.is_set ( material_flags::IS_STONE ) ) ; - } - bool gem_other_mat_is_allowed(MaterialInfo &mi ) { - return mi.isValid() && (mi.getToken() == "GLASS_GREEN" || mi.getToken() == "GLASS_CLEAR" || mi.getToken() == "GLASS_CRYSTAL"); - } - - void write_gems() - { - StockpileSettings::GemsSet *gems = mBuffer.mutable_gems(); - MaterialInfo mi; - // rough mats - FuncMaterialAllowed filter_rough = std::bind ( &StockpileSerializer::gem_mat_is_allowed, this, _1 ); - serialize_list_material ( filter_rough, [=] ( const std::string &token ) - { - gems->add_rough_mats ( token ); - }, mPile->settings.gems.rough_mats ); - // cut mats - FuncMaterialAllowed filter_cut = std::bind ( &StockpileSerializer::gem_cut_mat_is_allowed, this, _1 ); - serialize_list_material ( filter_cut, [=] ( const std::string &token ) - { - gems->add_cut_mats ( token ); - }, mPile->settings.gems.cut_mats ); - // rough other - for ( size_t i = 0; i < mPile->settings.gems.rough_other_mats.size(); ++i ) - { - if ( mPile->settings.gems.rough_other_mats.at ( i ) ) - { - mi.decode ( i, -1 ); - if ( !gem_other_mat_is_allowed(mi) ) continue; - debug() << " gem rough_other mat" << i << " is " << mi.getToken() << endl; - gems->add_rough_other_mats ( mi.getToken() ); - } - } - // cut other - for ( size_t i = 0; i < mPile->settings.gems.cut_other_mats.size(); ++i ) - { - if ( mPile->settings.gems.cut_other_mats.at ( i ) ) - { - mi.decode ( i, -1 ); - if ( !mi.isValid() ) mi.decode ( 0, i ); - if ( !gem_other_mat_is_allowed(mi) ) continue; - debug() << " gem cut_other mat" << i << " is " << mi.getToken() << endl; - gems->add_cut_other_mats ( mi.getToken() ); - } - } - } - - void read_gems() - { - if ( mBuffer.has_gems() ) - { - mPile->settings.flags.bits.gems = 1; - const StockpileSettings::GemsSet gems = mBuffer.gems(); - debug() << "gems: " < const std::string& - { - return gems.rough_mats ( idx ); - }, gems.rough_mats_size(), &mPile->settings.gems.rough_mats ); - - // cut - FuncMaterialAllowed filter_cut = std::bind ( &StockpileSerializer::gem_cut_mat_is_allowed, this, _1 ); - unserialize_list_material ( filter_cut, [=] ( const size_t & idx ) -> const std::string& - { - return gems.cut_mats ( idx ); - }, gems.cut_mats_size(), &mPile->settings.gems.cut_mats ); - - const size_t builtin_size = std::extentraws.mat_table.builtin ) >::value; - // rough other - mPile->settings.gems.rough_other_mats.clear(); - mPile->settings.gems.rough_other_mats.resize ( builtin_size, '\0' ); - for ( int i = 0; i < gems.rough_other_mats_size(); ++i ) - { - const std::string token = gems.rough_other_mats ( i ); - MaterialInfo mi; - mi.find ( token ); - if ( !mi.isValid() || mi.type >= builtin_size ) - { - debug() << "WARNING: invalid gem mat " << token << ". idx=" << mi.type << endl; - continue; - } - debug() << " rough_other mats " << mi.type << " is " << token << endl; - mPile->settings.gems.rough_other_mats.at ( mi.type ) = 1; - } - - // cut other - mPile->settings.gems.cut_other_mats.clear(); - mPile->settings.gems.cut_other_mats.resize ( builtin_size, '\0' ); - for ( int i = 0; i < gems.cut_other_mats_size(); ++i ) - { - const std::string token = gems.cut_other_mats ( i ); - MaterialInfo mi; - mi.find ( token ); - if ( !mi.isValid() || mi.type >= builtin_size ) - { - debug() << "WARNING: invalid gem mat " << token << ". idx=" << mi.type << endl; - continue; - } - debug() << " cut_other mats " << mi.type << " is " << token << endl; - mPile->settings.gems.cut_other_mats.at ( mi.type ) = 1; } - } - else - { - mPile->settings.flags.bits.gems = 0; - mPile->settings.gems.cut_other_mats.clear(); - mPile->settings.gems.cut_mats.clear(); - mPile->settings.gems.rough_other_mats.clear(); - mPile->settings.gems.rough_mats.clear(); - } - } - - bool finished_goods_type_is_allowed ( item_type::item_type type ) - { - switch ( type ) - { - case item_type::CHAIN: - case item_type::FLASK: - case item_type::GOBLET: - case item_type::INSTRUMENT: - case item_type::TOY: - case item_type::ARMOR: - case item_type::SHOES: - case item_type::HELM: - case item_type::GLOVES: - case item_type::FIGURINE: - case item_type::AMULET: - case item_type::SCEPTER: - case item_type::CROWN: - case item_type::RING: - case item_type::EARRING: - case item_type::BRACELET: - case item_type::GEM: - case item_type::TOTEM: - case item_type::PANTS: - case item_type::BACKPACK: - case item_type::QUIVER: - case item_type::SPLINT: - case item_type::CRUTCH: - case item_type::TOOL: - case item_type::BOOK: - return true; - default: - return false; - } - - } - - void finished_goods_setup_other_mats() - { - mOtherMatsFinishedGoods.insert ( std::make_pair ( 0,"WOOD" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 1,"PLANT_CLOTH" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 2,"BONE" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 3,"TOOTH" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 4,"HORN" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 5,"PEARL" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 6,"SHELL" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 7,"LEATHER" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 8,"SILK" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 9,"AMBER" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 10,"CORAL" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 11,"GREEN_GLASS" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 12,"CLEAR_GLASS" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 13,"CRYSTAL_GLASS" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 14,"YARN" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 15,"WAX" ) ); - } - - bool finished_goods_mat_is_allowed ( const MaterialInfo &mi ) - { - return mi.isValid() - && mi.material - && ( mi.material->flags.is_set ( material_flags::IS_GEM ) - || mi.material->flags.is_set ( material_flags::IS_METAL ) - || mi.material->flags.is_set ( material_flags::IS_STONE ) ) ; - } - - void write_finished_goods() - { - StockpileSettings::FinishedGoodsSet *finished_goods = mBuffer.mutable_finished_goods(); - - // type - FuncItemAllowed filter = std::bind ( &StockpileSerializer::finished_goods_type_is_allowed, this, _1 ); - serialize_list_item_type ( filter, [=] ( const std::string &token ) - { - finished_goods->add_type ( token ); - }, mPile->settings.finished_goods.type ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::finished_goods_mat_is_allowed, this, _1 ); - serialize_list_material ( mat_filter, [=] ( const std::string &token ) - { - finished_goods->add_mats ( token ); - }, mPile->settings.finished_goods.mats ); - - // other mats - serialize_list_other_mats ( mOtherMatsFinishedGoods, [=] ( const std::string &token ) - { - finished_goods->add_other_mats ( token ); - }, mPile->settings.finished_goods.other_mats ); - - // quality core - serialize_list_quality ( [=] ( const std::string &token ) - { - finished_goods->add_quality_core ( token ); - }, mPile->settings.finished_goods.quality_core ); - - // quality total - serialize_list_quality ( [=] ( const std::string &token ) - { - finished_goods->add_quality_total ( token ); - }, mPile->settings.finished_goods.quality_total ); - } - - void read_finished_goods() - { - if ( mBuffer.has_finished_goods() ) - { - mPile->settings.flags.bits.finished_goods = 1; - const StockpileSettings::FinishedGoodsSet finished_goods = mBuffer.finished_goods(); - debug() << "finished_goods: " < const std::string& - { - return finished_goods.type ( idx ); - }, finished_goods.type_size(), &mPile->settings.finished_goods.type ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::finished_goods_mat_is_allowed, this, _1 ); - unserialize_list_material ( mat_filter, [=] ( const size_t & idx ) -> const std::string& - { - return finished_goods.mats ( idx ); - }, finished_goods.mats_size(), &mPile->settings.finished_goods.mats ); - - // other mats - unserialize_list_other_mats ( mOtherMatsFinishedGoods, [=] ( const size_t & idx ) -> const std::string& - { - return finished_goods.other_mats ( idx ); - }, finished_goods.other_mats_size(), &mPile->settings.finished_goods.other_mats ); - - // core quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return finished_goods.quality_core ( idx ); - }, finished_goods.quality_core_size(), mPile->settings.finished_goods.quality_core ); - - // total quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return finished_goods.quality_total ( idx ); - }, finished_goods.quality_total_size(), mPile->settings.finished_goods.quality_total ); - - } - else - { - mPile->settings.flags.bits.finished_goods = 0; - mPile->settings.finished_goods.type.clear(); - mPile->settings.finished_goods.other_mats.clear(); - mPile->settings.finished_goods.mats.clear(); - quality_clear ( mPile->settings.finished_goods.quality_core ); - quality_clear ( mPile->settings.finished_goods.quality_total ); - } - } - - void write_leather() - { - StockpileSettings::LeatherSet *leather = mBuffer.mutable_leather(); - - FuncWriteExport setter = [=] ( const std::string &id ) - { - leather->add_mats ( id ); - }; - serialize_list_organic_mat ( setter, &mPile->settings.leather.mats, organic_mat_category::Leather ); - } - void read_leather() - { - if ( mBuffer.has_leather() ) - { - mPile->settings.flags.bits.leather = 1; - const StockpileSettings::LeatherSet leather = mBuffer.leather(); - debug() << "leather: " < std::string - { - return leather.mats ( idx ); - }, leather.mats_size(), &mPile->settings.leather.mats, organic_mat_category::Leather ); - } - else - { - mPile->settings.flags.bits.leather = 0; - mPile->settings.leather.mats.clear(); - } - } - - void write_cloth() - { - StockpileSettings::ClothSet * cloth = mBuffer.mutable_cloth(); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_thread_silk ( token ); - }, &mPile->settings.cloth.thread_silk, organic_mat_category::Silk ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_thread_plant ( token ); - }, &mPile->settings.cloth.thread_plant, organic_mat_category::PlantFiber ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_thread_yarn ( token ); - }, &mPile->settings.cloth.thread_yarn, organic_mat_category::Yarn ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_thread_metal ( token ); - }, &mPile->settings.cloth.thread_metal, organic_mat_category::MetalThread ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_cloth_silk ( token ); - }, &mPile->settings.cloth.cloth_silk, organic_mat_category::Silk ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_cloth_plant ( token ); - }, &mPile->settings.cloth.cloth_plant, organic_mat_category::PlantFiber ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_cloth_yarn ( token ); - }, &mPile->settings.cloth.cloth_yarn, organic_mat_category::Yarn ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_cloth_metal ( token ); - }, &mPile->settings.cloth.cloth_metal, organic_mat_category::MetalThread ); - - } - void read_cloth() - { - if ( mBuffer.has_cloth() ) - { - mPile->settings.flags.bits.cloth = 1; - const StockpileSettings::ClothSet cloth = mBuffer.cloth(); - debug() << "cloth: " < std::string - { - return cloth.thread_silk ( idx ); - }, cloth.thread_silk_size(), &mPile->settings.cloth.thread_silk, organic_mat_category::Silk ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.thread_plant ( idx ); - }, cloth.thread_plant_size(), &mPile->settings.cloth.thread_plant, organic_mat_category::PlantFiber ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.thread_yarn ( idx ); - }, cloth.thread_yarn_size(), &mPile->settings.cloth.thread_yarn, organic_mat_category::Yarn ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.thread_metal ( idx ); - }, cloth.thread_metal_size(), &mPile->settings.cloth.thread_metal, organic_mat_category::MetalThread ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.cloth_silk ( idx ); - }, cloth.cloth_silk_size(), &mPile->settings.cloth.cloth_silk, organic_mat_category::Silk ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.cloth_plant ( idx ); - }, cloth.cloth_plant_size(), &mPile->settings.cloth.cloth_plant, organic_mat_category::PlantFiber ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.cloth_yarn ( idx ); - }, cloth.cloth_yarn_size(), &mPile->settings.cloth.cloth_yarn, organic_mat_category::Yarn ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.cloth_metal ( idx ); - }, cloth.cloth_metal_size(), &mPile->settings.cloth.cloth_metal, organic_mat_category::MetalThread ); - } - else - { - mPile->settings.cloth.thread_metal.clear(); - mPile->settings.cloth.thread_plant.clear(); - mPile->settings.cloth.thread_silk.clear(); - mPile->settings.cloth.thread_yarn.clear(); - mPile->settings.cloth.cloth_metal.clear(); - mPile->settings.cloth.cloth_plant.clear(); - mPile->settings.cloth.cloth_silk.clear(); - mPile->settings.cloth.cloth_yarn.clear(); - mPile->settings.flags.bits.cloth = 0; - } - } - - bool wood_mat_is_allowed ( const df::plant_raw * plant ) - { - return plant && plant->flags.is_set ( plant_raw_flags::TREE ); - } - - void write_wood() - { - StockpileSettings::WoodSet * wood = mBuffer.mutable_wood(); - for ( size_t i = 0; i < mPile->settings.wood.mats.size(); ++i ) - { - if ( mPile->settings.wood.mats.at ( i ) ) - { - const df::plant_raw * plant = find_plant ( i ); - if ( !wood_mat_is_allowed ( plant ) ) continue; - wood->add_mats ( plant->id ); - debug() << " plant " << i << " is " << plant->id << endl; - } - } - } - void read_wood() - { - if ( mBuffer.has_wood() ) - { - mPile->settings.flags.bits.wood = 1; - const StockpileSettings::WoodSet wood = mBuffer.wood(); - debug() << "wood: " <settings.wood.mats.clear(); - mPile->settings.wood.mats.resize ( world->raws.plants.all.size(), '\0' ); - for ( int i = 0; i < wood.mats_size(); ++i ) - { - const std::string token = wood.mats ( i ); - const size_t idx = find_plant ( token ); - if ( idx < 0 || idx >= mPile->settings.wood.mats.size() ) - { - debug() << "WARNING wood mat index invalid " << token << ", idx=" << idx << endl; - continue; - } - debug() << " plant " << idx << " is " << token << endl; - mPile->settings.wood.mats.at ( idx ) = 1; - } - } - else - { - mPile->settings.flags.bits.wood = 0; - mPile->settings.wood.mats.clear(); - } - } - - bool weapons_mat_is_allowed ( const MaterialInfo &mi ) - { - return mi.isValid() && mi.material && ( - mi.material->flags.is_set ( material_flags::IS_METAL ) || - mi.material->flags.is_set ( material_flags::IS_STONE ) ); - - } - - void write_weapons() - { - StockpileSettings::WeaponsSet * weapons = mBuffer.mutable_weapons(); - - weapons->set_unusable ( mPile->settings.weapons.unusable ); - weapons->set_usable ( mPile->settings.weapons.usable ); - - // weapon type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - weapons->add_weapon_type ( token ); - }, mPile->settings.weapons.weapon_type, - std::vector ( world->raws.itemdefs.weapons.begin(),world->raws.itemdefs.weapons.end() ), - item_type::WEAPON ); - - // trapcomp type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - weapons->add_trapcomp_type ( token ); - }, mPile->settings.weapons.trapcomp_type, - std::vector ( world->raws.itemdefs.trapcomps.begin(),world->raws.itemdefs.trapcomps.end() ), - item_type::TRAPCOMP ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::weapons_mat_is_allowed, this, _1 ); - serialize_list_material ( mat_filter, [=] ( const std::string &token ) - { - weapons->add_mats ( token ); - }, mPile->settings.weapons.mats ); - - // other mats - serialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const std::string &token ) - { - weapons->add_other_mats ( token ); - }, mPile->settings.weapons.other_mats ); - - // quality core - serialize_list_quality ( [=] ( const std::string &token ) - { - weapons->add_quality_core ( token ); - }, mPile->settings.weapons.quality_core ); - - // quality total - serialize_list_quality ( [=] ( const std::string &token ) - { - weapons->add_quality_total ( token ); - }, mPile->settings.weapons.quality_total ); - } - - void read_weapons() - { - if ( mBuffer.has_weapons() ) - { - mPile->settings.flags.bits.weapons = 1; - const StockpileSettings::WeaponsSet weapons = mBuffer.weapons(); - debug() << "weapons: " < const std::string& - { - return weapons.weapon_type ( idx ); - }, weapons.weapon_type_size(), &mPile->settings.weapons.weapon_type, item_type::WEAPON ); - - // trapcomp type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return weapons.trapcomp_type ( idx ); - }, weapons.trapcomp_type_size(), &mPile->settings.weapons.trapcomp_type, item_type::TRAPCOMP ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::weapons_mat_is_allowed, this, _1 ); - unserialize_list_material ( mat_filter, [=] ( const size_t & idx ) -> const std::string& - { - return weapons.mats ( idx ); - }, weapons.mats_size(), &mPile->settings.weapons.mats ); - - // other mats - unserialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const size_t & idx ) -> const std::string& - { - return weapons.other_mats ( idx ); - }, weapons.other_mats_size(), &mPile->settings.weapons.other_mats ); - - - // core quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return weapons.quality_core ( idx ); - }, weapons.quality_core_size(), mPile->settings.weapons.quality_core ); - // total quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return weapons.quality_total ( idx ); - }, weapons.quality_total_size(), mPile->settings.weapons.quality_total ); - } - else - { - mPile->settings.flags.bits.weapons = 0; - mPile->settings.weapons.weapon_type.clear(); - mPile->settings.weapons.trapcomp_type.clear(); - mPile->settings.weapons.other_mats.clear(); - mPile->settings.weapons.mats.clear(); - quality_clear ( mPile->settings.weapons.quality_core ); - quality_clear ( mPile->settings.weapons.quality_total ); - } - - } - - void weapons_armor_setup_other_mats() - { - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 0,"WOOD" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 1,"PLANT_CLOTH" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 2,"BONE" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 3,"SHELL" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 4,"LEATHER" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 5,"SILK" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 6,"GREEN_GLASS" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 7,"CLEAR_GLASS" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 8,"CRYSTAL_GLASS" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 9,"YARN" ) ); - } - - bool armor_mat_is_allowed ( const MaterialInfo &mi ) - { - return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_METAL ); - } - - void write_armor() - { - StockpileSettings::ArmorSet * armor = mBuffer.mutable_armor(); - - armor->set_unusable ( mPile->settings.armor.unusable ); - armor->set_usable ( mPile->settings.armor.usable ); - - // armor type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_body ( token ); - }, mPile->settings.armor.body, - std::vector ( world->raws.itemdefs.armor.begin(),world->raws.itemdefs.armor.end() ), - item_type::ARMOR ); - - // helm type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_head ( token ); - }, mPile->settings.armor.head, - std::vector ( world->raws.itemdefs.helms.begin(),world->raws.itemdefs.helms.end() ), - item_type::HELM ); - - // shoes type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_feet ( token ); - }, mPile->settings.armor.feet, - std::vector ( world->raws.itemdefs.shoes.begin(),world->raws.itemdefs.shoes.end() ), - item_type::SHOES ); - - // gloves type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_hands ( token ); - }, mPile->settings.armor.hands, - std::vector ( world->raws.itemdefs.gloves.begin(),world->raws.itemdefs.gloves.end() ), - item_type::GLOVES ); - - // pant type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_legs ( token ); - }, mPile->settings.armor.legs, - std::vector ( world->raws.itemdefs.pants.begin(),world->raws.itemdefs.pants.end() ), - item_type::PANTS ); - - // shield type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_shield ( token ); - }, mPile->settings.armor.shield, - std::vector ( world->raws.itemdefs.shields.begin(),world->raws.itemdefs.shields.end() ), - item_type::SHIELD ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::armor_mat_is_allowed, this, _1 ); - serialize_list_material ( mat_filter, [=] ( const std::string &token ) - { - armor->add_mats ( token ); - }, mPile->settings.armor.mats ); - - // other mats - serialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const std::string &token ) - { - armor->add_other_mats ( token ); - }, mPile->settings.armor.other_mats ); - - // quality core - serialize_list_quality ( [=] ( const std::string &token ) - { - armor->add_quality_core ( token ); - }, mPile->settings.armor.quality_core ); - - // quality total - serialize_list_quality ( [=] ( const std::string &token ) - { - armor->add_quality_total ( token ); - }, mPile->settings.armor.quality_total ); - } - - void read_armor() - { - if ( mBuffer.has_armor() ) - { - mPile->settings.flags.bits.armor = 1; - const StockpileSettings::ArmorSet armor = mBuffer.armor(); - debug() << "armor: " < const std::string& - { - return armor.body ( idx ); - }, armor.body_size(), &mPile->settings.armor.body, item_type::ARMOR ); - - // head type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.head ( idx ); - }, armor.head_size(), &mPile->settings.armor.head, item_type::HELM ); - - // feet type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.feet ( idx ); - }, armor.feet_size(), &mPile->settings.armor.feet, item_type::SHOES ); - - // hands type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.hands ( idx ); - }, armor.hands_size(), &mPile->settings.armor.hands, item_type::GLOVES ); - - // legs type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.legs ( idx ); - }, armor.legs_size(), &mPile->settings.armor.legs, item_type::PANTS ); - - // shield type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.shield ( idx ); - }, armor.shield_size(), &mPile->settings.armor.shield, item_type::SHIELD ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::armor_mat_is_allowed, this, _1 ); - unserialize_list_material ( mat_filter, [=] ( const size_t & idx ) -> const std::string& - { - return armor.mats ( idx ); - }, armor.mats_size(), &mPile->settings.armor.mats ); - - // other mats - unserialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const size_t & idx ) -> const std::string& - { - return armor.other_mats ( idx ); - }, armor.other_mats_size(), &mPile->settings.armor.other_mats ); - - // core quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.quality_core ( idx ); - }, armor.quality_core_size(), mPile->settings.armor.quality_core ); - // total quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.quality_total ( idx ); - }, armor.quality_total_size(), mPile->settings.armor.quality_total ); - } - else - { - mPile->settings.flags.bits.armor = 0; - mPile->settings.armor.body.clear(); - mPile->settings.armor.head.clear(); - mPile->settings.armor.feet.clear(); - mPile->settings.armor.hands.clear(); - mPile->settings.armor.legs.clear(); - mPile->settings.armor.shield.clear(); - mPile->settings.armor.other_mats.clear(); - mPile->settings.armor.mats.clear(); - quality_clear ( mPile->settings.armor.quality_core ); - quality_clear ( mPile->settings.armor.quality_total ); - } - } -}; - -static bool file_exists ( const std::string& filename ) -{ - struct stat buf; - if ( stat ( filename.c_str(), &buf ) != -1 ) - { - return true; - } - return false; -} - -static bool is_dfstockfile ( const std::string& filename ) -{ - return filename.rfind ( ".dfstock" ) != std::string::npos; -} - -// exporting -static command_result savestock ( color_ostream &out, vector & parameters ) -{ - building_stockpilest *sp = virtual_cast ( world->selected_building ); - if ( !sp ) - { - out.printerr ( "Selected building isn't a stockpile.\n" ); - return CR_WRONG_USAGE; - } - - if ( parameters.size() > 2 ) - { - out.printerr ( "Invalid parameters\n" ); - return CR_WRONG_USAGE; - } - - bool debug = false; - std::string file; - for( size_t i = 0; i < parameters.size(); ++i ) - { - const std::string o = parameters.at(i); - if ( o == "--debug" || o == "-d" ) - debug = true; - else if ( !o.empty() && o[0] != '-' ) - { - file = o; - } - } - if ( file.empty() ) - { - out.printerr("You must supply a valid filename.\n"); - return CR_WRONG_USAGE; - } - - StockpileSerializer cereal ( sp ); - if ( debug ) - cereal.enable_debug ( out ); - - if ( !is_dfstockfile ( file ) ) file += ".dfstock"; - if ( !cereal.serialize_to_file ( file ) ) - { - out.printerr ( "serialize failed\n" ); - return CR_FAILURE; - } - return CR_OK; -} - - -// importing -static command_result loadstock ( color_ostream &out, vector & parameters ) -{ - building_stockpilest *sp = virtual_cast ( world->selected_building ); - if ( !sp ) - { - out.printerr ( "Selected building isn't a stockpile.\n" ); - return CR_WRONG_USAGE; - } - - if ( parameters.size() < 1 || parameters.size() > 2 ) - { - out.printerr ( "Invalid parameters\n" ); - return CR_WRONG_USAGE; - } - - bool debug = false; - std::string file; - for( size_t i = 0; i < parameters.size(); ++i ) - { - const std::string o = parameters.at(i); - if ( o == "--debug" || o == "-d" ) - debug = true; - else if ( !o.empty() && o[0] != '-' ) - { - file = o; - } - } - - if ( !is_dfstockfile ( file ) ) file += ".dfstock"; - if ( file.empty() || !file_exists ( file ) ) - { - out.printerr ( "loadstock: a .dfstock file is required to import\n" ); - return CR_WRONG_USAGE; - } - - StockpileSerializer cereal ( sp ); - if ( debug ) - cereal.enable_debug ( out ); - if ( !cereal.unserialize_from_file ( file ) ) - { - out.printerr ( "unserialization failed\n" ); - return CR_FAILURE; - } - return CR_OK; -} - - - - - diff --git a/plugins/stockpiles/CMakeLists.txt b/plugins/stockpiles/CMakeLists.txt new file mode 100644 index 000000000..2a2ac5740 --- /dev/null +++ b/plugins/stockpiles/CMakeLists.txt @@ -0,0 +1,44 @@ +PROJECT(stockpiles) + +# add *our* headers here. +SET(PROJECT_HDRS +StockpileUtils.h +OrganicMatLookup.h +StockpileSerializer.h +) + +SET(PROJECT_SRCS +OrganicMatLookup.cpp +StockpileSerializer.cpp +stockpiles.cpp +) + +SET(PROJECT_PROTOS +${CMAKE_CURRENT_SOURCE_DIR}/proto/stockpiles.proto +) + +#Create new lists of what sources and headers protoc will output after we invoke it +STRING(REPLACE ".proto" ".pb.cc;" PROJECT_PROTO_SRCS ${PROJECT_PROTOS}) +STRING(REPLACE ".proto" ".pb.h;" PROJECT_PROTO_HDRS ${PROJECT_PROTOS}) + +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO_HDRS} PROPERTIES GENERATED TRUE) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO_SRCS} PROPERTIES GENERATED TRUE) + +LIST(APPEND PROJECT_HDRS ${PROJECT_PROTO_HDRS}) +LIST(APPEND PROJECT_SRCS ${PROJECT_PROTO_SRCS}) + +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) + +#Generate sources from our proto files and store them in the source tree +ADD_CUSTOM_COMMAND( +OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} +COMMAND protoc-bin -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/ ${PROJECT_PROTOS} +DEPENDS protoc-bin ${PROJECT_PROTOS} +) + +IF(WIN32) + DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite) +ELSE() + DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite) +ENDIF() diff --git a/plugins/stockpiles/OrganicMatLookup.cpp b/plugins/stockpiles/OrganicMatLookup.cpp new file mode 100644 index 000000000..ca9670a96 --- /dev/null +++ b/plugins/stockpiles/OrganicMatLookup.cpp @@ -0,0 +1,155 @@ +#include "OrganicMatLookup.h" + +#include "StockpileUtils.h" + +#include "modules/Materials.h" +#include "MiscUtils.h" + +#include "df/world.h" +#include "df/world_data.h" + +#include "df/creature_raw.h" +#include "df/caste_raw.h" +#include "df/material.h" + +using namespace DFHack; +using namespace df::enums; +using df::global::world; + +using std::endl; + +/** + * Helper class for mapping the various organic mats between their material indices + * and their index in the stockpile_settings structures. + */ + +void OrganicMatLookup::food_mat_by_idx ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, std::vector::size_type food_idx, FoodMat & food_mat ) +{ + out << "food_lookup: food_idx(" << food_idx << ") "; + df::world_raws &raws = world->raws; + df::special_mat_table table = raws.mat_table; + int32_t main_idx = table.organic_indexes[mat_category][food_idx]; + int16_t type = table.organic_types[mat_category][food_idx]; + if ( mat_category == organic_mat_category::Fish || + mat_category == organic_mat_category::UnpreparedFish || + mat_category == organic_mat_category::Eggs ) + { + food_mat.creature = raws.creatures.all[type]; + food_mat.caste = food_mat.creature->caste[main_idx]; + out << " special creature type(" << type << ") caste("<< main_idx <<")" <::size_type idx ) +{ + FoodMat food_mat; + food_mat_by_idx ( out, mat_category, idx, food_mat ); + if ( food_mat.material.isValid() ) + { + return food_mat.material.getToken(); + } + else if ( food_mat.creature ) + { + return food_mat.creature->creature_id + ":" + food_mat.caste->caste_id; + } + return std::string(); +} + +size_t OrganicMatLookup::food_max_size ( organic_mat_category::organic_mat_category mat_category ) +{ + return world->raws.mat_table.organic_types[mat_category].size(); +} + +void OrganicMatLookup::food_build_map ( std::ostream &out ) +{ + if ( index_built ) + return; + df::world_raws &raws = world->raws; + df::special_mat_table table = raws.mat_table; + using df::enums::organic_mat_category::organic_mat_category; + df::enum_traits traits; + for ( int32_t mat_category = traits.first_item_value; mat_category <= traits.last_item_value; ++mat_category ) + { + for ( size_t i = 0; i < table.organic_indexes[mat_category].size(); ++i ) + { + int16_t type = table.organic_types[mat_category].at ( i ); + int32_t index = table.organic_indexes[mat_category].at ( i ); + food_index[mat_category].insert ( std::make_pair ( std::make_pair ( type,index ), i ) ); // wtf.. only in c++ + } + } + index_built = true; +} + +int16_t OrganicMatLookup::food_idx_by_token ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, const std::string & token ) +{ + int16_t food_idx = -1; + df::world_raws &raws = world->raws; + df::special_mat_table table = raws.mat_table; + out << "food_idx_by_token: "; + if ( mat_category == organic_mat_category::Fish || + mat_category == organic_mat_category::UnpreparedFish || + mat_category == organic_mat_category::Eggs ) + { + std::vector tokens; + split_string ( &tokens, token, ":" ); + if ( tokens.size() != 2 ) + { + out << "creature " << "invalid CREATURE:CASTE token: " << token << endl; + } + else + { + int16_t creature_idx = find_creature ( tokens[0] ); + if ( creature_idx < 0 ) + { + out << " creature invalid token " << tokens[0]; + } + else + { + food_idx = linear_index ( table.organic_types[mat_category], creature_idx ); + if ( tokens[1] == "MALE" ) + food_idx += 1; + if ( table.organic_types[mat_category][food_idx] == creature_idx ) + out << "creature " << token << " caste " << tokens[1] << " creature_idx(" << creature_idx << ") food_idx("<< food_idx << ")" << endl; + else + { + out << "ERROR creature caste not found: " << token << " caste " << tokens[1] << " creature_idx(" << creature_idx << ") food_idx("<< food_idx << ")" << endl; + food_idx = -1; + } + } + } + } + else + { + if ( !index_built ) + food_build_map ( out ); + MaterialInfo mat_info = food_mat_by_token ( out, token ); + int16_t type = mat_info.type; + int32_t index = mat_info.index; + int16_t food_idx2 = -1; + auto it = food_index[mat_category].find ( std::make_pair ( type, index ) ); + if ( it != food_index[mat_category].end() ) + { + out << "matinfo: " << token << " type(" << mat_info.type << ") idx(" << mat_info.index << ") food_idx(" << it->second << ")" << endl; + food_idx = it->second; + } + else + { + out << "matinfo: " << token << " type(" << mat_info.type << ") idx(" << mat_info.index << ") food_idx not found :(" << endl; + } + } + return food_idx; +} + +MaterialInfo OrganicMatLookup::food_mat_by_token ( std::ostream &out, const std::string & token ) +{ + MaterialInfo mat_info; + mat_info.find ( token ); + return mat_info; +} + +bool OrganicMatLookup::index_built = false; +std::vector OrganicMatLookup::food_index = std::vector ( 37 ); diff --git a/plugins/stockpiles/OrganicMatLookup.h b/plugins/stockpiles/OrganicMatLookup.h new file mode 100644 index 000000000..aca10b004 --- /dev/null +++ b/plugins/stockpiles/OrganicMatLookup.h @@ -0,0 +1,47 @@ +#pragma once + +#include "modules/Materials.h" + +#include "df/organic_mat_category.h" + +namespace df { +struct creature_raw; +struct caste_raw; +} + +/** + * Helper class for mapping the various organic mats between their material indices + * and their index in the stockpile_settings structures. + */ +class OrganicMatLookup +{ + +// pair of material type and index + typedef std::pair FoodMatPair; +// map for using type,index pairs to find the food index + typedef std::map FoodMatMap; + +public: + struct FoodMat + { + DFHack::MaterialInfo material; + df::creature_raw *creature; + df::caste_raw * caste; + FoodMat() : material ( -1 ), creature ( 0 ), caste ( 0 ) {} + }; + static void food_mat_by_idx ( std::ostream &out, df::enums::organic_mat_category::organic_mat_category mat_category, std::vector::size_type food_idx, FoodMat & food_mat ); + static std::string food_token_by_idx ( std::ostream &out, df::enums::organic_mat_category::organic_mat_category mat_category, std::vector::size_type idx ); + + static size_t food_max_size ( df::enums::organic_mat_category::organic_mat_category mat_category ); + static void food_build_map ( std::ostream &out ); + + static int16_t food_idx_by_token ( std::ostream &out, df::enums::organic_mat_category::organic_mat_category mat_category, const std::string & token ); + + static DFHack::MaterialInfo food_mat_by_token ( std::ostream &out, const std::string & token ); + + static bool index_built; + static std::vector food_index; +private: + OrganicMatLookup(); +}; + diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp new file mode 100644 index 000000000..52bd82591 --- /dev/null +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -0,0 +1,2195 @@ +#include "StockpileSerializer.h" + +// stockpiles plugin +#include "OrganicMatLookup.h" +#include "StockpileUtils.h" + +// dfhack +#include "MiscUtils.h" + +// df +#include "df/building_stockpilest.h" +#include "df/inorganic_raw.h" +#include "df/creature_raw.h" +#include "df/caste_raw.h" +#include "df/material.h" +#include "df/inorganic_raw.h" +#include "df/plant_raw.h" +#include "df/stockpile_group_set.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// protobuf +#include + +using std::endl; +using namespace DFHack; +using namespace df::enums; +using namespace google::protobuf; +using namespace dfstockpiles; +using df::global::world; + +using std::placeholders::_1; + + + +int NullBuffer::overflow ( int c ) +{ + return c; +} + +NullStream::NullStream() : std::ostream ( &m_sb ) {} + +StockpileSerializer::StockpileSerializer ( df::building_stockpilest * stockpile ) + : mDebug ( false ) + , mOut ( 0 ) + , mNull() + , mPile ( stockpile ) +{ + + // build other_mats indices + furniture_setup_other_mats(); + bars_blocks_setup_other_mats(); + finished_goods_setup_other_mats(); + weapons_armor_setup_other_mats(); +} + +StockpileSerializer::~StockpileSerializer() {} + +void StockpileSerializer::enable_debug ( std::ostream&out ) +{ + mDebug = true; + mOut = &out; +} + +bool StockpileSerializer::serialize_to_ostream ( std::ostream* output ) +{ + mBuffer.Clear(); + write(); + { + io::OstreamOutputStream zero_copy_output ( output ); + if ( !mBuffer.SerializeToZeroCopyStream ( &zero_copy_output ) ) return false; + } + return output->good(); +} + +bool StockpileSerializer::serialize_to_file ( const std::string & file ) +{ + std::fstream output ( file, std::ios::out | std::ios::binary | std::ios::trunc ); + return serialize_to_ostream ( &output ); +} + +bool StockpileSerializer::parse_from_istream ( std::istream* input ) +{ + mBuffer.Clear(); + io::IstreamInputStream zero_copy_input ( input ); + const bool res = mBuffer.ParseFromZeroCopyStream ( &zero_copy_input ) && input->eof(); + if ( res ) read(); + return res; +} + + +bool StockpileSerializer::unserialize_from_file ( const std::string & file ) +{ + std::fstream input ( file, std::ios::in | std::ios::binary ); + return parse_from_istream ( &input ); +} + +std::ostream & StockpileSerializer::debug() +{ + if ( mDebug ) + return *mOut; + return mNull; +} + + +void StockpileSerializer::write() +{ + // debug() << "GROUP SET " << bitfield_to_string(mPile->settings.flags) << endl; + write_general(); + if ( mPile->settings.flags.bits.animals ) + write_animals(); + if ( mPile->settings.flags.bits.food ) + write_food(); + if ( mPile->settings.flags.bits.furniture ) + write_furniture(); + if ( mPile->settings.flags.bits.refuse ) + write_refuse(); + if ( mPile->settings.flags.bits.stone ) + write_stone(); + if ( mPile->settings.flags.bits.ammo ) + write_ammo(); + if ( mPile->settings.flags.bits.coins ) + write_coins(); + if ( mPile->settings.flags.bits.bars_blocks ) + write_bars_blocks(); + if ( mPile->settings.flags.bits.gems ) + write_gems(); + if ( mPile->settings.flags.bits.finished_goods ) + write_finished_goods(); + if ( mPile->settings.flags.bits.leather ) + write_leather(); + if ( mPile->settings.flags.bits.cloth ) + write_cloth(); + if ( mPile->settings.flags.bits.wood ) + write_wood(); + if ( mPile->settings.flags.bits.weapons ) + write_weapons(); + if ( mPile->settings.flags.bits.armor ) + write_armor(); +} + +void StockpileSerializer::read () +{ + debug() << endl << "==READ==" << endl; + read_general(); + read_animals(); + read_food(); + read_furniture(); + read_refuse(); + read_stone(); + read_ammo(); + read_coins(); + read_bars_blocks(); + read_gems(); + read_finished_goods(); + read_leather(); + read_cloth(); + read_wood(); + read_weapons(); + read_armor(); +} + + +void StockpileSerializer::serialize_list_organic_mat ( FuncWriteExport add_value, const std::vector * list, organic_mat_category::organic_mat_category cat ) +{ + if ( !list ) + { + debug() << "serialize_list_organic_mat: list null" << endl; + } + for ( size_t i = 0; i < list->size(); ++i ) + { + if ( list->at ( i ) ) + { + std::string token = OrganicMatLookup::food_token_by_idx ( debug(), cat, i ); + if ( !token.empty() ) + { + add_value ( token ); + debug() << " organic_material " << i << " is " << token << endl; + } + else + { + debug() << "food mat invalid :(" << endl; + } + } + } +} + +void StockpileSerializer::unserialize_list_organic_mat ( FuncReadImport get_value, size_t list_size, std::vector *pile_list, organic_mat_category::organic_mat_category cat ) +{ + pile_list->clear(); + pile_list->resize ( OrganicMatLookup::food_max_size ( cat ), '\0' ); + for ( size_t i = 0; i < list_size; ++i ) + { + std::string token = get_value ( i ); + int16_t idx = OrganicMatLookup::food_idx_by_token ( debug(), cat, token ); + debug() << " organic_material " << idx << " is " << token << endl; + if ( idx >= pile_list->size() ) + { + debug() << "error organic mat index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; + continue; + } + pile_list->at ( idx ) = 1; + } +} + + +void StockpileSerializer::serialize_list_item_type ( FuncItemAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ) +{ + using df::enums::item_type::item_type; + df::enum_traits type_traits; + debug() << "item_type size = " << list.size() << " size limit = " << type_traits.last_item_value << " typecasted: " << ( size_t ) type_traits.last_item_value << endl; + for ( size_t i = 0; i <= ( size_t ) type_traits.last_item_value; ++i ) + { + if ( list.at ( i ) ) + { + const item_type type = ( item_type ) ( ( df::enum_traits::base_type ) i ); + std::string r_type ( type_traits.key_table[i+1] ); + if ( !is_allowed ( type ) ) continue; + add_value ( r_type ); + debug() << "item_type key_table[" << i+1 << "] type[" << ( int16_t ) type << "] is " << r_type < *pile_list ) +{ + pile_list->clear(); + pile_list->resize ( 112, '\0' ); // TODO remove hardcoded list size value + for ( int i = 0; i < pile_list->size(); ++i ) + { + pile_list->at ( i ) = is_allowed ( ( item_type::item_type ) i ) ? 0 : 1; + } + using df::enums::item_type::item_type; + df::enum_traits type_traits; + for ( int32_t i = 0; i < list_size; ++i ) + { + const std::string token = read_value ( i ); + // subtract one because item_type starts at -1 + const df::enum_traits::base_type idx = linear_index ( debug(), type_traits, token ) - 1; + const item_type type = ( item_type ) idx; + if ( !is_allowed ( type ) ) continue; + debug() << " item_type " << idx << " is " << token << endl; + if ( idx >= pile_list->size() ) + { + debug() << "error item_type index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; + continue; + } + pile_list->at ( idx ) = 1; + } +} + + +void StockpileSerializer::serialize_list_material ( FuncMaterialAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ) +{ + MaterialInfo mi; + for ( size_t i = 0; i < list.size(); ++i ) + { + if ( list.at ( i ) ) + { + mi.decode ( 0, i ); + if ( !is_allowed ( mi ) ) continue; + debug() << " material " << i << " is " << mi.getToken() << endl; + add_value ( mi.getToken() ); + } + } +} + + +void StockpileSerializer::unserialize_list_material ( FuncMaterialAllowed is_allowed, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ) +{ + // we initialize all possible (allowed) values to 0, + // then all other not-allowed values to 1 + // why? because that's how the memory is in DF before + // we muck with it. + std::set idx_set; + pile_list->clear(); + pile_list->resize ( world->raws.inorganics.size(), 0 ); + for ( int i = 0; i < pile_list->size(); ++i ) + { + MaterialInfo mi ( 0, i ); + pile_list->at ( i ) = is_allowed ( mi ) ? 0 : 1; + } + for ( int i = 0; i < list_size; ++i ) + { + const std::string token = read_value ( i ); + MaterialInfo mi; + mi.find ( token ); + if ( !is_allowed ( mi ) ) continue; + debug() << " material " << mi.index << " is " << token << endl; + if ( mi.index >= pile_list->size() ) + { + debug() << "error material index too large! idx[" << mi.index << "] max_size[" << pile_list->size() << "]" << endl; + continue; + } + pile_list->at ( mi.index ) = 1; + } +} + + +void StockpileSerializer::serialize_list_quality ( FuncWriteExport add_value, const bool ( &quality_list ) [7] ) +{ + using df::enums::item_quality::item_quality; + df::enum_traits quality_traits; + for ( size_t i = 0; i < 7; ++i ) + { + if ( quality_list[i] ) + { + const std::string f_type ( quality_traits.key_table[i] ); + add_value ( f_type ); + debug() << " quality: " << i << " is " << f_type < 0 && list_size <= 7 ) + { + using df::enums::item_quality::item_quality; + df::enum_traits quality_traits; + for ( int i = 0; i < list_size; ++i ) + { + const std::string quality = read_value ( i ); + df::enum_traits::base_type idx = linear_index ( debug(), quality_traits, quality ); + if ( idx < 0 ) + { + debug() << " invalid quality token " << quality << endl; + continue; + } + debug() << " quality: " << idx << " is " << quality << endl; + pile_list[idx] = true; + } + } + else + { + quality_clear ( pile_list ); + } +} + + +void StockpileSerializer::serialize_list_other_mats ( const std::map other_mats, FuncWriteExport add_value, std::vector list ) +{ + for ( size_t i = 0; i < list.size(); ++i ) + { + if ( list.at ( i ) ) + { + const std::string token = other_mats_index ( other_mats, i ); + if ( token.empty() ) + { + debug() << " invalid other material with index " << i << endl; + continue; + } + add_value ( token ); + debug() << " other mats " << i << " is " << token << endl; + } + } +} + + +void StockpileSerializer::unserialize_list_other_mats ( const std::map other_mats, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ) +{ + pile_list->clear(); + pile_list->resize ( other_mats.size(), '\0' ); + for ( int i = 0; i < list_size; ++i ) + { + const std::string token = read_value ( i ); + size_t idx = other_mats_token ( other_mats, token ); + if ( idx < 0 ) + { + debug() << "invalid other mat with token " << token; + continue; + } + debug() << " other_mats " << idx << " is " << token << endl; + if ( idx >= pile_list->size() ) + { + debug() << "error other_mats index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; + continue; + } + pile_list->at ( idx ) = 1; + } +} + + + +void StockpileSerializer::serialize_list_itemdef ( FuncWriteExport add_value, std::vector list, std::vector items, item_type::item_type type ) +{ + for ( size_t i = 0; i < list.size(); ++i ) + { + if ( list.at ( i ) ) + { + const df::itemdef *a = items.at ( i ); + // skip procedurally generated items + if ( a->base_flags.is_set ( 0 ) ) continue; + ItemTypeInfo ii; + if ( !ii.decode ( type, i ) ) continue; + add_value ( ii.getToken() ); + debug() << " itemdef type" << i << " is " << ii.getToken() << endl; + } + } +} + + +void StockpileSerializer::unserialize_list_itemdef ( FuncReadImport read_value, int32_t list_size, std::vector *pile_list, item_type::item_type type ) +{ + pile_list->clear(); + pile_list->resize ( Items::getSubtypeCount ( type ), '\0' ); + for ( int i = 0; i < list_size; ++i ) + { + std::string token = read_value ( i ); + ItemTypeInfo ii; + if ( !ii.find ( token ) ) continue; + debug() << " itemdef " << ii.subtype << " is " << token << endl; + if ( ii.subtype >= pile_list->size() ) + { + debug() << "error itemdef index too large! idx[" << ii.subtype << "] max_size[" << pile_list->size() << "]" << endl; + continue; + } + pile_list->at ( ii.subtype ) = 1; + } +} + + +std::string StockpileSerializer::other_mats_index ( const std::map other_mats, int idx ) +{ + auto it = other_mats.find ( idx ); + if ( it == other_mats.end() ) + return std::string(); + return it->second; +} + +int StockpileSerializer::other_mats_token ( const std::map other_mats, const std::string & token ) +{ + for ( auto it = other_mats.begin(); it != other_mats.end(); ++it ) + { + if ( it->second == token ) + return it->first; + } + return -1; +} + +void StockpileSerializer::write_general() +{ + mBuffer.set_max_bins ( mPile->max_barrels ); + mBuffer.set_max_wheelbarrows ( mPile->max_wheelbarrows ); + mBuffer.set_use_links_only ( mPile->use_links_only ); + mBuffer.set_unknown1 ( mPile->settings.unk1 ); + mBuffer.set_allow_inorganic ( mPile->settings.allow_inorganic ); + mBuffer.set_allow_organic ( mPile->settings.allow_organic ); + mBuffer.set_corpses ( mPile->settings.flags.bits.corpses ); +} + +void StockpileSerializer::read_general() +{ + if ( mBuffer.has_max_bins() ) + mPile->max_bins = mBuffer.max_bins(); + if ( mBuffer.has_max_wheelbarrows() ) + mPile->max_wheelbarrows = mBuffer.max_wheelbarrows(); + if ( mBuffer.has_max_barrels() ) + mPile->max_barrels = mBuffer.max_barrels(); + if ( mBuffer.has_use_links_only() ) + mPile->use_links_only = mBuffer.use_links_only(); + if ( mBuffer.has_unknown1() ) + mPile->settings.unk1 = mBuffer.unknown1(); + if ( mBuffer.has_allow_inorganic() ) + mPile->settings.allow_inorganic = mBuffer.allow_inorganic(); + if ( mBuffer.has_allow_organic() ) + mPile->settings.allow_organic = mBuffer.allow_organic(); + if ( mBuffer.has_corpses() ) + mPile->settings.flags.bits.corpses = mBuffer.corpses(); +} + +void StockpileSerializer::write_animals() +{ + StockpileSettings::AnimalsSet *animals= mBuffer.mutable_animals(); + animals->set_empty_cages ( mPile->settings.animals.empty_cages ); + animals->set_empty_traps ( mPile->settings.animals.empty_traps ); + for ( size_t i = 0; i < mPile->settings.animals.enabled.size(); ++i ) + { + if ( mPile->settings.animals.enabled.at ( i ) == 1 ) + { + df::creature_raw* r = find_creature ( i ); + debug() << "creature "<< r->creature_id << " " << i << endl; + animals->add_enabled ( r->creature_id ); + } + } +} + +void StockpileSerializer::read_animals() +{ + if ( mBuffer.has_animals() ) + { + mPile->settings.flags.bits.animals = 1; + debug() << "animals:" << endl; + mPile->settings.animals.empty_cages = mBuffer.animals().empty_cages(); + mPile->settings.animals.empty_traps = mBuffer.animals().empty_traps(); + + mPile->settings.animals.enabled.clear(); + mPile->settings.animals.enabled.resize ( world->raws.creatures.all.size(), '\0' ); + debug() << " pile has " << mPile->settings.animals.enabled.size() << endl; + for ( auto i = 0; i < mBuffer.animals().enabled_size(); ++i ) + { + std::string id = mBuffer.animals().enabled ( i ); + int idx = find_creature ( id ); + debug() << id << " " << idx << endl; + if ( idx < 0 || idx >= mPile->settings.animals.enabled.size() ) + { + debug() << "WARNING: animal index invalid: " << idx << endl; + continue; + } + mPile->settings.animals.enabled.at ( idx ) = ( char ) 1; + } + } + else + { + mPile->settings.animals.enabled.clear(); + mPile->settings.flags.bits.animals = 0; + mPile->settings.animals.empty_cages = false; + mPile->settings.animals.empty_traps = false; + } +} + +StockpileSerializer::food_pair StockpileSerializer::food_map ( organic_mat_category::organic_mat_category cat ) +{ + using df::enums::organic_mat_category::organic_mat_category; + using namespace std::placeholders; + switch ( cat ) + { + case organic_mat_category::Meat: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_meat ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().meat ( idx ); }; + return food_pair ( setter, &mPile->settings.food.meat, getter, mBuffer.food().meat_size() ); + } + case organic_mat_category::Fish: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_fish ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().fish ( idx ); }; + return food_pair ( setter, &mPile->settings.food.fish, getter, mBuffer.food().fish_size() ); + } + case organic_mat_category::UnpreparedFish: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_unprepared_fish ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().unprepared_fish ( idx ); }; + return food_pair ( setter, &mPile->settings.food.unprepared_fish, getter, mBuffer.food().unprepared_fish_size() ); + } + case organic_mat_category::Eggs: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_egg ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().egg ( idx ); }; + return food_pair ( setter, &mPile->settings.food.egg, getter, mBuffer.food().egg_size() ); + } + case organic_mat_category::Plants: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_plants ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().plants ( idx ); }; + return food_pair ( setter, &mPile->settings.food.plants, getter, mBuffer.food().plants_size() ); + } + case organic_mat_category::PlantDrink: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_drink_plant ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().drink_plant ( idx ); }; + return food_pair ( setter, &mPile->settings.food.drink_plant, getter, mBuffer.food().drink_plant_size() ); + } + case organic_mat_category::CreatureDrink: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_drink_animal ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().drink_animal ( idx ); }; + return food_pair ( setter, &mPile->settings.food.drink_animal, getter, mBuffer.food().drink_animal_size() ); + } + case organic_mat_category::PlantCheese: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_cheese_plant ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().cheese_plant ( idx ); }; + return food_pair ( setter, &mPile->settings.food.cheese_plant, getter, mBuffer.food().cheese_plant_size() ); + } + case organic_mat_category::CreatureCheese: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_cheese_animal ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().cheese_animal ( idx ); }; + return food_pair ( setter, &mPile->settings.food.cheese_animal, getter, mBuffer.food().cheese_animal_size() ); + } + case organic_mat_category::Seed: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_seeds ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().seeds ( idx ); }; + return food_pair ( setter, &mPile->settings.food.seeds, getter, mBuffer.food().seeds_size() ); + } + case organic_mat_category::Leaf: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_leaves ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().leaves ( idx ); }; + return food_pair ( setter, &mPile->settings.food.leaves, getter, mBuffer.food().leaves_size() ); + } + case organic_mat_category::PlantPowder: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_powder_plant ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().powder_plant ( idx ); }; + return food_pair ( setter, &mPile->settings.food.powder_plant, getter, mBuffer.food().powder_plant_size() ); + } + case organic_mat_category::CreaturePowder: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_powder_creature ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().powder_creature ( idx ); }; + return food_pair ( setter, &mPile->settings.food.powder_creature, getter, mBuffer.food().powder_creature_size() ); + } + case organic_mat_category::Glob: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_glob ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().glob ( idx ); }; + return food_pair ( setter, &mPile->settings.food.glob, getter, mBuffer.food().glob_size() ); + } + case organic_mat_category::PlantLiquid: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_liquid_plant ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().liquid_plant ( idx ); }; + return food_pair ( setter, &mPile->settings.food.liquid_plant, getter, mBuffer.food().liquid_plant_size() ); + } + case organic_mat_category::CreatureLiquid: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_liquid_animal ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().liquid_animal ( idx ); }; + return food_pair ( setter, &mPile->settings.food.liquid_animal, getter, mBuffer.food().liquid_animal_size() ); + } + case organic_mat_category::MiscLiquid: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_liquid_misc ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().liquid_misc ( idx ); }; + return food_pair ( setter, &mPile->settings.food.liquid_misc, getter, mBuffer.food().liquid_misc_size() ); + } + + case organic_mat_category::Paste: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_glob_paste ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().glob_paste ( idx ); }; + return food_pair ( setter, &mPile->settings.food.glob_paste, getter, mBuffer.food().glob_paste_size() ); + } + case organic_mat_category::Pressed: + { + FuncWriteExport setter = [=] ( const std::string &id ) + { + mBuffer.mutable_food()->add_glob_pressed ( id ); + }; + FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().glob_pressed ( idx ); }; + return food_pair ( setter, &mPile->settings.food.glob_pressed, getter, mBuffer.food().glob_pressed_size() ); + } + case organic_mat_category::Leather: + case organic_mat_category::Silk: + case organic_mat_category::PlantFiber: + case organic_mat_category::Bone: + case organic_mat_category::Shell: + case organic_mat_category::Wood: + case organic_mat_category::Horn: + case organic_mat_category::Pearl: + case organic_mat_category::Tooth: + case organic_mat_category::EdibleCheese: + case organic_mat_category::AnyDrink: + case organic_mat_category::EdiblePlant: + case organic_mat_category::CookableLiquid: + case organic_mat_category::CookablePowder: + case organic_mat_category::CookableSeed: + case organic_mat_category::CookableLeaf: + case organic_mat_category::Yarn: + case organic_mat_category::MetalThread: + default: + // not used in stockpile food menu + break; + } + return food_pair(); +} + + +void StockpileSerializer::write_food() +{ + StockpileSettings::FoodSet *food = mBuffer.mutable_food(); + debug() << " food: " << endl; + food->set_prepared_meals ( mPile->settings.food.prepared_meals ); + + using df::enums::organic_mat_category::organic_mat_category; + df::enum_traits traits; + for ( int32_t mat_category = traits.first_item_value; mat_category traits; + if ( mBuffer.has_food() ) + { + mPile->settings.flags.bits.food = 1; + const StockpileSettings::FoodSet food = mBuffer.food(); + debug() << "food:" <settings.food.prepared_meals = food.prepared_meals(); + else + mPile->settings.food.prepared_meals = true; + + debug() << " prepared_meals: " << mPile->settings.food.prepared_meals << endl; + + for ( int32_t mat_category = traits.first_item_value; mat_category clear(); + } + mPile->settings.flags.bits.food = 0; + mPile->settings.food.prepared_meals = false; + } +} + +void StockpileSerializer::furniture_setup_other_mats() +{ + mOtherMatsFurniture.insert ( std::make_pair ( 0,"WOOD" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 1,"PLANT_CLOTH" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 2,"BONE" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 3,"TOOTH" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 4,"HORN" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 5,"PEARL" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 6,"SHELL" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 7,"LEATHER" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 8,"SILK" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 9,"AMBER" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 10,"CORAL" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 11,"GREEN_GLASS" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 12,"CLEAR_GLASS" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 13,"CRYSTAL_GLASS" ) ); + mOtherMatsFurniture.insert ( std::make_pair ( 14,"YARN" ) ); +} + +void StockpileSerializer::write_furniture() +{ + StockpileSettings::FurnitureSet *furniture= mBuffer.mutable_furniture(); + furniture->set_sand_bags ( mPile->settings.furniture.sand_bags ); + + // FURNITURE type + using df::enums::furniture_type::furniture_type; + df::enum_traits type_traits; + for ( size_t i = 0; i < mPile->settings.furniture.type.size(); ++i ) + { + if ( mPile->settings.furniture.type.at ( i ) ) + { + std::string f_type ( type_traits.key_table[i] ); + furniture->add_type ( f_type ); + debug() << "furniture_type " << i << " is " << f_type <add_mats ( token ); + }, mPile->settings.furniture.mats ); + + // other mats + serialize_list_other_mats ( mOtherMatsFurniture, [=] ( const std::string &token ) + { + furniture->add_other_mats ( token ); + }, mPile->settings.furniture.other_mats ); + + serialize_list_quality ( [=] ( const std::string &token ) + { + furniture->add_quality_core ( token ); + }, mPile->settings.furniture.quality_core ); + serialize_list_quality ( [=] ( const std::string &token ) + { + furniture->add_quality_total ( token ); + }, mPile->settings.furniture.quality_total ); +} + +bool StockpileSerializer::furniture_mat_is_allowed ( const MaterialInfo &mi ) +{ + return mi.isValid() && mi.material + && ( mi.material->flags.is_set ( material_flags::IS_METAL ) + || mi.material->flags.is_set ( material_flags::IS_STONE ) ); +} + +void StockpileSerializer::read_furniture() +{ + if ( mBuffer.has_furniture() ) + { + mPile->settings.flags.bits.furniture = 1; + const StockpileSettings::FurnitureSet furniture = mBuffer.furniture(); + debug() << "furniture:" <settings.furniture.sand_bags = mBuffer.furniture().sand_bags(); + else + mPile->settings.furniture.sand_bags = false; + + // type + using df::enums::furniture_type::furniture_type; + df::enum_traits type_traits; + mPile->settings.furniture.type.clear(); + mPile->settings.furniture.type.resize ( type_traits.last_item_value+1, '\0' ); + if ( furniture.type_size() > 0 ) + { + for ( int i = 0; i < furniture.type_size(); ++i ) + { + const std::string type = furniture.type ( i ); + df::enum_traits::base_type idx = linear_index ( debug(), type_traits, type ); + debug() << " type " << idx << " is " << type << endl; + if ( idx < 0 || idx >= mPile->settings.furniture.type.size() ) + { + debug() << "WARNING: furniture type index invalid " << type << ", idx=" << idx << endl; + continue; + } + mPile->settings.furniture.type.at ( idx ) = 1; + } + } + + FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::furniture_mat_is_allowed, this, _1 ); + unserialize_list_material ( filter, [=] ( const size_t & idx ) -> const std::string& + { + return furniture.mats ( idx ); + }, furniture.mats_size(), &mPile->settings.furniture.mats ); + + // other materials + unserialize_list_other_mats ( mOtherMatsFurniture, [=] ( const size_t & idx ) -> const std::string& + { + return furniture.other_mats ( idx ); + }, furniture.other_mats_size(), &mPile->settings.furniture.other_mats ); + + // core quality + unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& + { + return furniture.quality_core ( idx ); + }, furniture.quality_core_size(), mPile->settings.furniture.quality_core ); + + // total quality + unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& + { + return furniture.quality_total ( idx ); + }, furniture.quality_total_size(), mPile->settings.furniture.quality_total ); + + } + else + { + mPile->settings.flags.bits.furniture = 0; + mPile->settings.furniture.type.clear(); + mPile->settings.furniture.other_mats.clear(); + mPile->settings.furniture.mats.clear(); + quality_clear ( mPile->settings.furniture.quality_core ); + quality_clear ( mPile->settings.furniture.quality_total ); + } +} + +bool StockpileSerializer::refuse_creature_is_allowed ( const df::creature_raw *raw ) +{ + if ( !raw ) return false; + // wagon and generated creatures not allowed, except angels + const bool is_wagon = raw->creature_id == "EQUIPMENT_WAGON"; + const bool is_generated = raw->flags.is_set ( creature_raw_flags::GENERATED ); + const bool is_angel = is_generated && raw->creature_id.find ( "DIVINE_" ) != std::string::npos; + return !is_wagon && ! ( is_generated && !is_angel ); +} + +void StockpileSerializer::refuse_write_helper ( std::function add_value, const vector & list ) +{ + for ( size_t i = 0; i < list.size(); ++i ) + { + if ( list.at ( i ) == 1 ) + { + df::creature_raw* r = find_creature ( i ); + // skip forgotten beasts, titans, demons, and night creatures + if ( !refuse_creature_is_allowed ( r ) ) continue; + debug() << "creature "<< r->creature_id << " " << i << endl; + add_value ( r->creature_id ); + } + } +} + +bool StockpileSerializer::refuse_type_is_allowed ( item_type::item_type type ) +{ + if ( type == item_type::NONE + || type == item_type::BAR + || type == item_type::SMALLGEM + || type == item_type::BLOCKS + || type == item_type::ROUGH + || type == item_type::BOULDER + || type == item_type::CORPSE + || type == item_type::CORPSEPIECE + || type == item_type::ROCK + || type == item_type::ORTHOPEDIC_CAST + ) return false; + return true; +} + + +void StockpileSerializer::write_refuse() +{ + StockpileSettings::RefuseSet *refuse = mBuffer.mutable_refuse(); + refuse->set_fresh_raw_hide ( mPile->settings.refuse.fresh_raw_hide ); + refuse->set_rotten_raw_hide ( mPile->settings.refuse.rotten_raw_hide ); + + // type + FuncItemAllowed filter = std::bind ( &StockpileSerializer::refuse_type_is_allowed, this, _1 ); + serialize_list_item_type ( filter, [=] ( const std::string &token ) + { + refuse->add_type ( token ); + }, mPile->settings.refuse.type ); + + // corpses + refuse_write_helper ( [=] ( const std::string &id ) + { + refuse->add_corpses ( id ); + }, mPile->settings.refuse.corpses ); + // body_parts + refuse_write_helper ( [=] ( const std::string &id ) + { + refuse->add_body_parts ( id ); + }, mPile->settings.refuse.body_parts ); + // skulls + refuse_write_helper ( [=] ( const std::string &id ) + { + refuse->add_skulls ( id ); + }, mPile->settings.refuse.skulls ); + // bones + refuse_write_helper ( [=] ( const std::string &id ) + { + refuse->add_bones ( id ); + }, mPile->settings.refuse.bones ); + // hair + refuse_write_helper ( [=] ( const std::string &id ) + { + refuse->add_hair ( id ); + }, mPile->settings.refuse.hair ); + // shells + refuse_write_helper ( [=] ( const std::string &id ) + { + refuse->add_shells ( id ); + }, mPile->settings.refuse.shells ); + // teeth + refuse_write_helper ( [=] ( const std::string &id ) + { + refuse->add_teeth ( id ); + }, mPile->settings.refuse.teeth ); + // horns + refuse_write_helper ( [=] ( const std::string &id ) + { + refuse->add_horns ( id ); + }, mPile->settings.refuse.horns ); +} + +void StockpileSerializer::refuse_read_helper ( std::function get_value, size_t list_size, std::vector* pile_list ) +{ + pile_list->clear(); + pile_list->resize ( world->raws.creatures.all.size(), '\0' ); + if ( list_size > 0 ) + { + for ( size_t i = 0; i < list_size; ++i ) + { + const std::string creature_id = get_value ( i ); + const int idx = find_creature ( creature_id ); + const df::creature_raw* creature = find_creature ( idx ); + if ( idx < 0 || !refuse_creature_is_allowed ( creature ) || idx >= pile_list->size() ) + { + debug() << "WARNING invalid refuse creature " << creature_id << ", idx=" << idx << endl; + continue; + } + debug() << " creature " << idx << " is " << creature_id << endl; + pile_list->at ( idx ) = 1; + } + } +} + + + +void StockpileSerializer::read_refuse() +{ + if ( mBuffer.has_refuse() ) + { + mPile->settings.flags.bits.refuse = 1; + const StockpileSettings::RefuseSet refuse = mBuffer.refuse(); + debug() << "refuse: " <settings.refuse.fresh_raw_hide = refuse.fresh_raw_hide(); + mPile->settings.refuse.rotten_raw_hide = refuse.rotten_raw_hide(); + + // type + FuncItemAllowed filter = std::bind ( &StockpileSerializer::refuse_type_is_allowed, this, _1 ); + unserialize_list_item_type ( filter, [=] ( const size_t & idx ) -> const std::string& + { + return refuse.type ( idx ); + }, refuse.type_size(), &mPile->settings.refuse.type ); + + // corpses + debug() << " corpses" << endl; + refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& + { + return refuse.corpses ( idx ); + }, refuse.corpses_size(), &mPile->settings.refuse.corpses ); + // body_parts + debug() << " body_parts" << endl; + refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& + { + return refuse.body_parts ( idx ); + }, refuse.body_parts_size(), &mPile->settings.refuse.body_parts ); + // skulls + debug() << " skulls" << endl; + refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& + { + return refuse.skulls ( idx ); + }, refuse.skulls_size(), &mPile->settings.refuse.skulls ); + // bones + debug() << " bones" << endl; + refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& + { + return refuse.bones ( idx ); + }, refuse.bones_size(), &mPile->settings.refuse.bones ); + // hair + debug() << " hair" << endl; + refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& + { + return refuse.hair ( idx ); + }, refuse.hair_size(), &mPile->settings.refuse.hair ); + // shells + debug() << " shells" << endl; + refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& + { + return refuse.shells ( idx ); + }, refuse.shells_size(), &mPile->settings.refuse.shells ); + // teeth + debug() << " teeth" << endl; + refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& + { + return refuse.teeth ( idx ); + }, refuse.teeth_size(), &mPile->settings.refuse.teeth ); + // horns + debug() << " horns" << endl; + refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& + { + return refuse.horns ( idx ); + }, refuse.horns_size(), &mPile->settings.refuse.horns ); + } + else + { + mPile->settings.flags.bits.refuse = 0; + mPile->settings.refuse.type.clear(); + mPile->settings.refuse.corpses.clear(); + mPile->settings.refuse.body_parts.clear(); + mPile->settings.refuse.skulls.clear(); + mPile->settings.refuse.bones.clear(); + mPile->settings.refuse.hair.clear(); + mPile->settings.refuse.shells.clear(); + mPile->settings.refuse.teeth.clear(); + mPile->settings.refuse.horns.clear(); + mPile->settings.refuse.fresh_raw_hide = false; + mPile->settings.refuse.rotten_raw_hide = false; + } +} + +bool StockpileSerializer::stone_is_allowed ( const MaterialInfo &mi ) +{ + if ( !mi.isValid() ) return false; + const bool is_allowed_soil = mi.inorganic->flags.is_set ( inorganic_flags::SOIL ) && !mi.inorganic->flags.is_set ( inorganic_flags::AQUIFER ); + const bool is_allowed_stone = mi.material->flags.is_set ( material_flags::IS_STONE ) && !mi.material->flags.is_set ( material_flags::NO_STONE_STOCKPILE ); + return is_allowed_soil || is_allowed_stone; +} + +void StockpileSerializer::write_stone() +{ + StockpileSettings::StoneSet *stone= mBuffer.mutable_stone(); + + FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::stone_is_allowed, this, _1 ); + serialize_list_material ( filter, [=] ( const std::string &token ) + { + stone->add_mats ( token ); + }, mPile->settings.stone.mats ); +} + +void StockpileSerializer::read_stone() +{ + if ( mBuffer.has_stone() ) + { + mPile->settings.flags.bits.stone = 1; + const StockpileSettings::StoneSet stone = mBuffer.stone(); + debug() << "stone: " < const std::string& + { + return stone.mats ( idx ); + }, stone.mats_size(), &mPile->settings.stone.mats ); + } + else + { + mPile->settings.flags.bits.stone = 0; + mPile->settings.stone.mats.clear(); + } +} + +bool StockpileSerializer::ammo_mat_is_allowed ( const MaterialInfo &mi ) +{ + return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_METAL ); +} + +void StockpileSerializer::write_ammo() +{ + StockpileSettings::AmmoSet *ammo = mBuffer.mutable_ammo(); + + // ammo type + serialize_list_itemdef ( [=] ( const std::string &token ) + { + ammo->add_type ( token ); + }, mPile->settings.ammo.type, + std::vector ( world->raws.itemdefs.ammo.begin(),world->raws.itemdefs.ammo.end() ), + item_type::AMMO ); + + // metal + FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::ammo_mat_is_allowed, this, _1 ); + serialize_list_material ( filter, [=] ( const std::string &token ) + { + ammo->add_mats ( token ); + }, mPile->settings.ammo.mats ); + + // other mats - only wood and bone + if ( mPile->settings.ammo.other_mats.size() > 2 ) + { + debug() << "WARNING: ammo other materials > 2! " << mPile->settings.ammo.other_mats.size() << endl; + } + + for ( size_t i = 0; i < std::min ( size_t ( 2 ), mPile->settings.ammo.other_mats.size() ); ++i ) + { + if ( !mPile->settings.ammo.other_mats.at ( i ) ) + continue; + const std::string token = i == 0 ? "WOOD" : "BONE"; + ammo->add_other_mats ( token ); + debug() << " other mats " << i << " is " << token << endl; + } + + // quality core + serialize_list_quality ( [=] ( const std::string &token ) + { + ammo->add_quality_core ( token ); + }, mPile->settings.ammo.quality_core ); + + // quality total + serialize_list_quality ( [=] ( const std::string &token ) + { + ammo->add_quality_total ( token ); + }, mPile->settings.ammo.quality_total ); +} + +void StockpileSerializer::read_ammo() +{ + if ( mBuffer.has_ammo() ) + { + mPile->settings.flags.bits.ammo = 1; + const StockpileSettings::AmmoSet ammo = mBuffer.ammo(); + debug() << "ammo: " < const std::string& + { + return ammo.type ( idx ); + }, ammo.type_size(), &mPile->settings.ammo.type, item_type::AMMO ); + + // materials metals + FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::ammo_mat_is_allowed, this, _1 ); + unserialize_list_material ( filter, [=] ( const size_t & idx ) -> const std::string& + { + return ammo.mats ( idx ); + }, ammo.mats_size(), &mPile->settings.ammo.mats ); + + // others + mPile->settings.ammo.other_mats.clear(); + mPile->settings.ammo.other_mats.resize ( 2, '\0' ); + if ( ammo.other_mats_size() > 0 ) + { + // TODO remove hardcoded value + for ( int i = 0; i < ammo.other_mats_size(); ++i ) + { + const std::string token = ammo.other_mats ( i ); + const int32_t idx = token == "WOOD" ? 0 : token == "BONE" ? 1 : -1; + debug() << " other mats " << idx << " is " << token << endl; + if ( idx != -1 ) + mPile->settings.ammo.other_mats.at ( idx ) = 1; + } + } + + // core quality + unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& + { + return ammo.quality_core ( idx ); + }, ammo.quality_core_size(), mPile->settings.ammo.quality_core ); + + // total quality + unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& + { + return ammo.quality_total ( idx ); + }, ammo.quality_total_size(), mPile->settings.ammo.quality_total ); + } + else + { + mPile->settings.flags.bits.ammo = 0; + mPile->settings.ammo.type.clear(); + mPile->settings.ammo.mats.clear(); + mPile->settings.ammo.other_mats.clear(); + quality_clear ( mPile->settings.ammo.quality_core ); + quality_clear ( mPile->settings.ammo.quality_total ); + } +} + +bool StockpileSerializer::coins_mat_is_allowed ( const MaterialInfo &mi ) +{ + return mi.isValid(); +} + +void StockpileSerializer::write_coins() +{ + StockpileSettings::CoinSet *coins = mBuffer.mutable_coin(); + FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::coins_mat_is_allowed, this, _1 ); + serialize_list_material ( filter, [=] ( const std::string &token ) + { + coins->add_mats ( token ); + }, mPile->settings.coins.mats ); +} + +void StockpileSerializer::read_coins() +{ + if ( mBuffer.has_coin() ) + { + mPile->settings.flags.bits.coins = 1; + const StockpileSettings::CoinSet coins = mBuffer.coin(); + debug() << "coins: " < const std::string& + { + return coins.mats ( idx ); + }, coins.mats_size(), &mPile->settings.coins.mats ); + } + else + { + mPile->settings.flags.bits.coins = 0; + mPile->settings.coins.mats.clear(); + } +} + +void StockpileSerializer::bars_blocks_setup_other_mats() +{ + mOtherMatsBars.insert ( std::make_pair ( 0,"COAL" ) ); + mOtherMatsBars.insert ( std::make_pair ( 1,"POTASH" ) ); + mOtherMatsBars.insert ( std::make_pair ( 2,"ASH" ) ); + mOtherMatsBars.insert ( std::make_pair ( 3,"PEARLASH" ) ); + mOtherMatsBars.insert ( std::make_pair ( 4,"SOAP" ) ); + + mOtherMatsBlocks.insert ( std::make_pair ( 0,"GREEN_GLASS" ) ); + mOtherMatsBlocks.insert ( std::make_pair ( 1,"CLEAR_GLASS" ) ); + mOtherMatsBlocks.insert ( std::make_pair ( 2,"CRYSTAL_GLASS" ) ); + mOtherMatsBlocks.insert ( std::make_pair ( 3,"WOOD" ) ); +} + +bool StockpileSerializer::bars_mat_is_allowed ( const MaterialInfo &mi ) +{ + return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_METAL ); +} + +bool StockpileSerializer::blocks_mat_is_allowed ( const MaterialInfo &mi ) +{ + return mi.isValid() && mi.material + && ( mi.material->flags.is_set ( material_flags::IS_METAL ) + || mi.material->flags.is_set ( material_flags::IS_STONE ) ); +} + + +void StockpileSerializer::write_bars_blocks() +{ + StockpileSettings::BarsBlocksSet *bars_blocks = mBuffer.mutable_barsblocks(); + MaterialInfo mi; + FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::bars_mat_is_allowed, this, _1 ); + serialize_list_material ( filter, [=] ( const std::string &token ) + { + bars_blocks->add_bars_mats ( token ); + }, mPile->settings.bars_blocks.bars_mats ); + + // blocks mats + filter = std::bind ( &StockpileSerializer::blocks_mat_is_allowed, this, _1 ); + serialize_list_material ( filter, [=] ( const std::string &token ) + { + bars_blocks->add_blocks_mats ( token ); + }, mPile->settings.bars_blocks.blocks_mats ); + + // bars other mats + serialize_list_other_mats ( mOtherMatsBars, [=] ( const std::string &token ) + { + bars_blocks->add_bars_other_mats ( token ); + }, mPile->settings.bars_blocks.bars_other_mats ); + + // blocks other mats + serialize_list_other_mats ( mOtherMatsBlocks, [=] ( const std::string &token ) + { + bars_blocks->add_blocks_other_mats ( token ); + }, mPile->settings.bars_blocks.blocks_other_mats ); +} + +void StockpileSerializer::read_bars_blocks() +{ + if ( mBuffer.has_barsblocks() ) + { + mPile->settings.flags.bits.bars_blocks = 1; + const StockpileSettings::BarsBlocksSet bars_blocks = mBuffer.barsblocks(); + debug() << "bars_blocks: " < const std::string& + { + return bars_blocks.bars_mats ( idx ); + }, bars_blocks.bars_mats_size(), &mPile->settings.bars_blocks.bars_mats ); + + // blocks + filter = std::bind ( &StockpileSerializer::blocks_mat_is_allowed, this, _1 ); + unserialize_list_material ( filter, [=] ( const size_t & idx ) -> const std::string& + { + return bars_blocks.blocks_mats ( idx ); + }, bars_blocks.blocks_mats_size(), &mPile->settings.bars_blocks.blocks_mats ); + // bars other mats + unserialize_list_other_mats ( mOtherMatsBars, [=] ( const size_t & idx ) -> const std::string& + { + return bars_blocks.bars_other_mats ( idx ); + }, bars_blocks.bars_other_mats_size(), &mPile->settings.bars_blocks.bars_other_mats ); + + + // blocks other mats + unserialize_list_other_mats ( mOtherMatsBlocks, [=] ( const size_t & idx ) -> const std::string& + { + return bars_blocks.blocks_other_mats ( idx ); + }, bars_blocks.blocks_other_mats_size(), &mPile->settings.bars_blocks.blocks_other_mats ); + + } + else + { + mPile->settings.flags.bits.bars_blocks = 0; + mPile->settings.bars_blocks.bars_other_mats.clear(); + mPile->settings.bars_blocks.bars_mats.clear(); + mPile->settings.bars_blocks.blocks_other_mats.clear(); + mPile->settings.bars_blocks.blocks_mats.clear(); + } +} + +bool StockpileSerializer::gem_mat_is_allowed ( const MaterialInfo &mi ) +{ + return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_GEM ); +} +bool StockpileSerializer::gem_cut_mat_is_allowed ( const MaterialInfo &mi ) +{ + return mi.isValid() && mi.material && ( mi.material->flags.is_set ( material_flags::IS_GEM ) || mi.material->flags.is_set ( material_flags::IS_STONE ) ) ; +} +bool StockpileSerializer::gem_other_mat_is_allowed ( MaterialInfo &mi ) +{ + return mi.isValid() && ( mi.getToken() == "GLASS_GREEN" || mi.getToken() == "GLASS_CLEAR" || mi.getToken() == "GLASS_CRYSTAL" ); +} + +void StockpileSerializer::write_gems() +{ + StockpileSettings::GemsSet *gems = mBuffer.mutable_gems(); + MaterialInfo mi; + // rough mats + FuncMaterialAllowed filter_rough = std::bind ( &StockpileSerializer::gem_mat_is_allowed, this, _1 ); + serialize_list_material ( filter_rough, [=] ( const std::string &token ) + { + gems->add_rough_mats ( token ); + }, mPile->settings.gems.rough_mats ); + // cut mats + FuncMaterialAllowed filter_cut = std::bind ( &StockpileSerializer::gem_cut_mat_is_allowed, this, _1 ); + serialize_list_material ( filter_cut, [=] ( const std::string &token ) + { + gems->add_cut_mats ( token ); + }, mPile->settings.gems.cut_mats ); + // rough other + for ( size_t i = 0; i < mPile->settings.gems.rough_other_mats.size(); ++i ) + { + if ( mPile->settings.gems.rough_other_mats.at ( i ) ) + { + mi.decode ( i, -1 ); + if ( !gem_other_mat_is_allowed ( mi ) ) continue; + debug() << " gem rough_other mat" << i << " is " << mi.getToken() << endl; + gems->add_rough_other_mats ( mi.getToken() ); + } + } + // cut other + for ( size_t i = 0; i < mPile->settings.gems.cut_other_mats.size(); ++i ) + { + if ( mPile->settings.gems.cut_other_mats.at ( i ) ) + { + mi.decode ( i, -1 ); + if ( !mi.isValid() ) mi.decode ( 0, i ); + if ( !gem_other_mat_is_allowed ( mi ) ) continue; + debug() << " gem cut_other mat" << i << " is " << mi.getToken() << endl; + gems->add_cut_other_mats ( mi.getToken() ); + } + } +} + +void StockpileSerializer::read_gems() +{ + if ( mBuffer.has_gems() ) + { + mPile->settings.flags.bits.gems = 1; + const StockpileSettings::GemsSet gems = mBuffer.gems(); + debug() << "gems: " < const std::string& + { + return gems.rough_mats ( idx ); + }, gems.rough_mats_size(), &mPile->settings.gems.rough_mats ); + + // cut + FuncMaterialAllowed filter_cut = std::bind ( &StockpileSerializer::gem_cut_mat_is_allowed, this, _1 ); + unserialize_list_material ( filter_cut, [=] ( const size_t & idx ) -> const std::string& + { + return gems.cut_mats ( idx ); + }, gems.cut_mats_size(), &mPile->settings.gems.cut_mats ); + + const size_t builtin_size = std::extentraws.mat_table.builtin ) >::value; + // rough other + mPile->settings.gems.rough_other_mats.clear(); + mPile->settings.gems.rough_other_mats.resize ( builtin_size, '\0' ); + for ( int i = 0; i < gems.rough_other_mats_size(); ++i ) + { + const std::string token = gems.rough_other_mats ( i ); + MaterialInfo mi; + mi.find ( token ); + if ( !mi.isValid() || mi.type >= builtin_size ) + { + debug() << "WARNING: invalid gem mat " << token << ". idx=" << mi.type << endl; + continue; + } + debug() << " rough_other mats " << mi.type << " is " << token << endl; + mPile->settings.gems.rough_other_mats.at ( mi.type ) = 1; + } + + // cut other + mPile->settings.gems.cut_other_mats.clear(); + mPile->settings.gems.cut_other_mats.resize ( builtin_size, '\0' ); + for ( int i = 0; i < gems.cut_other_mats_size(); ++i ) + { + const std::string token = gems.cut_other_mats ( i ); + MaterialInfo mi; + mi.find ( token ); + if ( !mi.isValid() || mi.type >= builtin_size ) + { + debug() << "WARNING: invalid gem mat " << token << ". idx=" << mi.type << endl; + continue; + } + debug() << " cut_other mats " << mi.type << " is " << token << endl; + mPile->settings.gems.cut_other_mats.at ( mi.type ) = 1; + } + } + else + { + mPile->settings.flags.bits.gems = 0; + mPile->settings.gems.cut_other_mats.clear(); + mPile->settings.gems.cut_mats.clear(); + mPile->settings.gems.rough_other_mats.clear(); + mPile->settings.gems.rough_mats.clear(); + } +} + +bool StockpileSerializer::finished_goods_type_is_allowed ( item_type::item_type type ) +{ + switch ( type ) + { + case item_type::CHAIN: + case item_type::FLASK: + case item_type::GOBLET: + case item_type::INSTRUMENT: + case item_type::TOY: + case item_type::ARMOR: + case item_type::SHOES: + case item_type::HELM: + case item_type::GLOVES: + case item_type::FIGURINE: + case item_type::AMULET: + case item_type::SCEPTER: + case item_type::CROWN: + case item_type::RING: + case item_type::EARRING: + case item_type::BRACELET: + case item_type::GEM: + case item_type::TOTEM: + case item_type::PANTS: + case item_type::BACKPACK: + case item_type::QUIVER: + case item_type::SPLINT: + case item_type::CRUTCH: + case item_type::TOOL: + case item_type::BOOK: + return true; + default: + return false; + } + +} + +void StockpileSerializer::finished_goods_setup_other_mats() +{ + mOtherMatsFinishedGoods.insert ( std::make_pair ( 0,"WOOD" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 1,"PLANT_CLOTH" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 2,"BONE" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 3,"TOOTH" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 4,"HORN" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 5,"PEARL" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 6,"SHELL" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 7,"LEATHER" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 8,"SILK" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 9,"AMBER" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 10,"CORAL" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 11,"GREEN_GLASS" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 12,"CLEAR_GLASS" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 13,"CRYSTAL_GLASS" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 14,"YARN" ) ); + mOtherMatsFinishedGoods.insert ( std::make_pair ( 15,"WAX" ) ); +} + +bool StockpileSerializer::finished_goods_mat_is_allowed ( const MaterialInfo &mi ) +{ + return mi.isValid() + && mi.material + && ( mi.material->flags.is_set ( material_flags::IS_GEM ) + || mi.material->flags.is_set ( material_flags::IS_METAL ) + || mi.material->flags.is_set ( material_flags::IS_STONE ) ) ; +} + +void StockpileSerializer::write_finished_goods() +{ + StockpileSettings::FinishedGoodsSet *finished_goods = mBuffer.mutable_finished_goods(); + + // type + FuncItemAllowed filter = std::bind ( &StockpileSerializer::finished_goods_type_is_allowed, this, _1 ); + serialize_list_item_type ( filter, [=] ( const std::string &token ) + { + finished_goods->add_type ( token ); + }, mPile->settings.finished_goods.type ); + + // materials + FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::finished_goods_mat_is_allowed, this, _1 ); + serialize_list_material ( mat_filter, [=] ( const std::string &token ) + { + finished_goods->add_mats ( token ); + }, mPile->settings.finished_goods.mats ); + + // other mats + serialize_list_other_mats ( mOtherMatsFinishedGoods, [=] ( const std::string &token ) + { + finished_goods->add_other_mats ( token ); + }, mPile->settings.finished_goods.other_mats ); + + // quality core + serialize_list_quality ( [=] ( const std::string &token ) + { + finished_goods->add_quality_core ( token ); + }, mPile->settings.finished_goods.quality_core ); + + // quality total + serialize_list_quality ( [=] ( const std::string &token ) + { + finished_goods->add_quality_total ( token ); + }, mPile->settings.finished_goods.quality_total ); +} + +void StockpileSerializer::read_finished_goods() +{ + if ( mBuffer.has_finished_goods() ) + { + mPile->settings.flags.bits.finished_goods = 1; + const StockpileSettings::FinishedGoodsSet finished_goods = mBuffer.finished_goods(); + debug() << "finished_goods: " < const std::string& + { + return finished_goods.type ( idx ); + }, finished_goods.type_size(), &mPile->settings.finished_goods.type ); + + // materials + FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::finished_goods_mat_is_allowed, this, _1 ); + unserialize_list_material ( mat_filter, [=] ( const size_t & idx ) -> const std::string& + { + return finished_goods.mats ( idx ); + }, finished_goods.mats_size(), &mPile->settings.finished_goods.mats ); + + // other mats + unserialize_list_other_mats ( mOtherMatsFinishedGoods, [=] ( const size_t & idx ) -> const std::string& + { + return finished_goods.other_mats ( idx ); + }, finished_goods.other_mats_size(), &mPile->settings.finished_goods.other_mats ); + + // core quality + unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& + { + return finished_goods.quality_core ( idx ); + }, finished_goods.quality_core_size(), mPile->settings.finished_goods.quality_core ); + + // total quality + unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& + { + return finished_goods.quality_total ( idx ); + }, finished_goods.quality_total_size(), mPile->settings.finished_goods.quality_total ); + + } + else + { + mPile->settings.flags.bits.finished_goods = 0; + mPile->settings.finished_goods.type.clear(); + mPile->settings.finished_goods.other_mats.clear(); + mPile->settings.finished_goods.mats.clear(); + quality_clear ( mPile->settings.finished_goods.quality_core ); + quality_clear ( mPile->settings.finished_goods.quality_total ); + } +} + +void StockpileSerializer::write_leather() +{ + StockpileSettings::LeatherSet *leather = mBuffer.mutable_leather(); + + FuncWriteExport setter = [=] ( const std::string &id ) + { + leather->add_mats ( id ); + }; + serialize_list_organic_mat ( setter, &mPile->settings.leather.mats, organic_mat_category::Leather ); +} +void StockpileSerializer::read_leather() +{ + if ( mBuffer.has_leather() ) + { + mPile->settings.flags.bits.leather = 1; + const StockpileSettings::LeatherSet leather = mBuffer.leather(); + debug() << "leather: " < std::string + { + return leather.mats ( idx ); + }, leather.mats_size(), &mPile->settings.leather.mats, organic_mat_category::Leather ); + } + else + { + mPile->settings.flags.bits.leather = 0; + mPile->settings.leather.mats.clear(); + } +} + +void StockpileSerializer::write_cloth() +{ + StockpileSettings::ClothSet * cloth = mBuffer.mutable_cloth(); + + serialize_list_organic_mat ( [=] ( const std::string &token ) + { + cloth->add_thread_silk ( token ); + }, &mPile->settings.cloth.thread_silk, organic_mat_category::Silk ); + + serialize_list_organic_mat ( [=] ( const std::string &token ) + { + cloth->add_thread_plant ( token ); + }, &mPile->settings.cloth.thread_plant, organic_mat_category::PlantFiber ); + + serialize_list_organic_mat ( [=] ( const std::string &token ) + { + cloth->add_thread_yarn ( token ); + }, &mPile->settings.cloth.thread_yarn, organic_mat_category::Yarn ); + + serialize_list_organic_mat ( [=] ( const std::string &token ) + { + cloth->add_thread_metal ( token ); + }, &mPile->settings.cloth.thread_metal, organic_mat_category::MetalThread ); + + serialize_list_organic_mat ( [=] ( const std::string &token ) + { + cloth->add_cloth_silk ( token ); + }, &mPile->settings.cloth.cloth_silk, organic_mat_category::Silk ); + + serialize_list_organic_mat ( [=] ( const std::string &token ) + { + cloth->add_cloth_plant ( token ); + }, &mPile->settings.cloth.cloth_plant, organic_mat_category::PlantFiber ); + + serialize_list_organic_mat ( [=] ( const std::string &token ) + { + cloth->add_cloth_yarn ( token ); + }, &mPile->settings.cloth.cloth_yarn, organic_mat_category::Yarn ); + + serialize_list_organic_mat ( [=] ( const std::string &token ) + { + cloth->add_cloth_metal ( token ); + }, &mPile->settings.cloth.cloth_metal, organic_mat_category::MetalThread ); + +} +void StockpileSerializer::read_cloth() +{ + if ( mBuffer.has_cloth() ) + { + mPile->settings.flags.bits.cloth = 1; + const StockpileSettings::ClothSet cloth = mBuffer.cloth(); + debug() << "cloth: " < std::string + { + return cloth.thread_silk ( idx ); + }, cloth.thread_silk_size(), &mPile->settings.cloth.thread_silk, organic_mat_category::Silk ); + + unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string + { + return cloth.thread_plant ( idx ); + }, cloth.thread_plant_size(), &mPile->settings.cloth.thread_plant, organic_mat_category::PlantFiber ); + + unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string + { + return cloth.thread_yarn ( idx ); + }, cloth.thread_yarn_size(), &mPile->settings.cloth.thread_yarn, organic_mat_category::Yarn ); + + unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string + { + return cloth.thread_metal ( idx ); + }, cloth.thread_metal_size(), &mPile->settings.cloth.thread_metal, organic_mat_category::MetalThread ); + + unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string + { + return cloth.cloth_silk ( idx ); + }, cloth.cloth_silk_size(), &mPile->settings.cloth.cloth_silk, organic_mat_category::Silk ); + + unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string + { + return cloth.cloth_plant ( idx ); + }, cloth.cloth_plant_size(), &mPile->settings.cloth.cloth_plant, organic_mat_category::PlantFiber ); + + unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string + { + return cloth.cloth_yarn ( idx ); + }, cloth.cloth_yarn_size(), &mPile->settings.cloth.cloth_yarn, organic_mat_category::Yarn ); + + unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string + { + return cloth.cloth_metal ( idx ); + }, cloth.cloth_metal_size(), &mPile->settings.cloth.cloth_metal, organic_mat_category::MetalThread ); + } + else + { + mPile->settings.cloth.thread_metal.clear(); + mPile->settings.cloth.thread_plant.clear(); + mPile->settings.cloth.thread_silk.clear(); + mPile->settings.cloth.thread_yarn.clear(); + mPile->settings.cloth.cloth_metal.clear(); + mPile->settings.cloth.cloth_plant.clear(); + mPile->settings.cloth.cloth_silk.clear(); + mPile->settings.cloth.cloth_yarn.clear(); + mPile->settings.flags.bits.cloth = 0; + } +} + +bool StockpileSerializer::wood_mat_is_allowed ( const df::plant_raw * plant ) +{ + return plant && plant->flags.is_set ( plant_raw_flags::TREE ); +} + +void StockpileSerializer::write_wood() +{ + StockpileSettings::WoodSet * wood = mBuffer.mutable_wood(); + for ( size_t i = 0; i < mPile->settings.wood.mats.size(); ++i ) + { + if ( mPile->settings.wood.mats.at ( i ) ) + { + const df::plant_raw * plant = find_plant ( i ); + if ( !wood_mat_is_allowed ( plant ) ) continue; + wood->add_mats ( plant->id ); + debug() << " plant " << i << " is " << plant->id << endl; + } + } +} +void StockpileSerializer::read_wood() +{ + if ( mBuffer.has_wood() ) + { + mPile->settings.flags.bits.wood = 1; + const StockpileSettings::WoodSet wood = mBuffer.wood(); + debug() << "wood: " <settings.wood.mats.clear(); + mPile->settings.wood.mats.resize ( world->raws.plants.all.size(), '\0' ); + for ( int i = 0; i < wood.mats_size(); ++i ) + { + const std::string token = wood.mats ( i ); + const size_t idx = find_plant ( token ); + if ( idx < 0 || idx >= mPile->settings.wood.mats.size() ) + { + debug() << "WARNING wood mat index invalid " << token << ", idx=" << idx << endl; + continue; + } + debug() << " plant " << idx << " is " << token << endl; + mPile->settings.wood.mats.at ( idx ) = 1; + } + } + else + { + mPile->settings.flags.bits.wood = 0; + mPile->settings.wood.mats.clear(); + } +} + +bool StockpileSerializer::weapons_mat_is_allowed ( const MaterialInfo &mi ) +{ + return mi.isValid() && mi.material && ( + mi.material->flags.is_set ( material_flags::IS_METAL ) || + mi.material->flags.is_set ( material_flags::IS_STONE ) ); + +} + +void StockpileSerializer::write_weapons() +{ + StockpileSettings::WeaponsSet * weapons = mBuffer.mutable_weapons(); + + weapons->set_unusable ( mPile->settings.weapons.unusable ); + weapons->set_usable ( mPile->settings.weapons.usable ); + + // weapon type + serialize_list_itemdef ( [=] ( const std::string &token ) + { + weapons->add_weapon_type ( token ); + }, mPile->settings.weapons.weapon_type, + std::vector ( world->raws.itemdefs.weapons.begin(),world->raws.itemdefs.weapons.end() ), + item_type::WEAPON ); + + // trapcomp type + serialize_list_itemdef ( [=] ( const std::string &token ) + { + weapons->add_trapcomp_type ( token ); + }, mPile->settings.weapons.trapcomp_type, + std::vector ( world->raws.itemdefs.trapcomps.begin(),world->raws.itemdefs.trapcomps.end() ), + item_type::TRAPCOMP ); + + // materials + FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::weapons_mat_is_allowed, this, _1 ); + serialize_list_material ( mat_filter, [=] ( const std::string &token ) + { + weapons->add_mats ( token ); + }, mPile->settings.weapons.mats ); + + // other mats + serialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const std::string &token ) + { + weapons->add_other_mats ( token ); + }, mPile->settings.weapons.other_mats ); + + // quality core + serialize_list_quality ( [=] ( const std::string &token ) + { + weapons->add_quality_core ( token ); + }, mPile->settings.weapons.quality_core ); + + // quality total + serialize_list_quality ( [=] ( const std::string &token ) + { + weapons->add_quality_total ( token ); + }, mPile->settings.weapons.quality_total ); +} + +void StockpileSerializer::read_weapons() +{ + if ( mBuffer.has_weapons() ) + { + mPile->settings.flags.bits.weapons = 1; + const StockpileSettings::WeaponsSet weapons = mBuffer.weapons(); + debug() << "weapons: " < const std::string& + { + return weapons.weapon_type ( idx ); + }, weapons.weapon_type_size(), &mPile->settings.weapons.weapon_type, item_type::WEAPON ); + + // trapcomp type + unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& + { + return weapons.trapcomp_type ( idx ); + }, weapons.trapcomp_type_size(), &mPile->settings.weapons.trapcomp_type, item_type::TRAPCOMP ); + + // materials + FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::weapons_mat_is_allowed, this, _1 ); + unserialize_list_material ( mat_filter, [=] ( const size_t & idx ) -> const std::string& + { + return weapons.mats ( idx ); + }, weapons.mats_size(), &mPile->settings.weapons.mats ); + + // other mats + unserialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const size_t & idx ) -> const std::string& + { + return weapons.other_mats ( idx ); + }, weapons.other_mats_size(), &mPile->settings.weapons.other_mats ); + + + // core quality + unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& + { + return weapons.quality_core ( idx ); + }, weapons.quality_core_size(), mPile->settings.weapons.quality_core ); + // total quality + unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& + { + return weapons.quality_total ( idx ); + }, weapons.quality_total_size(), mPile->settings.weapons.quality_total ); + } + else + { + mPile->settings.flags.bits.weapons = 0; + mPile->settings.weapons.weapon_type.clear(); + mPile->settings.weapons.trapcomp_type.clear(); + mPile->settings.weapons.other_mats.clear(); + mPile->settings.weapons.mats.clear(); + quality_clear ( mPile->settings.weapons.quality_core ); + quality_clear ( mPile->settings.weapons.quality_total ); + } + +} + +void StockpileSerializer::weapons_armor_setup_other_mats() +{ + mOtherMatsWeaponsArmor.insert ( std::make_pair ( 0,"WOOD" ) ); + mOtherMatsWeaponsArmor.insert ( std::make_pair ( 1,"PLANT_CLOTH" ) ); + mOtherMatsWeaponsArmor.insert ( std::make_pair ( 2,"BONE" ) ); + mOtherMatsWeaponsArmor.insert ( std::make_pair ( 3,"SHELL" ) ); + mOtherMatsWeaponsArmor.insert ( std::make_pair ( 4,"LEATHER" ) ); + mOtherMatsWeaponsArmor.insert ( std::make_pair ( 5,"SILK" ) ); + mOtherMatsWeaponsArmor.insert ( std::make_pair ( 6,"GREEN_GLASS" ) ); + mOtherMatsWeaponsArmor.insert ( std::make_pair ( 7,"CLEAR_GLASS" ) ); + mOtherMatsWeaponsArmor.insert ( std::make_pair ( 8,"CRYSTAL_GLASS" ) ); + mOtherMatsWeaponsArmor.insert ( std::make_pair ( 9,"YARN" ) ); +} + +bool StockpileSerializer::armor_mat_is_allowed ( const MaterialInfo &mi ) +{ + return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_METAL ); +} + +void StockpileSerializer::write_armor() +{ + StockpileSettings::ArmorSet * armor = mBuffer.mutable_armor(); + + armor->set_unusable ( mPile->settings.armor.unusable ); + armor->set_usable ( mPile->settings.armor.usable ); + + // armor type + serialize_list_itemdef ( [=] ( const std::string &token ) + { + armor->add_body ( token ); + }, mPile->settings.armor.body, + std::vector ( world->raws.itemdefs.armor.begin(),world->raws.itemdefs.armor.end() ), + item_type::ARMOR ); + + // helm type + serialize_list_itemdef ( [=] ( const std::string &token ) + { + armor->add_head ( token ); + }, mPile->settings.armor.head, + std::vector ( world->raws.itemdefs.helms.begin(),world->raws.itemdefs.helms.end() ), + item_type::HELM ); + + // shoes type + serialize_list_itemdef ( [=] ( const std::string &token ) + { + armor->add_feet ( token ); + }, mPile->settings.armor.feet, + std::vector ( world->raws.itemdefs.shoes.begin(),world->raws.itemdefs.shoes.end() ), + item_type::SHOES ); + + // gloves type + serialize_list_itemdef ( [=] ( const std::string &token ) + { + armor->add_hands ( token ); + }, mPile->settings.armor.hands, + std::vector ( world->raws.itemdefs.gloves.begin(),world->raws.itemdefs.gloves.end() ), + item_type::GLOVES ); + + // pant type + serialize_list_itemdef ( [=] ( const std::string &token ) + { + armor->add_legs ( token ); + }, mPile->settings.armor.legs, + std::vector ( world->raws.itemdefs.pants.begin(),world->raws.itemdefs.pants.end() ), + item_type::PANTS ); + + // shield type + serialize_list_itemdef ( [=] ( const std::string &token ) + { + armor->add_shield ( token ); + }, mPile->settings.armor.shield, + std::vector ( world->raws.itemdefs.shields.begin(),world->raws.itemdefs.shields.end() ), + item_type::SHIELD ); + + // materials + FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::armor_mat_is_allowed, this, _1 ); + serialize_list_material ( mat_filter, [=] ( const std::string &token ) + { + armor->add_mats ( token ); + }, mPile->settings.armor.mats ); + + // other mats + serialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const std::string &token ) + { + armor->add_other_mats ( token ); + }, mPile->settings.armor.other_mats ); + + // quality core + serialize_list_quality ( [=] ( const std::string &token ) + { + armor->add_quality_core ( token ); + }, mPile->settings.armor.quality_core ); + + // quality total + serialize_list_quality ( [=] ( const std::string &token ) + { + armor->add_quality_total ( token ); + }, mPile->settings.armor.quality_total ); +} + +void StockpileSerializer::read_armor() +{ + if ( mBuffer.has_armor() ) + { + mPile->settings.flags.bits.armor = 1; + const StockpileSettings::ArmorSet armor = mBuffer.armor(); + debug() << "armor: " < const std::string& + { + return armor.body ( idx ); + }, armor.body_size(), &mPile->settings.armor.body, item_type::ARMOR ); + + // head type + unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& + { + return armor.head ( idx ); + }, armor.head_size(), &mPile->settings.armor.head, item_type::HELM ); + + // feet type + unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& + { + return armor.feet ( idx ); + }, armor.feet_size(), &mPile->settings.armor.feet, item_type::SHOES ); + + // hands type + unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& + { + return armor.hands ( idx ); + }, armor.hands_size(), &mPile->settings.armor.hands, item_type::GLOVES ); + + // legs type + unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& + { + return armor.legs ( idx ); + }, armor.legs_size(), &mPile->settings.armor.legs, item_type::PANTS ); + + // shield type + unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& + { + return armor.shield ( idx ); + }, armor.shield_size(), &mPile->settings.armor.shield, item_type::SHIELD ); + + // materials + FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::armor_mat_is_allowed, this, _1 ); + unserialize_list_material ( mat_filter, [=] ( const size_t & idx ) -> const std::string& + { + return armor.mats ( idx ); + }, armor.mats_size(), &mPile->settings.armor.mats ); + + // other mats + unserialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const size_t & idx ) -> const std::string& + { + return armor.other_mats ( idx ); + }, armor.other_mats_size(), &mPile->settings.armor.other_mats ); + + // core quality + unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& + { + return armor.quality_core ( idx ); + }, armor.quality_core_size(), mPile->settings.armor.quality_core ); + // total quality + unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& + { + return armor.quality_total ( idx ); + }, armor.quality_total_size(), mPile->settings.armor.quality_total ); + } + else + { + mPile->settings.flags.bits.armor = 0; + mPile->settings.armor.body.clear(); + mPile->settings.armor.head.clear(); + mPile->settings.armor.feet.clear(); + mPile->settings.armor.hands.clear(); + mPile->settings.armor.legs.clear(); + mPile->settings.armor.shield.clear(); + mPile->settings.armor.other_mats.clear(); + mPile->settings.armor.mats.clear(); + quality_clear ( mPile->settings.armor.quality_core ); + quality_clear ( mPile->settings.armor.quality_total ); + } +} diff --git a/plugins/stockpiles/StockpileSerializer.h b/plugins/stockpiles/StockpileSerializer.h new file mode 100644 index 000000000..ff0a38241 --- /dev/null +++ b/plugins/stockpiles/StockpileSerializer.h @@ -0,0 +1,349 @@ +#pragma once + +// stockpiles plugin +#include "proto/stockpiles.pb.h" + +// dfhack +#include "modules/Materials.h" +#include "modules/Items.h" + +// df +#include "df/world.h" +#include "df/world_data.h" +#include "df/organic_mat_category.h" +#include "df/furniture_type.h" +#include "df/item_quality.h" +#include "df/item_type.h" + +// stl +#include +#include +#include +#include + +namespace df { +struct building_stockpilest; +} + + +/** + * Null buffer that acts like /dev/null for when debug is disabled + */ +class NullBuffer : public std::streambuf +{ +public: + int overflow ( int c ); +}; + +class NullStream : public std::ostream +{ +public: + NullStream(); +private: + NullBuffer m_sb; +}; + + +/** + * Class for serializing the stockpile_settings structure into a Google protobuf + */ +class StockpileSerializer +{ +public: + /** + * @param out for debugging + * @param stockpile stockpile to read or write settings to + */ + + StockpileSerializer ( df::building_stockpilest * stockpile ); + + ~StockpileSerializer(); + + void enable_debug ( std::ostream &out ); + + /** + * Since we depend on protobuf-lite, not the full lib, we copy this function from + * protobuf message.cc + */ + bool serialize_to_ostream(std::ostream* output); + + /** + * Will serialize stockpile settings to a file (overwrites existing files) + * @return success/failure + */ + bool serialize_to_file ( const std::string & file ); + + /** + * Again, copied from message.cc + */ + bool parse_from_istream(std::istream* input); + + + /** + * Read stockpile settings from file + */ + bool unserialize_from_file ( const std::string & file ); + +private: + + bool mDebug; + std::ostream * mOut; + NullStream mNull; + df::building_stockpilest * mPile; + dfstockpiles::StockpileSettings mBuffer; + std::map mOtherMatsFurniture; + std::map mOtherMatsFinishedGoods; + std::map mOtherMatsBars; + std::map mOtherMatsBlocks; + std::map mOtherMatsWeaponsArmor; + + + std::ostream & debug(); + + /** + read memory structures and serialize to protobuf + */ + void write(); + + // parse serialized data into ui indices + void read (); + + /** + * Find an enum's value based off the string label. + * @param traits the enum's trait struct + * @param token the string value in key_table + * @return the enum's value, -1 if not found + */ + template + static typename df::enum_traits::base_type linear_index ( std::ostream & out, df::enum_traits traits, const std::string &token ) + { + auto j = traits.first_item_value; + auto limit = traits.last_item_value; + // sometimes enums start at -1, which is bad news for array indexing + if ( j < 0 ) + { + j += abs ( traits.first_item_value ); + limit += abs ( traits.first_item_value ); + } + for ( ; j <= limit; ++j ) + { + // out << " linear_index("<< token <<") = table["< FuncReadImport; + // add the token to the serialized list during export + typedef std::function FuncWriteExport; + // are item's of item_type allowed? + typedef std::function FuncItemAllowed; + // is this material allowed? + typedef std::function FuncMaterialAllowed; + + // convenient struct for parsing food stockpile items + struct food_pair + { + // exporting + FuncWriteExport set_value; + std::vector * stockpile_values; + // importing + FuncReadImport get_value; + size_t serialized_count; + bool valid; + + food_pair ( FuncWriteExport s, std::vector* sp_v, FuncReadImport g, size_t count ) + : set_value ( s ) + , stockpile_values ( sp_v ) + , get_value ( g ) + , serialized_count ( count ) + , valid ( true ) + {} + food_pair(): valid( false ) {} + }; + + /** + * There are many repeated (un)serialization cases throughout the stockpile_settings structure, + * so the most common cases have been generalized into generic functions using lambdas. + * + * The basic process to serialize a stockpile_settings structure is: + * 1. loop through the list + * 2. for every element that is TRUE: + * 3. map the specific stockpile_settings index into a general material, creature, etc index + * 4. verify that type is allowed in the list (e.g., no stone in gems stockpiles) + * 5. add it to the protobuf using FuncWriteExport + * + * The unserialization process is the same in reverse. + */ + void serialize_list_organic_mat ( FuncWriteExport add_value, const std::vector * list, df::enums::organic_mat_category::organic_mat_category cat ); + + /** + * @see serialize_list_organic_mat + */ + void unserialize_list_organic_mat ( FuncReadImport get_value, size_t list_size, std::vector *pile_list, df::enums::organic_mat_category::organic_mat_category cat ); + + + /** + * @see serialize_list_organic_mat + */ + void serialize_list_item_type ( FuncItemAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ); + + + /** + * @see serialize_list_organic_mat + */ + void unserialize_list_item_type ( FuncItemAllowed is_allowed, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ); + + + /** + * @see serialize_list_organic_mat + */ + void serialize_list_material ( FuncMaterialAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ); + + /** + * @see serialize_list_organic_mat + */ + void unserialize_list_material ( FuncMaterialAllowed is_allowed, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ); + + /** + * @see serialize_list_organic_mat + */ + void serialize_list_quality ( FuncWriteExport add_value, const bool ( &quality_list ) [7] ); + + /** + * Set all values in a bool[7] to false + */ + void quality_clear ( bool ( &pile_list ) [7] ); + + + /** + * @see serialize_list_organic_mat + */ + void unserialize_list_quality ( FuncReadImport read_value, int32_t list_size, bool ( &pile_list ) [7] ); + + + /** + * @see serialize_list_organic_mat + */ + void serialize_list_other_mats ( const std::map other_mats, FuncWriteExport add_value, std::vector list ); + + /** + * @see serialize_list_organic_mat + */ + void unserialize_list_other_mats ( const std::map other_mats, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ); + + + /** + * @see serialize_list_organic_mat + */ + void serialize_list_itemdef ( FuncWriteExport add_value, std::vector list, std::vector items, df::enums::item_type::item_type type ); + + + /** + * @see serialize_list_organic_mat + */ + void unserialize_list_itemdef ( FuncReadImport read_value, int32_t list_size, std::vector *pile_list, df::enums::item_type::item_type type ); + + + /** + * Given a list of other_materials and an index, return its corresponding token + * @return empty string if not found + * @see other_mats_token + */ + std::string other_mats_index ( const std::map other_mats, int idx ); + + /** + * Given a list of other_materials and a token, return its corresponding index + * @return -1 if not found + * @see other_mats_index + */ + int other_mats_token ( const std::map other_mats, const std::string & token ); + + void write_general(); + void read_general(); + + + void write_animals(); + void read_animals(); + + + food_pair food_map ( df::enums::organic_mat_category::organic_mat_category cat ); + + + void write_food(); + void read_food(); + + void furniture_setup_other_mats(); + void write_furniture(); + bool furniture_mat_is_allowed ( const DFHack::MaterialInfo &mi ); + void read_furniture(); + + bool refuse_creature_is_allowed ( const df::creature_raw *raw ); + + + void refuse_write_helper ( std::function add_value, const std::vector & list ); + + + bool refuse_type_is_allowed ( df::enums::item_type::item_type type ); + + + void write_refuse(); + void refuse_read_helper ( std::function get_value, size_t list_size, std::vector* pile_list ); + + void read_refuse(); + + bool stone_is_allowed ( const DFHack::MaterialInfo &mi ); + + void write_stone(); + + void read_stone(); + + bool ammo_mat_is_allowed ( const DFHack::MaterialInfo &mi ); + + void write_ammo(); + void read_ammo(); + bool coins_mat_is_allowed ( const DFHack::MaterialInfo &mi ); + + void write_coins(); + + void read_coins(); + + void bars_blocks_setup_other_mats(); + + bool bars_mat_is_allowed ( const DFHack::MaterialInfo &mi ); + + bool blocks_mat_is_allowed ( const DFHack::MaterialInfo &mi ); + + void write_bars_blocks(); + void read_bars_blocks(); + bool gem_mat_is_allowed ( const DFHack::MaterialInfo &mi ); + bool gem_cut_mat_is_allowed ( const DFHack::MaterialInfo &mi ); + bool gem_other_mat_is_allowed(DFHack::MaterialInfo &mi ); + + void write_gems(); + + void read_gems(); + + bool finished_goods_type_is_allowed ( df::enums::item_type::item_type type ); + void finished_goods_setup_other_mats(); + bool finished_goods_mat_is_allowed ( const DFHack::MaterialInfo &mi ); + void write_finished_goods(); + void read_finished_goods(); + void write_leather(); + void read_leather(); + void write_cloth(); + void read_cloth(); + bool wood_mat_is_allowed ( const df::plant_raw * plant ); + void write_wood(); + void read_wood(); + bool weapons_mat_is_allowed ( const DFHack::MaterialInfo &mi ); + void write_weapons(); + void read_weapons(); + void weapons_armor_setup_other_mats(); + bool armor_mat_is_allowed ( const DFHack::MaterialInfo &mi ); + void write_armor(); + void read_armor(); + +}; diff --git a/plugins/stockpiles/StockpileUtils.h b/plugins/stockpiles/StockpileUtils.h new file mode 100644 index 000000000..b7cfd93d9 --- /dev/null +++ b/plugins/stockpiles/StockpileUtils.h @@ -0,0 +1,53 @@ +#pragma once + +#include "MiscUtils.h" + +#include "df/world.h" +#include "df/world_data.h" +#include "df/creature_raw.h" +#include "df/plant_raw.h" + + + + +// Utility Functions {{{ +// A set of convenience functions for doing common lookups + + +/** + * Retrieve creature raw from index + */ +static df::creature_raw* find_creature ( int32_t idx ) +{ + return df::global::world->raws.creatures.all[idx]; +} + +/** + * Retrieve creature index from id string + * @return -1 if not found + */ +static int16_t find_creature ( const std::string &creature_id ) +{ + return linear_index ( df::global::world->raws.creatures.all, &df::creature_raw::creature_id, creature_id ); +} + +/** + * Retrieve plant raw from index +*/ +static df::plant_raw* find_plant ( size_t idx ) +{ + return df::global::world->raws.plants.all[idx]; +} + +/** + * Retrieve plant index from id string + * @return -1 if not found + */ +static size_t find_plant ( const std::string &plant_id ) +{ + return linear_index ( df::global::world->raws.plants.all, &df::plant_raw::id, plant_id ); +} + +// }}} utility Functions + + diff --git a/plugins/stockpiles/proto/.gitignore b/plugins/stockpiles/proto/.gitignore new file mode 100644 index 000000000..befabf79d --- /dev/null +++ b/plugins/stockpiles/proto/.gitignore @@ -0,0 +1,3 @@ +*.pb.cc +*.pb.cc.rule +*.pb.h diff --git a/plugins/proto/stockpiles.proto b/plugins/stockpiles/proto/stockpiles.proto similarity index 100% rename from plugins/proto/stockpiles.proto rename to plugins/stockpiles/proto/stockpiles.proto diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp new file mode 100644 index 000000000..6553ae6dd --- /dev/null +++ b/plugins/stockpiles/stockpiles.cpp @@ -0,0 +1,303 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "StockpileSerializer.h" + +#include "df/world.h" +#include "df/world_data.h" + +#include "df/ui.h" +#include "df/building_stockpilest.h" +#include "df/stockpile_settings.h" +#include "df/global_objects.h" +#include "df/viewscreen_dwarfmodest.h" + + + +// os +#include + +// stl +#include +#include + +using std::vector; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; +using namespace google::protobuf; +using namespace dfstockpiles; + +using df::global::world; +using df::global::ui; +using df::global::selection_rect; + +using df::building_stockpilest; +using std::placeholders::_1; + +static command_result copystock ( color_ostream &out, vector & parameters ); +static bool copystock_guard ( df::viewscreen *top ); + +static command_result savestock ( color_ostream &out, vector & parameters ); +static bool savestock_guard ( df::viewscreen *top ); + +static command_result loadstock ( color_ostream &out, vector & parameters ); +static bool loadstock_guard ( df::viewscreen *top ); + +DFHACK_PLUGIN ( "stockpiles" ); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands ) +{ + if ( world && ui ) + { + commands.push_back ( + PluginCommand ( + "copystock", "Copy stockpile under cursor.", + copystock, copystock_guard, + " - In 'q' or 't' mode: select a stockpile and invoke in order\n" + " to switch to the 'p' stockpile creation mode, and initialize\n" + " the custom settings from the selected stockpile.\n" + " - In 'p': invoke in order to switch back to 'q'.\n" + ) + ); + commands.push_back ( + PluginCommand ( + "savestock", "Save the active stockpile's settings to a file.", + savestock, savestock_guard, + "Must be in 'q' mode and have a stockpile selected.\n" + "example: 'savestock food.dfstock' will save the settings to 'food.dfstock'\n" + "in your stockpile folder.\n" + "Omitting the filename will result in text output directly to the console\n\n" + " -d, --debug: enable debug output\n" + " : filename to save stockpile settings to (will be overwritten!)\n" + ) + ); + commands.push_back ( + PluginCommand ( + "loadstock", "Load settings from a file and apply them to the active stockpile.", + loadstock, loadstock_guard, + "Must be in 'q' mode and have a stockpile selected.\n" + "example: 'loadstock food.dfstock' will load the settings from 'food.dfstock'\n" + "in your stockpile folder and apply them to the selected stockpile.\n" + " -d, --debug: enable debug output\n" + " : filename to load stockpile settings from\n" + ) + ); + } + std::cerr << "world: " << sizeof ( df::world ) << " ui: " << sizeof ( df::ui ) + << " b_stock: " << sizeof ( building_stockpilest ) << endl; + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +static bool copystock_guard ( df::viewscreen *top ) +{ + using namespace ui_sidebar_mode; + + if ( !Gui::dwarfmode_hotkey ( top ) ) + return false; + + switch ( ui->main.mode ) + { + case Stockpiles: + return true; + case BuildingItems: + case QueryBuilding: + return !!virtual_cast ( world->selected_building ); + default: + return false; + } +} + +static command_result copystock ( color_ostream &out, vector & parameters ) +{ + // HOTKEY COMMAND: CORE ALREADY SUSPENDED + + // For convenience: when used in the stockpiles mode, switch to 'q' + if ( ui->main.mode == ui_sidebar_mode::Stockpiles ) + { + world->selected_building = NULL; // just in case it contains some kind of garbage + ui->main.mode = ui_sidebar_mode::QueryBuilding; + selection_rect->start_x = -30000; + + out << "Switched back to query building." << endl; + return CR_OK; + } + + building_stockpilest *sp = virtual_cast ( world->selected_building ); + if ( !sp ) + { + out.printerr ( "Selected building isn't a stockpile.\n" ); + return CR_WRONG_USAGE; + } + + ui->stockpile.custom_settings = sp->settings; + ui->main.mode = ui_sidebar_mode::Stockpiles; + world->selected_stockpile_type = stockpile_category::Custom; + + out << "Stockpile options copied." << endl; + return CR_OK; +} + + +static bool savestock_guard ( df::viewscreen *top ) +{ + using namespace ui_sidebar_mode; + + if ( !Gui::dwarfmode_hotkey ( top ) ) + return false; + + switch ( ui->main.mode ) + { + case Stockpiles: + return true; + case BuildingItems: + case QueryBuilding: + return !!virtual_cast ( world->selected_building ); + default: + return false; + } +} + +static bool loadstock_guard ( df::viewscreen *top ) +{ + using namespace ui_sidebar_mode; + + if ( !Gui::dwarfmode_hotkey ( top ) ) + return false; + + switch ( ui->main.mode ) + { + case Stockpiles: + return true; + case BuildingItems: + case QueryBuilding: + return !!virtual_cast ( world->selected_building ); + default: + return false; + } +} + + + +static bool file_exists ( const std::string& filename ) +{ + struct stat buf; + if ( stat ( filename.c_str(), &buf ) != -1 ) + { + return true; + } + return false; +} + +static bool is_dfstockfile ( const std::string& filename ) +{ + return filename.rfind ( ".dfstock" ) != std::string::npos; +} + +// exporting +static command_result savestock ( color_ostream &out, vector & parameters ) +{ + building_stockpilest *sp = virtual_cast ( world->selected_building ); + if ( !sp ) + { + out.printerr ( "Selected building isn't a stockpile.\n" ); + return CR_WRONG_USAGE; + } + + if ( parameters.size() > 2 ) + { + out.printerr ( "Invalid parameters\n" ); + return CR_WRONG_USAGE; + } + + bool debug = false; + std::string file; + for ( size_t i = 0; i < parameters.size(); ++i ) + { + const std::string o = parameters.at ( i ); + if ( o == "--debug" || o == "-d" ) + debug = true; + else if ( !o.empty() && o[0] != '-' ) + { + file = o; + } + } + if ( file.empty() ) + { + out.printerr ( "You must supply a valid filename.\n" ); + return CR_WRONG_USAGE; + } + + StockpileSerializer cereal ( sp ); + if ( debug ) + cereal.enable_debug ( out ); + + if ( !is_dfstockfile ( file ) ) file += ".dfstock"; + if ( !cereal.serialize_to_file ( file ) ) + { + out.printerr ( "serialize failed\n" ); + return CR_FAILURE; + } + return CR_OK; +} + + +// importing +static command_result loadstock ( color_ostream &out, vector & parameters ) +{ + building_stockpilest *sp = virtual_cast ( world->selected_building ); + if ( !sp ) + { + out.printerr ( "Selected building isn't a stockpile.\n" ); + return CR_WRONG_USAGE; + } + + if ( parameters.size() < 1 || parameters.size() > 2 ) + { + out.printerr ( "Invalid parameters\n" ); + return CR_WRONG_USAGE; + } + + bool debug = false; + std::string file; + for ( size_t i = 0; i < parameters.size(); ++i ) + { + const std::string o = parameters.at ( i ); + if ( o == "--debug" || o == "-d" ) + debug = true; + else if ( !o.empty() && o[0] != '-' ) + { + file = o; + } + } + + if ( !is_dfstockfile ( file ) ) file += ".dfstock"; + if ( file.empty() || !file_exists ( file ) ) + { + out.printerr ( "loadstock: a .dfstock file is required to import\n" ); + return CR_WRONG_USAGE; + } + + StockpileSerializer cereal ( sp ); + if ( debug ) + cereal.enable_debug ( out ); + if ( !cereal.unserialize_from_file ( file ) ) + { + out.printerr ( "unserialization failed\n" ); + return CR_FAILURE; + } + return CR_OK; +} + + + + + From 1cde8cffa40f8533ffd8e65b803ba45244ee7d59 Mon Sep 17 00:00:00 2001 From: Casey Link Date: Tue, 2 Dec 2014 20:00:16 +0100 Subject: [PATCH 006/115] stockpiles: implement GUI controls --- plugins/lua/stockpiles.lua | 84 +++++++++++ plugins/stockpiles/CMakeLists.txt | 16 +-- plugins/stockpiles/stockpiles.cpp | 227 ++++++++++++++++++++++++++++++ 3 files changed, 319 insertions(+), 8 deletions(-) create mode 100644 plugins/lua/stockpiles.lua diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua new file mode 100644 index 000000000..66a9d2898 --- /dev/null +++ b/plugins/lua/stockpiles.lua @@ -0,0 +1,84 @@ +local _ENV = mkmodule('plugins.stockpiles') + +--[[ + + Native functions: + + * stockpiles_list_settings(dir_path), list files in directory + * stockpiles_load(file), with full path + * stockpiles_save(file), with full path + +--]] + +local gui = require 'gui' +local script = require 'gui.script' +local persist = require 'persist-table' + +if persist.GlobalTable.stockpiles == nil then + persist.GlobalTable.stockpiles = {} + persist.GlobalTable.stockpiles['settings_path'] = './stocksettings' +end + +function tablify(iterableObject) + t={} + for k,v in ipairs(iterableObject) do + t[k] = v~=nil and v or 'nil' + end + return t +end + +function load_settings() + script.start(function() + local path = persist.GlobalTable.stockpiles['settings_path'] + local list = stockpiles_list_settings(path) + local tok,i = script.showListPrompt('Stockpile Settings','Load which stockpile?',COLOR_WHITE,tablify(list)) + if tok then + local filename = list[i]; + stockpiles_load(path..'/'..filename); + end + end) +end + +function save_settings(stockpile) + script.start(function() + --local sp = dfhack.gui.geSelectedBuilding(true) + local suggested = stockpile.name + if #suggested == 0 then + suggested = 'Stock1' + end + local path = persist.GlobalTable.stockpiles['settings_path'] + local sok,filename = script.showInputPrompt('Stockpile Settings', 'Enter stockpile name', COLOR_WHITE, suggested) + if sok then + stockpiles_save(path..'/'..filename); + end + end) +end + +function manage_settings(sp) + if not guard() then return false end + script.start(function() + local list = {'Load', 'Save'} + local tok,i = script.showListPrompt('Stockpile Settings','Load or Save Settings?',COLOR_WHITE,tablify(list)) + if tok then + if i == 1 then + load_settings() + else + save_settings(sp) + end + end + end) +end + +function guard() + if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Stockpile') then + qerror("This script requires a stockpile selected in the 'q' mode") + return false + end + return true +end + +function set_path(path) + persist.GlobalTable.stockpiles['settings_path'] = path +end + +return _ENV diff --git a/plugins/stockpiles/CMakeLists.txt b/plugins/stockpiles/CMakeLists.txt index 2a2ac5740..713c3d6d1 100644 --- a/plugins/stockpiles/CMakeLists.txt +++ b/plugins/stockpiles/CMakeLists.txt @@ -2,15 +2,15 @@ PROJECT(stockpiles) # add *our* headers here. SET(PROJECT_HDRS -StockpileUtils.h -OrganicMatLookup.h -StockpileSerializer.h + StockpileUtils.h + OrganicMatLookup.h + StockpileSerializer.h ) SET(PROJECT_SRCS -OrganicMatLookup.cpp -StockpileSerializer.cpp -stockpiles.cpp + OrganicMatLookup.cpp + StockpileSerializer.cpp + stockpiles.cpp ) SET(PROJECT_PROTOS @@ -38,7 +38,7 @@ DEPENDS protoc-bin ${PROJECT_PROTOS} ) IF(WIN32) - DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite) + DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite lua) ELSE() - DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite) + DFHACK_PLUGIN(stockpiles ${PROJECT_SRCS} ${PROJECT_HDRS} LINK_LIBRARIES protobuf-lite lua) ENDIF() diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 6553ae6dd..1fd2f31f0 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -3,11 +3,21 @@ #include "Export.h" #include "PluginManager.h" +#include "DataFuncs.h" +#include "LuaTools.h" + +#include "../uicommon.h" + #include "StockpileSerializer.h" + +#include "modules/Gui.h" +#include "modules/Filesystem.h" + #include "df/world.h" #include "df/world_data.h" +#include "DataDefs.h" #include "df/ui.h" #include "df/building_stockpilest.h" #include "df/stockpile_settings.h" @@ -89,6 +99,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & paramete return CR_OK; } +bool manage_settings ( building_stockpilest *sp ) +{ + // Find strings representing the job to order, and the trigger condition. + // There might be a memory leak here; C++ is odd like that. + auto L = Lua::Core::State; + color_ostream_proxy out ( Core::getInstance().getConsole() ); + + CoreSuspendClaimer suspend; + Lua::StackUnwinder top ( L ); + + if ( !lua_checkstack ( L, 2 ) ) + return false; + + if ( !Lua::PushModulePublic ( out, L, "plugins.stockpiles", "manage_settings" ) ) + return false; + + Lua::Push ( L, sp ); + + if ( !Lua::SafeCall ( out, L, 1, 2 ) ) + return false; + + return true; +} + +struct stockpiles_import_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + bool handleInput ( set *input ) + { + df::building_stockpilest *sp = get_selected_stockpile(); + if ( !sp ) + return false; + + if ( input->count ( interface_key::CUSTOM_L ) ) + { + manage_settings ( sp ); + return true; + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE ( void, feed, ( set *input ) ) + { + if ( !handleInput ( input ) ) + INTERPOSE_NEXT ( feed ) ( input ); + } + + DEFINE_VMETHOD_INTERPOSE ( void, render, () ) + { + INTERPOSE_NEXT ( render ) (); + + df::building_stockpilest *sp = get_selected_stockpile(); + if ( !sp ) + return; + + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = dims.y2 - 7; + int y2 = dims.y2 - 8; + + int links = 0; + links += sp->links.give_to_pile.size(); + links += sp->links.take_from_pile.size(); + links += sp->links.give_to_workshop.size(); + links += sp->links.take_from_workshop.size(); + if ( links + 12 >= y ) + { + y += 1; + y2 += 1; + } + + OutputHotkeyString ( x, y, "Load/Save Settings", "l", true, left_margin, COLOR_WHITE, COLOR_LIGHTRED ); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE ( stockpiles_import_hook, feed ); +IMPLEMENT_VMETHOD_INTERPOSE ( stockpiles_import_hook, render ); + +DFHACK_PLUGIN_IS_ENABLED ( is_enabled ); + +DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) +{ + if ( !gps ) + return CR_FAILURE; + + if ( enable != is_enabled ) + { + if ( + !INTERPOSE_HOOK ( stockpiles_import_hook, feed ).apply ( enable ) || + !INTERPOSE_HOOK ( stockpiles_import_hook, render ).apply ( enable ) + ) + return CR_FAILURE; + + is_enabled = enable; + } + + return CR_OK; +} + +static std::vector list_dir ( const std::string &path, bool recursive = false ) +{ + color_ostream_proxy out ( Core::getInstance().getConsole() ); + std::vector files; + std::stack dirs; + dirs.push(path); + out << "list_dir start" << endl; + while (!dirs.empty() ) { + const std::string current = dirs.top(); + out << "\t walking " << current << endl; + dirs.pop(); + std::vector entries; + const int res = DFHack::getdir(current, entries); + if ( res != 0 ) + continue; + for ( std::vector::iterator it = entries.begin() ; it != entries.end(); ++it ) + { + if ( (*it).empty() || (*it)[0] == '.' ) continue; + // shitty cross platform c++ we've got to construct the actual path manually + std::ostringstream child_path_s; + child_path_s << current << "/" << *it; + const std::string child = child_path_s.str(); + if ( recursive && Filesystem::isdir ( child ) ) + { + out << "\t\tgot child dir: " << child << endl; + dirs.push ( child ); + } + else if ( Filesystem::isfile ( child ) ) + { + const std::string rel_path ( child.substr ( std::string ( "./"+path).length()-1 ) ); + out << "\t\t adding file: " << child << " as " << rel_path << endl; + files.push_back ( rel_path ); + } + } + } + out << "listdir_stop" << endl; + return files; +} +static std::vector clean_dfstock_list ( const std::string &path ) +{ + std::vector files ( list_dir ( path, true) ); + files.erase ( std::remove_if ( files.begin(), files.end(), [] ( const std::string &f ) + { + return !is_dfstockfile ( f ); + } ), files.end() ); + std::transform ( files.begin(), files.end(), files.begin(), [] ( const std::string &f ) + { + return f.substr ( 0, f.find_last_of ( "." ) ); + } ); + return files; +} + +static int stockpiles_list_settings ( lua_State *L ) +{ + auto path = luaL_checkstring ( L, 1 ); + color_ostream &out = *Lua::GetOutput ( L ); + if ( !Filesystem::isdir(path) ) + { + lua_pushfstring ( L, "invalid directory: %s", path ); + lua_error ( L ); + return 0; + } + std::vector files = clean_dfstock_list ( path ); + Lua::PushVector ( L, files, true ); + return 1; +} + +static void stockpiles_load ( color_ostream &out, std::string filename ) +{ + out << "stockpiles_load " << filename << " "; + std::vector params; + params.push_back ( filename ); + command_result r = loadstock ( out, params ); + out << " result = "<< r << endl; +} + + +static void stockpiles_save ( color_ostream &out, std::string filename ) +{ + + out << "stockpiles_save " << filename << " "; + std::vector params; + params.push_back ( filename ); + command_result r = savestock ( out, params ); + out << " result = "<< r << endl; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(stockpiles_load), + DFHACK_LUA_FUNCTION(stockpiles_save), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS +{ + DFHACK_LUA_COMMAND ( stockpiles_list_settings ), + DFHACK_LUA_END +}; From b6118d272cc92e35a793d73738abeed2ddd715cc Mon Sep 17 00:00:00 2001 From: Casey Link Date: Tue, 2 Dec 2014 20:23:12 +0100 Subject: [PATCH 007/115] stockpiles: integrate gui plugin into dfhack.init-example --- dfhack.init-example | 8 ++++-- plugins/lua/stockpiles.lua | 19 ++++++++++--- scripts/gui/stockpiles.lua | 55 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 scripts/gui/stockpiles.lua diff --git a/dfhack.init-example b/dfhack.init-example index c1cd1ac86..638f61aa1 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -72,9 +72,13 @@ keybinding add Ctrl-Shift-Z@dwarfmode/Default "stocks show" # open an overview window summarising some stocks (dfstatus) keybinding add Ctrl-Shift-I@dwarfmode/Default "gui/dfstatus" -# q->stockpile; p - copy & paste stockpiles +# q->stockpile - copy & paste stockpiles keybinding add Alt-P copystock +# q->stockpile - load and save stockpile settings out of game +keybinding add Alt-L@dwarfmode/QueryBuilding/Some/Stockpile "gui/stockpiles -load" +keybinding add Alt-S@dwarfmode/QueryBuilding/Some/Stockpile "gui/stockpiles -save" + # q->workshop - duplicate the selected job keybinding add Ctrl-D job-duplicate @@ -189,7 +193,7 @@ enable search enable automaterial # Other interface improvement tools -enable dwarfmonitor mousequery automelt autotrade buildingplan resume trackstop zone stocks autochop +enable dwarfmonitor mousequery automelt autotrade buildingplan resume trackstop zone stocks autochop stockpiles # allow the fortress bookkeeper to queue jobs through the manager stockflow enable diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index 66a9d2898..c2ac67c44 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -14,9 +14,13 @@ local gui = require 'gui' local script = require 'gui.script' local persist = require 'persist-table' -if persist.GlobalTable.stockpiles == nil then - persist.GlobalTable.stockpiles = {} - persist.GlobalTable.stockpiles['settings_path'] = './stocksettings' +function init() + if dfhack.isMapLoaded() then + if persist.GlobalTable.stockpiles == nil then + persist.GlobalTable.stockpiles = {} + persist.GlobalTable.stockpiles['settings_path'] = './stocksettings' + end + end end function tablify(iterableObject) @@ -28,6 +32,7 @@ function tablify(iterableObject) end function load_settings() + init() script.start(function() local path = persist.GlobalTable.stockpiles['settings_path'] local list = stockpiles_list_settings(path) @@ -40,6 +45,7 @@ function load_settings() end function save_settings(stockpile) + init() script.start(function() --local sp = dfhack.gui.geSelectedBuilding(true) local suggested = stockpile.name @@ -55,6 +61,7 @@ function save_settings(stockpile) end function manage_settings(sp) + init() if not guard() then return false end script.start(function() local list = {'Load', 'Save'} @@ -78,7 +85,13 @@ function guard() end function set_path(path) + init() persist.GlobalTable.stockpiles['settings_path'] = path end +function get_path() + init() + return persist.GlobalTable.stockpiles['settings_path'] +end + return _ENV diff --git a/scripts/gui/stockpiles.lua b/scripts/gui/stockpiles.lua new file mode 100644 index 000000000..557692fda --- /dev/null +++ b/scripts/gui/stockpiles.lua @@ -0,0 +1,55 @@ +-- lave/load stockpile settings with a GUI + +local stock = require 'plugins.stockpiles' + +function world_guard() + if not dfhack.isMapLoaded() then + qerror("World is not loaded") + return false + end + return true +end +function guard() + if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Stockpile') then + qerror("This script requires a stockpile selected in the 'q' mode") + return false + end + return true +end + +utils = require('utils') +validArgs = validArgs or utils.invert({ + 'help', + 'load', + 'save', + 'dir', +}) + +args = utils.processArgs({...}, validArgs) + +function usage() + print("") + print("Stockpile Settings. Arguments: ") + print("-save to save the current stockpile") + print("-load to load settings into the current stockpile") + print("-dir set the default directory to save settings into") + if dfhack.isMapLoaded() then + print(" Current directory is: " .. stock.get_path()) + end + print("") +end + +if args.load then + if not guard() then return end + stock.load_settings() +elseif args.save then + if not guard() then return end + local sp = dfhack.gui.getSelectedBuilding(true) + stock.save_settings(sp) +elseif args.dir then + if not world_guard() then return end + stock.set_path(args.dir) +else + usage() +end + From 085c18ab2affb5669d3f2048f045fdf44d9e8747 Mon Sep 17 00:00:00 2001 From: Casey Link Date: Tue, 2 Dec 2014 20:28:25 +0100 Subject: [PATCH 008/115] update NEWS and README with new stockpiles UI info --- NEWS | 2 ++ Readme.rst | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/NEWS b/NEWS index 1b9ec3f8b..80509dbc3 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,8 @@ DFHack 0.40.19-r1 New scripts: remove-stress [all]: set selected or all units unit to -1,000,000 stress this script replaces removebadthoughts.rb + gui/stockpiles: an in-game interface for saving and loading stockpile + settings files. Misc improvements: cmd-prompt can now access selected items, units, and buildings autolabor plugin: add an optional talent pool parameter diff --git a/Readme.rst b/Readme.rst index 15933e0f6..bcabd2965 100644 --- a/Readme.rst +++ b/Readme.rst @@ -311,6 +311,9 @@ Toggle between displaying/not displaying liquid depth as numbers. stockpile settings management ----------------------------- +Save and load stockpile settings. See the gui/stockpiles for an in-game GUI to +this plugin. + copystock ~~~~~~~~~ @@ -2195,6 +2198,15 @@ directory. A graphical interface for creating items. +* gui/stockpiles + + Load and save stockpile settings from the 'q' menu. + Usage: + gui/stockpiles -save to save the current stockpile + gui/stockpiles -load to load settings into the current stockpile + gui/stockpiles -dir set the default directory to save settings into + gui/stockpiles -help to see this message + binpatch ======== From 1f1780cae93ad26abe0ddd88ba6fb3988bde70d8 Mon Sep 17 00:00:00 2001 From: Casey Link Date: Tue, 2 Dec 2014 20:29:42 +0100 Subject: [PATCH 009/115] stockpiles: fix usage help --- scripts/gui/stockpiles.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/gui/stockpiles.lua b/scripts/gui/stockpiles.lua index 557692fda..b4478ee3a 100644 --- a/scripts/gui/stockpiles.lua +++ b/scripts/gui/stockpiles.lua @@ -30,11 +30,11 @@ args = utils.processArgs({...}, validArgs) function usage() print("") print("Stockpile Settings. Arguments: ") - print("-save to save the current stockpile") - print("-load to load settings into the current stockpile") - print("-dir set the default directory to save settings into") + print("-save to save the current stockpile") + print("-load to load settings into the current stockpile") + print("-dir set the default directory to save settings into") if dfhack.isMapLoaded() then - print(" Current directory is: " .. stock.get_path()) + print(" Current directory is: " .. stock.get_path()) end print("") end From 1525823948dbe5b301991856d583c12ffd137479 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 3 Dec 2014 16:09:39 -0500 Subject: [PATCH 010/115] Create stocksettings directory on startup if necessary --- plugins/stockpiles/stockpiles.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 1fd2f31f0..81a3c5c5b 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -5,6 +5,7 @@ #include "DataFuncs.h" #include "LuaTools.h" +#include "modules/Filesystem.h" #include "../uicommon.h" @@ -97,9 +98,18 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Thu, 4 Dec 2014 10:47:17 +0100 Subject: [PATCH 011/115] stockpiles: more error handling & cleanup * prevent crashes when the path doesn't exist * remove duplicated functions * sort file list case insensitively --- plugins/stockpiles/StockpileSerializer.cpp | 12 ++++ plugins/stockpiles/StockpileUtils.h | 29 ++++++++- plugins/stockpiles/stockpiles.cpp | 68 +++++++++------------- 3 files changed, 68 insertions(+), 41 deletions(-) diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp index 52bd82591..5ae7145bf 100644 --- a/plugins/stockpiles/StockpileSerializer.cpp +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -72,6 +72,7 @@ void StockpileSerializer::enable_debug ( std::ostream&out ) bool StockpileSerializer::serialize_to_ostream ( std::ostream* output ) { + if ( output->fail( ) ) return false; mBuffer.Clear(); write(); { @@ -84,11 +85,17 @@ bool StockpileSerializer::serialize_to_ostream ( std::ostream* output ) bool StockpileSerializer::serialize_to_file ( const std::string & file ) { std::fstream output ( file, std::ios::out | std::ios::binary | std::ios::trunc ); + if ( output.fail() ) + { + *mOut << "ERROR: failed to open file for writing: " << file << endl; + return false; + } return serialize_to_ostream ( &output ); } bool StockpileSerializer::parse_from_istream ( std::istream* input ) { + if ( input->fail( ) ) return false; mBuffer.Clear(); io::IstreamInputStream zero_copy_input ( input ); const bool res = mBuffer.ParseFromZeroCopyStream ( &zero_copy_input ) && input->eof(); @@ -100,6 +107,11 @@ bool StockpileSerializer::parse_from_istream ( std::istream* input ) bool StockpileSerializer::unserialize_from_file ( const std::string & file ) { std::fstream input ( file, std::ios::in | std::ios::binary ); + if ( input.fail() ) + { + *mOut << "ERROR: failed to open file for reading: " << file << endl; + return false; + } return parse_from_istream ( &input ); } diff --git a/plugins/stockpiles/StockpileUtils.h b/plugins/stockpiles/StockpileUtils.h index b7cfd93d9..4cb5669df 100644 --- a/plugins/stockpiles/StockpileUtils.h +++ b/plugins/stockpiles/StockpileUtils.h @@ -7,8 +7,12 @@ #include "df/creature_raw.h" #include "df/plant_raw.h" +#include +#include +#include - +// os +#include // Utility Functions {{{ // A set of convenience functions for doing common lookups @@ -48,6 +52,29 @@ static size_t find_plant ( const std::string &plant_id ) return linear_index ( df::global::world->raws.plants.all, &df::plant_raw::id, plant_id ); } +struct less_than_no_case: public std::binary_function< char,char,bool > +{ + bool operator () (char x, char y) const + { + return toupper( static_cast< unsigned char >(x)) < toupper( static_cast< unsigned char >(y)); + } +}; + +static bool CompareNoCase(const std::string &a, const std::string &b) +{ + return std::lexicographical_compare( a.begin(),a.end(), b.begin(),b.end(), less_than_no_case() ); +} + + +/** + * Checks if the parameter has the dfstock extension. + * Doesn't check if the file exists or not. + */ +static bool is_dfstockfile ( const std::string& filename ) +{ + return filename.rfind ( ".dfstock" ) != std::string::npos; +} + // }}} utility Functions diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 81a3c5c5b..9c6df1a1c 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -9,6 +9,7 @@ #include "../uicommon.h" +#include "StockpileUtils.h" #include "StockpileSerializer.h" @@ -25,11 +26,6 @@ #include "df/global_objects.h" #include "df/viewscreen_dwarfmodest.h" - - -// os -#include - // stl #include #include @@ -98,19 +94,13 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters ) { @@ -317,7 +289,7 @@ static command_result loadstock ( color_ostream &out, vector & paramete } if ( !is_dfstockfile ( file ) ) file += ".dfstock"; - if ( file.empty() || !file_exists ( file ) ) + if ( file.empty() || !Filesystem::exists ( file ) ) { out.printerr ( "loadstock: a .dfstock file is required to import\n" ); return CR_WRONG_USAGE; @@ -334,10 +306,11 @@ static command_result loadstock ( color_ostream &out, vector & paramete return CR_OK; } +/** + * calls the lua function manage_settings() to kickoff the GUI + */ bool manage_settings ( building_stockpilest *sp ) { - // Find strings representing the job to order, and the trigger condition. - // There might be a memory leak here; C++ is odd like that. auto L = Lua::Core::State; color_ostream_proxy out ( Core::getInstance().getConsole() ); @@ -395,7 +368,6 @@ struct stockpiles_import_hook : public df::viewscreen_dwarfmodest int left_margin = dims.menu_x1 + 1; int x = left_margin; int y = dims.y2 - 7; - int y2 = dims.y2 - 8; int links = 0; links += sp->links.give_to_pile.size(); @@ -405,7 +377,6 @@ struct stockpiles_import_hook : public df::viewscreen_dwarfmodest if ( links + 12 >= y ) { y += 1; - y2 += 1; } OutputHotkeyString ( x, y, "Load/Save Settings", "l", true, left_margin, COLOR_WHITE, COLOR_LIGHTRED ); @@ -477,6 +448,10 @@ static std::vector list_dir ( const std::string &path, bool recursi static std::vector clean_dfstock_list ( const std::string &path ) { + if ( !Filesystem::exists ( path ) ) + { + return std::vector(); + } std::vector files ( list_dir ( path, true) ); files.erase ( std::remove_if ( files.begin(), files.end(), [] ( const std::string &f ) { @@ -486,16 +461,23 @@ static std::vector clean_dfstock_list ( const std::string &path ) { return f.substr ( 0, f.find_last_of ( "." ) ); } ); + std::sort ( files.begin(),files.end(), CompareNoCase ); return files; } static int stockpiles_list_settings ( lua_State *L ) { auto path = luaL_checkstring ( L, 1 ); + if ( !Filesystem::exists ( path ) ) + { + lua_pushfstring ( L, "stocksettings path invalid: %s", path ); + lua_error ( L ); + return 0; + } color_ostream &out = *Lua::GetOutput ( L ); if ( !Filesystem::isdir(path) ) { - lua_pushfstring ( L, "invalid directory: %s", path ); + lua_pushfstring ( L, "stocksettings path invalid: %s", path ); lua_error ( L ); return 0; } @@ -507,6 +489,11 @@ static int stockpiles_list_settings ( lua_State *L ) static void stockpiles_load ( color_ostream &out, std::string filename ) { out << "stockpiles_load " << filename << " "; + if ( !Filesystem::exists ( filename ) ) + { + out.printerr ( "invalid file: %s\n", filename.c_str() ); + return; + } std::vector params; params.push_back ( filename ); command_result r = loadstock ( out, params ); @@ -524,9 +511,10 @@ static void stockpiles_save ( color_ostream &out, std::string filename ) out << " result = "<< r << endl; } -DFHACK_PLUGIN_LUA_FUNCTIONS { - DFHACK_LUA_FUNCTION(stockpiles_load), - DFHACK_LUA_FUNCTION(stockpiles_save), +DFHACK_PLUGIN_LUA_FUNCTIONS +{ + DFHACK_LUA_FUNCTION ( stockpiles_load ), + DFHACK_LUA_FUNCTION ( stockpiles_save ), DFHACK_LUA_END }; From 7e6066daf2396a954a06d9fe360a895f0f2778d7 Mon Sep 17 00:00:00 2001 From: Casey Link Date: Thu, 4 Dec 2014 11:52:38 +0100 Subject: [PATCH 012/115] stockpiles: user friendly error handling * display errors in the GUI when necessary * handle older versions of DFHack without the persist module --- plugins/lua/stockpiles.lua | 50 +++++++++++++++++++--- plugins/stockpiles/StockpileSerializer.cpp | 4 +- plugins/stockpiles/stockpiles.cpp | 30 +++++++++++++ 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index c2ac67c44..0e190a90b 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -9,12 +9,19 @@ local _ENV = mkmodule('plugins.stockpiles') * stockpiles_save(file), with full path --]] +-- +function safe_require(module) + local status, module = pcall(require, module) + return status and module or nil +end + local gui = require 'gui' local script = require 'gui.script' -local persist = require 'persist-table' +local persist = safe_require('persist-table') function init() + if persist == nil then return end if dfhack.isMapLoaded() then if persist.GlobalTable.stockpiles == nil then persist.GlobalTable.stockpiles = {} @@ -33,13 +40,21 @@ end function load_settings() init() + local path = get_path() + local ok, list = pcall(stockpiles_list_settings, path) + if not ok then + show_message_box("Stockpile Settings", "The stockpile settings folder doesn't exist.", true) + return + end + if #list == 0 then + show_message_box("Stockpile Settings", "There are no saved stockpile settings.", true) + return + end script.start(function() - local path = persist.GlobalTable.stockpiles['settings_path'] - local list = stockpiles_list_settings(path) local tok,i = script.showListPrompt('Stockpile Settings','Load which stockpile?',COLOR_WHITE,tablify(list)) if tok then local filename = list[i]; - stockpiles_load(path..'/'..filename); + stockpiles_load(path..'/'..filename) end end) end @@ -47,15 +62,19 @@ end function save_settings(stockpile) init() script.start(function() - --local sp = dfhack.gui.geSelectedBuilding(true) local suggested = stockpile.name if #suggested == 0 then suggested = 'Stock1' end - local path = persist.GlobalTable.stockpiles['settings_path'] + local path = get_path() local sok,filename = script.showInputPrompt('Stockpile Settings', 'Enter stockpile name', COLOR_WHITE, suggested) if sok then - stockpiles_save(path..'/'..filename); + if filename == nil or filename == '' then + script.showMessage('Stockpile Settings', 'Invalid File Name', COLOR_RED) + else + print("saving...", path..'/'..filename) + stockpiles_save(path..'/'..filename) + end end end) end @@ -76,6 +95,16 @@ function manage_settings(sp) end) end +function show_message_box(title, msg, iserror) + local color = COLOR_WHITE + if iserror then + color = COLOR_RED + end + script.start(function() + script.showMessage(title, msg, color) + end) +end + function guard() if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Stockpile') then qerror("This script requires a stockpile selected in the 'q' mode") @@ -86,11 +115,18 @@ end function set_path(path) init() + if persist == nil then + qerror("This version of DFHack doesn't support setting the stockpile settings path. Sorry.") + return + end persist.GlobalTable.stockpiles['settings_path'] = path end function get_path() init() + if persist == nil then + return "stocksettings" + end return persist.GlobalTable.stockpiles['settings_path'] end diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp index 5ae7145bf..1f532951d 100644 --- a/plugins/stockpiles/StockpileSerializer.cpp +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -87,7 +87,7 @@ bool StockpileSerializer::serialize_to_file ( const std::string & file ) std::fstream output ( file, std::ios::out | std::ios::binary | std::ios::trunc ); if ( output.fail() ) { - *mOut << "ERROR: failed to open file for writing: " << file << endl; + debug() << "ERROR: failed to open file for writing: " << file << endl; return false; } return serialize_to_ostream ( &output ); @@ -109,7 +109,7 @@ bool StockpileSerializer::unserialize_from_file ( const std::string & file ) std::fstream input ( file, std::ios::in | std::ios::binary ); if ( input.fail() ) { - *mOut << "ERROR: failed to open file for reading: " << file << endl; + debug() << "ERROR: failed to open file for reading: " << file << endl; return false; } return parse_from_istream ( &input ); diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 9c6df1a1c..f9aee708c 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -13,6 +13,7 @@ #include "StockpileSerializer.h" +#include "modules/Filesystem.h" #include "modules/Gui.h" #include "modules/Filesystem.h" @@ -331,6 +332,30 @@ bool manage_settings ( building_stockpilest *sp ) return true; } +bool show_message_box ( const std::string & title, const std::string & msg, bool is_error = false ) +{ + auto L = Lua::Core::State; + color_ostream_proxy out ( Core::getInstance().getConsole() ); + + CoreSuspendClaimer suspend; + Lua::StackUnwinder top ( L ); + + if ( !lua_checkstack ( L, 4 ) ) + return false; + + if ( !Lua::PushModulePublic ( out, L, "plugins.stockpiles", "show_message_box" ) ) + return false; + + Lua::Push ( L, title ); + Lua::Push ( L, msg ); + Lua::Push ( L, is_error ); + + if ( !Lua::SafeCall ( out, L, 3, 0 ) ) + return false; + + return true; +} + struct stockpiles_import_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; @@ -498,6 +523,8 @@ static void stockpiles_load ( color_ostream &out, std::string filename ) params.push_back ( filename ); command_result r = loadstock ( out, params ); out << " result = "<< r << endl; + if ( r != CR_OK ) + show_message_box ( "Stockpile Settings Error", "Couldn't load. Does the folder exist?", true ); } @@ -509,6 +536,8 @@ static void stockpiles_save ( color_ostream &out, std::string filename ) params.push_back ( filename ); command_result r = savestock ( out, params ); out << " result = "<< r << endl; + if ( r != CR_OK ) + show_message_box ( "Stockpile Settings Error", "Couldn't save. Does the folder exist?", true ); } DFHACK_PLUGIN_LUA_FUNCTIONS @@ -526,3 +555,4 @@ DFHACK_PLUGIN_LUA_COMMANDS + From 346f397bca65b0f5915d32abc7e0d9e20a028c84 Mon Sep 17 00:00:00 2001 From: Casey Link Date: Thu, 4 Dec 2014 14:28:43 +0100 Subject: [PATCH 013/115] stockpiles: fix loading crash --- plugins/stockpiles/stockpiles.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index f9aee708c..ac9de5f57 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -288,11 +288,15 @@ static command_result loadstock ( color_ostream &out, vector & paramete file = o; } } - - if ( !is_dfstockfile ( file ) ) file += ".dfstock"; - if ( file.empty() || !Filesystem::exists ( file ) ) + if ( file.empty() ) { + out.printerr ( "ERROR: missing .dfstock file parameter\n"); + return DFHack::CR_WRONG_USAGE; + } + if ( !is_dfstockfile ( file ) ) + file += ".dfstock"; + if ( !Filesystem::exists ( file ) ) { - out.printerr ( "loadstock: a .dfstock file is required to import\n" ); + out.printerr ( "ERROR: the .dfstock file doesn't exist: %s\n", file.c_str()); return CR_WRONG_USAGE; } @@ -495,7 +499,7 @@ static int stockpiles_list_settings ( lua_State *L ) auto path = luaL_checkstring ( L, 1 ); if ( !Filesystem::exists ( path ) ) { - lua_pushfstring ( L, "stocksettings path invalid: %s", path ); + lua_pushfstring ( L, "stocksettings folder doesn't exist: %s", path ); lua_error ( L ); return 0; } @@ -514,11 +518,6 @@ static int stockpiles_list_settings ( lua_State *L ) static void stockpiles_load ( color_ostream &out, std::string filename ) { out << "stockpiles_load " << filename << " "; - if ( !Filesystem::exists ( filename ) ) - { - out.printerr ( "invalid file: %s\n", filename.c_str() ); - return; - } std::vector params; params.push_back ( filename ); command_result r = loadstock ( out, params ); From 7072252902910e7d1af309cd0f0ec089b19fa096 Mon Sep 17 00:00:00 2001 From: Casey Link Date: Thu, 4 Dec 2014 14:32:00 +0100 Subject: [PATCH 014/115] fix build error on windows with certain #include permutations the lack of this define would break the build on windows as windows.h pollutes the namespace with min and max macros that conflict with std::min and std::max. --- library/include/modules/Filesystem.h | 1 + 1 file changed, 1 insertion(+) diff --git a/library/include/modules/Filesystem.h b/library/include/modules/Filesystem.h index 3798bbeb2..9268cb288 100644 --- a/library/include/modules/Filesystem.h +++ b/library/include/modules/Filesystem.h @@ -69,6 +69,7 @@ SOFTWARE. #ifdef _WIN32 #include +#define NOMINMAX #include #include #include From 0f06eb7f6f168fed2f1a52df16f8e93c98cbf418 Mon Sep 17 00:00:00 2001 From: Casey Link Date: Fri, 5 Dec 2014 12:42:59 +0100 Subject: [PATCH 015/115] stockpiles: quiet debug --- plugins/stockpiles/stockpiles.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index ac9de5f57..0b48116d7 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -438,14 +438,14 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) static std::vector list_dir ( const std::string &path, bool recursive = false ) { - color_ostream_proxy out ( Core::getInstance().getConsole() ); +// color_ostream_proxy out ( Core::getInstance().getConsole() ); std::vector files; std::stack dirs; dirs.push(path); - out << "list_dir start" << endl; +// out << "list_dir start" << endl; while (!dirs.empty() ) { const std::string current = dirs.top(); - out << "\t walking " << current << endl; +// out << "\t walking " << current << endl; dirs.pop(); std::vector entries; const int res = DFHack::getdir(current, entries); @@ -460,18 +460,18 @@ static std::vector list_dir ( const std::string &path, bool recursi const std::string child = child_path_s.str(); if ( recursive && Filesystem::isdir ( child ) ) { - out << "\t\tgot child dir: " << child << endl; +// out << "\t\tgot child dir: " << child << endl; dirs.push ( child ); } else if ( Filesystem::isfile ( child ) ) { const std::string rel_path ( child.substr ( std::string ( "./"+path).length()-1 ) ); - out << "\t\t adding file: " << child << " as " << rel_path << endl; +// out << "\t\t adding file: " << child << " as " << rel_path << endl; files.push_back ( rel_path ); } } } - out << "listdir_stop" << endl; +// out << "listdir_stop" << endl; return files; } From 332566f6a10afdb99e57ebc7598df8102261eb2d Mon Sep 17 00:00:00 2001 From: Casey Link Date: Fri, 5 Dec 2014 13:27:58 +0100 Subject: [PATCH 016/115] stockpiles: disable gui/script when plugin is disabled --- plugins/lua/stockpiles.lua | 1 + plugins/stockpiles/stockpiles.cpp | 6 ++++++ scripts/gui/stockpiles.lua | 11 +++++++++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index 0e190a90b..88b7b2c3f 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -7,6 +7,7 @@ local _ENV = mkmodule('plugins.stockpiles') * stockpiles_list_settings(dir_path), list files in directory * stockpiles_load(file), with full path * stockpiles_save(file), with full path + * isEnabled() --]] -- diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 0b48116d7..724783511 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -494,6 +494,12 @@ static std::vector clean_dfstock_list ( const std::string &path ) return files; } +static bool isEnabled( lua_State *L ) +{ + Lua::Push(L, is_enabled); + return 1; +} + static int stockpiles_list_settings ( lua_State *L ) { auto path = luaL_checkstring ( L, 1 ); diff --git a/scripts/gui/stockpiles.lua b/scripts/gui/stockpiles.lua index b4478ee3a..c5e1a9547 100644 --- a/scripts/gui/stockpiles.lua +++ b/scripts/gui/stockpiles.lua @@ -2,6 +2,10 @@ local stock = require 'plugins.stockpiles' +function check_enabled() + return stock.isEnabled() +end + function world_guard() if not dfhack.isMapLoaded() then qerror("World is not loaded") @@ -9,6 +13,7 @@ function world_guard() end return true end + function guard() if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Stockpile') then qerror("This script requires a stockpile selected in the 'q' mode") @@ -39,7 +44,10 @@ function usage() print("") end -if args.load then +if not check_enabled() then + qerror("Stockpiles plugin not enabled. Enable it with: enable stockpiles") + return +elseif args.load then if not guard() then return end stock.load_settings() elseif args.save then @@ -52,4 +60,3 @@ elseif args.dir then else usage() end - From 729f7e72361faf82eb5da88ee8847b9fc40de70e Mon Sep 17 00:00:00 2001 From: Casey Link Date: Fri, 5 Dec 2014 13:32:01 +0100 Subject: [PATCH 017/115] stockpiles: update NEWS & Readme --- NEWS | 4 ++-- Readme.rst | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 80509dbc3..b1ce46815 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,8 @@ DFHack Future Fixes New Plugins New Scripts + gui/stockpiles: an in-game interface for saving and loading stockpile + settings files. Misc Improvements DFHack 0.40.19-r1 @@ -17,8 +19,6 @@ DFHack 0.40.19-r1 New scripts: remove-stress [all]: set selected or all units unit to -1,000,000 stress this script replaces removebadthoughts.rb - gui/stockpiles: an in-game interface for saving and loading stockpile - settings files. Misc improvements: cmd-prompt can now access selected items, units, and buildings autolabor plugin: add an optional talent pool parameter diff --git a/Readme.rst b/Readme.rst index bcabd2965..aa44be116 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2207,6 +2207,9 @@ directory. gui/stockpiles -dir set the default directory to save settings into gui/stockpiles -help to see this message +Don't forget to `enable stockpiles` and create the `stocksettings` directory in +the DF folder before trying to use this plugin. + binpatch ======== From 52f4cb3fc1d74da971e10ce64abfce49c45f334c Mon Sep 17 00:00:00 2001 From: Casey Link Date: Fri, 5 Dec 2014 14:49:40 +0100 Subject: [PATCH 018/115] stockpiles: load prompt uses typing + filtering --- plugins/lua/stockpiles.lua | 71 ++++++++++++++++++++++++++++++++++++-- scripts/gui/stockpiles.lua | 1 - 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index 88b7b2c3f..72bd6a057 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -18,9 +18,66 @@ end local gui = require 'gui' +local widgets = require('gui.widgets') +local dlg = require('gui.dialogs') local script = require 'gui.script' local persist = safe_require('persist-table') + +function ListFilterDialog(args) + args.text = args.prompt or 'Type or select an option' + args.text_pen = COLOR_WHITE + args.with_filter = true + args.icon_width = 2 + + local choices = {} + + if not args.hide_none then + table.insert(choices, { + icon = '?', text = args.none_caption or 'none', + index = -1, name = -1 + }) + end + + local filter = args.item_filter + + for i,v in ipairs(args.items) do + if not filter or filter(v,-1) then + local name = v + local icon + table.insert(choices, { + icon = icon, text = string.lower(name), index = i + }) + end + end + + args.choices = choices + + if args.on_select then + local cb = args.on_select + args.on_select = function(idx, obj) + return cb(obj.index, args.items[obj.index]) + end + end + + return dlg.ListBox(args) +end + +function showFilterPrompt(title, list, text,item_filter,hide_none) + ListFilterDialog{ + frame_title=title, + items=list, + prompt=text, + item_filter=item_filter, + hide_none=hide_none, + on_select=script.mkresume(true), + on_cancel=script.mkresume(false), + on_close=script.qresume(nil) + }:show() + + return script.wait() +end + function init() if persist == nil then return end if dfhack.isMapLoaded() then @@ -51,10 +108,18 @@ function load_settings() show_message_box("Stockpile Settings", "There are no saved stockpile settings.", true) return end + + local choice_list = {} + for i,v in ipairs(list) do + choice_list[i] = string.gsub(v, "/", "/ ") + choice_list[i] = string.gsub(choice_list[i], "-", " - ") + choice_list[i] = string.gsub(choice_list[i], "_", " ") + end + script.start(function() - local tok,i = script.showListPrompt('Stockpile Settings','Load which stockpile?',COLOR_WHITE,tablify(list)) - if tok then - local filename = list[i]; + local ok2,index,name=showFilterPrompt('Stockpile Settings', choice_list, 'Choose a stockpile', function(item) return true end, true) + if ok2 then + local filename = list[index]; stockpiles_load(path..'/'..filename) end end) diff --git a/scripts/gui/stockpiles.lua b/scripts/gui/stockpiles.lua index c5e1a9547..638ae2894 100644 --- a/scripts/gui/stockpiles.lua +++ b/scripts/gui/stockpiles.lua @@ -46,7 +46,6 @@ end if not check_enabled() then qerror("Stockpiles plugin not enabled. Enable it with: enable stockpiles") - return elseif args.load then if not guard() then return end stock.load_settings() From b0e0bbda9b4f4bd1723650a181da589a8a4a548d Mon Sep 17 00:00:00 2001 From: Casey Link Date: Fri, 5 Dec 2014 16:52:57 +0100 Subject: [PATCH 019/115] stockpiles: show proper cased names in filter list --- plugins/lua/stockpiles.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index 72bd6a057..8b318eb0f 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -46,7 +46,7 @@ function ListFilterDialog(args) local name = v local icon table.insert(choices, { - icon = icon, text = string.lower(name), index = i + icon = icon, search_key = string.lower(name), text = name, index = i }) end end From b9a2ecb440f4fd593dd46f575d70dbb3044d9194 Mon Sep 17 00:00:00 2001 From: Casey Link Date: Fri, 5 Dec 2014 17:39:29 +0100 Subject: [PATCH 020/115] stockpiles: properly initialize quality arrays Props to @fricy for identifying this bug. --- plugins/stockpiles/StockpileSerializer.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp index 1f532951d..fa3d648ba 100644 --- a/plugins/stockpiles/StockpileSerializer.cpp +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -342,6 +342,7 @@ void StockpileSerializer::quality_clear ( bool ( &pile_list ) [7] ) void StockpileSerializer::unserialize_list_quality ( FuncReadImport read_value, int32_t list_size, bool ( &pile_list ) [7] ) { + quality_clear ( pile_list ); if ( list_size > 0 && list_size <= 7 ) { using df::enums::item_quality::item_quality; @@ -359,10 +360,6 @@ void StockpileSerializer::unserialize_list_quality ( FuncReadImport read_value, pile_list[idx] = true; } } - else - { - quality_clear ( pile_list ); - } } From a615723b381be211097de7e2fb31c8c07b7c4d7a Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Dec 2014 18:47:35 -0500 Subject: [PATCH 021/115] Update remaining plugins to use REQUIRE_GLOBAL --- plugins/fastdwarf.cpp | 7 ++-- plugins/feature.cpp | 5 ++- plugins/fixpositions.cpp | 5 ++- plugins/fixveins.cpp | 5 ++- plugins/flows.cpp | 5 ++- plugins/follow.cpp | 7 ++-- plugins/forceequip.cpp | 6 ++-- plugins/getplants.cpp | 5 ++- plugins/infiniteSky.cpp | 5 ++- plugins/initflags.cpp | 5 ++- plugins/isoworldremote.cpp | 14 +++----- plugins/jobutils.cpp | 13 ++++--- plugins/lair.cpp | 2 +- plugins/liquids.cpp | 6 ++-- plugins/manipulator.cpp | 14 ++++---- plugins/misery.cpp | 9 +++-- plugins/mousequery.cpp | 9 +++-- plugins/petcapRemover.cpp | 8 ++--- plugins/plants.cpp | 6 ++-- plugins/power-meter.cpp | 9 +++-- plugins/probe.cpp | 8 ++--- plugins/prospector.cpp | 6 ++-- plugins/regrass.cpp | 3 +- plugins/remotefortressreader.cpp | 19 +++++------ plugins/rename.cpp | 12 +++---- plugins/rendermax/rendermax.cpp | 58 +++++++++++++++++++------------- plugins/resume.cpp | 8 ++--- plugins/reveal.cpp | 9 +++-- plugins/search.cpp | 13 ++++--- plugins/seedwatch.cpp | 9 ++--- plugins/showmood.cpp | 5 ++- plugins/siege-engine.cpp | 13 ++++--- plugins/sort.cpp | 19 +++++------ plugins/steam-engine.cpp | 12 +++---- plugins/stockflow.cpp | 8 ++--- plugins/stockpiles.cpp | 9 +++-- plugins/stocks.cpp | 4 +-- plugins/strangemood.cpp | 22 ++++++------ plugins/tiletypes.cpp | 5 ++- plugins/trackstop.cpp | 14 ++------ plugins/treefarm.cpp | 10 +++--- plugins/tubefill.cpp | 6 ++-- plugins/tweak/tweak.cpp | 15 +++++---- plugins/weather.cpp | 6 ++-- plugins/workNow.cpp | 5 ++- plugins/workflow.cpp | 12 +++---- plugins/zone.cpp | 42 +++++++++++------------ 47 files changed, 236 insertions(+), 261 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 5416f6d00..47b2d5cac 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -17,13 +17,10 @@ using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::debug_turbospeed; - -// dfhack interface DFHACK_PLUGIN("fastdwarf"); - DFHACK_PLUGIN_IS_ENABLED(active); +REQUIRE_GLOBAL(world); +using df::global::debug_turbospeed; // not required static bool enable_fastdwarf = false; static bool enable_teledwarf = false; diff --git a/plugins/feature.cpp b/plugins/feature.cpp index cbfd7b5e0..30c71cda2 100644 --- a/plugins/feature.cpp +++ b/plugins/feature.cpp @@ -17,7 +17,8 @@ using std::endl; using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("feature"); +REQUIRE_GLOBAL(world); static command_result feature(color_ostream &out, vector ¶meters) @@ -92,8 +93,6 @@ static command_result feature(color_ostream &out, vector ¶meters) return CR_OK; } -DFHACK_PLUGIN("feature"); - DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/fixpositions.cpp b/plugins/fixpositions.cpp index bd8fb2791..7a22fd2b8 100644 --- a/plugins/fixpositions.cpp +++ b/plugins/fixpositions.cpp @@ -18,7 +18,8 @@ using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("fixpositions"); +REQUIRE_GLOBAL(world); command_result df_fixdiplomats (color_ostream &out, vector ¶meters) { @@ -226,8 +227,6 @@ command_result df_fixmerchants (color_ostream &out, vector ¶meters) return CR_OK; } -DFHACK_PLUGIN("fixpositions"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/fixveins.cpp b/plugins/fixveins.cpp index f02f61673..5f612c9ad 100644 --- a/plugins/fixveins.cpp +++ b/plugins/fixveins.cpp @@ -19,7 +19,8 @@ using std::string; using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("fixveins"); +REQUIRE_GLOBAL(world); bool setTileMaterial(df::tiletype &tile, const df::tiletype_material mat) { @@ -95,8 +96,6 @@ command_result df_fixveins (color_ostream &out, vector & parameters) return CR_OK; } -DFHACK_PLUGIN("fixveins"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("fixveins", diff --git a/plugins/flows.cpp b/plugins/flows.cpp index 26188f512..070776b10 100644 --- a/plugins/flows.cpp +++ b/plugins/flows.cpp @@ -15,7 +15,8 @@ using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("flows"); +REQUIRE_GLOBAL(world); command_result df_flows (color_ostream &out, vector & parameters) { @@ -56,8 +57,6 @@ command_result df_flows (color_ostream &out, vector & parameters) return CR_OK; } -DFHACK_PLUGIN("flows"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("flows", diff --git a/plugins/follow.cpp b/plugins/follow.cpp index de3428300..ff0a94787 100644 --- a/plugins/follow.cpp +++ b/plugins/follow.cpp @@ -16,7 +16,9 @@ using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("follow"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(world); command_result follow (color_ostream &out, std::vector & parameters); @@ -24,9 +26,6 @@ df::unit *followedUnit; int32_t prevX, prevY, prevZ; uint8_t prevMenuWidth; -DFHACK_PLUGIN("follow"); -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/forceequip.cpp b/plugins/forceequip.cpp index 54da374b5..1f1eb1ce6 100644 --- a/plugins/forceequip.cpp +++ b/plugins/forceequip.cpp @@ -46,13 +46,13 @@ using namespace df::enums; using MapExtras::Block; using MapExtras::MapCache; -using df::global::world; + +DFHACK_PLUGIN("forceequip"); +REQUIRE_GLOBAL(world); const int const_GloveRightHandedness = 1; const int const_GloveLeftHandedness = 2; -DFHACK_PLUGIN("forceequip"); - command_result df_forceequip(color_ostream &out, vector & parameters); const string forceequip_help = diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index f4771b58b..ed3272265 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -22,7 +22,8 @@ using std::set; using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("getplants"); +REQUIRE_GLOBAL(world); command_result df_getplants (color_ostream &out, vector & parameters) { @@ -148,8 +149,6 @@ command_result df_getplants (color_ostream &out, vector & parameters) return CR_OK; } -DFHACK_PLUGIN("getplants"); - DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/infiniteSky.cpp b/plugins/infiniteSky.cpp index 774ac4d51..f2853f128 100644 --- a/plugins/infiniteSky.cpp +++ b/plugins/infiniteSky.cpp @@ -23,12 +23,11 @@ using namespace std; using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("infiniteSky"); +REQUIRE_GLOBAL(world); command_result infiniteSky (color_ostream &out, std::vector & parameters); -DFHACK_PLUGIN("infiniteSky"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/initflags.cpp b/plugins/initflags.cpp index 6aa78930a..24c773679 100644 --- a/plugins/initflags.cpp +++ b/plugins/initflags.cpp @@ -12,13 +12,12 @@ using std::endl; using namespace DFHack; using namespace df::enums; -using df::global::d_init; +DFHACK_PLUGIN("initflags"); +REQUIRE_GLOBAL(d_init); command_result twaterlvl(color_ostream &out, vector & parameters); command_result tidlers(color_ostream &out, vector & parameters); -DFHACK_PLUGIN("initflags"); - DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (d_init) { diff --git a/plugins/isoworldremote.cpp b/plugins/isoworldremote.cpp index 78baad1e1..607035d7b 100644 --- a/plugins/isoworldremote.cpp +++ b/plugins/isoworldremote.cpp @@ -31,10 +31,11 @@ using namespace DFHack; using namespace df::enums; using namespace isoworldremote; -using df::global::gamemode; -using df::global::world; -using df::global::cur_year; -using df::global::cur_season; +DFHACK_PLUGIN("isoworldremote"); +REQUIRE_GLOBAL(gamemode); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(cur_year); +REQUIRE_GLOBAL(cur_season); // Here go all the command declarations... // mostly to allow having the mandatory stuff on top of the file and commands on the bottom @@ -47,11 +48,6 @@ static command_result GetRawNames(color_ostream &stream, const MapRequest *in, R bool gather_embark_tile_layer(int EmbX, int EmbY, int EmbZ, EmbarkTileLayer * tile, MapExtras::MapCache * MP); bool gather_embark_tile(int EmbX, int EmbY, EmbarkTile * tile, MapExtras::MapCache * MP); - -// A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case -DFHACK_PLUGIN("isoworldremote"); - // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { diff --git a/plugins/jobutils.cpp b/plugins/jobutils.cpp index dbfe26b90..3e3b6b2a0 100644 --- a/plugins/jobutils.cpp +++ b/plugins/jobutils.cpp @@ -31,11 +31,12 @@ using std::endl; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::ui; -using df::global::ui_build_selector; -using df::global::ui_workshop_job_cursor; -using df::global::job_next_id; +DFHACK_PLUGIN("jobutils"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_build_selector); +REQUIRE_GLOBAL(ui_workshop_job_cursor); +REQUIRE_GLOBAL(job_next_id); /* Plugin registration */ @@ -45,8 +46,6 @@ static command_result job_material(color_ostream &out, vector & paramet static command_result job_duplicate(color_ostream &out, vector & parameters); static command_result job_cmd(color_ostream &out, vector & parameters); -DFHACK_PLUGIN("jobutils"); - DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (!world || !ui) diff --git a/plugins/lair.cpp b/plugins/lair.cpp index cafbbcc33..0ae612e07 100644 --- a/plugins/lair.cpp +++ b/plugins/lair.cpp @@ -12,9 +12,9 @@ #include "modules/Gui.h" using namespace DFHack; using namespace df::enums; -using df::global::world; DFHACK_PLUGIN("lair"); +REQUIRE_GLOBAL(world); enum state { diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 5ec25de34..68229aaa3 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -46,15 +46,15 @@ using std::set; using namespace MapExtras; using namespace DFHack; using namespace df::enums; -using df::global::world; + +DFHACK_PLUGIN("liquids"); +REQUIRE_GLOBAL(world); CommandHistory liquids_hist; command_result df_liquids (color_ostream &out, vector & parameters); command_result df_liquids_here (color_ostream &out, vector & parameters); -DFHACK_PLUGIN("liquids"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { liquids_hist.load("liquids.history"); diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 93459e732..776100bd7 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -36,10 +36,12 @@ using std::string; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::ui; -using df::global::gps; -using df::global::enabler; +DFHACK_PLUGIN("manipulator"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(enabler); struct SkillLevel { @@ -1291,10 +1293,6 @@ struct unitlist_hook : df::viewscreen_unitlistst IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, render); -DFHACK_PLUGIN("manipulator"); - -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (!gps) diff --git a/plugins/misery.cpp b/plugins/misery.cpp index bc617b906..bc4ba3777 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -15,11 +15,12 @@ using namespace std; using namespace DFHack; -using df::global::world; -using df::global::ui; - +DFHACK_PLUGIN("misery"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); + static int factor = 1; static map processedThoughtCountTable; @@ -28,8 +29,6 @@ static vector > fakeThoughts; static int count; const int maxCount = 1000; -DFHACK_PLUGIN("misery"); - command_result misery(color_ostream& out, vector& parameters); DFhackCExport command_result plugin_shutdown(color_ostream& out) { diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index c0143e830..1b9eedd96 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -21,14 +21,13 @@ #include "TileTypes.h" #include "DataFuncs.h" -using df::global::world; -using df::global::ui; -using df::global::ui_build_selector; +DFHACK_PLUGIN("mousequery"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_build_selector); using namespace df::enums::ui_sidebar_mode; -DFHACK_PLUGIN("mousequery"); - #define PLUGIN_VERSION 0.18 static int32_t last_clicked_x, last_clicked_y, last_clicked_z; diff --git a/plugins/petcapRemover.cpp b/plugins/petcapRemover.cpp index 44870eb76..7eaa3b1e2 100644 --- a/plugins/petcapRemover.cpp +++ b/plugins/petcapRemover.cpp @@ -20,17 +20,17 @@ using namespace DFHack; using namespace std; -using df::global::world; +DFHACK_PLUGIN("petcapRemover"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); static int32_t howOften = 10000; static int32_t popcap = 100; static int32_t pregtime = 200000; -DFHACK_PLUGIN_IS_ENABLED(is_enabled); command_result petcapRemover (color_ostream &out, std::vector & parameters); -DFHACK_PLUGIN("petcapRemover"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 9509e07f8..5d84ca96e 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -18,11 +18,11 @@ using std::vector; using std::string; using namespace DFHack; -using df::global::world; - -const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3 - 1; // 3 years minus 1 - let the game handle the actual growing-up DFHACK_PLUGIN("plants"); +REQUIRE_GLOBAL(world); + +const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3 - 1; // 3 years minus 1 - let the game handle the actual growing-up /* Immolate/Extirpate no longer work in 0.40 enum do_what diff --git a/plugins/power-meter.cpp b/plugins/power-meter.cpp index 0f8135eed..039b133f2 100644 --- a/plugins/power-meter.cpp +++ b/plugins/power-meter.cpp @@ -38,12 +38,11 @@ using std::stack; using namespace DFHack; using namespace df::enums; -using df::global::gps; -using df::global::world; -using df::global::ui; -using df::global::ui_build_selector; - DFHACK_PLUGIN("power-meter"); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_build_selector); static const uint32_t METER_BIT = 0x80000000U; diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 28b62c24e..045326d62 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -33,15 +33,15 @@ using std::vector; using std::string; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::cursor; + +DFHACK_PLUGIN("probe"); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(cursor); command_result df_probe (color_ostream &out, vector & parameters); command_result df_cprobe (color_ostream &out, vector & parameters); command_result df_bprobe (color_ostream &out, vector & parameters); -DFHACK_PLUGIN("probe"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("probe", diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 8b505c221..995dea9ba 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -38,9 +38,11 @@ using namespace std; using namespace DFHack; using namespace df::enums; -using df::global::world; using df::coord2d; +DFHACK_PLUGIN("prospector"); +REQUIRE_GLOBAL(world); + struct matdata { const static int invalid_z = -30000; @@ -198,8 +200,6 @@ void printVeins(color_ostream &con, MatMap &mat_map, command_result prospector (color_ostream &out, vector & parameters); -DFHACK_PLUGIN("prospector"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/regrass.cpp b/plugins/regrass.cpp index 9c687ad7d..22b6a6747 100644 --- a/plugins/regrass.cpp +++ b/plugins/regrass.cpp @@ -22,9 +22,8 @@ using std::vector; using namespace std; using namespace DFHack; -using df::global::world; - DFHACK_PLUGIN("regrass"); +REQUIRE_GLOBAL(world); command_result df_regrass (color_ostream &out, vector & parameters); diff --git a/plugins/remotefortressreader.cpp b/plugins/remotefortressreader.cpp index 3ef673119..8c1307f61 100644 --- a/plugins/remotefortressreader.cpp +++ b/plugins/remotefortressreader.cpp @@ -56,6 +56,9 @@ using namespace df::enums; using namespace RemoteFortressReader; using namespace std; +DFHACK_PLUGIN("RemoteFortressReader"); +REQUIRE_GLOBAL(world); + // Here go all the command declarations... // mostly to allow having the mandatory stuff on top of the file and commands on the bottom @@ -80,10 +83,6 @@ const char* growth_locations[] = { }; #define GROWTH_LOCATIONS_SIZE 8 -// A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case -DFHACK_PLUGIN("RemoteFortressReader"); - // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { @@ -387,9 +386,9 @@ RemoteFortressReader::TiletypeVariant TranslateVariant(df::tiletype_variant vari static command_result CheckHashes(color_ostream &stream, const EmptyMessage *in) { clock_t start = clock(); - for (int i = 0; i < df::global::world->map.map_blocks.size(); i++) + for (int i = 0; i < world->map.map_blocks.size(); i++) { - df::map_block * block = df::global::world->map.map_blocks[i]; + df::map_block * block = world->map.map_blocks[i]; fletcher16((uint8_t*)(block->tiletype), 16 * 16 * sizeof(df::enums::tiletype::tiletype)); } clock_t end = clock(); @@ -417,7 +416,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage - df::world_raws *raws = &df::global::world->raws; + df::world_raws *raws = &world->raws; MaterialInfo mat; for (int i = 0; i < raws->inorganics.size(); i++) { @@ -509,7 +508,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i - df::world_raws *raws = &df::global::world->raws; + df::world_raws *raws = &world->raws; if (!raws) return CR_OK;//'. @@ -617,9 +616,9 @@ static command_result GetPlantList(color_ostream &stream, const BlockRequest *in for (int xx = min_x; xx < max_x; xx++) for (int yy = min_y; yy < max_y; yy++) { - if (xx < 0 || yy < 0 || xx >= df::global::world->map.x_count_block || yy >= df::global::world->map.y_count_block) + if (xx < 0 || yy < 0 || xx >= world->map.x_count_block || yy >= world->map.y_count_block) continue; - df::map_block_column * column = df::global::world->map.column_index[xx][yy]; + df::map_block_column * column = world->map.column_index[xx][yy]; for (int i = 0; i < column->plants.size(); i++) { df::plant * plant = column->plants[i]; diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 383066b5e..0ef3e02d5 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -46,17 +46,17 @@ using namespace DFHack; using namespace df::enums; using namespace dfproto; -using df::global::ui; -using df::global::ui_sidebar_menus; -using df::global::world; +DFHACK_PLUGIN("rename"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_sidebar_menus); +REQUIRE_GLOBAL(world); DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); static command_result rename(color_ostream &out, vector & parameters); -DFHACK_PLUGIN("rename"); -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (world && ui) { diff --git a/plugins/rendermax/rendermax.cpp b/plugins/rendermax/rendermax.cpp index a6b2f17c7..a2edd3b0a 100644 --- a/plugins/rendermax/rendermax.cpp +++ b/plugins/rendermax/rendermax.cpp @@ -28,6 +28,21 @@ using df::viewscreen_dwarfmodest; using namespace DFHack; using std::vector; using std::string; + +DFHACK_PLUGIN("rendermax"); +REQUIRE_GLOBAL(cur_year_tick); +REQUIRE_GLOBAL(cursor); +REQUIRE_GLOBAL(enabler); +REQUIRE_GLOBAL(gametype); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_area_map_width); +REQUIRE_GLOBAL(ui_menu_width); +REQUIRE_GLOBAL(window_x); +REQUIRE_GLOBAL(window_y); +REQUIRE_GLOBAL(window_z); +REQUIRE_GLOBAL(world); + enum RENDERER_MODE { MODE_DEFAULT,MODE_TRIPPY,MODE_TRUECOLOR,MODE_LUA,MODE_LIGHT @@ -37,9 +52,6 @@ lightingEngine *engine=NULL; static command_result rendermax(color_ostream &out, vector & parameters); -DFHACK_PLUGIN("rendermax"); - - DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( @@ -93,34 +105,34 @@ void removeOld() engine=0; } if(current_mode!=MODE_DEFAULT) - delete df::global::enabler->renderer; + delete enabler->renderer; current_mode=MODE_DEFAULT; } void installNew(df::renderer* r,RENDERER_MODE newMode) { - df::global::enabler->renderer=r; + enabler->renderer=r; current_mode=newMode; } static void lockGrids() { if(current_mode!=MODE_LUA) return ; - renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + renderer_lua* r=reinterpret_cast(enabler->renderer); r->dataMutex.lock(); } static void unlockGrids() { if(current_mode!=MODE_LUA) return ; - renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + renderer_lua* r=reinterpret_cast(enabler->renderer); r->dataMutex.unlock(); } static void resetGrids() { if(current_mode!=MODE_LUA) return ; - renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + renderer_lua* r=reinterpret_cast(enabler->renderer); for(size_t i=0;iforeMult.size();i++) { r->foreMult[i]=rgbf(1,1,1); @@ -133,16 +145,16 @@ static int getGridsSize(lua_State* L) { if(current_mode!=MODE_LUA) return -1; - renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); - lua_pushnumber(L,df::global::gps->dimx); - lua_pushnumber(L,df::global::gps->dimy); + renderer_lua* r=reinterpret_cast(enabler->renderer); + lua_pushnumber(L,gps->dimx); + lua_pushnumber(L,gps->dimy); return 2; } static int getCell(lua_State* L) { if(current_mode!=MODE_LUA) return 0; - renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + renderer_lua* r=reinterpret_cast(enabler->renderer); int x=luaL_checknumber(L,1); int y=luaL_checknumber(L,2); int id=r->xyToTile(x,y); @@ -193,7 +205,7 @@ static int setCell(lua_State* L) { if(current_mode!=MODE_LUA) return 0; - renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + renderer_lua* r=reinterpret_cast(enabler->renderer); int x=luaL_checknumber(L,1); int y=luaL_checknumber(L,2); @@ -242,7 +254,7 @@ static int invalidate(lua_State* L) { if(current_mode!=MODE_LUA) return 0; - renderer_lua* r=reinterpret_cast(df::global::enabler->renderer); + renderer_lua* r=reinterpret_cast(enabler->renderer); if(lua_gettop(L)==0) { r->invalidate(); @@ -324,7 +336,7 @@ static command_result rendermax(color_ostream &out, vector & parameters { if(parameters.size()==0) return CR_WRONG_USAGE; - if(!df::global::enabler->renderer->uses_opengl()) + if(!enabler->renderer->uses_opengl()) { out.printerr("Sorry, this plugin needs open gl enabled printmode. Try STANDARD or other non-2D\n"); return CR_FAILURE; @@ -333,7 +345,7 @@ static command_result rendermax(color_ostream &out, vector & parameters if(cmd=="trippy") { removeOld(); - installNew(new renderer_trippy(df::global::enabler->renderer),MODE_TRIPPY); + installNew(new renderer_trippy(enabler->renderer),MODE_TRIPPY); return CR_OK; } else if(cmd=="truecolor") @@ -341,7 +353,7 @@ static command_result rendermax(color_ostream &out, vector & parameters if(current_mode!=MODE_TRUECOLOR) { removeOld(); - installNew(new renderer_test(df::global::enabler->renderer),MODE_TRUECOLOR); + installNew(new renderer_test(enabler->renderer),MODE_TRUECOLOR); } if(current_mode==MODE_TRUECOLOR && parameters.size()==2) { @@ -356,10 +368,10 @@ static command_result rendermax(color_ostream &out, vector & parameters else if(col=="blue") cur=blue; - renderer_test* r=reinterpret_cast(df::global::enabler->renderer); + renderer_test* r=reinterpret_cast(enabler->renderer); tthread::lock_guard guard(r->dataMutex); - int h=df::global::gps->dimy; - int w=df::global::gps->dimx; + int h=gps->dimy; + int w=gps->dimx; int cx=w/2; int cy=h/2; int rad=cx; @@ -385,7 +397,7 @@ static command_result rendermax(color_ostream &out, vector & parameters else if(cmd=="lua") { removeOld(); - installNew(new renderer_lua(df::global::enabler->renderer),MODE_LUA); + installNew(new renderer_lua(enabler->renderer),MODE_LUA); lockGrids(); resetGrids(); unlockGrids(); @@ -396,7 +408,7 @@ static command_result rendermax(color_ostream &out, vector & parameters if(current_mode!=MODE_LIGHT) { removeOld(); - renderer_light *myRender=new renderer_light(df::global::enabler->renderer); + renderer_light *myRender=new renderer_light(enabler->renderer); installNew(myRender,MODE_LIGHT); engine=new lightingEngineViewscreen(myRender); @@ -444,7 +456,7 @@ static command_result rendermax(color_ostream &out, vector & parameters else removeOld(); CoreSuspender guard; - df::global::gps->force_full_display_count++; + gps->force_full_display_count++; return CR_OK; } return CR_WRONG_USAGE; diff --git a/plugins/resume.cpp b/plugins/resume.cpp index 01127ef94..e0db688ff 100644 --- a/plugins/resume.cpp +++ b/plugins/resume.cpp @@ -34,13 +34,13 @@ using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::gps; -using df::global::ui; -using df::global::world; - DFHACK_PLUGIN("resume"); #define PLUGIN_VERSION 0.2 +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(world); + #ifndef HAVE_NULLPTR #define nullptr 0L #endif diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index c0d71bb7b..c6b7cafbf 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -20,7 +20,10 @@ using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("reveal"); +DFHACK_PLUGIN_IS_ENABLED(is_active); + +REQUIRE_GLOBAL(world); /* * Anything that might reveal Hell is unsafe. @@ -72,8 +75,6 @@ command_result revflood(color_ostream &out, vector & params); command_result revforget(color_ostream &out, vector & params); command_result nopause(color_ostream &out, vector & params); -DFHACK_PLUGIN("reveal"); - DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand("reveal","Reveal the map. 'reveal hell' will also reveal hell. 'reveal demon' won't pause.",reveal,false, @@ -96,8 +97,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector abbreviations; @@ -250,8 +253,6 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) return CR_OK; } -DFHACK_PLUGIN("seedwatch"); - DFhackCExport command_result plugin_init(color_ostream &out, vector& commands) { commands.push_back(PluginCommand("seedwatch", "Switches cookery based on quantity of seeds, to keep reserves", df_seedwatch)); diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index e0bc5a08d..34b2c8f44 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -25,7 +25,8 @@ using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("showmood"); +REQUIRE_GLOBAL(world); command_result df_showmood (color_ostream &out, vector & parameters) { @@ -291,8 +292,6 @@ command_result df_showmood (color_ostream &out, vector & parameters) return CR_OK; } -DFHACK_PLUGIN("showmood"); - DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("showmood", "Shows items needed for current strange mood.", df_showmood, false, diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index b3e5a208d..4ad8fa341 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -65,16 +65,15 @@ using std::stack; using namespace DFHack; using namespace df::enums; -using df::global::gamemode; -using df::global::gps; -using df::global::world; -using df::global::ui; -using df::global::ui_build_selector; -using df::global::process_jobs; - using Screen::Pen; DFHACK_PLUGIN("siege-engine"); +REQUIRE_GLOBAL(gamemode); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_build_selector); +REQUIRE_GLOBAL(process_jobs); /* Aiming is simulated by using a normal distribution to perturb X and Y. diff --git a/plugins/sort.cpp b/plugins/sort.cpp index e4aad61e2..7d80b4aa1 100644 --- a/plugins/sort.cpp +++ b/plugins/sort.cpp @@ -37,14 +37,15 @@ using std::endl; using namespace DFHack; using namespace df::enums; -using df::global::ui; -using df::global::world; -using df::global::ui_building_in_assign; -using df::global::ui_building_item_cursor; -using df::global::ui_building_assign_type; -using df::global::ui_building_assign_is_marked; -using df::global::ui_building_assign_units; -using df::global::ui_building_assign_items; +DFHACK_PLUGIN("sort"); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui_building_in_assign); +REQUIRE_GLOBAL(ui_building_item_cursor); +REQUIRE_GLOBAL(ui_building_assign_type); +REQUIRE_GLOBAL(ui_building_assign_is_marked); +REQUIRE_GLOBAL(ui_building_assign_units); +REQUIRE_GLOBAL(ui_building_assign_items); static bool unit_list_hotkey(df::viewscreen *top); static bool item_list_hotkey(df::viewscreen *top); @@ -52,8 +53,6 @@ static bool item_list_hotkey(df::viewscreen *top); static command_result sort_units(color_ostream &out, vector & parameters); static command_result sort_items(color_ostream &out, vector & parameters); -DFHACK_PLUGIN("sort"); - DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/steam-engine.cpp b/plugins/steam-engine.cpp index 138de3916..2a22d5953 100644 --- a/plugins/steam-engine.cpp +++ b/plugins/steam-engine.cpp @@ -117,14 +117,14 @@ using std::set; using namespace DFHack; using namespace df::enums; -using df::global::gps; -using df::global::world; -using df::global::ui; -using df::global::ui_build_selector; -using df::global::cursor; - DFHACK_PLUGIN("steam-engine"); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_build_selector); +REQUIRE_GLOBAL(cursor); + /* * List of known steam engine workshop raws. */ diff --git a/plugins/stockflow.cpp b/plugins/stockflow.cpp index 257680682..33068479e 100644 --- a/plugins/stockflow.cpp +++ b/plugins/stockflow.cpp @@ -18,12 +18,9 @@ using namespace DFHack; using namespace std; -using df::global::world; -using df::global::ui; using df::building_stockpilest; DFHACK_PLUGIN("stockflow"); - #define AUTOENABLE false #ifdef DFHACK_PLUGIN_IS_ENABLED DFHACK_PLUGIN_IS_ENABLED(enabled); @@ -31,6 +28,9 @@ DFHACK_PLUGIN_IS_ENABLED(enabled); bool enabled = false; #endif +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); + bool fast = false; const char *tagline = "Allows the fortress bookkeeper to queue jobs through the manager."; const char *usage = ( @@ -72,7 +72,7 @@ public: } else { // Gather orders when the bookkeeper starts updating stockpile records, // and enqueue them when the job is done. - for (df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next) { + for (df::job_list_link* link = &world->job_list; link != NULL; link = link->next) { if (link->item == NULL) continue; if (link->item->job_type == job_type::UpdateStockpileRecords) { found = true; diff --git a/plugins/stockpiles.cpp b/plugins/stockpiles.cpp index ebf4b0d40..ae257c55a 100644 --- a/plugins/stockpiles.cpp +++ b/plugins/stockpiles.cpp @@ -58,9 +58,10 @@ using namespace df::enums; using namespace google::protobuf; using namespace dfstockpiles; -using df::global::world; -using df::global::ui; -using df::global::selection_rect; +DFHACK_PLUGIN ( "stockpiles" ); +REQUIRE_GLOBAL ( world ); +REQUIRE_GLOBAL ( ui ); +REQUIRE_GLOBAL ( selection_rect ); using df::building_stockpilest; using std::placeholders::_1; @@ -74,8 +75,6 @@ static bool savestock_guard ( df::viewscreen *top ); static command_result loadstock ( color_ostream &out, vector & parameters ); static bool loadstock_guard ( df::viewscreen *top ); -DFHACK_PLUGIN ( "stockpiles" ); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands ) { if ( world && ui ) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index c5fbed4d5..56692d5c4 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -28,11 +28,11 @@ #include "df/building_cagest.h" #include "df/ui_advmode.h" -using df::global::world; - DFHACK_PLUGIN("stocks"); #define PLUGIN_VERSION 0.12 +REQUIRE_GLOBAL(world); + DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 74c717816..09c4ead11 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -32,15 +32,17 @@ using std::vector; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::ui; -using df::global::d_init; -using df::global::created_item_count; -using df::global::created_item_type; -using df::global::created_item_subtype; -using df::global::created_item_mattype; -using df::global::created_item_matindex; -using df::global::debug_nomoods; +DFHACK_PLUGIN("strangemood"); + +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(d_init); +REQUIRE_GLOBAL(created_item_count); +REQUIRE_GLOBAL(created_item_type); +REQUIRE_GLOBAL(created_item_subtype); +REQUIRE_GLOBAL(created_item_mattype); +REQUIRE_GLOBAL(created_item_matindex); +REQUIRE_GLOBAL(debug_nomoods); Random::MersenneRNG rng; @@ -1302,8 +1304,6 @@ command_result df_strangemood (color_ostream &out, vector & parameters) return CR_OK; } -DFHACK_PLUGIN("strangemood"); - DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("strangemood", "Force a strange mood to happen.\n", df_strangemood, false, diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index b624b944f..1a249825c 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -44,7 +44,8 @@ using namespace MapExtras; using namespace DFHack; using namespace df::enums; -using df::global::world; +DFHACK_PLUGIN("tiletypes"); +REQUIRE_GLOBAL(world); CommandHistory tiletypes_hist; @@ -53,8 +54,6 @@ command_result df_tiletypes_command (color_ostream &out, vector & param 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"); diff --git a/plugins/trackstop.cpp b/plugins/trackstop.cpp index c7109191a..a10b0105b 100644 --- a/plugins/trackstop.cpp +++ b/plugins/trackstop.cpp @@ -15,18 +15,18 @@ using namespace DFHack; using namespace std; -using df::global::world; -using df::global::ui; using df::building_rollersst; using df::building_trapst; using df::enums::trap_type::trap_type; using df::enums::screw_pump_direction::screw_pump_direction; DFHACK_PLUGIN("trackstop"); - #define AUTOENABLE false DFHACK_PLUGIN_IS_ENABLED(enabled); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(world); /* * Interface hooks @@ -270,14 +270,6 @@ IMPLEMENT_VMETHOD_INTERPOSE(roller_hook, render); DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { // Accept the "enable trackstop" / "disable trackstop" commands. if (enable != enabled) { - // Check for global variables that, if missing, result in total failure. - // Missing enabler and ui_menu_width also produce visible effects, but not nearly as severe. - // This could be moved to the plugin_init step, but that's louder for no real benefit. - if (!(gps && ui && world)) { - out.printerr("trackstop: Missing required global variables.\n"); - return CR_FAILURE; - } - if (!INTERPOSE_HOOK(trackstop_hook, feed).apply(enable) || !INTERPOSE_HOOK(trackstop_hook, render).apply(enable) || !INTERPOSE_HOOK(roller_hook, feed).apply(enable) || diff --git a/plugins/treefarm.cpp b/plugins/treefarm.cpp index 6660b07f8..1578bf373 100644 --- a/plugins/treefarm.cpp +++ b/plugins/treefarm.cpp @@ -21,8 +21,11 @@ using namespace DFHack; -using df::global::world; -using df::global::ui; +DFHACK_PLUGIN("treefarm"); +DFHACK_PLUGIN_IS_ENABLED(enabled); + +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); void checkFarms(color_ostream& out, void* ptr); command_result treefarm (color_ostream &out, std::vector & parameters); @@ -30,9 +33,6 @@ command_result treefarm (color_ostream &out, std::vector & paramet EventManager::EventHandler handler(&checkFarms, -1); int32_t frequency = 1200*30; -DFHACK_PLUGIN_IS_ENABLED(enabled); -DFHACK_PLUGIN("treefarm"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/tubefill.cpp b/plugins/tubefill.cpp index efdc6f1a9..73c615c9b 100644 --- a/plugins/tubefill.cpp +++ b/plugins/tubefill.cpp @@ -17,7 +17,9 @@ using namespace DFHack; using namespace df::enums; -using df::global::world; + +DFHACK_PLUGIN("tubefill"); +REQUIRE_GLOBAL(world); bool isDesignatedHollow(df::coord pos) { @@ -33,8 +35,6 @@ bool isDesignatedHollow(df::coord pos) command_result tubefill(color_ostream &out, std::vector & params); -DFHACK_PLUGIN("tubefill"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("tubefill","Fill in all the adamantine tubes again.",tubefill, false, diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index d49fbe2da..8e53dc0a7 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -92,19 +92,20 @@ using std::endl; using namespace DFHack; using namespace df::enums; -using df::global::ui; -using df::global::world; -using df::global::ui_build_selector; -using df::global::ui_menu_width; -using df::global::ui_area_map_width; +DFHACK_PLUGIN("tweak"); + +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_build_selector); +REQUIRE_GLOBAL(ui_building_item_cursor); +REQUIRE_GLOBAL(ui_menu_width); +REQUIRE_GLOBAL(ui_area_map_width); +REQUIRE_GLOBAL(world); using namespace DFHack::Gui; static command_result tweak(color_ostream &out, vector & parameters); static std::multimap tweak_hooks; -DFHACK_PLUGIN("tweak"); - #define TWEAK_HOOK(tweak, cls, func) tweak_hooks.insert(std::pair\ (tweak, INTERPOSE_HOOK(cls, func))) diff --git a/plugins/weather.cpp b/plugins/weather.cpp index e94f56dab..481c1a779 100644 --- a/plugins/weather.cpp +++ b/plugins/weather.cpp @@ -13,15 +13,15 @@ using std::string; using namespace DFHack; using namespace df::enums; -using df::global::current_weather; +DFHACK_PLUGIN("weather"); + +REQUIRE_GLOBAL(current_weather); bool locked = false; unsigned char locked_data[25]; command_result weather (color_ostream &out, vector & parameters); -DFHACK_PLUGIN("weather"); - DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( diff --git a/plugins/workNow.cpp b/plugins/workNow.cpp index e8f34939a..b26428f8a 100644 --- a/plugins/workNow.cpp +++ b/plugins/workNow.cpp @@ -14,10 +14,9 @@ using namespace std; using namespace DFHack; -using df::global::process_jobs; -using df::global::process_dig; - DFHACK_PLUGIN("workNow"); +REQUIRE_GLOBAL(process_jobs); +REQUIRE_GLOBAL(process_dig); static int mode = 0; diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 16baec260..3ee0097df 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -49,10 +49,12 @@ using std::flush; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::ui; -using df::global::ui_workshop_job_cursor; -using df::global::job_next_id; +DFHACK_PLUGIN("workflow"); + +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_workshop_job_cursor); +REQUIRE_GLOBAL(job_next_id); /* Plugin registration */ @@ -61,8 +63,6 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet static void init_state(color_ostream &out); static void cleanup_state(color_ostream &out); -DFHACK_PLUGIN("workflow"); - DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (!world || !ui) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c88e82154..386200d41 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -74,23 +74,27 @@ using std::vector; using std::string; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::cursor; -using df::global::ui; -using df::global::ui_build_selector; -using df::global::gps; -using df::global::cur_year; -using df::global::cur_year_tick; - -using df::global::ui_building_item_cursor; -using df::global::ui_building_assign_type; -using df::global::ui_building_assign_is_marked; -using df::global::ui_building_assign_units; -using df::global::ui_building_assign_items; -using df::global::ui_building_in_assign; - -using df::global::ui_menu_width; -using df::global::ui_area_map_width; + +DFHACK_PLUGIN("zone"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(cursor); +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_build_selector); +REQUIRE_GLOBAL(gps); +REQUIRE_GLOBAL(cur_year); +REQUIRE_GLOBAL(cur_year_tick); + +REQUIRE_GLOBAL(ui_building_item_cursor); +REQUIRE_GLOBAL(ui_building_assign_type); +REQUIRE_GLOBAL(ui_building_assign_is_marked); +REQUIRE_GLOBAL(ui_building_assign_units); +REQUIRE_GLOBAL(ui_building_assign_items); +REQUIRE_GLOBAL(ui_building_in_assign); + +REQUIRE_GLOBAL(ui_menu_width); +REQUIRE_GLOBAL(ui_area_map_width); using namespace DFHack::Gui; @@ -98,10 +102,6 @@ command_result df_zone (color_ostream &out, vector & parameters); command_result df_autonestbox (color_ostream &out, vector & parameters); command_result df_autobutcher(color_ostream &out, vector & parameters); -DFHACK_PLUGIN("zone"); - -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable); const string zone_help = From 88b51fcb5b5ba23fca194555253cb7051afe0cb9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Dec 2014 20:29:08 -0500 Subject: [PATCH 022/115] Allow strangemood to work if debug_nomoods is not available --- plugins/strangemood.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 09c4ead11..22bafc056 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -42,7 +42,7 @@ REQUIRE_GLOBAL(created_item_type); REQUIRE_GLOBAL(created_item_subtype); REQUIRE_GLOBAL(created_item_mattype); REQUIRE_GLOBAL(created_item_matindex); -REQUIRE_GLOBAL(debug_nomoods); +using df::global::debug_nomoods; Random::MersenneRNG rng; @@ -476,7 +476,7 @@ command_result df_strangemood (color_ostream &out, vector & parameters) out.printerr("ARTIFACTS are not enabled!\n"); return CR_FAILURE; } - if (*debug_nomoods) + if (debug_nomoods && *debug_nomoods) { out.printerr("Strange moods disabled via debug flag!\n"); return CR_FAILURE; From 09681cf029993a2e8c5d0bae055e2f9f06d3d631 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Dec 2014 20:55:57 -0500 Subject: [PATCH 023/115] Update skeleton plugin --- plugins/skeleton/skeleton.cpp | 22 ++++++++++++++-------- plugins/skeleton/skeletonShort.cpp | 9 +++++---- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/plugins/skeleton/skeleton.cpp b/plugins/skeleton/skeleton.cpp index 293e43c8d..ea2f4fb4a 100644 --- a/plugins/skeleton/skeleton.cpp +++ b/plugins/skeleton/skeleton.cpp @@ -10,23 +10,29 @@ #include "DataDefs.h" //#include "df/world.h" +// our own, empty header. +#include "skeleton.h" + using namespace DFHack; using namespace df::enums; -// our own, empty header. -#include "skeleton.h" +// A plugin must be able to return its name and version. +// The name string provided must correspond to the filename - +// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case +DFHACK_PLUGIN("skeleton"); +// Any globals a plugin requires (e.g. world) should be listed here. +// For example, this line expands to "using df::global::world" and prevents the +// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml): +// +// REQUIRE_GLOBAL(world); // 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 skeleton (color_ostream &out, std::vector & parameters); -// A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case -DFHACK_PLUGIN("skeleton"); - // Mandatory init function. If you have some global state, create it here. -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { // Fill the command list with your commands. commands.push_back(PluginCommand( @@ -42,7 +48,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters); - DFHACK_PLUGIN("skeleton2"); +//REQUIRE_GLOBAL(world); + +command_result skeleton2 (color_ostream &out, std::vector & parameters); -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( "skeleton2", @@ -24,7 +25,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sun, 30 Nov 2014 21:46:46 +0200 Subject: [PATCH 024/115] Removed (for now) all the cruft involving item selection gui. Added unit_action for adventurer to perform work. --- scripts/gui/advfort.lua | 264 ++++++++++++---------------------------- 1 file changed, 81 insertions(+), 183 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 5eb554c1c..1ae5f97d8 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -14,14 +14,14 @@ workshop={key="CHANGETAB",desc="Show building menu"}, } -- building filters build_filter={ -forbid_all=true, --this forbits all except the "allow" -allow={"MetalSmithsForge"}, --ignored if forbit_all=false -forbid={"Custom"} --ignored if forbit_all==true + forbid_all=false, --this forbits all except the "allow" + allow={"MetalSmithsForge"}, --ignored if forbit_all=false + forbid={} --ignored if forbit_all==true } build_filter.HUMANish={ -forbid_all=true, -allow={"Masons"}, -forbid={} + forbid_all=true, + allow={"Masons"}, + forbid={} } local gui = require 'gui' @@ -34,7 +34,7 @@ local utils=require 'utils' local tile_attrs = df.tiletype.attrs -settings={build_by_items=false,check_inv=false,df_assign=true} +settings={build_by_items=false,check_inv=true,df_assign=false} function hasValue(tbl,val) for k,v in pairs(tbl) do @@ -138,30 +138,57 @@ function inSite() end end --[[ low level job management ]]-- -function getLastJobLink() - local st=df.global.world.job_list - while st.next~=nil do - st=st.next +function findAction(unit,ltype) + ltype=ltype or df.unit_action_type.None + for i,v in ipairs(unit.actions) do + if v.type==ltype then + return v + end + end +end +function add_action(unit,action_data) + local action=findAction(unit) --find empty action + if action then + action:assign(action_data) + action.id=unit.next_action_id + unit.next_action_id=unit.next_action_id+1 + else + local tbl=copyall(action_data) + tbl.new=true + tbl.id=unit.next_action_id + unit.actions:insert("#",tbl) + unit.next_action_id=unit.next_action_id+1 end - return st end -function addNewJob(job) - local lastLink=getLastJobLink() - local newLink=df.job_list_link:new() - newLink.prev=lastLink - lastLink.next=newLink - newLink.item=job - job.list_link=newLink +function addJobAction(job,unit) --what about job2? + if job==nil then + error("invalid job") + end + if findAction(unit,df.unit_action_type.Job) or findAction(unit,df.unit_action_type.Job2) then + print("Already has job action") + return + end + local action=findAction(unit) + local pos=copyall(unit.pos) + --local pos=copyall(job.pos) + unit.path.dest:assign(pos) + --job + local data={type=df.unit_action_type.Job,data={job={x=pos.x,y=pos.y,z=pos.z,timer=10}}} + --job2: + --local data={type=df.unit_action_type.Job2,data={job2={timer=10}}} + add_action(unit,data) + --add_action(unit,{type=df.unit_action_type.Unsteady,data={unsteady={timer=5}}}) end function makeJob(args) local newJob=df.job:new() newJob.id=df.global.job_next_id df.global.job_next_id=df.global.job_next_id+1 - --newJob.flags.special=true + newJob.flags.special=true newJob.job_type=args.job_type newJob.completion_timer=-1 newJob.pos:assign(args.pos) + --newJob.pos:assign(args.unit.pos) args.job=newJob local failed for k,v in ipairs(args.pre_actions or {}) do @@ -185,7 +212,8 @@ function makeJob(args) end end if failed==nil then - addNewJob(newJob) + dfhack.job.linkIntoWorld(newJob,true) + addJobAction(newJob,args.unit) return newJob else newJob:delete() @@ -345,7 +373,7 @@ function IsWall(args) end function IsTree(args) local tt=dfhack.maps.getTileType(args.pos) - if tile_attrs[tt].shape==df.tiletype_shape.TREE then + if tile_attrs[tt].material==df.tiletype_material.TREE then return true else return false, "Can only do it on trees" @@ -586,155 +614,6 @@ function EnumItems(args) end return ret end -jobitemEditor=defclass(jobitemEditor,gui.FramedScreen) -jobitemEditor.ATTRS{ - frame_style = gui.GREY_LINE_FRAME, - frame_inset = 1, - allow_add=false, - allow_remove=false, - allow_any_item=false, - job=DEFAULT_NIL, - items=DEFAULT_NIL, -} -function jobitemEditor:init(args) - --self.job=args.job - if self.job==nil then qerror("This screen must have job target") end - if self.items==nil then qerror("This screen must have item list") end - local itemChoices={} - table.insert(itemChoices,{text=""}) - for k,v in pairs(self.items) do - table.insert(itemChoices,{item=v,text=dfhack.items.getDescription(v, 0)}) - end - self.itemChoices=itemChoices - self:addviews{ - wid.Label{ - view_id = 'label', - text = args.prompt, - text_pen = args.text_pen, - frame = { l = 0, t = 0 }, - }, - wid.List{ - view_id = 'itemList', - frame = { l = 0, t = 2 ,b=2}, - on_submit = self:callback("selectJobItem") - }, - wid.Label{ - frame = { b=1,l=1}, - text ={{text= ": cancel", - key = "LEAVESCREEN", - on_activate= self:callback("dismiss") - }, - { - gap=3, - text= ": accept", - key = "SEC_SELECT", - on_activate= self:callback("commit"), - enabled=self:callback("jobValid") - }, - { - gap=3, - text= ": add", - key = "CUSTOM_A", - enabled=false, - --on_activate= self:callback("commit") - }, - { - gap=3, - text= ": remove", - key = "CUSTOM_R", - enabled=false, - --on_activate= self:callback("commit") - },} - }, - } - self.assigned={} - self:fill(self.job) -end - -function jobitemEditor:fill(job) - local choices={} - for item_id, trg_job_item in ipairs(job.job_items) do - local str="" - if self.assigned[item_id] ~=nil then - str=dfhack.items.getDescription(self.assigned[item_id],0) - end - local text={string.format("%3d:",item_id)} - if self.assigned[item_id]~=nil then - for subid,assigned_item in ipairs(self.assigned[item_id]) do - table.insert(text," "..dfhack.items.getDescription(assigned_item,0)) - table.insert(text,"\n") - end - else - table.insert(text,"") - end - table.insert(choices,{text=text, - job_item=trg_job_item,job_item_idx=item_id}) - end - self.choices=choices - self.subviews.itemList:setChoices(choices) -end -function jobitemEditor:countMatched(job_item_idx,job_item) - local sum=0 - if self.assigned[job_item_idx]==nil then return false end - for k,v in pairs(self.assigned[job_item_idx]) do - sum=sum+v:getTotalDimension() - end - return job_item.quantity<=sum -end -function jobitemEditor:jobValid() - for k,v in pairs(self.job.job_items) do - if not self:countMatched(k,v) then - return false - end - end - return true -end -function jobitemEditor:itemChosen(job_choice,index,choice) - if choice.item==nil then - self.assigned[job_choice.job_item_idx]=nil - else - self.assigned[job_choice.job_item_idx]=self.assigned[job_choice.job_item_idx] or {} - table.insert(self.assigned[job_choice.job_item_idx],choice.item) - if not self:countMatched(job_choice.job_item_idx,job_choice.job_item) then - self:selectJobItem(nil,job_choice) - end - end - self:fill(self.job) -end -function jobitemEditor:selectJobItem(index,choice) - local filtered_items={} - for k,v in pairs(self.itemChoices) do - if (v.text=="" or isSuitableItem(choice.job_item,v.item)) and (v.item==nil or not self:isAssigned(v.item)) then - table.insert(filtered_items,v) - end - end - dialog.showListPrompt("Item choice", "Choose item:", nil, filtered_items, self:callback("itemChosen",choice), nil,nil,true) --custom item choice dialog req -end -function jobitemEditor:isAssigned(item) - for k,v in pairs(self.assigned) do - for subid, aitem in pairs(v) do - if aitem.id==item.id then - return true - end - end - end -end -function jobitemEditor:commit() - - for job_item_id,v in pairs(self.assigned) do - for sub_id,cur_item in pairs(v) do - self.job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_item_id}) - end - end - local uncollected = getItemsUncollected(job) - if #uncollected == 0 then - self.job.flags.working=true - else - uncollected[1].is_fetching=1 - self.job.flags.fetching=true - end - self:dismiss() -end function AssignJobItems(args) if settings.df_assign then --use df default logic and hope that it would work @@ -864,23 +743,25 @@ function CancelJob(unit) end function ContinueJob(unit) local c_job=unit.job.current_job - if c_job then - c_job.flags.suspend=false - for k,v in pairs(c_job.items) do - if v.is_fetching==1 then - unit.path.dest:assign(v.item.pos) - return - end + --no job to continue + if not c_job then return end + --reset suspends... + c_job.flags.suspend=false + for k,v in pairs(c_job.items) do --try fetching missing items + if v.is_fetching==1 then + unit.path.dest:assign(v.item.pos) + return end - unit.path.dest:assign(c_job.pos) end + unit.path.dest:assign(c_job.pos) -- FIXME: overwritten by actions + addJobAction(c_job,unit) end actions={ {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}}, - {"DetailWall" ,df.job_type.DetailWall,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall,IsHardMaterial}}, - {"DetailFloor" ,df.job_type.DetailFloor,{MakePredicateWieldsItem(df.job_skill.MINING),IsFloor,IsHardMaterial,SameSquare}}, - {"CarveTrack" ,df.job_type.CarveTrack,{MakePredicateWieldsItem(df.job_skill.MINING),IsFloor,IsHardMaterial} + {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}}, + {"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMaterial,SameSquare}}, + {"CarveTrack" ,df.job_type.CarveTrack,{IsFloor,IsHardMaterial} ,{SetCarveDir}}, {"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, {"CarveUpwardStaircase" ,df.job_type.CarveUpwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, @@ -1399,9 +1280,13 @@ function usetool:onInput(keys) if mode<0 then mode=#actions-1 end --elseif keys.A_LOOK then -- self:sendInputToParent("A_LOOK") + elseif keys["A_SHORT_WAIT"] then + --ContinueJob(adv) + self:sendInputToParent("A_SHORT_WAIT") elseif keys[keybinds.continue.key] then ContinueJob(adv) - self:sendInputToParent("A_WAIT") + self:sendInputToParent("A_SHORT_WAIT") + self.long_wait=true else if self.mode~=nil then if keys[keybinds.workshop.key] then @@ -1421,6 +1306,19 @@ function usetool:onInput(keys) self.subviews.siteLabel.visible=false end end +function usetool:onIdle() + local adv=df.global.world.units.active[0] + local job=findAction(adv,df.unit_action_type.Job) + + if self.long_wait and not job then + if adv.job.current_job.completion_timer==0 or adv.job.current_job.completion_timer==-1 then + self.long_wait=false + end + ContinueJob(adv) + self:sendInputToParent("A_SHORT_WAIT") --todo continue till finished + end + self._native.parent:logic() +end function usetool:isOnBuilding() local adv=df.global.world.units.active[0] local bld=dfhack.buildings.findAtTile(adv.pos) From 05887ffdb8bfb295fd0599a211c8c0f801a95186 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 7 Dec 2014 22:17:37 +0200 Subject: [PATCH 025/115] Fixed advfort somewhat: now it teleports items so you can build/craft stuff. --- scripts/gui/advfort.lua | 98 +++++++++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 18 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 1ae5f97d8..9f2e6fc41 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,5 +1,14 @@ -- allows to do jobs in adv. mode. +--[==[ + version: 0.002 + changelog: + *0.002 + - kind-of fixed the item problem... now they get teleported (if teleport_items=true which should be default for adventurer) + - gather plants still not working... Other jobs seem to work. + - added new-and-improved waiting. Interestingly it could be improved to be interuptable. +--]==] + --keybinding, change to your hearts content. Only the key part. keybinds={ nextJob={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, @@ -24,6 +33,9 @@ build_filter.HUMANish={ forbid={} } +--[[ FIXME: maybe let player select which to disable?]] +for k,v in ipairs(df.global.ui.economic_stone) do df.global.ui.economic_stone[k]=0 end + local gui = require 'gui' local wid=require 'gui.widgets' local dialog=require 'gui.dialogs' @@ -34,7 +46,7 @@ local utils=require 'utils' local tile_attrs = df.tiletype.attrs -settings={build_by_items=false,check_inv=true,df_assign=false} +settings={build_by_items=false,use_worn=false,check_inv=true,teleport_items=true,df_assign=false} function hasValue(tbl,val) for k,v in pairs(tbl) do @@ -48,7 +60,7 @@ function reverseRaceLookup(id) return df.global.world.raws.creatures.all[id].creature_id end function deon_filter(name,type_id,subtype_id,custom_id, parent) - print(name) + --print(name) local adv=df.global.world.units.active[0] local race_filter=build_filter[reverseRaceLookup(adv.race)] if race_filter then @@ -410,9 +422,10 @@ function itemsAtPos(pos,tbl) return ret end function AssignBuildingRef(args) - local bld=dfhack.buildings.findAtTile(args.pos) + local bld=args.building or dfhack.buildings.findAtTile(args.pos) args.job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=bld.id}) bld.jobs:insert("#",args.job) + args.building=args.building or bld return true end function chooseBuildingWidthHeightDir(args) --TODO nicer selection dialog @@ -517,7 +530,30 @@ function isSuitableItem(job_item,item) local matinfo=dfhack.matinfo.decode(item) --print(matinfo:getCraftClass()) --print("Matching ",item," vs ",job_item) + if not matinfo:matches(job_item) then + --[[ + local true_flags={} + for k,v in pairs(job_item.flags1) do + if v then + table.insert(true_flags,k) + end + end + for k,v in pairs(job_item.flags2) do + if v then + table.insert(true_flags,k) + end + end + for k,v in pairs(job_item.flags3) do + if v then + table.insert(true_flags,k) + end + end + for k,v in pairs(true_flags) do + print(v) + end + --]] + return false,"matinfo" end -- some bonus checks: @@ -614,6 +650,15 @@ function EnumItems(args) end return ret end +function putItemsInBuilding(building,job_item_refs) + for k,v in ipairs(job_item_refs) do + --local pos=dfhack.items.getPosition(v) + if not dfhack.items.moveToBuilding(v.item,building,0) then + print("Could not put item:",k,v.item) + end + v.is_fetching=0 + end +end function AssignJobItems(args) if settings.df_assign then --use df default logic and hope that it would work @@ -621,9 +666,14 @@ function AssignJobItems(args) end -- first find items that you want to use for the job local job=args.job - local its=EnumItems{pos=args.from_pos,unit=args.unit, - inv={[df.unit_inventory_item.T_mode.Hauled]=settings.check_inv,[df.unit_inventory_item.T_mode.Worn]=settings.check_inv, - [df.unit_inventory_item.T_mode.Weapon]=settings.check_inv,},deep=true} + local its + if settings.check_inv then + its=EnumItems{pos=args.from_pos,unit=args.unit, + inv={[df.unit_inventory_item.T_mode.Hauled]=settings.use_worn,[df.unit_inventory_item.T_mode.Worn]=settings.use_worn, + [df.unit_inventory_item.T_mode.Weapon]=settings.use_worn,},deep=true} + else + its=EnumItems{pos=args.from_pos} + end --[[ job item editor... jobitemEditor{job=args.job,items=its}:show() local ok=job.flags.working or job.flags.fetching @@ -638,6 +688,7 @@ function AssignJobItems(args) job.items[#job.items-1]:delete() job.items:erase(#job.items-1) end]] + printall(its) local item_counts={} for job_id, trg_job_item in ipairs(job.job_items) do item_counts[job_id]=trg_job_item.quantity @@ -648,15 +699,17 @@ function AssignJobItems(args) if not used_item_id[cur_item.id] then local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) - --if msg then - -- print(cur_item,msg) - --end + --[[ + if msg then + print(cur_item,msg) + end + ]]-- if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then --cur_item.flags.in_job=true job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) item_counts[job_id]=item_counts[job_id]-cur_item:getTotalDimension() - --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) + print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) used_item_id[cur_item.id]=true end end @@ -670,6 +723,9 @@ function AssignJobItems(args) end end end + if settings.teleport_items then + putItemsInBuilding(args.building,job.items) + end local uncollected = getItemsUncollected(job) if #uncollected == 0 then job.flags.working=true @@ -683,7 +739,9 @@ function AssignJobItems(args) return true --]=] end + function CheckAndFinishBuilding(args,bld) + args.building=args.building or bld for idx,job in pairs(bld.jobs) do if job.job_type==df.job_type.ConstructBuilding then args.job=job @@ -694,6 +752,7 @@ function CheckAndFinishBuilding(args,bld) if args.job~=nil then local ok,msg=AssignJobItems(args) if not ok then + print(msg) return false,msg else AssignUnitToJob(args.job,args.unit,args.from_pos) @@ -706,7 +765,8 @@ function CheckAndFinishBuilding(args,bld) end end function AssignJobToBuild(args) - local bld=dfhack.buildings.findAtTile(args.pos) + local bld=args.building or dfhack.buildings.findAtTile(args.pos) + args.building=bld args.job_type=df.job_type.ConstructBuilding if bld~=nil then CheckAndFinishBuilding(args,bld) @@ -753,7 +813,8 @@ function ContinueJob(unit) return end end - unit.path.dest:assign(c_job.pos) -- FIXME: overwritten by actions + + --unit.path.dest:assign(c_job.pos) -- FIXME: job pos is not always the target pos!! addJobAction(c_job,unit) end @@ -831,11 +892,10 @@ function usetool:init(args) } } } -end - -function usetool:onIdle() - - self._native.parent:logic() + local labors=df.global.world.units.active[0].status.labors + for i,v in ipairs(labors) do + labors[i]=true + end end MOVEMENT_KEYS = { A_CARE_MOVE_N = { 0, -1, 0 }, A_CARE_MOVE_S = { 0, 1, 0 }, @@ -1306,11 +1366,13 @@ function usetool:onInput(keys) self.subviews.siteLabel.visible=false end end + function usetool:onIdle() local adv=df.global.world.units.active[0] + local job_ptr=adv.job.current_job local job=findAction(adv,df.unit_action_type.Job) - if self.long_wait and not job then + if job_ptr and self.long_wait and not job then if adv.job.current_job.completion_timer==0 or adv.job.current_job.completion_timer==-1 then self.long_wait=false end From f84a5dbce124bad5ac716c5a7f4076e5449f5423 Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 8 Dec 2014 00:55:46 +0200 Subject: [PATCH 026/115] Fixed a bug where it does not find building ref. --- scripts/gui/advfort.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 9f2e6fc41..608bdf00c 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -941,6 +941,7 @@ function setFiltersUp(specific,args) end function onWorkShopJobChosen(args,idx,choice) args.pos=args.from_pos + args.building=args.building or dfhack.buildings.findAtTile(args.pos) args.job_type=choice.job_id args.post_actions={AssignBuildingRef} args.pre_actions={dfhack.curry(setFiltersUp,choice.filter),AssignJobItems} @@ -1029,7 +1030,7 @@ function usetool:onWorkShopButtonClicked(building,index,choice) if #building.jobs>0 then local job=building.jobs[#building.jobs-1] AssignUnitToJob(job,adv,adv.pos) - AssignJobItems{job=job,from_pos=adv.pos,pos=adv.pos,unit=adv} + AssignJobItems{job=job,from_pos=adv.pos,pos=adv.pos,unit=adv,building=building} end elseif df.interface_button_building_category_selectorst:is_instance(choice.button) or df.interface_button_building_material_selectorst:is_instance(choice.button) then @@ -1074,7 +1075,7 @@ function usetool:openShopWindow(building) local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) if filter_pile then - local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z} + local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z,building=building} ,screen=self,bld=building,common=filter_pile.common} choices={} for k,v in pairs(filter_pile) do @@ -1108,7 +1109,8 @@ function usetool:armCleanTrap(building) end --building.trap_type==df.trap_type.PressurePlate then --settings/link - local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.CleanTrap} + local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos,from_pos=adv.pos, + building=building,job_type=df.job_type.CleanTrap} if building.trap_type==df.trap_type.CageTrap then args.job_type=df.job_type.LoadCageTrap local job_filter={items={{quantity=1,item_type=df.item_type.CAGE}} } @@ -1131,7 +1133,8 @@ function usetool:armCleanTrap(building) end function usetool:hiveActions(building) local adv=df.global.world.units.active[0] - local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive} + local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos, + from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive,building=building} local job_filter={items={{quantity=1,item_type=df.item_type.VERMIN}} } args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} local job,msg=makeJob(args) From 17772954cc503d87b3ae9bf85ba089425cf23e9b Mon Sep 17 00:00:00 2001 From: Putnam3145 Date: Sun, 7 Dec 2014 21:04:39 -0800 Subject: [PATCH 027/115] Made restrictive mat filter work properly Yeah, not sure what the heck I was doing there before. --- scripts/gui/hack-wish.lua | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/gui/hack-wish.lua b/scripts/gui/hack-wish.lua index 7767e753f..f14f787f1 100644 --- a/scripts/gui/hack-wish.lua +++ b/scripts/gui/hack-wish.lua @@ -71,7 +71,7 @@ function getMatFilter(itemtype) end function getRestrictiveMatFilter(itemType) - if not args.veryRestrictive then return nil else + if not args.restrictive then return nil end local itemTypes={ WEAPON=function(mat,parent,typ,idx) return (mat.flags.ITEMS_WEAPON or mat.flags.ITEMS_WEAPON_RANGED) @@ -82,15 +82,12 @@ function getRestrictiveMatFilter(itemType) ARMOR=function(mat,parent,typ,idx) return (mat.flags.ITEMS_ARMOR) end, - SHOES,SHIELD,HELM,GLOVES=ARMOR,ARMOR,ARMOR,ARMOR, INSTRUMENT=function(mat,parent,typ,idx) return (mat.flags.ITEMS_HARD) end, - GOBLET,FLASK,TOY,RING,CROWN,SCEPTER,FIGURINE,TOOL=INSTRUMENT,INSTRUMENT,INSTRUMENT,INSTRUMENT,INSTRUMENT,INSTRUMENT,INSTRUMENT, AMULET=function(mat,parent,typ,idx) return (mat.flags.ITEMS_SOFT or mat.flags.ITEMS_HARD) end, - EARRING,BRACELET=AMULET,AMULET, ROCK=function(mat,parent,typ,idx) return (mat.flags.IS_STONE) end, @@ -100,8 +97,17 @@ function getRestrictiveMatFilter(itemType) end } - return itemTypes[df.item_type[itemType]] + for k,v in ipairs({'GOBLET','FLASK','TOY','RING','CROWN','SCEPTER','FIGURINE','TOOL'}) do + itemTypes[v]=itemTypes.INSTRUMENT + end + for k,v in ipairs({'SHOES','SHIELD','HELM','GLOVES'}) do + itemTypes[v]=itemTypes.ARMOR end + for k,v in ipairs({'EARRING','BRACELET'}) do + itemTypes[v]=itemTypes.AMULET + end + itemTypes.BOULDER=itemTypes.ROCK + return itemTypes[df.item_type[itemType]] end function createItem(mat,itemType,quality,creator,description) From f47565465ef937ad439e66a5c1224e6f517413c6 Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 8 Dec 2014 22:19:22 +0200 Subject: [PATCH 028/115] more fixes, improvements --- scripts/gui/advfort.lua | 48 +++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 608bdf00c..dc6b33cf9 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,8 +1,13 @@ -- allows to do jobs in adv. mode. --[==[ - version: 0.002 + version: 0.003 changelog: + *0.003 + - fixed farms (i think...) + - added faster time pasing (yay for random deaths from local wildlife) + - still hasn't fixed gather plants. but you can visit local market, buy a few local fruits/vegetables eat them and use seeds + - other stuff *0.002 - kind-of fixed the item problem... now they get teleported (if teleport_items=true which should be default for adventurer) - gather plants still not working... Other jobs seem to work. @@ -659,6 +664,15 @@ function putItemsInBuilding(building,job_item_refs) v.is_fetching=0 end end +function putItemsInHauling(unit,job_item_refs) + for k,v in ipairs(job_item_refs) do + --local pos=dfhack.items.getPosition(v) + if not dfhack.items.moveToInventory(v.item,unit,0,0) then + print("Could not put item:",k,v.item) + end + v.is_fetching=0 + end +end function AssignJobItems(args) if settings.df_assign then --use df default logic and hope that it would work @@ -688,7 +702,7 @@ function AssignJobItems(args) job.items[#job.items-1]:delete() job.items:erase(#job.items-1) end]] - printall(its) + local item_counts={} for job_id, trg_job_item in ipairs(job.job_items) do item_counts[job_id]=trg_job_item.quantity @@ -709,7 +723,7 @@ function AssignJobItems(args) --cur_item.flags.in_job=true job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) item_counts[job_id]=item_counts[job_id]-cur_item:getTotalDimension() - print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) + --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) used_item_id[cur_item.id]=true end end @@ -719,23 +733,30 @@ function AssignJobItems(args) if not settings.build_by_items then for job_id, trg_job_item in ipairs(job.job_items) do if item_counts[job_id]>0 then + print("Not enough items for this job") return false, "Not enough items for this job" end end end - if settings.teleport_items then + local haul_fix=false + if job.job_type==df.job_type.PlantSeeds then + haul_fix=true + end + if settings.teleport_items and not haul_fix then putItemsInBuilding(args.building,job.items) end local uncollected = getItemsUncollected(job) if #uncollected == 0 then job.flags.working=true + if haul_fix then + putItemsInHauling(args.unit,job.items) + end else job.flags.fetching=true uncollected[1].is_fetching=1 end - --todo set working for workshops if items are present, else set fetching (and at least one item to is_fetching=1) - --job.flags.working=true + return true --]=] end @@ -1177,8 +1198,7 @@ function usetool:farmPlot(building) if not job then print(msg) else - --print(AssignJobItems(args)) --WHY U NO WORK? - + print(AssignJobItems(args)) --WHY U NO WORK? end end MODES={ @@ -1311,7 +1331,7 @@ function usetool:fieldInput(keys) self:sendInputToParent("LEAVESCREEN") end if ok then - self:sendInputToParent("A_WAIT") + self:sendInputToParent("A_SHORT_WAIT") else dfhack.gui.showAnnouncement(msg,5,1) end @@ -1347,8 +1367,8 @@ function usetool:onInput(keys) --ContinueJob(adv) self:sendInputToParent("A_SHORT_WAIT") elseif keys[keybinds.continue.key] then - ContinueJob(adv) - self:sendInputToParent("A_SHORT_WAIT") + --ContinueJob(adv) + --self:sendInputToParent("A_SHORT_WAIT") self.long_wait=true else if self.mode~=nil then @@ -1373,10 +1393,10 @@ end function usetool:onIdle() local adv=df.global.world.units.active[0] local job_ptr=adv.job.current_job - local job=findAction(adv,df.unit_action_type.Job) + local job_action=findAction(adv,df.unit_action_type.Job) - if job_ptr and self.long_wait and not job then - if adv.job.current_job.completion_timer==0 or adv.job.current_job.completion_timer==-1 then + if job_ptr and self.long_wait and not job_action then + if adv.job.current_job.completion_timer==-1 then self.long_wait=false end ContinueJob(adv) From 550cddc60481f00826ea7ccbcd8fc3897ccb7441 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 10 Dec 2014 00:09:28 +0200 Subject: [PATCH 029/115] Gui for item selection in advfort --- scripts/gui/advfort.lua | 71 ++++++++++----- scripts/gui/advfort_items.lua | 159 ++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 23 deletions(-) create mode 100644 scripts/gui/advfort_items.lua diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index dc6b33cf9..476753450 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -51,7 +51,7 @@ local utils=require 'utils' local tile_attrs = df.tiletype.attrs -settings={build_by_items=false,use_worn=false,check_inv=true,teleport_items=true,df_assign=false} +settings={build_by_items=false,use_worn=false,check_inv=false,teleport_items=true,df_assign=false,gui_item_select=false} function hasValue(tbl,val) for k,v in pairs(tbl) do @@ -707,48 +707,65 @@ function AssignJobItems(args) for job_id, trg_job_item in ipairs(job.job_items) do item_counts[job_id]=trg_job_item.quantity end + local item_suitability={} local used_item_id={} for job_id, trg_job_item in ipairs(job.job_items) do + item_suitability[job_id]={} + for _,cur_item in pairs(its) do if not used_item_id[cur_item.id] then - local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) + local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) + if item_suitable or settings.build_by_items then + table.insert(item_suitability[job_id],cur_item) + end --[[ if msg then print(cur_item,msg) end ]]-- - - if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then - --cur_item.flags.in_job=true - job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) - item_counts[job_id]=item_counts[job_id]-cur_item:getTotalDimension() - --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) - used_item_id[cur_item.id]=true + if not settings.gui_item_select then + if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then + --cur_item.flags.in_job=true + job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) + item_counts[job_id]=item_counts[job_id]-cur_item:getTotalDimension() + --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) + used_item_id[cur_item.id]=true + end end end end end + if settings.gui_item_select then + require('gui.script').start(function() + require('hack.scripts.gui.advfort_items').showItemEditor(job,item_suitability) + end) - if not settings.build_by_items then - for job_id, trg_job_item in ipairs(job.job_items) do - if item_counts[job_id]>0 then - print("Not enough items for this job") - return false, "Not enough items for this job" + else + if not settings.build_by_items then + for job_id, trg_job_item in ipairs(job.job_items) do + if item_counts[job_id]>0 then + print("Not enough items for this job") + return false, "Not enough items for this job" + end end end end - local haul_fix=false - if job.job_type==df.job_type.PlantSeeds then - haul_fix=true - end - if settings.teleport_items and not haul_fix then + local item_mode="teleport" + local item_modes={ + [df.job_type.PlantSeeds]="haul", + [df.job_type.ConstructBuilding]="default", + } + item_mode=item_modes[job.job_type] + + if settings.teleport_items and item_mode=="teleport" then putItemsInBuilding(args.building,job.items) end + local uncollected = getItemsUncollected(job) if #uncollected == 0 then job.flags.working=true - if haul_fix then + if item_mode=="haul" then putItemsInHauling(args.unit,job.items) end else @@ -773,7 +790,6 @@ function CheckAndFinishBuilding(args,bld) if args.job~=nil then local ok,msg=AssignJobItems(args) if not ok then - print(msg) return false,msg else AssignUnitToJob(args.job,args.unit,args.from_pos) @@ -1017,6 +1033,7 @@ function siegeWeaponActionChosen(building,actionid) local ok,msg=makeJob(args) if not ok then dfhack.gui.showAnnouncement(msg,5,1) + CancelJob(args.unit) end end end @@ -1051,7 +1068,11 @@ function usetool:onWorkShopButtonClicked(building,index,choice) if #building.jobs>0 then local job=building.jobs[#building.jobs-1] AssignUnitToJob(job,adv,adv.pos) - AssignJobItems{job=job,from_pos=adv.pos,pos=adv.pos,unit=adv,building=building} + local ok,msg=AssignJobItems{job=job,from_pos=adv.pos,pos=adv.pos,unit=adv,building=building} + if not ok then + dfhack.gui.showAnnouncement(msg,5,1) + CancelJob(adv) + end end elseif df.interface_button_building_category_selectorst:is_instance(choice.button) or df.interface_button_building_material_selectorst:is_instance(choice.button) then @@ -1198,7 +1219,11 @@ function usetool:farmPlot(building) if not job then print(msg) else - print(AssignJobItems(args)) --WHY U NO WORK? + local ok, msg=AssignJobItems(args) + if not ok then + dfhack.gui.showAnnouncement(msg,5,1) + CancelJob(args.unit) + end end end MODES={ diff --git a/scripts/gui/advfort_items.lua b/scripts/gui/advfort_items.lua new file mode 100644 index 000000000..10639627e --- /dev/null +++ b/scripts/gui/advfort_items.lua @@ -0,0 +1,159 @@ +local _ENV = mkmodule('hack.scripts.gui.advfort_items') + +local gui=require('gui') +local wid=require('gui.widgets') +local gscript=require('gui.script') + +jobitemEditor=defclass(jobitemEditor,gui.FramedScreen) +jobitemEditor.ATTRS{ + frame_style = gui.GREY_LINE_FRAME, + frame_inset = 1, + allow_add=false, + allow_remove=false, + allow_any_item=false, + job=DEFAULT_NIL, + items=DEFAULT_NIL, +} +function update_slot_text(slot) + local items="" + for i,v in ipairs(slot.items) do + items=items.." "..dfhack.items.getDescription(v,0) + if i~=#slot.items then + items=items.."," + end + end + + slot.text=string.format("%02d. Filled(%d/%d):%s",slot.id+1,slot.filled_amount,slot.job_item.quantity,items) +end +--items-> table => key-> id of job.job_items, value-> table => key (num), value => item(ref) +function jobitemEditor:init(args) + --self.job=args.job + if self.job==nil then qerror("This screen must have job target") end + if self.items==nil then qerror("This screen must have item list") end + + self:addviews{ + wid.Label{ + view_id = 'label', + text = args.prompt, + text_pen = args.text_pen, + frame = { l = 0, t = 0 }, + }, + wid.List{ + view_id = 'itemList', + frame = { l = 0, t = 2 ,b=2}, + }, + wid.Label{ + frame = { b=1,l=1}, + text ={{text= ": cancel", + key = "LEAVESCREEN", + on_activate= self:callback("dismiss") + }, + { + gap=3, + text= ": accept", + key = "SEC_SELECT", + on_activate= self:callback("commit"), + enabled=self:callback("jobValid") + }, + { + gap=3, + text= ": add", + key = "CUSTOM_A", + enabled=self:callback("can_add"), + on_activate= self:callback("add_item") + }, + { + gap=3, + text= ": remove", + key = "CUSTOM_R", + enabled=self:callback("can_remove"), + on_activate= self:callback("remove_item") + },} + }, + } + self.assigned={} + self:fill() +end +function jobitemEditor:get_slot() + local idx,choice=self.subviews.itemList:getSelected() + return choice +end +function jobitemEditor:can_add() + local slot=self:get_slot() + return slot.filled_amount0 +end +function jobitemEditor:add_item() + local cur_slot=self:get_slot() + local choices={} + table.insert(choices,{text=""}) + for k,v in pairs(cur_slot.choices) do + if not self.assigned[v.id] then + table.insert(choices,{text=dfhack.items.getDescription(v,0),item=v}) + end + end + gscript.start(function () + local _,_2,choice=gscript.showListPrompt("which item?", "Select item", COLOR_WHITE, choices) + if choice ~= nil and choice.item~=nil then + self:add_item_to_slot(cur_slot,choice.item) + end + end + ) +end +function jobitemEditor:add_item_to_slot(slot,item) + table.insert(slot.items,item) + slot.filled_amount=slot.filled_amount+item:getTotalDimension() + self.assigned[item.id]=true + update_slot_text(slot) + self.subviews.itemList:setChoices(self.slots) +end +function jobitemEditor:remove_item() + local slot=self:get_slot() + for k,v in pairs(slot.items) do + self.assigned[v.id]=nil + end + slot.items={} + slot.filled_amount=0 + update_slot_text(slot) + self.subviews.itemList:setChoices(self.slots) +end +function jobitemEditor:fill() + self.slots={} + for k,v in pairs(self.items) do + table.insert(self.slots,{job_item=self.job.job_items[k], id=k, items={},choices=v,filled_amount=0,slot_id=#self.slots}) + update_slot_text(self.slots[#self.slots]) + end + self.subviews.itemList:setChoices(self.slots) +end + +function jobitemEditor:jobValid() + for k,v in pairs(self.slots) do + if v.filled_amount Date: Fri, 12 Dec 2014 02:47:59 +0100 Subject: [PATCH 030/115] Fixed error with passing nil to unit.find --- scripts/full-heal.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/full-heal.lua b/scripts/full-heal.lua index 208082520..8de3f9eda 100644 --- a/scripts/full-heal.lua +++ b/scripts/full-heal.lua @@ -29,7 +29,11 @@ if args.help then return end -unit = df.unit.find(args.unit) or dfhack.gui.getSelectedUnit() +if(args.unit) then + unit = df.unit.find(args.unit) +else + unit = dfhack.gui.getSelectedUnit() +end if not unit then qerror('Error: please select a unit or pass its id as an argument.') From e22c3b099b1ea562fc068c2c013dc11fbf34fcd0 Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 12 Dec 2014 10:32:22 +0200 Subject: [PATCH 031/115] New and improved. Fixed bugs, now work gets done automatically. --- scripts/gui/advfort.lua | 273 ++++++++++++++++------------------ scripts/gui/advfort_items.lua | 6 +- 2 files changed, 132 insertions(+), 147 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 476753450..e84b7a412 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,8 +1,12 @@ -- allows to do jobs in adv. mode. --[==[ - version: 0.003 + version: 0.01 changelog: + *0.01 + - instant job startation + - item selection screen (!) + - BUG:custom jobs need stuff on ground to work *0.003 - fixed farms (i think...) - added faster time pasing (yay for random deaths from local wildlife) @@ -48,10 +52,11 @@ local buildings=require 'dfhack.buildings' local bdialog=require 'gui.buildings' local workshopJobs=require 'dfhack.workshops' local utils=require 'utils' +local gscript=require 'gui.script' local tile_attrs = df.tiletype.attrs -settings={build_by_items=false,use_worn=false,check_inv=false,teleport_items=true,df_assign=false,gui_item_select=false} +settings={build_by_items=false,use_worn=false,check_inv=true,teleport_items=true,df_assign=false,gui_item_select=true} function hasValue(tbl,val) for k,v in pairs(tbl) do @@ -196,47 +201,54 @@ function addJobAction(job,unit) --what about job2? add_action(unit,data) --add_action(unit,{type=df.unit_action_type.Unsteady,data={unsteady={timer=5}}}) end -function makeJob(args) - local newJob=df.job:new() - newJob.id=df.global.job_next_id - df.global.job_next_id=df.global.job_next_id+1 - newJob.flags.special=true - newJob.job_type=args.job_type - newJob.completion_timer=-1 - newJob.pos:assign(args.pos) - --newJob.pos:assign(args.unit.pos) - args.job=newJob - local failed - for k,v in ipairs(args.pre_actions or {}) do - local ok,msg=v(args) - if not ok then - failed=msg - break - end +function make_native_job(args) + if args.job == nil then + local newJob=df.job:new() + newJob.id=df.global.job_next_id + df.global.job_next_id=df.global.job_next_id+1 + newJob.flags.special=true + newJob.job_type=args.job_type + newJob.completion_timer=-1 + + newJob.pos:assign(args.pos) + --newJob.pos:assign(args.unit.pos) + args.job=newJob end - if failed==nil then - AssignUnitToJob(newJob,args.unit,args.from_pos) - for k,v in ipairs(args.post_actions or {}) do +end +function makeJob(args) + gscript.start(function () + make_native_job(args) + local failed + for k,v in ipairs(args.pre_actions or {}) do local ok,msg=v(args) if not ok then failed=msg break end end - if failed then - UnassignJob(newJob,args.unit) + if failed==nil then + AssignUnitToJob(args.job,args.unit,args.from_pos) + for k,v in ipairs(args.post_actions or {}) do + local ok,msg=v(args) + if not ok then + failed=msg + break + end + end + if failed then + UnassignJob(args.job,args.unit) + end end - end - if failed==nil then - dfhack.job.linkIntoWorld(newJob,true) - addJobAction(newJob,args.unit) - return newJob - else - newJob:delete() - return false,failed - end - + if failed==nil then + dfhack.job.linkIntoWorld(args.job,true) + addJobAction(args.job,args.unit) + args.screen:wait_tick() + else + args.job:delete() + dfhack.gui.showAnnouncement(msg,5,1) + end + end) end function UnassignJob(job,unit,unit_pos) @@ -463,7 +475,7 @@ function chooseBuildingWidthHeightDir(args) --TODO nicer selection dialog return false --width = ..., height = ..., direction = ... end - +CheckAndFinishBuilding=nil function BuildingChosen(inp_args,type_id,subtype_id,custom_id) local args=inp_args or {} @@ -484,7 +496,8 @@ function BuildingChosen(inp_args,type_id,subtype_id,custom_id) --if settings.build_by_items then -- args.items=itemsAtPos(inp_args.from_pos) --end - buildings.constructBuilding(args) + args.building=buildings.constructBuilding(args) + CheckAndFinishBuilding(args,args.building) end @@ -667,14 +680,37 @@ end function putItemsInHauling(unit,job_item_refs) for k,v in ipairs(job_item_refs) do --local pos=dfhack.items.getPosition(v) + print("moving:",tostring(v),tostring(v.item)) + printall(v) if not dfhack.items.moveToInventory(v.item,unit,0,0) then print("Could not put item:",k,v.item) end v.is_fetching=0 end end +function finish_item_assign(args) + local job=args.job + local item_modes={ + [df.job_type.PlantSeeds]="haul", + } + local item_mode=item_modes[job.job_type] or "teleport" + if settings.teleport_items and item_mode=="teleport" then + putItemsInBuilding(args.building,job.items) + end + + local uncollected = getItemsUncollected(job) + if #uncollected == 0 then + job.flags.working=true + if item_mode=="haul" then + putItemsInHauling(args.unit,job.items) + end + else + job.flags.fetching=true + uncollected[1].is_fetching=1 + end +end function AssignJobItems(args) - + print("----") if settings.df_assign then --use df default logic and hope that it would work return true end @@ -688,16 +724,7 @@ function AssignJobItems(args) else its=EnumItems{pos=args.from_pos} end - --[[ job item editor... - jobitemEditor{job=args.job,items=its}:show() - local ok=job.flags.working or job.flags.fetching - if not ok then - return ok, "Stuff" - else - return ok - end - --]] - -- [=[ + --[[while(#job.items>0) do --clear old job items job.items[#job.items-1]:delete() job.items:erase(#job.items-1) @@ -723,7 +750,7 @@ function AssignJobItems(args) if msg then print(cur_item,msg) end - ]]-- + --]] if not settings.gui_item_select then if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then --cur_item.flags.in_job=true @@ -736,11 +763,23 @@ function AssignJobItems(args) end end end - if settings.gui_item_select then - require('gui.script').start(function() - require('hack.scripts.gui.advfort_items').showItemEditor(job,item_suitability) - end) - + print("before block") + if settings.gui_item_select and #job.job_items>0 then + local item_dialog=require('hack.scripts.gui.advfort_items') + --local rr=require('gui.script').start(function() + print("before dialog") + local ret=item_dialog.showItemEditor(job,item_suitability) + print("post dialog",ret) + --showItemEditor(job,item_suitability) + if ret then + finish_item_assign(args) + return true + else + print("Failed job, i'm confused...") + end + + --end) + return false,"Selecting items" else if not settings.build_by_items then for job_id, trg_job_item in ipairs(job.job_items) do @@ -750,35 +789,15 @@ function AssignJobItems(args) end end end - end - local item_mode="teleport" - local item_modes={ - [df.job_type.PlantSeeds]="haul", - [df.job_type.ConstructBuilding]="default", - } - item_mode=item_modes[job.job_type] - - if settings.teleport_items and item_mode=="teleport" then - putItemsInBuilding(args.building,job.items) + finish_item_assign(args) + return true end - local uncollected = getItemsUncollected(job) - if #uncollected == 0 then - job.flags.working=true - if item_mode=="haul" then - putItemsInHauling(args.unit,job.items) - end - else - job.flags.fetching=true - uncollected[1].is_fetching=1 - - end - return true - --]=] + end -function CheckAndFinishBuilding(args,bld) +CheckAndFinishBuilding=function (args,bld) args.building=args.building or bld for idx,job in pairs(bld.jobs) do if job.job_type==df.job_type.ConstructBuilding then @@ -788,18 +807,12 @@ function CheckAndFinishBuilding(args,bld) end if args.job~=nil then - local ok,msg=AssignJobItems(args) - if not ok then - return false,msg - else - AssignUnitToJob(args.job,args.unit,args.from_pos) - end + args.pre_actions={AssignJobItems} else local t={items=buildings.getFiltersByType({},bld:getType(),bld:getSubtype(),bld:getCustomType())} args.pre_actions={dfhack.curry(setFiltersUp,t),AssignJobItems,AssignBuildingRef} - local ok,msg=makeJob(args) - return ok,msg end + makeJob(args) end function AssignJobToBuild(args) local bld=args.building or dfhack.buildings.findAtTile(args.pos) @@ -982,21 +995,7 @@ function onWorkShopJobChosen(args,idx,choice) args.job_type=choice.job_id args.post_actions={AssignBuildingRef} args.pre_actions={dfhack.curry(setFiltersUp,choice.filter),AssignJobItems} - local job,msg=makeJob(args) - if not job then - dfhack.gui.showAnnouncement(msg,5,1) - end - args.job=job - - --[[for _,v in ipairs(choice.filter) do - local filter=require("utils").clone(args.common) - filter.new=true - require("utils").assign(filter,v) - --printall(filter) - job.job_items:insert("#",filter) - end--]] - --local ok,msg=AssignJobItems(args) - --print(ok,msg) + makeJob(args) end function siegeWeaponActionChosen(building,actionid) local args @@ -1030,11 +1029,7 @@ function siegeWeaponActionChosen(building,actionid) end if args~=nil then args.post_actions={AssignBuildingRef} - local ok,msg=makeJob(args) - if not ok then - dfhack.gui.showAnnouncement(msg,5,1) - CancelJob(args.unit) - end + makeJob(args) end end function putItemToBuilding(building,item) @@ -1063,16 +1058,19 @@ function usetool:openSiegeWindow(building) end function usetool:onWorkShopButtonClicked(building,index,choice) local adv=df.global.world.units.active[0] + local args={unit=adv,building=building} if df.interface_button_building_new_jobst:is_instance(choice.button) then + print("pre-click") choice.button:click() + print("post-click",#building.jobs) if #building.jobs>0 then local job=building.jobs[#building.jobs-1] - AssignUnitToJob(job,adv,adv.pos) - local ok,msg=AssignJobItems{job=job,from_pos=adv.pos,pos=adv.pos,unit=adv,building=building} - if not ok then - dfhack.gui.showAnnouncement(msg,5,1) - CancelJob(adv) - end + args.job=job + args.pos=adv.pos + args.from_pos=adv.pos + args.pre_actions={AssignJobItems} + args.screen=self + makeJob(args) end elseif df.interface_button_building_category_selectorst:is_instance(choice.button) or df.interface_button_building_material_selectorst:is_instance(choice.button) then @@ -1167,29 +1165,24 @@ function usetool:armCleanTrap(building) else return end - local job,msg=makeJob(args) - if not job then - print(msg) - end + args.screen=self + makeJob(args) end end function usetool:hiveActions(building) local adv=df.global.world.units.active[0] local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos, - from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive,building=building} + from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive,building=building,screen=self} local job_filter={items={{quantity=1,item_type=df.item_type.VERMIN}} } args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} - local job,msg=makeJob(args) - if not job then - print(msg) - end + makeJob(args) --InstallColonyInHive, --CollectHiveProducts, end function usetool:operatePump(building) local adv=df.global.world.units.active[0] - makeJob{unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.OperatePump} + makeJob{unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.OperatePump,screen=self} end function usetool:farmPlot(building) local adv=df.global.world.units.active[0] @@ -1203,28 +1196,18 @@ function usetool:farmPlot(building) end --check if there tile is without plantseeds,add job - local args={unit=adv,pos=adv.pos,from_pos=adv.pos, - } - if not do_harvest then - local seedjob={items={{quantity=1,item_type=df.item_type.SEEDS}}} - args.job_type=df.job_type.PlantSeeds - args.pre_actions={dfhack.curry(setFiltersUp,seedjob)} - args.post_actions={AssignBuildingRef} - - else + local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self} + if do_harvest then args.job_type=df.job_type.HarvestPlants args.post_actions={AssignBuildingRef} - end - local job,msg=makeJob(args) - if not job then - print(msg) else - local ok, msg=AssignJobItems(args) - if not ok then - dfhack.gui.showAnnouncement(msg,5,1) - CancelJob(args.unit) - end + local seedjob={items={{quantity=1,item_type=df.item_type.SEEDS}}} + args.job_type=df.job_type.PlantSeeds + args.pre_actions={dfhack.curry(setFiltersUp,seedjob)} + args.post_actions={AssignBuildingRef,AssignJobItems} end + + makeJob(args) end MODES={ [df.building_type.Table]={ --todo filters... @@ -1293,7 +1276,9 @@ function usetool:shopInput(keys) self:openShopWindowButtoned(self.in_shop) end end - +function usetool:wait_tick() + self:sendInputToParent("A_SHORT_WAIT") +end function usetool:setupFields() local adv=df.global.world.units.active[0] local civ_id=df.global.world.units.active[0].civ_id @@ -1347,7 +1332,7 @@ function usetool:fieldInput(keys) if type(cur_mode[2])=="function" then ok,msg=cur_mode[2](state) else - ok,msg=makeJob(state) + makeJob(state) --(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) end @@ -1355,11 +1340,7 @@ function usetool:fieldInput(keys) if code=="SELECT" then self:sendInputToParent("LEAVESCREEN") end - if ok then - self:sendInputToParent("A_SHORT_WAIT") - else - dfhack.gui.showAnnouncement(msg,5,1) - end + self.long_wait=true end return code end diff --git a/scripts/gui/advfort_items.lua b/scripts/gui/advfort_items.lua index 10639627e..e44e3857a 100644 --- a/scripts/gui/advfort_items.lua +++ b/scripts/gui/advfort_items.lua @@ -13,6 +13,7 @@ jobitemEditor.ATTRS{ allow_any_item=false, job=DEFAULT_NIL, items=DEFAULT_NIL, + on_okay=DEFAULT_NIL, } function update_slot_text(slot) local items="" @@ -144,13 +145,16 @@ function jobitemEditor:commit() end end self:dismiss() + if self.on_okay then self:on_okay() end end function showItemEditor(job,item_selections) jobitemEditor{ job = job, items = item_selections, - on_close = gscript.qresume(nil) + on_close = gscript.qresume(nil), + on_okay = gscript.mkresume(true) + --on_cancel=gscript.mkresume(false) }:show() return gscript.wait() From b93eac1c7a0ac57b9bcc4dbfa152a2029ab42c51 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Mon, 15 Dec 2014 10:22:05 +1100 Subject: [PATCH 032/115] Fix "exportlegends maps" Correct list index error which skipped export of detailed map. Allow region-maps-only export to start from detailed maps viewscreen. More informative error message. Closes #445., --- scripts/exportlegends.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua index 369e1c7b4..03f7aa6fb 100644 --- a/scripts/exportlegends.lua +++ b/scripts/exportlegends.lua @@ -51,7 +51,7 @@ end function wait_for_export_maps_vs() local vs = dfhack.gui.getCurViewscreen() if dfhack.gui.getCurFocus() == "export_graphical_map" then - vs.sel_idx = i + vs.sel_idx = i-1 print(' Exporting: '..MAPS[i]..' map') gui.simulateInput(vs, 'SELECT') i = i + 1 @@ -88,6 +88,9 @@ if dfhack.gui.getCurFocus() == "legends" then export_site_maps() else dfhack.printerr('Valid arguments are "all", "info", "maps" or "sites"') end +elseif args[1] == "maps" and + dfhack.gui.getCurFocus() == "export_graphical_map" then + wait_for_export_maps_vs() else - dfhack.printerr('Not in main legends view') + dfhack.printerr('Exportlegends must be run from the main legends view') end \ No newline at end of file From f3826abc0c0d91e69518f1372353fe37d970d250 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Mon, 15 Dec 2014 23:11:23 +1100 Subject: [PATCH 033/115] cleaning up in needs_porting Removed obsolete plugins. Started hotkeys port. Mostly-finished position port. Corrected dwarfmonitor date display. Documented putontable.lua in readme. --- NEWS | 5 + Readme.rst | 10 + needs_porting/SegmentedFinder.h | 543 -------------- needs_porting/_notes.txt | 43 +- needs_porting/digpattern.cpp | 259 ------- needs_porting/hotkey-notes.lua | 40 + needs_porting/hotkeynotedump.py | 20 - needs_porting/incrementalsearch.cpp | 1064 --------------------------- needs_porting/position.py | 71 -- plugins/dwarfmonitor.cpp | 6 +- scripts/position.lua | 40 + 11 files changed, 111 insertions(+), 1990 deletions(-) delete mode 100644 needs_porting/SegmentedFinder.h delete mode 100644 needs_porting/digpattern.cpp create mode 100644 needs_porting/hotkey-notes.lua delete mode 100644 needs_porting/hotkeynotedump.py delete mode 100644 needs_porting/incrementalsearch.cpp delete mode 100644 needs_porting/position.py create mode 100644 scripts/position.lua diff --git a/NEWS b/NEWS index 1b9ec3f8b..a05e75465 100644 --- a/NEWS +++ b/NEWS @@ -1,9 +1,14 @@ DFHack Future Internals Fixes + plugins/dwarfmonitor: correct date display (month index, separator) + scripts/putontable: added to the readme New Plugins New Scripts + position: Reports the current date, time, month, and season, plus + some location info. Port/update of position.py Misc Improvements + further work on needs_porting DFHack 0.40.19-r1 Internals: diff --git a/Readme.rst b/Readme.rst index 15933e0f6..52c966742 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2399,6 +2399,16 @@ Example:: multicmd locate-ore iron ; digv +position +======== +Reports the current date, time, month, and season. Location reporting is a +work in progress. + +putontable +========== +Makes item appear on the table, like in adventure mode shops. Arguments: '-a' +or '--all' for all items. + quicksave ========= diff --git a/needs_porting/SegmentedFinder.h b/needs_porting/SegmentedFinder.h deleted file mode 100644 index b3fe5aecf..000000000 --- a/needs_porting/SegmentedFinder.h +++ /dev/null @@ -1,543 +0,0 @@ -#ifndef SEGMENTED_FINDER_H -#define SEGMENTED_FINDER_H -#include -#include -#include - -class SegmentedFinder; -class SegmentFinder -{ - public: - SegmentFinder(DFHack::t_memrange & mr, DFHack::Context * DF, SegmentedFinder * SF) - { - _DF = DF; - mr_ = mr; - valid=false; - if(mr.valid) - { - mr_.buffer = (uint8_t *)malloc (mr_.end - mr_.start); - _SF = SF; - try - { - DF->ReadRaw(mr_.start,(mr_.end - mr_.start),mr_.buffer); - valid = true; - } - catch (DFHack::Error::MemoryAccessDenied &) - { - free(mr_.buffer); - valid = false; - mr.valid = false; // mark the range passed in as bad - cout << "Range 0x" << hex << mr_.start << " - 0x" << mr_.end; - - if (strlen(mr_.name) != 0) - cout << " (" << mr_.name << ")"; - - cout << dec << " not readable." << endl; - cout << "Skipping this range on future scans." << endl; - } - } - } - ~SegmentFinder() - { - if(valid) - free(mr_.buffer); - } - bool isValid() - { - return valid; - } - template - bool Find (needleType needle, const uint8_t increment , vector &newfound, comparator oper) - { - if(!valid) return !newfound.empty(); - //loop - for(uint64_t offset = 0; offset < (mr_.end - mr_.start) - sizeof(hayType); offset += increment) - { - if( oper(_SF,(hayType *)(mr_.buffer + offset), needle) ) - newfound.push_back(mr_.start + offset); - } - return !newfound.empty(); - } - - template < class needleType, class hayType, typename comparator > - uint64_t FindInRange (needleType needle, comparator oper, uint64_t start, uint64_t length) - { - if(!valid) return 0; - uint64_t stopper = min((mr_.end - mr_.start) - sizeof(hayType), (start - mr_.start) - sizeof(hayType) + length); - //loop - for(uint64_t offset = start - mr_.start; offset < stopper; offset +=1) - { - if( oper(_SF,(hayType *)(mr_.buffer + offset), needle) ) - return mr_.start + offset; - } - return 0; - } - - template - bool Filter (needleType needle, vector &found, vector &newfound, comparator oper) - { - if(!valid) return !newfound.empty(); - for( uint64_t i = 0; i < found.size(); i++) - { - if(mr_.isInRange(found[i])) - { - uint64_t corrected = found[i] - mr_.start; - if( oper(_SF,(hayType *)(mr_.buffer + corrected), needle) ) - newfound.push_back(found[i]); - } - } - return !newfound.empty(); - } - private: - friend class SegmentedFinder; - SegmentedFinder * _SF; - DFHack::Context * _DF; - DFHack::t_memrange mr_; - bool valid; -}; - -class SegmentedFinder -{ - public: - SegmentedFinder(vector & ranges, DFHack::Context * DF) - { - _DF = DF; - for(size_t i = 0; i < ranges.size(); i++) - { - segments.push_back(new SegmentFinder(ranges[i], DF, this)); - } - } - ~SegmentedFinder() - { - for(size_t i = 0; i < segments.size(); i++) - { - delete segments[i]; - } - } - SegmentFinder * getSegmentForAddress (uint64_t addr) - { - for(size_t i = 0; i < segments.size(); i++) - { - if(segments[i]->mr_.isInRange(addr)) - { - return segments[i]; - } - } - return 0; - } - template - bool Find (const needleType needle, const uint8_t increment, vector &found, comparator oper) - { - found.clear(); - for(size_t i = 0; i < segments.size(); i++) - { - segments[i]->Find(needle, increment, found, oper); - } - return !(found.empty()); - } - - template < class needleType, class hayType, typename comparator > - uint64_t FindInRange (needleType needle, comparator oper, uint64_t start, uint64_t length) - { - SegmentFinder * sf = getSegmentForAddress(start); - if(sf) - { - return sf->FindInRange(needle, oper, start, length); - } - return 0; - } - - template - bool Filter (const needleType needle, vector &found, comparator oper) - { - vector newfound; - for(size_t i = 0; i < segments.size(); i++) - { - segments[i]->Filter(needle, found, newfound, oper); - } - found.clear(); - found = newfound; - return !(found.empty()); - } - - template - bool Incremental (needleType needle, const uint8_t increment ,vector &found, comparator oper) - { - if(found.empty()) - { - return Find (needle,increment,found,oper); - } - else - { - return Filter (needle,found,oper); - } - } - - template - T * Translate(uint64_t address) - { - for(size_t i = 0; i < segments.size(); i++) - { - if(segments[i]->mr_.isInRange(address)) - { - return (T *) (segments[i]->mr_.buffer + address - segments[i]->mr_.start); - } - } - return 0; - } - - template - T Read(uint64_t address) - { - return *Translate(address); - } - - template - bool Read(uint64_t address, T& target) - { - T * test = Translate(address); - if(test) - { - target = *test; - return true; - } - return false; - } - private: - DFHack::Context * _DF; - vector segments; -}; - -template -bool equalityP (SegmentedFinder* s, T *x, T y) -{ - return (*x) == y; -} - -struct vecTriplet -{ - uint32_t start; - uint32_t finish; - uint32_t alloc_finish; -}; - -template -bool vectorLength (SegmentedFinder* s, vecTriplet *x, Needle &y) -{ - if(x->start <= x->finish && x->finish <= x->alloc_finish) - if((x->finish - x->start) == y) - return true; - return false; -} - -// find a vector of 32bit pointers, where an object pointed to has a string 'y' as the first member -bool vectorString (SegmentedFinder* s, vecTriplet *x, const char *y) -{ - uint32_t object_ptr; - // iterate over vector of pointers - for(uint32_t idx = x->start; idx < x->finish; idx += sizeof(uint32_t)) - { - // deref ptr idx, get ptr to object - if(!s->Read(idx,object_ptr)) - { - return false; - } - // deref ptr to first object, get ptr to string - uint32_t string_ptr; - if(!s->Read(object_ptr,string_ptr)) - return false; - // get string location in our local cache - char * str = s->Translate(string_ptr); - if(!str) - return false; - if(strcmp(y, str) == 0) - return true; - } - return false; -} - -// find a vector of 32bit pointers, where the first object pointed to has a string 'y' as the first member -bool vectorStringFirst (SegmentedFinder* s, vecTriplet *x, const char *y) -{ - uint32_t object_ptr; - uint32_t idx = x->start; - // deref ptr idx, get ptr to object - if(!s->Read(idx,object_ptr)) - { - return false; - } - // deref ptr to first object, get ptr to string - uint32_t string_ptr; - if(!s->Read(object_ptr,string_ptr)) - return false; - // get string location in our local cache - char * str = s->Translate(string_ptr); - if(!str) - return false; - if(strcmp(y, str) == 0) - return true; - return false; -} - -// test if the address is between vector.start and vector.finish -// not very useful alone, but could be a good step to filter some things -bool vectorAddrWithin (SegmentedFinder* s, vecTriplet *x, uint32_t address) -{ - if(address < x->finish && address >= x->start) - return true; - return false; -} - -// test if an object address is within the vector of pointers -// -bool vectorOfPtrWithin (SegmentedFinder* s, vecTriplet *x, uint32_t address) -{ - uint32_t object_ptr; - for(uint32_t idx = x->start; idx < x->finish; idx += sizeof(uint32_t)) - { - if(!s->Read(idx,object_ptr)) - { - return false; - } - if(object_ptr == address) - return true; - } - return false; -} - -bool vectorAll (SegmentedFinder* s, vecTriplet *x, int ) -{ - if(x->start <= x->finish && x->finish <= x->alloc_finish) - { - if(s->getSegmentForAddress(x->start) == s->getSegmentForAddress(x->finish) - && s->getSegmentForAddress(x->finish) == s->getSegmentForAddress(x->alloc_finish)) - return true; - } - return false; -} - -class Bytestreamdata -{ - public: - void * object; - uint32_t length; - uint32_t allocated; - uint32_t n_used; -}; - -class Bytestream -{ -public: - Bytestream(void * obj, uint32_t len, bool alloc = false) - { - d = new Bytestreamdata(); - d->allocated = alloc; - d->object = obj; - d->length = len; - d->n_used = 1; - constant = false; - } - Bytestream() - { - d = new Bytestreamdata(); - d->allocated = false; - d->object = 0; - d->length = 0; - d->n_used = 1; - constant = false; - } - Bytestream( Bytestream & bs) - { - d =bs.d; - d->n_used++; - constant = false; - } - Bytestream( const Bytestream & bs) - { - d =bs.d; - d->n_used++; - constant = true; - } - ~Bytestream() - { - d->n_used --; - if(d->allocated && d->object && d->n_used == 0) - { - free (d->object); - free (d); - } - } - bool Allocate(size_t bytes) - { - if(constant) - return false; - if(d->allocated) - { - d->object = realloc(d->object, bytes); - } - else - { - d->object = malloc( bytes ); - } - - if(d->object) - { - d->allocated = bytes; - return true; - } - else - { - d->allocated = 0; - return false; - } - } - template < class T > - bool insert( T what ) - { - if(constant) - return false; - if(d->length+sizeof(T) >= d->allocated) - Allocate((d->length+sizeof(T)) * 2); - (*(T *)( (uint64_t)d->object + d->length)) = what; - d->length += sizeof(T); - return true; - } - Bytestreamdata * d; - bool constant; -}; -std::ostream& operator<< ( std::ostream& out, Bytestream& bs ) -{ - if(bs.d->object) - { - out << "bytestream " << dec << bs.d->length << "/" << bs.d->allocated << " bytes" << endl; - for(size_t i = 0; i < bs.d->length; i++) - { - out << hex << (int) ((uint8_t *) bs.d->object)[i] << " "; - } - out << endl; - } - else - { - out << "empty bytestresm" << endl; - } - return out; -} - -std::istream& operator>> ( std::istream& out, Bytestream& bs ) -{ - string read; - while(!out.eof()) - { - string tmp; - out >> tmp; - read.append(tmp); - } - cout << read << endl; - bs.d->length = 0; - size_t first = read.find_first_of("\""); - size_t last = read.find_last_of("\""); - size_t start = first + 1; - if(first == read.npos) - { - std::transform(read.begin(), read.end(), read.begin(), (int(*)(int)) tolower); - bs.Allocate(read.size()); // overkill. size / 2 should be good, but this is safe - int state = 0; - char big = 0; - char small = 0; - string::iterator it = read.begin(); - // iterate through string, construct a bytestream out of 00-FF bytes - while(it != read.end()) - { - char reads = *it; - if((reads >='0' && reads <= '9')) - { - if(state == 0) - { - big = reads - '0'; - state = 1; - } - else if(state == 1) - { - small = reads - '0'; - state = 0; - bs.insert(big*16 + small); - } - } - if((reads >= 'a' && reads <= 'f')) - { - if(state == 0) - { - big = reads - 'a' + 10; - state = 1; - } - else if(state == 1) - { - small = reads - 'a' + 10; - state = 0; - bs.insert(big*16 + small); - } - } - it++; - } - // we end in state= 1. should we add or should we trim... or throw errors? - // I decided on adding - if (state == 1) - { - small = 0; - bs.insert(big*16 + small); - } - } - else - { - if(last == first) - { - // only one " - last = read.size(); - } - size_t length = last - start; - // construct bytestream out of stuff between "" - bs.d->length = length; - if(length) - { - // todo: Bytestream should be able to handle this without external code - bs.Allocate(length); - bs.d->length = length; - const char* strstart = read.c_str(); - memcpy(bs.d->object, strstart + start, length); - } - else - { - bs.d->object = 0; - } - } - cout << bs; - return out; -} - -bool findBytestream (SegmentedFinder* s, void *addr, Bytestream compare ) -{ - if(memcmp(addr, compare.d->object, compare.d->length) == 0) - return true; - return false; -} - -bool findString (SegmentedFinder* s, uint32_t *addr, const char * compare ) -{ - // read string pointer, translate to local scheme - char *str = s->Translate(*addr); - // verify - if(!str) - return false; - if(strcmp(str, compare) == 0) - return true; - return false; -} - -bool findStrBuffer (SegmentedFinder* s, uint32_t *addr, const char * compare ) -{ - if(memcmp((const char *)addr, compare, strlen(compare)) == 0) - return true; - return false; -} - -#endif // SEGMENTED_FINDER_H diff --git a/needs_porting/_notes.txt b/needs_porting/_notes.txt index 041e0932d..35a7ef5bf 100644 --- a/needs_porting/_notes.txt +++ b/needs_porting/_notes.txt @@ -1,30 +1,13 @@ -Notes by PeridexisErrant, on the needs_porting scripts and plugins: - -I deleted: - attachtest.py obsolete - digger.cpp less useful than digger2, replaced by autochop - digger2.cpp replaced by digfort - dfstatus.cpp replaced by dfstatus.lua - drawtile.cpp replaced by tiletypes - fix-3708.cpp obsolete, bug fixed in vanilla - lair.cpp replaced by lair - reveal.py replaced by reveal - treedump.py replaced by prospect & autochop - veinlook.cpp replaced by prospect - veinswap.cpp replaced by changevein - - -To investigate further: - creaturemanager.cpp modify skills and labors of creatures, kill creatures, etc; impressive but I suspect most functions implemented elsewhere - digpattern.cpp allows multi-Z designations. Obsolete, or is there more here? - incrementalsearch.cpp linux-only memory stuff; unqualified to judge - SegementedFinder.h more memory stuff - - -To port: - copypaste.cpp high value target - a proof of concept plugin to allow copy-pasting in DF; does both terrain and buildings/constructions - dfbauxtite.cpp changes material of mechanisms to bauxtite (ie magma-safe) - hellhole.cpp instantly creates a hole to the HFS - hotkeynotedump.py outputs a list of hotkeys and names; most useful before keybindings were possible. Trival to port but low value. - itemdesignator.cpp mostly replaced by Falconne's enhanced stocks, but could port the interesting 'set fire to things' function - position.py outputs very detailed time& place info +TODO: + copypaste.cpp + high value target - a proof of concept plugin to allow copy-pasting in DF; does both terrain and buildings/constructions + creaturemanager.cpp + modify skills and labors of creatures, kill creatures, etc; impressive but I suspect most functions implemented elsewhere + dfbauxtite.cpp + changes material of mechanisms to bauxtite (ie magma-safe) + hellhole.cpp + instantly creates a hole to the HFS + +In progress: + hotkey-notes.lua + prints list of hotkeys with name and jump position diff --git a/needs_porting/digpattern.cpp b/needs_porting/digpattern.cpp deleted file mode 100644 index 90d960ba1..000000000 --- a/needs_porting/digpattern.cpp +++ /dev/null @@ -1,259 +0,0 @@ -#include -#include // for memset -#include -#include -#include -#include -#include -#include -using namespace std; - -#include -#include -using namespace MapExtras; -//#include - -void usage(int argc, const char * argv[]) -{ - cout - << "Usage:" << endl - << argv[0] << " [option 1] [option 2] [...]" << endl - << "-q : Suppress \"Press any key to continue\" at program termination" << endl - << "-u : Dig upwards times (default 5)" << endl - << "-d : Dig downwards times (default 5)" << endl - ; -} - -void digat(MapCache * MCache, DFHack::DFCoord xy) -{ - int16_t tt; - tt = MCache->tiletypeAt(xy); - if(!DFHack::isWallTerrain(tt)) - return; - - // found a good tile, dig+unset material - DFHack::t_designation des = MCache->designationAt(xy); - - if(MCache->testCoord(xy)) - { - MCache->clearMaterialAt(xy); - - if(des.bits.dig == DFHack::designation_no) - des.bits.dig = DFHack::designation_default; - MCache->setDesignationAt(xy,des); - } -} - -int strtoint(const string &str) -{ - stringstream ss(str); - int result; - return ss >> result ? result : -1; -} - -typedef struct -{ - int16_t x; - int16_t y; -} pos; - -int main (int argc, const char* argv[]) -{ - // Command line options - bool updown = false; - bool quiet = true; - // let's be more useful when double-clicked on windows - #ifndef LINUX_BUILD - quiet = false; - #endif - int dig_up_n = 5; - int dig_down_n = 5; - - for(int i = 1; i < argc; i++) - { - string arg_cur = argv[i]; - string arg_next = ""; - int arg_next_int = -99999; - /* Check if argv[i+1] is a number >= 0 */ - if (i < argc-1) { - arg_next = argv[i+1]; - arg_next_int = strtoint(arg_next); - if (arg_next != "0" && arg_next_int == 0) { - arg_next_int = -99999; - } - } - if (arg_cur == "-x") - { - updown = true; - } - else if (arg_cur == "-q") - { - quiet = true; - } - else if(arg_cur == "-u" && i < argc-1) - { - if (arg_next_int < 0 || arg_next_int >= 99999) { - usage(argc, argv); - return 1; - } - dig_up_n = arg_next_int; - i++; - } - else if(arg_cur == "-d" && i < argc-1) - { - if (arg_next_int < 0 || arg_next_int >= 99999) { - usage(argc, argv); - return 1; - } - dig_down_n = arg_next_int; - i++; - } - else - { - usage(argc, argv); - return 1; - } - } - - DFHack::ContextManager DFMgr("Memory.xml"); - DFHack::Context * DF; - try - { - DF = DFMgr.getSingleContext(); - DF->Attach(); - } - catch (exception& e) - { - cerr << "Error getting context: " << e.what() << endl; - if (!quiet) - cin.ignore(); - - return 1; - } - - uint32_t x_max,y_max,z_max; - DFHack::Maps * Maps = DF->getMaps(); - DFHack::Gui * Gui = DF->getGui(); - - // init the map - if(!Maps->Start()) - { - cerr << "Can't init map. Make sure you have a map loaded in DF." << endl; - DF->Detach(); - if (!quiet) - cin.ignore(); - - return 1; - } - - int32_t cx, cy, cz; - Maps->getSize(x_max,y_max,z_max); - uint32_t tx_max = x_max * 16; - uint32_t ty_max = y_max * 16; - - Gui->getCursorCoords(cx,cy,cz); - if (cx == -30000) - { - cerr << "Cursor is not active. Point the cursor at the position to dig at." << endl; - DF->Detach(); - if (!quiet) - { - cin.ignore(); - } - return 1; - } - - DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); - if(xy.x == 0 || xy.x == tx_max - 1 || xy.y == 0 || xy.y == ty_max - 1) - { - cerr << "I won't dig the borders. That would be cheating!" << endl; - DF->Detach(); - if (!quiet) - { - cin.ignore(); - } - return 1; - } - MapCache * MCache = new MapCache(Maps); - - DFHack::t_designation des = MCache->designationAt(xy); - int16_t tt = MCache->tiletypeAt(xy); - int16_t veinmat = MCache->veinMaterialAt(xy); - - /* - if( veinmat == -1 ) - { - cerr << "This tile is non-vein. Bye :)" << endl; - delete MCache; - DF->Detach(); - if (!quiet) { - cin.ignore(); - } - return 1; - } - */ - printf("Digging at (%d/%d/%d), tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, des.whole); - - // 1 < xy.x < tx_max - 1 - // 1 < xy.y < ty_max - 1 - // xy.z - - // X____ - // X_XXX - // XXXXX - // __XXX - // __XXX - // _____ - pos map[] = - { - { 0,0 } - , { 0,1 } - , { 0,2 } , { 2,2 }, { 3,2 }, { 4,2 } - , { 0,3 }, { 1,3 }, { 2,3 }, { 3,3 }, { 4,3 } - , { 2,4 }, { 3,4 }, { 4,4 } - // this is mirrored, goes left instead of right - , {-2,2 }, {-3,2 }, {-4,2 } - , {-1,3 }, {-2,3 }, {-3,3 }, {-4,3 } - , {-2,4 }, {-3,4 }, {-4,4 } - }; - - DFHack::DFCoord npos = xy; - - if (dig_up_n > 0) - { - for (int j = 0; j < dig_up_n; j++) - { - for (int i = 0; i < sizeof(map)/sizeof(map[0]); i++) - { - npos=xy; - npos.x += map[i].x; - npos.y -= 4*j + map[i].y; - printf("Digging at (%d/%d/%d)\n", npos.x, npos.y, npos.z); - digat(MCache, npos); - } - } - } - if (dig_down_n > 0) - { - for (int j = 0; j < dig_down_n; j++) - { - for (int i = 0; i < sizeof(map)/sizeof(map[0]); i++) - { - npos=xy; - npos.x += map[i].x; - npos.y += 4*j + map[i].y; - printf("Digging at (%d/%d/%d)\n", npos.x, npos.y, npos.z); - digat(MCache, npos); - } - } - } - - MCache->WriteAll(); - delete MCache; - DF->Detach(); - if (!quiet) { - cout << "Done. Press any key to continue" << endl; - cin.ignore(); - } - return 0; -} diff --git a/needs_porting/hotkey-notes.lua b/needs_porting/hotkey-notes.lua new file mode 100644 index 000000000..a84506512 --- /dev/null +++ b/needs_porting/hotkey-notes.lua @@ -0,0 +1,40 @@ +-- prints info on assigned hotkeys to the console + +local hotkeys = {'F1 ', 'F2 ', 'F3 ', 'F4 ', 'F5 ', 'F6 ', + 'F7 ', 'F8 ', 'F9 ', 'F10', 'F11', 'F12'} + +for i=1, #hotkeys do + local hk = hotkeys[i] + hk = {id=hk} + -- PLACEHOLDER PROPERTIES ONLY! + hk.name = '_name' + hk.x = df.global.window_x + hk.y = df.global.window_y + hk.z = df.global.window_z + + print(hk.id..' '..hk.name..' X= '..hk.x..', Y= '..hk.y..', Z= '..hk.z) +end + +--[[ +# the (very) old Python version... +from context import Context, ContextManager + +cm = ContextManager("Memory.xml") +df = cm.get_single_context() + +df.attach() + +gui = df.gui + +print "Hotkeys" + +hotkeys = gui.read_hotkeys() + +for key in hotkeys: + print "x: %d\ny: %d\tz: %d\ttext: %s" % (key.x, key.y, key.z, key.name) + +df.detach() + +print "Done. Press any key to continue" +raw_input() +]]-- diff --git a/needs_porting/hotkeynotedump.py b/needs_porting/hotkeynotedump.py deleted file mode 100644 index 77cd0b811..000000000 --- a/needs_porting/hotkeynotedump.py +++ /dev/null @@ -1,20 +0,0 @@ -from context import Context, ContextManager - -cm = ContextManager("Memory.xml") -df = cm.get_single_context() - -df.attach() - -gui = df.gui - -print "Hotkeys" - -hotkeys = gui.read_hotkeys() - -for key in hotkeys: - print "x: %d\ny: %d\tz: %d\ttext: %s" % (key.x, key.y, key.z, key.name) - -df.detach() - -print "Done. Press any key to continue" -raw_input() \ No newline at end of file diff --git a/needs_porting/incrementalsearch.cpp b/needs_porting/incrementalsearch.cpp deleted file mode 100644 index e70e09527..000000000 --- a/needs_porting/incrementalsearch.cpp +++ /dev/null @@ -1,1064 +0,0 @@ -// this is an incremental search tool. It only works on Linux. -// here be dragons... and ugly code :P -#include -#include -#include -#include -#include -#include -#include -#include -#include -using namespace std; - -#ifndef LINUX_BUILD - #define WINVER 0x0500 - // this one prevents windows from infecting the global namespace with filth - #define NOMINMAX - #define WIN32_LEAN_AND_MEAN - #include -#endif - -#include -#include "SegmentedFinder.h" - -inline void printRange(DFHack::t_memrange * tpr) -{ - std::cout << std::hex << tpr->start << " - " << tpr->end << "|" << (tpr->read ? "r" : "-") << (tpr->write ? "w" : "-") << (tpr->execute ? "x" : "-") << "|" << tpr->name << std::endl; -} - -/* - * Since the start and/or end of a memory range can change each time we - * detach and re-attach to the DF process, we save the indexes of the - * memory ranges we want to look at, and re-read the ranges each time - * we re-attach. Hopefully we won't encounter any circumstances where - * entire new ranges are added or removed... - */ -bool getRanges(DFHack::Process * p, vector & selected_ranges) -{ - vector ranges; - ranges.clear(); - selected_ranges.clear(); - p->getMemRanges(ranges); - cout << "Which range to search? (default is 1-4)" << endl; - for(size_t i = 0; i< ranges.size();i++) - { - cout << dec << "(" << i << ") "; - printRange(&(ranges[i])); - } - int start, end; - while(1) - { - string select; - cout << ">>"; - std::getline(cin, select); - if(select.empty()) - { - // empty input, assume default. observe the length of the memory - // range vector these are hardcoded values, intended for my - // convenience only - if(p->getDescriptor()->getOS() == DFHack::OS_WINDOWS) - { - start = min(11, (int)ranges.size()); - end = min(14, (int)ranges.size()); - } - else if(p->getDescriptor()->getOS() == DFHack::OS_LINUX) - { - start = min(2, (int)ranges.size()); - end = min(4, (int)ranges.size()); - } - else - { - start = 1; - end = 1; - } - break; - } - // I like the C variants here. much less object clutter - else if(sscanf(select.c_str(), "%d-%d", &start, &end) == 2) - { - start = min(start, (int)ranges.size()); - end = min(end, (int)ranges.size()); - break; - } - else - { - continue; - } - break; - } - cout << "selected ranges:" <>"; - std::getline(cin, select); - if(select.empty()) - { - output = def; - break; - } - else if( sscanf(select.c_str(), "%d", &output) == 1 ) - { - break; - } - else - { - continue; - } - } - return true; -} - -bool getString (string prompt, string & output) -{ - cout << prompt; - cout << ">>"; - string select; - std::getline(cin, select); - if(select.empty()) - { - return false; - } - else - { - output = select; - return true; - } -} - -bool readRanges(DFHack::Context * DF, vector & selected_ranges, - vector & ranges) -{ - DFHack::Process * p = DF->getProcess(); - - vector new_ranges; - new_ranges.clear(); - p->getMemRanges(new_ranges); - - for (size_t i = 0; i < selected_ranges.size(); i++) - { - int idx = selected_ranges[i]; - - if (ranges.size() == i) - // First time ranges vector has been filled in. - ranges.push_back(new_ranges[idx]); - // If something was wrong with the range on one memory read, - // don't read it again. - else if(ranges[i].valid) - ranges[i] = new_ranges[idx]; - } - - return true; -} - - -template -bool Incremental ( vector &found, const char * what, T& output, - const char *singular = "address", const char *plural = "addresses", bool numberz = false ) -{ - string select; - if(found.empty()) - { - cout << "search ready - insert " << what << ", 'p' for results, 'p #' to limit number of results" << endl; - } - else if( found.size() == 1) - { - cout << "Found single "<< singular <<"!" << endl; - cout << hex << "0x" << found[0] << endl; - } - else - { - cout << "Found " << dec << found.size() << " " << plural <<"." << endl; - } - incremental_more: - cout << ">>"; - std::getline(cin, select); - size_t num = 0; - if( sscanf(select.c_str(),"p %zd", &num) && num > 0) - { - cout << "Found "<< plural <<":" << endl; - for(size_t i = 0; i < min(found.size(), num);i++) - { - cout << hex << "0x" << found[i] << endl; - } - goto incremental_more; - } - else if(select == "p") - { - cout << "Found "<< plural <<":" << endl; - for(size_t i = 0; i < found.size();i++) - { - cout << hex << "0x" << found[i] << endl; - } - goto incremental_more; - } - else if(select == "q") - { - return false; - } - else if(select.empty()) - { - goto incremental_more; - } - else - { - if(numberz) - { - if( sscanf(select.c_str(),"0x%x", &output) == 1 ) - { - //cout << dec << output << endl; - return true; - } - if( sscanf(select.c_str(),"%d", &output) == 1 ) - { - //cout << dec << output << endl; - return true; - } - } - stringstream ss (stringstream::in | stringstream::out); - ss << select; - ss >> output; - cout << output; - if(!ss.fail()) - { - return true; - } - cout << "not a valid value for type: " << what << endl; - goto incremental_more; - } -} - -void FindIntegers(DFHack::ContextManager & DFMgr, - vector & selected_ranges) -{ - vector ranges; - - // input / validation of variable size - int size; - do - { - getNumber("Select variable size (1,2,4 bytes)",size, 4); - } while (size != 1 && size != 2 && size != 4); - // input / validation of variable alignment (default is to use the same alignment as size) - int alignment; - do - { - getNumber("Select variable alignment (1,2,4 bytes)",alignment, size); - } while (alignment != 1 && alignment != 2 && alignment != 4); - - uint32_t test1; - vector found; - found.reserve(100); - while(Incremental(found, "integer",test1,"address", "addresses",true)) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - SegmentedFinder sf(ranges,DF); - switch(size) - { - case 1: - sf.Incremental(test1,alignment,found, equalityP); - break; - case 2: - sf.Incremental(test1,alignment,found, equalityP); - break; - case 4: - sf.Incremental(test1,alignment,found, equalityP); - break; - } - DF->Detach(); - } -} - -void FindVectorByLength(DFHack::ContextManager & DFMgr, - vector & selected_ranges ) -{ - vector ranges; - - int element_size; - do - { - getNumber("Select searched vector item size in bytes",element_size, 4); - } while (element_size < 1); - - uint32_t length; - vector found; - found.reserve(100); - while (Incremental(found, "vector length",length,"vector","vectors")) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - SegmentedFinder sf(ranges,DF); - //sf.Incremental(0,4,found,vectorAll); - //sf.Filter(length * element_size,found,vectorLength); - sf.Incremental(length * element_size, 4 , found, vectorLength); - DF->Detach(); - } -} - -void FindVectorByObjectRawname(DFHack::ContextManager & DFMgr, - vector & selected_ranges) -{ - vector ranges; - vector found; - string select; - - while (Incremental(found, "raw name",select,"vector","vectors")) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - SegmentedFinder sf(ranges,DF); - sf.Find(0,4,found, vectorAll); - sf.Filter(select.c_str(),found, vectorString); - DF->Detach(); - } -} - -void FindVectorByFirstObjectRawname(DFHack::ContextManager & DFMgr, - vector & selected_ranges) -{ - vector ranges; - vector found; - string select; - while (Incremental(found, "raw name",select,"vector","vectors")) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - SegmentedFinder sf(ranges,DF); - sf.Find(0,4,found, vectorAll); - sf.Filter(select.c_str(),found, vectorStringFirst); - DF->Detach(); - } -} - -struct VectorSizeFunctor : public binary_function -{ - VectorSizeFunctor(SegmentedFinder & sf):sf_(sf){} - bool operator()( uint64_t lhs, uint64_t rhs) - { - vecTriplet* left = sf_.Translate(lhs); - vecTriplet* right = sf_.Translate(rhs); - return ((left->finish - left->start) < (right->finish - right->start)); - } - SegmentedFinder & sf_; -}; - -void FindVectorByBounds(DFHack::ContextManager & DFMgr, - vector & selected_ranges) -{ - vector ranges; - vector found; - uint32_t select; - while (Incremental(found, "address between vector.start and vector.end",select,"vector","vectors")) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - SegmentedFinder sf(ranges,DF); - sf.Find(0,4,found, vectorAll); - sf.Filter(select,found, vectorAddrWithin); - // sort by size of vector - std::sort(found.begin(), found.end(), VectorSizeFunctor(sf)); - DF->Detach(); - } -} - -void FindPtrVectorsByObjectAddress(DFHack::ContextManager & DFMgr, - vector & selected_ranges) -{ - vector ranges; - vector found; - uint32_t select; - while (Incremental(found, "object address",select,"vector","vectors")) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - SegmentedFinder sf(ranges,DF); - sf.Find(0,4,found, vectorAll); - sf.Filter(select,found, vectorOfPtrWithin); - DF->Detach(); - } -} - -void FindStrBufs(DFHack::ContextManager & DFMgr, - vector & selected_ranges) -{ - vector ranges; - vector found; - string select; - while (Incremental(found,"buffer",select,"buffer","buffers")) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - SegmentedFinder sf(ranges,DF); - sf.Find< const char * ,uint32_t>(select.c_str(),1,found, findStrBuffer); - DF->Detach(); - } -} - - - -void FindStrings(DFHack::ContextManager & DFMgr, - vector & selected_ranges) -{ - vector ranges; - vector found; - string select; - while (Incremental(found,"string",select,"string","strings")) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - SegmentedFinder sf(ranges,DF); - sf.Incremental< const char * ,uint32_t>(select.c_str(),1,found, findString); - DF->Detach(); - } -} - -void FindData(DFHack::ContextManager & DFMgr, - vector & selected_ranges) -{ - vector ranges; - vector found; - Bytestream select; - while (Incremental(found,"byte stream",select,"byte stream","byte streams")) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - SegmentedFinder sf(ranges,DF); - sf.Incremental< Bytestream ,uint32_t>(select,1,found, findBytestream); - DF->Detach(); - } -} - -bool TriggerIncremental ( vector &found ) -{ - string select; - if(found.empty()) - { - cout << "search ready - position the DF cursor and hit enter when ready" << endl; - } - else if( found.size() == 1 ) - { - cout << "Found single coord!" << endl; - cout << hex << "0x" << found[0] << endl; - } - else - { - cout << "Found " << dec << found.size() << " coords." << endl; - } - incremental_more: - cout << ">>"; - std::getline(cin, select); - size_t num = 0; - if( sscanf(select.c_str(),"p %zd", &num) && num > 0) - { - cout << "Found coords:" << endl; - for(size_t i = 0; i < min(found.size(), num);i++) - { - cout << hex << "0x" << found[i] << endl; - } - goto incremental_more; - } - else if(select == "p") - { - cout << "Found coords:" << endl; - for(size_t i = 0; i < found.size();i++) - { - cout << hex << "0x" << found[i] << endl; - } - goto incremental_more; - } - else if(select == "q") - { - return false; - } - else return true; -} - -void FindCoords(DFHack::ContextManager & DFMgr, vector & selected_ranges) -{ - vector found; - vector ranges; - - int size = 4; - do - { - getNumber("Select coord size (2,4 bytes)",size, 4); - } while (size != 2 && size != 4); - while (TriggerIncremental(found)) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - DFHack::Gui * pos = DF->getGui(); - pos->Start(); - int32_t x, y, z; - pos->getCursorCoords(x,y,z); - cout << "Searching for: " << dec << x << ":" << y << ":" << z << endl; - Bytestream select; - if(size == 2) - { - select.insert(x); - select.insert(y); - select.insert(z); - } - else - { - select.insert(x); - select.insert(y); - select.insert(z); - } - cout << select << endl; - SegmentedFinder sf(ranges,DF); - sf.Incremental< Bytestream ,uint32_t>(select,1,found, findBytestream); - DF->Detach(); - } -} - -void PtrTrace(DFHack::ContextManager & DFMgr, - vector & selected_ranges) -{ - vector ranges; - - int element_size; - do - { - getNumber("Set search granularity",element_size, 4); - } while (element_size < 1); - - vector found; - set check; // to detect circles - uint32_t select; - while (Incremental(found,"address",select,"addresses","addresses",true)) - { - DFMgr.Refresh(); - found.clear(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - readRanges(DF, selected_ranges, ranges); - SegmentedFinder sf(ranges,DF); - cout <<"Starting: 0x" << hex << select << endl; - while(sf.getSegmentForAddress(select)) - { - sf.Incremental(select,element_size,found, equalityP); - if(found.empty()) - { - cout << "."; - cout.flush(); - select -=element_size; - continue; - } - cout << endl; - cout <<"Object start: 0x" << hex << select << endl; - cout <<"Pointer: 0x" << hex << found[0] << endl; - // make sure we don't go in circles' - if(check.count(select)) - { - break; - } - check.insert(select); - // ascend - select = found[0]; - found.clear(); - } - DF->Detach(); - } -} -/* -{ - vector found; - Bytestream select; - while (Incremental(found,"byte stream",select,"byte stream","byte streams")) - { - DFMgr.Refresh(); - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - SegmentedFinder sf(ranges,DF); - sf.Incremental< Bytestream ,uint32_t>(select,1,found, findBytestream); - DF->Detach(); - } -} -*/ -void DataPtrTrace(DFHack::ContextManager & DFMgr, - vector & selected_ranges) -{ - vector ranges; - int element_size; - do - { - getNumber("Set search granularity",element_size, 4); - } while (element_size < 1); - - vector found; - set check; // to detect circles - uint32_t select; - Bytestream bs_select; - DFHack::Context * DF = DFMgr.getSingleContext(); - DF->Attach(); - DFMgr.Refresh(); - readRanges(DF, selected_ranges, ranges); - found.clear(); - SegmentedFinder sf(ranges,DF); - while(found.empty()) - { - Incremental(found,"byte stream",bs_select,"byte stream","byte streams"); - - sf.Incremental< Bytestream ,uint32_t>(bs_select,1,found, findBytestream); - } - select = found[0]; - - - - - cout <<"Starting: 0x" << hex << select << endl; - while(sf.getSegmentForAddress(select)) - { - sf.Incremental(select,element_size,found, equalityP); - if(found.empty()) - { - cout << "."; - cout.flush(); - select -=element_size; - continue; - } - cout << endl; - cout <<"Object start: 0x" << hex << select << endl; - cout <<"Pointer: 0x" << hex << found[0] << endl; - // make sure we don't go in circles' - if(check.count(select)) - { - break; - } - check.insert(select); - // ascend - select = found[0]; - found.clear(); - } - DF->Detach(); -} - -void printFound(vector &found, const char * what) -{ - cout << what << ":" << endl; - for(size_t i = 0; i < found.size();i++) - { - cout << hex << "0x" << found[i] << endl; - } -} - -void printFoundStrVec(vector &found, const char * what, SegmentedFinder & s) -{ - cout << what << ":" << endl; - for(size_t i = 0; i < found.size();i++) - { - cout << hex << "0x" << found[i] << endl; - cout << "--------------------------" << endl; - vecTriplet * vt = s.Translate(found[i]); - if(vt) - { - int j = 0; - for(uint32_t idx = vt->start; idx < vt->finish; idx += sizeof(uint32_t)) - { - uint32_t object_ptr; - // deref ptr idx, get ptr to object - if(!s.Read(idx,object_ptr)) - { - cout << "BAD!" << endl; - break; - } - // deref ptr to first object, get ptr to string - uint32_t string_ptr; - if(!s.Read(object_ptr,string_ptr)) - { - cout << "BAD!" << endl; - break; - } - // get string location in our local cache - char * str = s.Translate(string_ptr); - if(!str) - { - cout << "BAD!" << endl; - break; - } - cout << dec << j << ":" << hex << "0x" << object_ptr << " : " << str << endl; - j++; - } - } - else - { - cout << "BAD!" << endl; - break; - } - cout << "--------------------------" << endl; - } -} - -// meh -#pragma pack(1) -struct tilecolors -{ - uint16_t fore; - uint16_t back; - uint16_t bright; -}; -#pragma pack() - -void autoSearch(DFHack::Context * DF, vector & selected_ranges) -{ - vector ranges; - vector allVectors; - vector filtVectors; - vector to_filter; - - readRanges(DF, selected_ranges, ranges); - - cout << "stealing memory..." << endl; - SegmentedFinder sf(ranges, DF); - cout << "looking for vectors..." << endl; - sf.Find(0,4,allVectors, vectorAll); -/* - // trim vectors. anything with > 10000 entries is not interesting - for(uint64_t i = 0; i < allVectors.size();i++) - { - vecTriplet* vtrip = sf.Translate(allVectors[i]); - if(vtrip) - { - uint64_t length = (vtrip->finish - vtrip->start) / 4; - if(length < 10000 ) - { - filtVectors.push_back(allVectors[i]); - } - } - } -*/ - filtVectors = allVectors; - cout << "-------------------" << endl; - cout << "!!LANGUAGE TABLES!!" << endl; - cout << "-------------------" << endl; - - uint64_t kulet_vector; - uint64_t word_table_offset; - uint64_t DWARF_vector; - uint64_t DWARF_object; - - // find lang vector (neutral word table) - to_filter = filtVectors; - sf.Filter("ABBEY",to_filter, vectorStringFirst); - uint64_t lang_addr = to_filter[0]; - - // find dwarven language word table - to_filter = filtVectors; - sf.Filter("kulet",to_filter, vectorStringFirst); - kulet_vector = to_filter[0]; - - // find vector of languages - to_filter = filtVectors; - sf.Filter("DWARF",to_filter, vectorStringFirst); - - // verify - for(size_t i = 0; i < to_filter.size(); i++) - { - vecTriplet * vec = sf.Translate(to_filter[i]); - if(((vec->finish - vec->start) / 4) == 4) // verified - { - DWARF_vector = to_filter[i]; - DWARF_object = sf.Read(vec->start); - // compute word table offset from dwarf word table and dwarf language object addresses - word_table_offset = kulet_vector - DWARF_object; - break; - } - } - cout << "translation vector: " << hex << "0x" << DWARF_vector << endl; - cout << "lang vector: " << hex << "0x" << lang_addr << endl; - cout << "word table offset: " << hex << "0x" << word_table_offset << endl; - - cout << "-------------" << endl; - cout << "!!MATERIALS!!" << endl; - cout << "-------------" << endl; - // inorganics vector - to_filter = filtVectors; - //sf.Find(257 * 4,4,to_filter,vectorLength); - sf.Filter("IRON",to_filter, vectorString); - sf.Filter("ONYX",to_filter, vectorString); - sf.Filter("RAW_ADAMANTINE",to_filter, vectorString); - sf.Filter("BLOODSTONE",to_filter, vectorString); - printFound(to_filter,"inorganics"); - - // organics vector - to_filter = filtVectors; - sf.Filter(52 * 4,to_filter,vectorLength); - sf.Filter("MUSHROOM_HELMET_PLUMP",to_filter, vectorStringFirst); - printFound(to_filter,"organics"); - - // new organics vector - to_filter = filtVectors; - sf.Filter("MUSHROOM_HELMET_PLUMP",to_filter, vectorString); - sf.Filter("MEADOW-GRASS",to_filter, vectorString); - sf.Filter("TUNNEL_TUBE",to_filter, vectorString); - sf.Filter("WEED_BLADE",to_filter, vectorString); - sf.Filter("EYEBALL",to_filter, vectorString); - printFound(to_filter,"organics 31.19"); - - // tree vector - to_filter = filtVectors; - sf.Filter(31 * 4,to_filter,vectorLength); - sf.Filter("MANGROVE",to_filter, vectorStringFirst); - printFound(to_filter,"trees"); - - // plant vector - to_filter = filtVectors; - sf.Filter(21 * 4,to_filter,vectorLength); - sf.Filter("MUSHROOM_HELMET_PLUMP",to_filter, vectorStringFirst); - printFound(to_filter,"plants"); - - // color descriptors - //AMBER, 112 - to_filter = filtVectors; - sf.Filter(112 * 4,to_filter,vectorLength); - sf.Filter("AMBER",to_filter, vectorStringFirst); - printFound(to_filter,"color descriptors"); - if(!to_filter.empty()) - { - uint64_t vec = to_filter[0]; - vecTriplet *vtColors = sf.Translate(vec); - uint32_t colorObj = sf.Read(vtColors->start); - cout << "Amber color:" << hex << "0x" << colorObj << endl; - // TODO: find string 'amber', the floats - } - - // all descriptors - //AMBER, 338 - to_filter = filtVectors; - sf.Filter(338 * 4,to_filter,vectorLength); - sf.Filter("AMBER",to_filter, vectorStringFirst); - printFound(to_filter,"all descriptors"); - - // creature type - //ELEPHANT, ?? (demons abound) - to_filter = filtVectors; - //sf.Find(338 * 4,4,to_filter,vectorLength); - sf.Filter("ELEPHANT",to_filter, vectorString); - sf.Filter("CAT",to_filter, vectorString); - sf.Filter("DWARF",to_filter, vectorString); - sf.Filter("WAMBLER_FLUFFY",to_filter, vectorString); - sf.Filter("TOAD",to_filter, vectorString); - sf.Filter("DEMON_1",to_filter, vectorString); - - vector toad_first = to_filter; - vector elephant_first = to_filter; - sf.Filter("TOAD",toad_first, vectorStringFirst); - sf.Filter("ELEPHANT",elephant_first, vectorStringFirst); - printFoundStrVec(toad_first,"toad-first creature types",sf); - printFound(elephant_first,"elephant-first creature types"); - printFound(to_filter,"all creature types"); - - uint64_t to_use = 0; - if(!elephant_first.empty()) - { - to_use = elephant_first[0]; - vecTriplet *vtCretypes = sf.Translate(to_use); - uint32_t elephant = sf.Read(vtCretypes->start); - uint64_t Eoffset; - cout << "Elephant: 0x" << hex << elephant << endl; - cout << "Elephant: rawname = 0x0" << endl; - uint8_t letter_E = 'E'; - Eoffset = sf.FindInRange (letter_E,equalityP, elephant, 0x300 ); - if(Eoffset) - { - cout << "Elephant: big E = 0x" << hex << Eoffset - elephant << endl; - } - Eoffset = sf.FindInRange ("FEMALE",vectorStringFirst, elephant, 0x300 ); - if(Eoffset) - { - cout << "Elephant: caste vector = 0x" << hex << Eoffset - elephant << endl; - } - Eoffset = sf.FindInRange ("SKIN",vectorStringFirst, elephant, 0x2000 ); - if(Eoffset) - { - cout << "Elephant: extract? vector = 0x" << hex << Eoffset - elephant << endl; - } - tilecolors eletc = {7,0,0}; - Bytestream bs_eletc(&eletc, sizeof(tilecolors)); - cout << bs_eletc; - Eoffset = sf.FindInRange (bs_eletc, findBytestream, elephant, 0x300 ); - if(Eoffset) - { - cout << "Elephant: colors = 0x" << hex << Eoffset - elephant << endl; - } - //cout << "Amber color:" << hex << "0x" << colorObj << endl; - // TODO: find string 'amber', the floats - } - if(!toad_first.empty()) - { - to_use = toad_first[0]; - vecTriplet *vtCretypes = sf.Translate(to_use); - uint32_t toad = sf.Read(vtCretypes->start); - uint64_t Eoffset; - cout << "Toad: 0x" << hex << toad << endl; - cout << "Toad: rawname = 0x0" << endl; - Eoffset = sf.FindInRange (0xF9,equalityP, toad, 0x300 ); - if(Eoffset) - { - cout << "Toad: character (not reliable) = 0x" << hex << Eoffset - toad << endl; - } - Eoffset = sf.FindInRange ("FEMALE",vectorStringFirst, toad, 0x300 ); - if(Eoffset) - { - cout << "Toad: caste vector = 0x" << hex << Eoffset - toad << endl; - } - Eoffset = sf.FindInRange ("SKIN",vectorStringFirst, toad, 0x2000 ); - if(Eoffset) - { - cout << "Toad: extract? vector = 0x" << hex << Eoffset - toad << endl; - } - tilecolors toadtc = {2,0,0}; - Bytestream bs_toadc(&toadtc, sizeof(tilecolors)); - Eoffset = sf.FindInRange (bs_toadc, findBytestream, toad, 0x300 ); - if(Eoffset) - { - cout << "Toad: colors = 0x" << hex << Eoffset - toad << endl; - } - } - if(to_use) - { - - } -} - -int main (void) -{ - string select; - DFHack::ContextManager DFMgr("Memory.xml"); - DFHack::Context * DF = DFMgr.getSingleContext(); - try - { - DF->Attach(); - } - catch (exception& e) - { - cerr << e.what() << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - return 1; - } - DFHack::Process * p = DF->getProcess(); - vector selected_ranges; - getRanges(p, selected_ranges); - - string prompt = - "Select search type: 1=number(default), 2=vector by length, 3=vector>object>string,\n" - " 4=string, 5=automated offset search, 6=vector by address in its array,\n" - " 7=pointer vector by address of an object, 8=vector>first object>string\n" - " 9=string buffers, 10=known data, 11=backpointers, 12=data+backpointers\n" - " 13=coord lookup\n" - " 0= exit\n"; - int mode; - bool finish = 0; - do - { - getNumber(prompt,mode, 1, false); - switch (mode) - { - case 0: - finish = 1; - break; - case 1: - DF->Detach(); - FindIntegers(DFMgr, selected_ranges); - break; - case 2: - DF->Detach(); - FindVectorByLength(DFMgr, selected_ranges); - break; - case 3: - DF->Detach(); - FindVectorByObjectRawname(DFMgr, selected_ranges); - break; - case 4: - DF->Detach(); - FindStrings(DFMgr, selected_ranges); - break; - case 5: - autoSearch(DF,selected_ranges); - break; - case 6: - DF->Detach(); - FindVectorByBounds(DFMgr,selected_ranges); - break; - case 7: - DF->Detach(); - FindPtrVectorsByObjectAddress(DFMgr,selected_ranges); - break; - case 8: - DF->Detach(); - FindVectorByFirstObjectRawname(DFMgr, selected_ranges); - break; - case 9: - DF->Detach(); - FindStrBufs(DFMgr, selected_ranges); - break; - case 10: - DF->Detach(); - FindData(DFMgr, selected_ranges); - break; - case 11: - DF->Detach(); - PtrTrace(DFMgr, selected_ranges); - break; - case 12: - DF->Detach(); - DataPtrTrace(DFMgr, selected_ranges); - break; - case 13: - DF->Detach(); - FindCoords(DFMgr, selected_ranges); - break; - default: - cout << "Unknown function, try again." << endl; - } - } while ( !finish ); - #ifndef LINUX_BUILD - cout << "Done. Press any key to continue" << endl; - cin.ignore(); - #endif - return 0; -} diff --git a/needs_porting/position.py b/needs_porting/position.py deleted file mode 100644 index 122440589..000000000 --- a/needs_porting/position.py +++ /dev/null @@ -1,71 +0,0 @@ -import sys -from pydfhack import ContextManager - -df_cm = ContextManager("Memory.xml") -df = df_cm.get_single_context() - -if not df.attach(): - print "Unable to attach!" - print "Press any key to continue" - - raw_input() - sys.exit(1) - -gui = df.gui - -if gui is not None: - maps = df.maps - world = df.world - - have_maps = maps.start() - world.start() - - gm = world.read_game_mode() - - if gm is not None: - print gm - - date_tuple = (world.read_current_year(), world.read_current_month(), world.read_current_day(), world.read_current_tick()) - - print "Year: %d Month: %d Day: %d Tick: %d" % date_tuple - - v_coords = gui.get_view_coords() - c_coords = gui.get_cursor_coords() - w_coords = (-1, -1, -1) - world_pos_string = "" - - if have_maps is True: - w_coords = maps.getPosition() - - x = (v_coords[0] + w_coords[0]) * 48 - y = (v_coords[1] + w_coords[1]) * 48 - z = (v_coords[2] + w_coords[2]) - - world_pos_string = " world: %d/%d/%d" % (x, y, z) - - print "Map world offset: %d/%d/%d embark squares" % w_coords - - if v_coords != (-1, -1, -1): - print "view coords: %d/%d/%d" % v_coords - - if have_maps is True: - print world_pos_string - - if c_coords != (-1, -1, -1): - print "cursor coords: %d/%d/%d" % c_coords - - if have_maps is True: - print world_pos_string - - window_size = gui.get_window_size() - - if window_size != (-1, -1): - print "window size : %d %d" % window_size -else: - print "cursor and window parameters are unsupported on your version of DF" - -if not df.detach(): - print "Unable to detach!" - -print "Done. Press any key to continue" -raw_input() \ No newline at end of file diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index cda764f54..3cb9d9b23 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1720,10 +1720,10 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest int y = 0; ostringstream date_str; - auto month = World::ReadCurrentMonth(); + auto month = World::ReadCurrentMonth() + 1; auto day = World::ReadCurrentDay(); - date_str << "Date:" << World::ReadCurrentYear() << "/" << - ((month < 10) ? "0" : "") << month << "/" << + date_str << "Date:" << World::ReadCurrentYear() << "-" << + ((month < 10) ? "0" : "") << month << "-" << ((day < 10) ? "0" : "") << day; OutputString(COLOR_GREY, x, y, date_str.str()); diff --git a/scripts/position.lua b/scripts/position.lua new file mode 100644 index 000000000..e4521cb69 --- /dev/null +++ b/scripts/position.lua @@ -0,0 +1,40 @@ +--prints current time and position + +local months = { + 'Granite, in early Spring.', + 'Slate, in mid Spring.', + 'Felsite, in late Spring.', + 'Hematite, in early Summer.', + 'Malachite, in mid Summer.', + 'Galena, in late Summer.', + 'Limestone, in early Autumn.', + 'Sandstone, in mid Autumn.', + 'Timber, in late Autumn.', + 'Moonstone, in early Winter.', + 'Opal, in mid Winter.', + 'Obsidian, in late Winter.', +} + +--Fortress mode counts 1200 ticks per day and 403200 per year +--Adventurer mode counts 86400 ticks to a day and 29030400 ticks per year +--Twelve months per year, 28 days to every month, 336 days per year + +local julian_day = math.floor(df.global.cur_year_tick / 1200) + 1 +local month = math.floor(julian_day / 28) + 1 --days and months are 1-indexed +local day = julian_day % 28 + +local time_of_day = math.floor(df.global.cur_year_tick_advmode / 336) +local second = time_of_day % 60 +local minute = (time_of_day / 60) % 60 +local hour = math.floor(time_of_day / 3600) % 24 + +print('Time:') +print(' The time is '..string.format('%02d:%02d:%02d', hour, minute, second)) +print(' The date is '..string.format('%05d-%02d-%02d', df.global.cur_year, month, day)) +print(' It is the month of '..months[month]) + +print('Place:') +print(' Local window is at x='..df.global.window_x + ..' y='..df.global.window_y..' z='..df.global.window_z) + +--TODO: add cursor, regional/embark square co-ords, window size? From 5fc8a1f51fc1fefced83056155a05e92eac0260c Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 15 Dec 2014 13:41:22 -0500 Subject: [PATCH 034/115] digFlood CLEAR error. --- plugins/digFlood.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/digFlood.cpp b/plugins/digFlood.cpp index 1646f4deb..b51c94c02 100644 --- a/plugins/digFlood.cpp +++ b/plugins/digFlood.cpp @@ -178,8 +178,10 @@ command_result digFlood (color_ostream &out, std::vector & paramet continue; } - if ( parameters[a] == "CLEAR" ) + if ( parameters[a] == "CLEAR" ) { autodigMaterials.clear(); + continue; + } if ( parameters[a] == "digAll0" ) { digAll = false; From 7c372d49846ffeb56c3d57e78941b2f7667e614a Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Tue, 16 Dec 2014 11:23:50 +1100 Subject: [PATCH 035/115] remove mostly-obsolete plugins dfbauxtite - simply disable temperature, if the many more magma-safe materials aren't helping. itemdesignator is replaced by enhanced stocks screen, excpet for settings things on fire for which other tools exist. --- needs_porting/dfbauxite.cpp | 161 ------------------------------- needs_porting/itemdesignator.cpp | 148 ---------------------------- 2 files changed, 309 deletions(-) delete mode 100644 needs_porting/dfbauxite.cpp delete mode 100644 needs_porting/itemdesignator.cpp diff --git a/needs_porting/dfbauxite.cpp b/needs_porting/dfbauxite.cpp deleted file mode 100644 index 3a2fdb978..000000000 --- a/needs_porting/dfbauxite.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* -DFBauxite - Converts all your mechanisms to bauxite (for use in magma). -Author: Alex Legg - -Based on code from and uses DFHack - www.sourceforge.net/projects/dfhack -*/ - -#include -#include -#include -#include -#include -#include -#include -using namespace std; - - -#include -#include -#include -#include -#include -#include -#include -using namespace DFHack; - - -int main () -{ - DFHack::Process *proc; - DFHack::memory_info *meminfo; - DFHack::DfVector *items_vector; - DFHack::t_item_df40d item_40d; - DFHack::t_matglossPair item_40d_material; - vector stoneMat; - uint32_t item_material_offset; - uint32_t temp; - int32_t type; - int items; - int found = 0, converted = 0; - - DFHack::ContextManager DF("Memory.xml"); - try - { - DF.Attach(); - } - catch (exception& e) - { - cerr << e.what() << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - return 1; - } - - // Find out which material is bauxite - if(!DF.ReadStoneMatgloss(stoneMat)) - { - cout << "Materials not supported for this version of DF, exiting." << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - DF.Detach(); - return EXIT_FAILURE; - } - int bauxiteIndex = -1; - for (int i = 0; i < stoneMat.size();i++) - { - if(strcmp(stoneMat[i].id, "BAUXITE") == 0) - { - bauxiteIndex = i; - break; - } - } - if(bauxiteIndex == -1) - { - cout << "Cannot locate bauxite in the DF raws, exiting" << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - DF.Detach(); - return EXIT_FAILURE; - } - - // Get some basics needed for full access - proc = DF.getProcess(); - meminfo = proc->getDescriptor(); - - // Get the object name/ID mapping - //FIXME: work on the 'supported features' system required - - // Check availability of required addresses and offsets (doing custom stuff here) - - items = meminfo->getAddress("items"); - item_material_offset = meminfo->getOffset("item_materials"); - if( !items || ! item_material_offset) - { - cout << "Items not supported for this DF version, exiting" << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - DF.Detach(); - return EXIT_FAILURE; - } - - items_vector = new DFHack::DfVector (proc, items); - for(uint32_t i = 0; i < items_vector->size(); i++) - { - // get pointer to object - temp = items_vector->at (i); - // read object - proc->read (temp, sizeof (DFHack::t_item_df40d), (uint8_t *) &item_40d); - - // resolve object type - type = -1; - - // skip things we can't identify - if(!meminfo->resolveObjectToClassID (temp, type)) - continue; - string classname; - if(!meminfo->resolveClassIDToClassname (type, classname)) - continue; - - if(classname == "item_trapparts") - { - proc->read (temp + item_material_offset, sizeof (DFHack::t_matglossPair), (uint8_t *) &item_40d_material); - - cout << dec << "Mechanism at x:" << item_40d.x << " y:" << item_40d.y << " z:" << item_40d.z << " ID:" << item_40d.ID << endl; - - if (item_40d_material.index != bauxiteIndex) - { - item_40d_material.index = bauxiteIndex; - proc->write (temp + item_material_offset, sizeof (DFHack::t_matglossPair), (uint8_t *) &item_40d_material); - converted++; - } - - found++; - } - } - - - if (found == 0) - { - cout << "No mechanisms to convert" << endl; - } else { - cout << found << " mechanisms found" << endl; - cout << converted << " mechanisms converted" << endl; - } - - DF.Resume(); - DF.Detach(); - - delete items_vector; - -#ifndef LINUX_BUILD - cout << "Done. Press any key to continue" << endl; - cin.ignore(); -#endif - - return 0; -} diff --git a/needs_porting/itemdesignator.cpp b/needs_porting/itemdesignator.cpp deleted file mode 100644 index 63b41eb72..000000000 --- a/needs_porting/itemdesignator.cpp +++ /dev/null @@ -1,148 +0,0 @@ -// Item designator - -#include -#include -#include -#include -#include -using namespace std; - -#include -#include -using namespace DFHack; - -int main () -{ - - DFHack::ContextManager CM ("Memory.xml"); - DFHack::Context * DF; - DFHack::VersionInfo *mem; - DFHack::Gui * Gui; - DFHack::Materials * Mats; - DFHack::Items * Items; - cout << "This utility lets you mass-designate items by type and material." << endl - << "Like set on fire all MICROCLINE item_stone..." << endl - << "Some unusual combinations might be untested and cause the program to crash..."<< endl - << "so, watch your step and backup your fort" << endl; - try - { - DF = CM.getSingleContext(); - DF->Attach(); - mem = DF->getMemoryInfo(); - Gui = DF->getGui(); - Mats = DF->getMaterials(); - Mats->ReadAllMaterials(); - Items = DF->getItems(); - } - catch (exception& e) - { - cerr << e.what() << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - return 1; - } - DFHack::Process * p = DF->getProcess(); - DFHack::OffsetGroup* itemGroup = mem->getGroup("Items"); - unsigned vector_addr = itemGroup->getAddress("items_vector"); - DFHack::DfVector p_items (p, vector_addr); - uint32_t numItems = p_items.size(); - - map< string, map > > itemmap; - map< string, map< string, vector< dfh_item > > >::iterator it1; - int failedItems = 0; - map bad_mat_items; - for(uint32_t i=0; i< numItems; i++) - { - DFHack::dfh_item temp; - Items->readItem(p_items[i],temp); - string typestr = Items->getItemClass(temp); - string material = Mats->getDescription(temp.matdesc); - itemmap[typestr][material].push_back(temp); - } - - int i =0; - for( it1 = itemmap.begin(); it1!=itemmap.end();it1++) - { - cout << i << ": " << it1->first << "\n"; - i++; - } - if(i == 0) - { - cout << "No items found" << endl; - DF->Detach(); - return 0; - } - cout << endl << "Select an item type from the list:"; - int number; - string in; - stringstream ss; - getline(cin, in); - ss.str(in); - ss >> number; - int j = 0; - it1 = itemmap.begin(); - while(j < number && it1!=itemmap.end()) - { - it1++; - j++; - } - cout << it1->first << "\n"; - map >::iterator it2; - i=0; - for(it2 = it1->second.begin();it2!=it1->second.end();it2++){ - cout << i << ":\t" << it2->first << " [" << it2->second.size() << "]" << endl; - i++; - } - cout << endl << "Select a material type: "; - int number2; - ss.clear(); - getline(cin, in); - ss.str(in); - ss >> number2; - - decideAgain: - cout << "Select a designation - (d)ump, (f)orbid, (m)melt, set on fi(r)e :" << flush; - string designationType; - getline(cin,designationType); - DFHack::t_itemflags changeFlag = {0}; - if(designationType == "d" || designationType == "dump") - { - changeFlag.dump = 1; - } - else if(designationType == "f" || designationType == "forbid") - { - changeFlag.forbid = 1; - } - else if(designationType == "m" || designationType == "melt") - { - changeFlag.melt = 1; - } - else if(designationType == "r" || designationType == "fire") - { - changeFlag.on_fire = 1; - } - else - { - goto decideAgain; - } - j=0; - it2= it1->second.begin(); - while(j < number2 && it2!=it1->second.end()) - { - it2++; - j++; - } - for(uint32_t k = 0;k< it2->second.size();k++) - { - DFHack::dfh_item & t = it2->second[k]; - t.base.flags.whole |= changeFlag.whole; - Items->writeItem(t); - } - DF->Detach(); -#ifndef LINUX_BUILD - cout << "Done. Press any key to continue" << endl; - cin.ignore(); -#endif - return 0; -} From 0b7f890d87d698a678219353978aa99ef04b573e Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Tue, 16 Dec 2014 21:10:18 -0700 Subject: [PATCH 036/115] Trackstop bugfix: No longer prevents cancelling building removal. Thanks to Ramblurr for pointing this out. --- NEWS | 1 + plugins/trackstop.cpp | 48 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 1b9ec3f8b..d2795467a 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ DFHack Future Internals Fixes + trackstop: No longer prevents cancelling the removal of a track stop or roller. New Plugins New Scripts Misc Improvements diff --git a/plugins/trackstop.cpp b/plugins/trackstop.cpp index c7109191a..117009c8d 100644 --- a/plugins/trackstop.cpp +++ b/plugins/trackstop.cpp @@ -8,6 +8,7 @@ #include "df/building_rollersst.h" #include "df/building_trapst.h" +#include "df/job.h" #include "df/viewscreen_dwarfmodest.h" #include "modules/Gui.h" @@ -44,15 +45,35 @@ struct trackstop_hook : public df::viewscreen_dwarfmodest { building_trapst *get_selected_trackstop() { if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) || ui->main.mode != ui_sidebar_mode::QueryBuilding) { + // Not in a building's 'q' menu. return nullptr; } building_trapst *ts = virtual_cast(world->selected_building); - if (ts && ts->trap_type == df::trap_type::TrackStop && ts->construction_stage) { - return ts; + if (!ts) { + // Not a trap type of building. + return nullptr; + } + + if (ts->trap_type != df::trap_type::TrackStop) { + // Not a trackstop. + return nullptr; } - return nullptr; + if (ts->construction_stage < ts->getMaxBuildStage()) { + // Not yet fully constructed. + return nullptr; + } + + for (auto it = ts->jobs.begin(); it != ts->jobs.end(); it++) { + auto job = *it; + if (job->job_type == df::job_type::DestroyBuilding) { + // Slated for removal. + return nullptr; + } + } + + return ts; } bool handleInput(set *input) { @@ -178,15 +199,30 @@ struct roller_hook : public df::viewscreen_dwarfmodest { building_rollersst *get_selected_roller() { if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) || ui->main.mode != ui_sidebar_mode::QueryBuilding) { + // Not in a building's 'q' menu. return nullptr; } building_rollersst *roller = virtual_cast(world->selected_building); - if (roller && roller->construction_stage) { - return roller; + if (!roller) { + // Not a roller. + return nullptr; + } + + if (roller->construction_stage < roller->getMaxBuildStage()) { + // Not yet fully constructed. + return nullptr; + } + + for (auto it = roller->jobs.begin(); it != roller->jobs.end(); it++) { + auto job = *it; + if (job->job_type == df::job_type::DestroyBuilding) { + // Slated for removal. + return nullptr; + } } - return nullptr; + return roller; } bool handleInput(set *input) { From 651a8ab4974b3b5c0c008fb893e13a3ae9c53fc6 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 17 Dec 2014 23:05:48 +0200 Subject: [PATCH 037/115] fixed crashing bug. --- scripts/gui/advfort.lua | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index e84b7a412..921dd87cf 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,8 +1,11 @@ -- allows to do jobs in adv. mode. --[==[ - version: 0.01 + version: 0.011 changelog: + *0.011 + - fixed crash with building jobs (other jobs might have been crashing too!) + - fixed bug with building asking twice to input items *0.01 - instant job startation - item selection screen (!) @@ -56,7 +59,7 @@ local gscript=require 'gui.script' local tile_attrs = df.tiletype.attrs -settings={build_by_items=false,use_worn=false,check_inv=true,teleport_items=true,df_assign=false,gui_item_select=true} +settings={build_by_items=false,use_worn=false,check_inv=true,teleport_items=true,df_assign=false,gui_item_select=true,only_in_sites=false} function hasValue(tbl,val) for k,v in pairs(tbl) do @@ -214,6 +217,7 @@ function make_native_job(args) newJob.pos:assign(args.pos) --newJob.pos:assign(args.unit.pos) args.job=newJob + args.unlinked=true end end function makeJob(args) @@ -241,7 +245,10 @@ function makeJob(args) end end if failed==nil then - dfhack.job.linkIntoWorld(args.job,true) + if args.unlinked then + dfhack.job.linkIntoWorld(args.job,true) + args.unlinked=false + end addJobAction(args.job,args.unit) args.screen:wait_tick() else @@ -692,6 +699,7 @@ function finish_item_assign(args) local job=args.job local item_modes={ [df.job_type.PlantSeeds]="haul", + [df.job_type.Eat]="haul", } local item_mode=item_modes[job.job_type] or "teleport" if settings.teleport_items and item_mode=="teleport" then @@ -810,7 +818,7 @@ CheckAndFinishBuilding=function (args,bld) args.pre_actions={AssignJobItems} else local t={items=buildings.getFiltersByType({},bld:getType(),bld:getSubtype(),bld:getCustomType())} - args.pre_actions={dfhack.curry(setFiltersUp,t),AssignJobItems,AssignBuildingRef} + args.pre_actions={dfhack.curry(setFiltersUp,t),AssignBuildingRef}--,AssignJobItems end makeJob(args) end @@ -1209,6 +1217,19 @@ function usetool:farmPlot(building) makeJob(args) end +function usetool:bedActions(building) + local adv=df.global.world.units.active[0] + local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self,building=building, + job_type=df.job_type.Sleep,post_actions={AssignBuildingRef}} + makeJob(args) +end +function usetool:chairActions(building) + local adv=df.global.world.units.active[0] + local eatjob={items={{quantity=1,item_type=df.item_type.FOOD}}} + local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self,job_type=df.job_type.Eat,building=building, + pre_actions={dfhack.curry(setFiltersUp,eatjob),AssignJobItems},post_actions={AssignBuildingRef}} + makeJob(args) +end MODES={ [df.building_type.Table]={ --todo filters... name="Put items", @@ -1261,7 +1282,15 @@ MODES={ [df.building_type.Hive]={ name="Hive actions", input=usetool.hiveActions, - } + }, + [df.building_type.Bed]={ + name="Rest", + input=usetool.bedActions, + }, + [df.building_type.Chair]={ + name="Eat", + input=usetool.chairActions, + }, } function usetool:shopMode(enable,mode,building) self.subviews.shopLabel.visible=enable From 080c0bc5b270df1dabd280baa6ed9b861a7b53e4 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Wed, 17 Dec 2014 17:12:10 -0500 Subject: [PATCH 038/115] Revert "Log output of "dfhack" script to dfhack.log" --- package/darwin/dfhack | 2 +- package/linux/dfhack | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package/darwin/dfhack b/package/darwin/dfhack index 814ecd013..55aa81a98 100755 --- a/package/darwin/dfhack +++ b/package/darwin/dfhack @@ -12,6 +12,6 @@ fi old_tty_settings=$(stty -g) cd "${PWD}" -DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe "$@" 2>&1 | tee dfhack.log +DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe "$@" stty "$old_tty_settings" echo "" diff --git a/package/linux/dfhack b/package/linux/dfhack index 44677d87e..4fa3e20e7 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -65,7 +65,7 @@ case "$1" in ret=$? ;; *) - setarch i386 -R env LD_PRELOAD=$PRELOAD_LIB ./libs/Dwarf_Fortress "$@" 2>&1 | tee dfhack.log + setarch i386 -R env LD_PRELOAD=$PRELOAD_LIB ./libs/Dwarf_Fortress "$@" ret=$? ;; esac From dba7d1bd5d18afcba0ada74554c2d569e7a6bdad Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Thu, 18 Dec 2014 09:50:00 +1100 Subject: [PATCH 039/115] Add file checks to digfort Checking file type and existence before we start avoids a few issues, and prevents (highly) misleading feedback of "done" regardless of action taken. Closes #453 --- NEWS | 1 + scripts/digfort.rb | 79 +++++++++++++++++++++++++++------------------- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/NEWS b/NEWS index 1b9ec3f8b..9c9207986 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ DFHack Future Internals Fixes + digfort.rb: check file type and existence before starting New Plugins New Scripts Misc Improvements diff --git a/scripts/digfort.rb b/scripts/digfort.rb index 74db423d3..e20d64f23 100644 --- a/scripts/digfort.rb +++ b/scripts/digfort.rb @@ -1,11 +1,26 @@ # designate an area for digging according to a plan in csv format -raise "usage: digfort " if not $script_args[0] -planfile = File.read($script_args[0]) +fname = $script_args[0].to_s +print(fname) + +if not $script_args[0] then + puts " Usage: digfort " + throw :script_finished +end +if not fname[-4..-1] == ".csv" then + puts " The plan file must be in .csv format." + throw :script_finished +end +if not File.file?(fname) then + puts " The specified file does not exist." + throw :script_finished +end + +planfile = File.read(fname) if df.cursor.x == -30000 - puts "place the game cursor to the top-left corner of the design" - throw :script_finished + puts "place the game cursor to the top-left corner of the design" + throw :script_finished end # a sample CSV file @@ -30,17 +45,17 @@ EOS offset = [0, 0] tiles = [] planfile.each_line { |l| - if l =~ /#.*start\s*\(\s*(-?\d+)\s*[,;]\s*(-?\d+)/ - raise "Error: multiple start() comments" if offset != [0, 0] - offset = [$1.to_i, $2.to_i] - end + if l =~ /#.*start\s*\(\s*(-?\d+)\s*[,;]\s*(-?\d+)/ + raise "Error: multiple start() comments" if offset != [0, 0] + offset = [$1.to_i, $2.to_i] + end - l = l.chomp.sub(/#.*/, '') - next if l == '' - tiles << l.split(/[;,]/).map { |t| - t = t.strip - (t[0] == ?") ? t[1..-2] : t - } + l = l.chomp.sub(/#.*/, '') + next if l == '' + tiles << l.split(/[;,]/).map { |t| + t = t.strip + (t[0] == ?") ? t[1..-2] : t + } } x = x0 = df.cursor.x - offset[0] @@ -48,23 +63,23 @@ y = df.cursor.y - offset[1] z = df.cursor.z tiles.each { |line| - next if line.empty? or line == [''] - line.each { |tile| - t = df.map_tile_at(x, y, z) - s = t.shape_basic - case tile - when 'd'; t.dig(:Default) if s == :Wall - when 'u'; t.dig(:UpStair) if s == :Wall - when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor - when 'i'; t.dig(:UpDownStair) if s == :Wall - when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor - when 'r'; t.dig(:Ramp) if s == :Wall - when 'x'; t.dig(:No) - end - x += 1 - } - x = x0 - y += 1 + next if line.empty? or line == [''] + line.each { |tile| + t = df.map_tile_at(x, y, z) + s = t.shape_basic + case tile + when 'd'; t.dig(:Default) if s == :Wall + when 'u'; t.dig(:UpStair) if s == :Wall + when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor + when 'i'; t.dig(:UpDownStair) if s == :Wall + when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor + when 'r'; t.dig(:Ramp) if s == :Wall + when 'x'; t.dig(:No) + end + x += 1 + } + x = x0 + y += 1 } -puts 'done' +puts ' done' From 695f04a51d3a3b7f0c4c448c4e4f0a262fae5918 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Wed, 17 Dec 2014 21:26:01 -0500 Subject: [PATCH 040/115] Delete embark.lua This has been replaced by embark-tools, and there have been reports of this not working in 0.40.xx (http://www.bay12forums.com/smf/index.php?topic=139553.msg5885446#msg5885446) --- scripts/embark.lua | 53 ---------------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 scripts/embark.lua diff --git a/scripts/embark.lua b/scripts/embark.lua deleted file mode 100644 index f3609683c..000000000 --- a/scripts/embark.lua +++ /dev/null @@ -1,53 +0,0 @@ --- lets you embark anywhere. -helpstring=[[ embark [-t|-d|-e|-h] - -t test if the patch can be applied - -d disable the patch if enabled - -e enable the patch if disabled - -h shows help -]] -args={...} -if args[1]=="-h" then - print(helpstring) - return -end -local ms=require("memscan") -local dfu=require("plugins.dfusion") -local patch -function embark() --windows only? - local seg=ms.get_code_segment() - local idx,off - local patch=dfu.patches.embark_anywhere - if patch~=nil then - return patch - else - idx,off=seg.uint8_t:find_one{0x66, 0x83, 0x7F ,0x1A ,0xFF,0x74,0x04} - if idx then - patch=dfu.BinaryPatch{name="embark_anywhere",pre_data={0x74,0x04},data={0x90,0x90},address=off+5} - return patch - else - qerror("Offset for embark patch not found!") - end - end -end -patch=embark() -if args[1]=="-t" then - if patch:test() and not patch.is_applied then - print("all okay, patch can be applied") - elseif patch.is_applied then - print("patch is currently applied, can be removed") - else - qerror("patch can't be applied") - end -elseif args[1]=="-d" then - if patch.is_applied then - patch:remove() - print("patch removed") - end -elseif args[1]=="-e" then - if not patch.is_applied then - patch:apply() - print("patch applied") - end -else - print(helpstring) -end \ No newline at end of file From 64eb9d76bd41aa4b2125e7071e63836f9413f1b0 Mon Sep 17 00:00:00 2001 From: Peridexis Errant Date: Sat, 20 Dec 2014 17:08:07 +1100 Subject: [PATCH 041/115] adventure keybind contexts, bound companion-order --- dfhack.init-example | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index c1cd1ac86..b94e59354 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -52,8 +52,9 @@ keybinding add Ctrl-Shift-F@dwarfmode forum-dwarves # Generic adv mode bindings # ############################## -keybinding add Ctrl-B adv-bodyswap -keybinding add Ctrl-Shift-B "adv-bodyswap force" +keybinding add Ctrl-B@dungeonmode adv-bodyswap +keybinding add Ctrl-Shift-B@dungeonmode "adv-bodyswap force" +keybinding add Shift-O@dungeonmode gui/companion-order ############################## # Generic legends bindings # From 93def956f5c9e8ad4252a697157d316a29e22645 Mon Sep 17 00:00:00 2001 From: Peridexis Errant Date: Sun, 21 Dec 2014 21:03:44 +1100 Subject: [PATCH 042/115] finish position.lua It would be great to report the name of the Age, and the position of the fort within the world, but aside from those it's finished. --- Readme.rst | 4 ++-- scripts/position.lua | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Readme.rst b/Readme.rst index 52c966742..465037cea 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2401,8 +2401,8 @@ Example:: position ======== -Reports the current date, time, month, and season. Location reporting is a -work in progress. +Reports the current time: date, clock time, month, and season. Also reports +location: z-level, cursor position, window size, and mouse location. putontable ========== diff --git a/scripts/position.lua b/scripts/position.lua index e4521cb69..8c3dbe72a 100644 --- a/scripts/position.lua +++ b/scripts/position.lua @@ -25,16 +25,19 @@ local day = julian_day % 28 local time_of_day = math.floor(df.global.cur_year_tick_advmode / 336) local second = time_of_day % 60 -local minute = (time_of_day / 60) % 60 +local minute = math.floor(time_of_day / 60) % 60 local hour = math.floor(time_of_day / 3600) % 24 print('Time:') print(' The time is '..string.format('%02d:%02d:%02d', hour, minute, second)) print(' The date is '..string.format('%05d-%02d-%02d', df.global.cur_year, month, day)) print(' It is the month of '..months[month]) +--TODO: print(' It is the Age of '..age_name) print('Place:') -print(' Local window is at x='..df.global.window_x - ..' y='..df.global.window_y..' z='..df.global.window_z) - ---TODO: add cursor, regional/embark square co-ords, window size? +print(' The z-level is z='..df.global.window_z) +print(' The cursor is at x='..df.global.cursor.x..', y='..df.global.cursor.y) +print(' The window is '..df.global.gps.dimx..' tiles wide and '..df.global.gps.dimy..' tiles high') +if df.global.gps.mouse_x == -1 then print(' The mouse is not in the DF window') else +print(' The mouse is at x='..df.global.gps.mouse_x..', y='..df.global.gps.mouse_y..' within the window') end +--TODO: print(' The fortress is at '..x, y..' on the world map ('..worldsize..' square)') From ee958c0f8ffd66145fc6756bdaa52d9c9bbacf27 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 21 Dec 2014 16:42:12 -0500 Subject: [PATCH 043/115] Update some plugins/scripts for 0.40.22 * autolabor, manipulator: Add BUILD_CONSTRUCTION and BUILD_ROAD labors * mousequery: Support "toggle engravings" sidebar mode * devel/export-dt-ini.lua: Add body_part_flags Conflicts: plugins/mousequery.cpp --- plugins/autolabor.cpp | 4 +++- plugins/manipulator.cpp | 6 ++++-- plugins/mousequery.cpp | 33 +++++++++++++++++++++------------ scripts/devel/export-dt-ini.lua | 1 + 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 4ef746183..dd44f6c3c 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -477,7 +477,9 @@ static const struct labor_default default_labor_infos[] = { /* PULL_LEVER */ {HAULERS, false, 1, 200, 0}, /* REMOVE_CONSTRUCTION */ {HAULERS, false, 1, 200, 0}, /* HAUL_WATER */ {HAULERS, false, 1, 200, 0}, - /* GELD */ {AUTOMATIC, false, 1, 200, 0} + /* GELD */ {AUTOMATIC, false, 1, 200, 0}, + /* BUILD_ROAD */ {AUTOMATIC, false, 1, 200, 0}, + /* BUILD_CONSTRUCTION */ {AUTOMATIC, false, 1, 200, 0} }; static const int responsibility_penalties[] = { diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 93459e732..e767d135a 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -189,7 +189,9 @@ const SkillColumn columns[] = { {12, 4, profession::ALCHEMIST, unit_labor::ALCHEMIST, job_skill::ALCHEMY, "Al"}, {12, 4, profession::NONE, unit_labor::CLEAN, job_skill::NONE, "Cl"}, {12, 4, profession::NONE, unit_labor::PULL_LEVER, job_skill::NONE, "Lv"}, - {12, 4, profession::NONE, unit_labor::REMOVE_CONSTRUCTION, job_skill::NONE, "Co"}, + {12, 4, profession::NONE, unit_labor::BUILD_ROAD, job_skill::NONE, "Ro"}, + {12, 4, profession::NONE, unit_labor::BUILD_CONSTRUCTION, job_skill::NONE, "Co"}, + {12, 4, profession::NONE, unit_labor::REMOVE_CONSTRUCTION, job_skill::NONE, "CR"}, // Military - Weapons {13, 7, profession::WRESTLER, unit_labor::NONE, job_skill::WRESTLING, "Wr"}, {13, 7, profession::AXEMAN, unit_labor::NONE, job_skill::AXE, "Ax"}, @@ -1219,7 +1221,7 @@ void viewscreen_unitlaborsst::render() OutputString(10, x, y, Screen::getKeyDisplay(interface_key::OPTION20)); OutputString(15, x, y, ": Toggle View, "); - + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP)); OutputString(15, x, y, ": Sort by Skill, "); diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index c0143e830..4f27b504b 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -99,7 +99,7 @@ static vector get_units_at(const df::coord pos, bool only_one) { df::unit *unit = world->units.active[i]; - if(unit->pos.x == pos.x && unit->pos.y == pos.y && unit->pos.z == pos.z && + if(unit->pos.x == pos.x && unit->pos.y == pos.y && unit->pos.z == pos.z && !(unit->flags1.whole & bad_flags.whole) && unit->profession != profession::THIEF && unit->profession != profession::MASTER_THIEF) { @@ -169,7 +169,7 @@ static df::interface_key get_default_query_mode(const df::coord pos) { // For containers use item view, for everything else, query view return (type == building_type::Box || type == building_type::Cabinet || - type == building_type::Weaponrack || type == building_type::Armorstand) + type == building_type::Weaponrack || type == building_type::Armorstand) ? df::interface_key::D_BUILDITEM : df::interface_key::D_BUILDJOB; } } @@ -210,13 +210,22 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest case DesignateCarveTrack: case DesignateEngrave: case DesignateCarveFortification: + case DesignateItemsClaim: + case DesignateItemsForbid: + case DesignateItemsMelt: + case DesignateItemsUnmelt: + case DesignateItemsDump: + case DesignateItemsUndump: + case DesignateItemsHide: + case DesignateItemsUnhide: case DesignateChopTrees: case DesignateToggleEngravings: - case DesignateRemoveConstruction: + case DesignateToggleMarker: case DesignateTrafficHigh: case DesignateTrafficNormal: case DesignateTrafficLow: case DesignateTrafficRestricted: + case DesignateRemoveConstruction: return true; case Burrows: @@ -268,7 +277,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest bool isInAreaSelectionMode() { - bool selectableMode = + bool selectableMode = isInDesignationMenu() || ui->main.mode == Stockpiles || ui->main.mode == Zones; @@ -357,13 +366,13 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest enabler->mouse_lbut = 0; - // Can't check limits earlier as we must be sure we are in query or default mode + // Can't check limits earlier as we must be sure we are in query or default mode // (so we can clear the button down flag) int right_bound = (dims.menu_x1 > 0) ? dims.menu_x1 - 2 : gps->dimx - 2; if (mx < 1 || mx > right_bound || my < 1 || my > gps->dimy - 2) return false; - if (ui->main.mode == df::ui_sidebar_mode::Zones || + if (ui->main.mode == df::ui_sidebar_mode::Zones || ui->main.mode == df::ui_sidebar_mode::Stockpiles) { int32_t x, y, z; @@ -414,7 +423,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest using namespace df::enums::ui_sidebar_mode; if ((ui->main.mode == QueryBuilding || ui->main.mode == BuildingItems || - ui->main.mode == ViewUnits || ui->main.mode == LookAround) || + ui->main.mode == ViewUnits || ui->main.mode == LookAround) || (isInTrackableMode() && tracking_enabled)) { sendKey(df::interface_key::LEAVESCREEN); @@ -549,7 +558,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest mpos_valid = false; // Check if in lever binding mode - if (Gui::getFocusString(Core::getTopViewscreen()) == + if (Gui::getFocusString(Core::getTopViewscreen()) == "dwarfmode/QueryBuilding/Some/Lever/AddJob") { return; @@ -651,7 +660,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (Gui::getDesignationCoords(x, y, z)) { color = COLOR_WHITE; - if (ui->main.mode == df::ui_sidebar_mode::Zones || + if (ui->main.mode == df::ui_sidebar_mode::Zones || ui->main.mode == df::ui_sidebar_mode::Stockpiles) { auto dX = abs(x - mpos.x); @@ -670,8 +679,8 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (shouldTrack()) { - if (delta_t <= scroll_delay && (mx < scroll_buffer || - mx > dims.menu_x1 - scroll_buffer || + if (delta_t <= scroll_delay && (mx < scroll_buffer || + mx > dims.menu_x1 - scroll_buffer || my < scroll_buffer || my > gps->dimy - scroll_buffer)) { @@ -831,7 +840,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Mon, 22 Dec 2014 14:36:12 -0500 Subject: [PATCH 044/115] Add option to install newer libstdc++ on OS X Also include a bzipped copy of libstdc++.6.dylib Fixes #437 Fixes #436 Fixes #460 Fixes #462 --- library/CMakeLists.txt | 6 ++++++ package/darwin/libstdc++.6.dylib.bz2 | Bin 0 -> 351602 bytes 2 files changed, 6 insertions(+) create mode 100644 package/darwin/libstdc++.6.dylib.bz2 diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index e0ad3606b..e4de8bafc 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -306,6 +306,12 @@ IF(UNIX) DESTINATION .) install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run DESTINATION .) + OPTION(INSTALL_NEW_LIBSTDCXX "Install a version of libstdc++ from GCC 4.5.4 to fix various crashes" ON) + IF(INSTALL_NEW_LIBSTDCXX) + execute_process(COMMAND bunzip2 --keep --force ${dfhack_SOURCE_DIR}/package/darwin/libstdc++.6.dylib.bz2) + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/libstdc++.6.dylib + DESTINATION ./hack/) + ENDIF(INSTALL_NEW_LIBSTDCXX) else() # On linux, copy our version of the df launch script which sets LD_PRELOAD install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack diff --git a/package/darwin/libstdc++.6.dylib.bz2 b/package/darwin/libstdc++.6.dylib.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..6ff995c6386920a0afe2cf1f0b0614c755716fed GIT binary patch literal 351602 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

?X(_ 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 *ucqIcV{4L>ORWGLoYF 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 Jp&)ANdInetAFcoZ literal 0 HcmV?d00001 From eb8bd8294261de027a29c661b31c0eed76e46e78 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 22 Dec 2014 14:47:02 -0500 Subject: [PATCH 045/115] Add libstdc++.6.dylib to .gitignore --- package/darwin/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 package/darwin/.gitignore diff --git a/package/darwin/.gitignore b/package/darwin/.gitignore new file mode 100644 index 000000000..3ffea3207 --- /dev/null +++ b/package/darwin/.gitignore @@ -0,0 +1 @@ +libstdc++.6.dylib From 5547722414617d3780aae08c902e6a2d87009f2a Mon Sep 17 00:00:00 2001 From: Timothy Collett Date: Tue, 23 Dec 2014 16:56:15 -0500 Subject: [PATCH 046/115] Initial work on fortplan plugin, including separating out code that needs to be shared with buildingplan --- library/include/Hooks.h | 1 + plugins/CMakeLists.txt | 3 +- plugins/buildingplan-lib.cpp | 658 ++++++++++++++++++++ plugins/buildingplan-lib.h | 495 +++++++++++++++ plugins/buildingplan.cpp | 1111 +--------------------------------- plugins/fortplan.cpp | 188 ++++++ plugins/uicommon.h | 16 +- 7 files changed, 1354 insertions(+), 1118 deletions(-) create mode 100644 plugins/buildingplan-lib.cpp create mode 100644 plugins/buildingplan-lib.h create mode 100644 plugins/fortplan.cpp diff --git a/library/include/Hooks.h b/library/include/Hooks.h index 83325c031..067107fce 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -47,6 +47,7 @@ namespace SDL // these functions are here because they call into DFHack::Core and therefore need to // be declared as friend functions/known #ifdef _DARWIN +#include "modules/Graphic.h" DFhackCExport int DFH_SDL_NumJoysticks(void); DFhackCExport void DFH_SDL_Quit(void); DFhackCExport int DFH_SDL_PollEvent(SDL::Event* event); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 3cec373e7..f2f64c49d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -93,7 +93,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(autotrade autotrade.cpp) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua) - DFHACK_PLUGIN(buildingplan buildingplan.cpp) + DFHACK_PLUGIN(buildingplan buildingplan-lib.cpp buildingplan.cpp) DFHACK_PLUGIN(catsplosion catsplosion.cpp) DFHACK_PLUGIN(changeitem changeitem.cpp) DFHACK_PLUGIN(changelayer changelayer.cpp) @@ -122,6 +122,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(flows flows.cpp) DFHACK_PLUGIN(follow follow.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp) + DFHACK_PLUGIN(fortplan buildingplan-lib.cpp fortplan.cpp) DFHACK_PLUGIN(getplants getplants.cpp) DFHACK_PLUGIN(hotkeys hotkeys.cpp) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp new file mode 100644 index 000000000..949571cf4 --- /dev/null +++ b/plugins/buildingplan-lib.cpp @@ -0,0 +1,658 @@ +#include "buildingplan-lib.h" + +#define PLUGIN_VERSION 0.00 +static void debug(const string &msg) +{ + if (!show_debugging) + return; + + color_ostream_proxy out(Core::getInstance().getConsole()); + out << "DEBUG (" << PLUGIN_VERSION << "): " << msg << endl; +} + +/* + * Material Choice Screen + */ + +static std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } + +bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) +{ + return (mat_mask.whole) ? mat.matches(mat_mask) : true; +} + +bool ItemFilter::matches(const df::dfhack_material_category mask) const +{ + return mask.whole & mat_mask.whole; +} + +bool ItemFilter::matches(DFHack::MaterialInfo &material) const +{ + for (auto it = materials.begin(); it != materials.end(); ++it) + if (material.matches(*it)) + return true; + return false; +} + +bool ItemFilter::matches(df::item *item) +{ + if (item->getQuality() < min_quality) + return false; + + if (decorated_only && !item->hasImprovements()) + return false; + + auto imattype = item->getActualMaterial(); + auto imatindex = item->getActualMaterialIndex(); + auto item_mat = DFHack::MaterialInfo(imattype, imatindex); + + return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); +} + +std::vector ItemFilter::getMaterialFilterAsVector() +{ + std::vector descriptions; + + transform_(materials, descriptions, material_to_string_fn); + + if (descriptions.size() == 0) + bitfield_to_string(&descriptions, mat_mask); + + if (descriptions.size() == 0) + descriptions.push_back("any"); + + return descriptions; +} + +std::string ItemFilter::getMaterialFilterAsSerial() +{ + std::string str; + + str.append(bitfield_to_string(mat_mask, ",")); + str.append("/"); + if (materials.size() > 0) + { + for (size_t i = 0; i < materials.size(); i++) + str.append(materials[i].getToken() + ","); + + if (str[str.size()-1] == ',') + str.resize(str.size () - 1); + } + + return str; +} + +bool ItemFilter::parseSerializedMaterialTokens(std::string str) +{ + valid = false; + std::vector tokens; + split_string(&tokens, str, "/"); + + if (tokens.size() > 0 && !tokens[0].empty()) + { + if (!parseJobMaterialCategory(&mat_mask, tokens[0])) + return false; + } + + if (tokens.size() > 1 && !tokens[1].empty()) + { + std::vector mat_names; + split_string(&mat_names, tokens[1], ","); + for (auto m = mat_names.begin(); m != mat_names.end(); m++) + { + DFHack::MaterialInfo material; + if (!material.find(*m) || !material.isValid()) + return false; + + materials.push_back(material); + } + } + + valid = true; + return true; +} + +std::string ItemFilter::getMinQuality() +{ + return ENUM_KEY_STR(item_quality, min_quality); +} + +bool ItemFilter::isValid() +{ + return valid; +} + +void ItemFilter::clear() +{ + mat_mask.whole = 0; + materials.clear(); +} + +static DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } + +ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) +{ + selected_column = 0; + masks_column.setTitle("Type"); + masks_column.multiselect = true; + masks_column.allow_search = false; + masks_column.left_margin = 2; + materials_column.left_margin = MAX_MASK + 3; + materials_column.setTitle("Material"); + materials_column.multiselect = true; + this->filter = filter; + + masks_column.changeHighlight(0); + + populateMasks(); + populateMaterials(); + + masks_column.selectDefaultEntry(); + materials_column.selectDefaultEntry(); + materials_column.changeHighlight(0); +} + +void ViewscreenChooseMaterial::feed(set *input) +{ + bool key_processed = false; + switch (selected_column) + { + case 0: + key_processed = masks_column.feed(input); + if (input->count(interface_key::SELECT)) + populateMaterials(); // Redo materials lists based on category selection + break; + case 1: + key_processed = materials_column.feed(input); + break; + } + + if (key_processed) + return; + + if (input->count(interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + } + if (input->count(interface_key::CUSTOM_SHIFT_C)) + { + filter->clear(); + masks_column.clearSelection(); + materials_column.clearSelection(); + populateMaterials(); + } + else if (input->count(interface_key::SEC_SELECT)) + { + // Convert list selections to material filters + + + filter->mat_mask.whole = 0; + filter->materials.clear(); + + // Category masks + auto masks = masks_column.getSelectedElems(); + for (auto it = masks.begin(); it != masks.end(); ++it) + filter->mat_mask.whole |= it->whole; + + // Specific materials + auto materials = materials_column.getSelectedElems(); + transform_(materials, filter->materials, material_info_identity_fn); + + Screen::dismiss(this); + } + else if (input->count(interface_key::CURSOR_LEFT)) + { + --selected_column; + validateColumn(); + } + else if (input->count(interface_key::CURSOR_RIGHT)) + { + selected_column++; + validateColumn(); + } + else if (enabler->tracking_on && enabler->mouse_lbut) + { + if (masks_column.setHighlightByMouse()) + selected_column = 0; + else if (materials_column.setHighlightByMouse()) + selected_column = 1; + + enabler->mouse_lbut = enabler->mouse_rbut = 0; + } +} + +void ViewscreenChooseMaterial::render() +{ + if (Screen::isDismissed(this)) + return; + + dfhack_viewscreen::render(); + + Screen::clear(); + Screen::drawBorder(" Building Material "); + + masks_column.display(selected_column == 0); + materials_column.display(selected_column == 1); + + int32_t y = gps->dimy - 3; + int32_t x = 2; + OutputHotkeyString(x, y, "Toggle", "Enter"); + x += 3; + OutputHotkeyString(x, y, "Save", "Shift-Enter"); + x += 3; + OutputHotkeyString(x, y, "Clear", "C"); + x += 3; + OutputHotkeyString(x, y, "Cancel", "Esc"); +} + +// START Room Reservation +ReservedRoom::ReservedRoom(df::building *building, std::string noble_code) +{ + this->building = building; + config = DFHack::World::AddPersistentData("buildingplan/reservedroom"); + config.val() = noble_code; + config.ival(1) = building->id; + pos = df::coord(building->centerx, building->centery, building->z); +} + +ReservedRoom::ReservedRoom(PersistentDataItem &config, color_ostream &out) +{ + this->config = config; + + building = df::building::find(config.ival(1)); + if (!building) + return; + pos = df::coord(building->centerx, building->centery, building->z); +} + +bool ReservedRoom::checkRoomAssignment() +{ + if (!isValid()) + return false; + + auto np = getOwnersNobleCode(); + bool correctOwner = false; + for (auto iter = np.begin(); iter != np.end(); iter++) + { + if (iter->position->code == getCode()) + { + correctOwner = true; + break; + } + } + + if (correctOwner) + return true; + + for (auto iter = world->units.active.begin(); iter != world->units.active.end(); iter++) + { + df::unit* unit = *iter; + if (!Units::isCitizen(unit)) + continue; + + if (DFHack::Units::isDead(unit)) + continue; + + np = getUniqueNoblePositions(unit); + for (auto iter = np.begin(); iter != np.end(); iter++) + { + if (iter->position->code == getCode()) + { + Buildings::setOwner(building, unit); + break; + } + } + } + + return true; +} + +std::string RoomMonitor::getReservedNobleCode(int32_t buildingId) +{ + for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) + { + if (buildingId == iter->getId()) + return iter->getCode(); + } + + return ""; +} + +void RoomMonitor::toggleRoomForPosition(int32_t buildingId, std::string noble_code) +{ + bool found = false; + for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) + { + if (buildingId != iter->getId()) + { + continue; + } + else + { + if (noble_code == iter->getCode()) + { + iter->remove(); + reservedRooms.erase(iter); + } + else + { + iter->setCode(noble_code); + } + found = true; + break; + } + } + + if (!found) + { + ReservedRoom room(df::building::find(buildingId), noble_code); + reservedRooms.push_back(room); + } +} + +void RoomMonitor::doCycle() +{ + for (auto iter = reservedRooms.begin(); iter != reservedRooms.end();) + { + if (iter->checkRoomAssignment()) + { + ++iter; + } + else + { + iter->remove(); + iter = reservedRooms.erase(iter); + } + } +} + +void RoomMonitor::reset(color_ostream &out) +{ + reservedRooms.clear(); + std::vector items; + DFHack::World::GetPersistentData(&items, "buildingplan/reservedroom"); + + for (auto i = items.begin(); i != items.end(); i++) + { + ReservedRoom rr(*i, out); + if (rr.isValid()) + addRoom(rr); + } +} + + +static void delete_item_fn(df::job_item *x) { delete x; } + +// START Planning + +PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter) +{ + this->building = building; + this->filter = *filter; + pos = df::coord(building->centerx, building->centery, building->z); + config = DFHack::World::AddPersistentData("buildingplan/constraints"); + config.val() = filter->getMaterialFilterAsSerial(); + config.ival(1) = building->id; + config.ival(2) = filter->min_quality + 1; + config.ival(3) = static_cast(filter->decorated_only) + 1; +} + +PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out) +{ + this->config = config; + + if (!filter.parseSerializedMaterialTokens(config.val())) + { + out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str()); + return; + } + + building = df::building::find(config.ival(1)); + if (!building) + return; + + pos = df::coord(building->centerx, building->centery, building->z); + filter.min_quality = static_cast(config.ival(2) - 1); + filter.decorated_only = config.ival(3) - 1; +} + +bool PlannedBuilding::assignClosestItem(std::vector *items_vector) +{ + decltype(items_vector->begin()) closest_item; + int32_t closest_distance = -1; + for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++) + { + auto item = *item_iter; + if (!filter.matches(item)) + continue; + + auto pos = item->pos; + auto distance = abs(pos.x - building->centerx) + + abs(pos.y - building->centery) + + abs(pos.z - building->z) * 50; + + if (closest_distance > -1 && distance >= closest_distance) + continue; + + closest_distance = distance; + closest_item = item_iter; + } + + if (closest_distance > -1 && assignItem(*closest_item)) + { + debug("Item assigned"); + items_vector->erase(closest_item); + remove(); + return true; + } + + return false; +} + +bool PlannedBuilding::assignItem(df::item *item) +{ + auto ref = df::allocate(); + if (!ref) + { + Core::printerr("Could not allocate general_ref_building_holderst\n"); + return false; + } + + ref->building_id = building->id; + + if (building->jobs.size() != 1) + return false; + + auto job = building->jobs[0]; + + for_each_(job->job_items, delete_item_fn); + job->job_items.clear(); + job->flags.bits.suspend = false; + + bool rough = false; + Job::attachJobItem(job, item, df::job_item_ref::Hauled); + if (item->getType() == item_type::BOULDER) + rough = true; + building->mat_type = item->getMaterial(); + building->mat_index = item->getMaterialIndex(); + + job->mat_type = building->mat_type; + job->mat_index = building->mat_index; + + if (building->needsDesign()) + { + auto act = (df::building_actual *) building; + act->design = new df::building_design(); + act->design->flags.bits.rough = rough; + } + + return true; +} + +bool PlannedBuilding::isValid() +{ + bool valid = filter.isValid() && + building && Buildings::findAtTile(pos) == building && + building->getBuildStage() == 0; + + if (!valid) + remove(); + + return valid; +} + +void Planner::reset(color_ostream &out) +{ + planned_buildings.clear(); + std::vector items; + DFHack::World::GetPersistentData(&items, "buildingplan/constraints"); + + for (auto i = items.begin(); i != items.end(); i++) + { + PlannedBuilding pb(*i, out); + if (pb.isValid()) + planned_buildings.push_back(pb); + } +} + +void Planner::initialize() +{ + std::vector item_names; + typedef df::enum_traits item_types; + int size = item_types::last_item_value - item_types::first_item_value+1; + for (size_t i = 1; i < size; i++) + { + is_relevant_item_type[(df::item_type) (i-1)] = false; + std::string item_name = toLower(item_types::key_table[i]); + std::string item_name_clean; + for (auto c = item_name.begin(); c != item_name.end(); c++) + { + if (*c == '_') + continue; + item_name_clean += *c; + } + item_names.push_back(item_name_clean); + } + + typedef df::enum_traits building_types; + size = building_types::last_item_value - building_types::first_item_value+1; + for (size_t i = 1; i < size; i++) + { + auto building_type = (df::building_type) (i-1); + if (building_type == building_type::Weapon || building_type == building_type::Floodgate) + continue; + + std::string building_name = toLower(building_types::key_table[i]); + for (size_t j = 0; j < item_names.size(); j++) + { + if (building_name == item_names[j]) + { + auto btype = (df::building_type) (i-1); + auto itype = (df::item_type) j; + + item_for_building_type[btype] = itype; + default_item_filters[btype] = ItemFilter(); + available_item_vectors[itype] = std::vector(); + is_relevant_item_type[itype] = true; + + if (planmode_enabled.find(btype) == planmode_enabled.end()) + { + planmode_enabled[btype] = false; + } + } + } + } +} + +void Planner::doCycle() +{ + debug("Running Cycle"); + if (planned_buildings.size() == 0) + return; + + debug("Planned count: " + int_to_string(planned_buildings.size())); + + gather_available_items(); + for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) + { + if (building_iter->isValid()) + { + if (show_debugging) + debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType())); + + auto required_item_type = item_for_building_type[building_iter->getType()]; + auto items_vector = &available_item_vectors[required_item_type]; + if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector)) + { + debug("Unable to allocate an item"); + ++building_iter; + continue; + } + } + debug("Removing building plan"); + building_iter = planned_buildings.erase(building_iter); + } +} + +bool Planner::allocatePlannedBuilding(df::building_type type) +{ + coord32_t cursor; + if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) + return false; + + auto newinst = Buildings::allocInstance(cursor.get_coord16(), type); + if (!newinst) + return false; + + df::job_item *filter = new df::job_item(); + filter->item_type = item_type::NONE; + filter->mat_index = 0; + filter->flags2.bits.building_material = true; + std::vector filters; + filters.push_back(filter); + + if (!Buildings::constructWithFilters(newinst, filters)) + { + delete newinst; + return false; + } + + for (auto iter = newinst->jobs.begin(); iter != newinst->jobs.end(); iter++) + { + (*iter)->flags.bits.suspend = true; + } + + if (type == building_type::Door) + { + auto door = virtual_cast(newinst); + if (door) + door->door_flags.bits.pet_passable = true; + } + + addPlannedBuilding(newinst); + + return true; +} + +PlannedBuilding *Planner::getSelectedPlannedBuilding() +{ + for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++) + { + if (building_iter->isCurrentlySelectedBuilding()) + { + return &(*building_iter); + } + } + + return nullptr; +} + +void Planner::cycleDefaultQuality(df::building_type type) +{ + auto quality = &getDefaultItemFilterForType(type)->min_quality; + *quality = static_cast(*quality + 1); + if (*quality == item_quality::Artifact) + (*quality) = item_quality::Ordinary; +} \ No newline at end of file diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h new file mode 100644 index 000000000..48f0a8d7b --- /dev/null +++ b/plugins/buildingplan-lib.h @@ -0,0 +1,495 @@ +#ifndef BUILDINGPLAN_H +#define BUILDINGPLAN_H + +#include "uicommon.h" + +#include + +// DF data structure definition headers +#include "DataDefs.h" +#include "Types.h" +#include "df/build_req_choice_genst.h" +#include "df/build_req_choice_specst.h" +#include "df/item.h" +#include "df/ui.h" +#include "df/ui_build_selector.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/items_other_id.h" +#include "df/job.h" +#include "df/world.h" +#include "df/building_constructionst.h" +#include "df/building_design.h" +#include "df/entity_position.h" + +#include "modules/Buildings.h" +#include "modules/Maps.h" +#include "modules/Items.h" +#include "modules/Units.h" +#include "modules/Gui.h" + +#include "TileTypes.h" +#include "df/job_item.h" +#include "df/dfhack_material_category.h" +#include "df/general_ref_building_holderst.h" +#include "modules/Job.h" +#include "df/building_design.h" +#include "df/buildings_other_id.h" +#include "modules/World.h" +#include "df/building.h" +#include "df/building_doorst.h" + +using df::global::ui; +using df::global::ui_build_selector; +using df::global::world; + +struct MaterialDescriptor +{ + df::item_type item_type; + int16_t item_subtype; + int16_t type; + int32_t index; + bool valid; + + bool matches(const MaterialDescriptor &a) const + { + return a.valid && valid && + a.type == type && + a.index == index && + a.item_type == item_type && + a.item_subtype == item_subtype; + } +}; + +#define MAX_MASK 10 +#define MAX_MATERIAL 21 +#define SIDEBAR_WIDTH 30 + +static bool canReserveRoom(df::building *building) +{ + if (!building) + return false; + + if (building->jobs.size() > 0 && building->jobs[0]->job_type == job_type::DestroyBuilding) + return false; + + return building->is_room; +} + +static std::vector getUniqueNoblePositions(df::unit *unit) +{ + std::vector np; + Units::getNoblePositions(&np, unit); + for (auto iter = np.begin(); iter != np.end(); iter++) + { + if (iter->position->code == "MILITIA_CAPTAIN") + { + np.erase(iter); + break; + } + } + + return np; +} + +static void delete_item_fn(df::job_item *x); + +static MaterialInfo &material_info_identity_fn(MaterialInfo &m); + +static map planmode_enabled, saved_planmodes; + +static void enable_quickfort_fn(pair& pair); + +static void debug(const std::string &msg); +static std::string material_to_string_fn(MaterialInfo m); + +static bool show_debugging = true; +static bool show_help = false; + +struct ItemFilter +{ + df::dfhack_material_category mat_mask; + std::vector materials; + df::item_quality min_quality; + bool decorated_only; + + ItemFilter() : min_quality(df::item_quality::Ordinary), decorated_only(false), valid(true) + { } + + bool matchesMask(DFHack::MaterialInfo &mat); + + bool matches(const df::dfhack_material_category mask) const; + + bool matches(DFHack::MaterialInfo &material) const; + + bool matches(df::item *item); + + std::vector getMaterialFilterAsVector(); + + std::string getMaterialFilterAsSerial(); + + bool parseSerializedMaterialTokens(std::string str); + + std::string getMinQuality(); + + bool isValid(); + + void clear(); + +private: + bool valid; +}; + +class ViewscreenChooseMaterial : public dfhack_viewscreen +{ +public: + ViewscreenChooseMaterial(ItemFilter *filter); + + void feed(set *input); + + void render(); + + std::string getFocusString() { return "buildingplan_choosemat"; } + +private: + ListColumn masks_column; + ListColumn materials_column; + int selected_column; + ItemFilter *filter; + + df::building_type btype; + + void addMaskEntry(df::dfhack_material_category &mask, const std::string &text) + { + auto entry = ListEntry(pad_string(text, MAX_MASK, false), mask); + if (filter->matches(mask)) + entry.selected = true; + + masks_column.add(entry); + } + + void populateMasks() + { + masks_column.clear(); + df::dfhack_material_category mask; + + mask.whole = 0; + mask.bits.stone = true; + addMaskEntry(mask, "Stone"); + + mask.whole = 0; + mask.bits.wood = true; + addMaskEntry(mask, "Wood"); + + mask.whole = 0; + mask.bits.metal = true; + addMaskEntry(mask, "Metal"); + + mask.whole = 0; + mask.bits.soap = true; + addMaskEntry(mask, "Soap"); + + masks_column.filterDisplay(); + } + + void populateMaterials() + { + materials_column.clear(); + df::dfhack_material_category selected_category; + std::vector selected_masks = masks_column.getSelectedElems(); + if (selected_masks.size() == 1) + selected_category = selected_masks[0]; + else if (selected_masks.size() > 1) + return; + + df::world_raws &raws = world->raws; + for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; i++) + { + auto obj = raws.mat_table.builtin[i]; + if (obj) + { + MaterialInfo material; + material.decode(i, -1); + addMaterialEntry(selected_category, material, material.toString()); + } + } + + for (size_t i = 0; i < raws.inorganics.size(); i++) + { + df::inorganic_raw *p = raws.inorganics[i]; + MaterialInfo material; + material.decode(0, i); + addMaterialEntry(selected_category, material, material.toString()); + } + + decltype(selected_category) wood_flag; + wood_flag.bits.wood = true; + if (!selected_category.whole || selected_category.bits.wood) + { + for (size_t i = 0; i < raws.plants.all.size(); i++) + { + df::plant_raw *p = raws.plants.all[i]; + for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++) + { + auto t = p->material[j]; + if (p->material[j]->id != "WOOD") + continue; + + MaterialInfo material; + material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i); + auto name = material.toString(); + ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); + if (filter->matches(material)) + entry.selected = true; + + materials_column.add(entry); + } + } + } + materials_column.sort(); + } + + void addMaterialEntry(df::dfhack_material_category &selected_category, + MaterialInfo &material, std::string name) + { + if (!selected_category.whole || material.matches(selected_category)) + { + ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); + if (filter->matches(material)) + entry.selected = true; + + materials_column.add(entry); + } + } + + void validateColumn() + { + set_to_limit(selected_column, 1); + } + + void resize(int32_t x, int32_t y) + { + dfhack_viewscreen::resize(x, y); + masks_column.resize(); + materials_column.resize(); + } +}; + +class ReservedRoom +{ +public: + ReservedRoom(df::building *building, std::string noble_code); + + ReservedRoom(PersistentDataItem &config, color_ostream &out); + + bool checkRoomAssignment(); + + void remove() { DFHack::World::DeletePersistentData(config); } + + bool isValid() + { + if (!building) + return false; + + if (Buildings::findAtTile(pos) != building) + return false; + + return canReserveRoom(building); + } + + int32_t getId() + { + if (!isValid()) + return 0; + + return building->id; + } + + std::string getCode() { return config.val(); } + + void setCode(const std::string &noble_code) { config.val() = noble_code; } + +private: + df::building *building; + PersistentDataItem config; + df::coord pos; + + std::vector getOwnersNobleCode() + { + if (!building->owner) + return std::vector (); + + return getUniqueNoblePositions(building->owner); + } +}; + +class RoomMonitor +{ +public: + RoomMonitor() { } + + std::string getReservedNobleCode(int32_t buildingId); + + void toggleRoomForPosition(int32_t buildingId, std::string noble_code); + + void doCycle(); + + void reset(color_ostream &out); + +private: + std::vector reservedRooms; + + void addRoom(ReservedRoom &rr) + { + for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) + { + if (iter->getId() == rr.getId()) + return; + } + + reservedRooms.push_back(rr); + } +}; + +// START Planning +class PlannedBuilding +{ +public: + PlannedBuilding(df::building *building, ItemFilter *filter); + + PlannedBuilding(PersistentDataItem &config, color_ostream &out); + + bool assignClosestItem(std::vector *items_vector); + + bool assignItem(df::item *item); + + bool isValid(); + + df::building_type getType() { return building->getType(); } + + bool isCurrentlySelectedBuilding() { return isValid() && (building == world->selected_building); } + + ItemFilter *getFilter() { return &filter; } + + void remove() { DFHack::World::DeletePersistentData(config); } + +private: + df::building *building; + PersistentDataItem config; + df::coord pos; + ItemFilter filter; +}; + +class Planner +{ +public: + bool in_dummmy_screen; + + Planner() : quickfort_mode(false), in_dummmy_screen(false) { } + + bool isPlanableBuilding(const df::building_type type) const + { + return item_for_building_type.find(type) != item_for_building_type.end(); + } + + void reset(color_ostream &out); + + void initialize(); + + void addPlannedBuilding(df::building *bld) + { + PlannedBuilding pb(bld, &default_item_filters[bld->getType()]); + planned_buildings.push_back(pb); + } + + void doCycle(); + + bool allocatePlannedBuilding(df::building_type type); + + PlannedBuilding *getSelectedPlannedBuilding(); + + void removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); } + + ItemFilter *getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; } + + void cycleDefaultQuality(df::building_type type); + + void enableQuickfortMode() + { + saved_planmodes = planmode_enabled; + for_each_(planmode_enabled, enable_quickfort_fn); + + quickfort_mode = true; + } + + void disableQuickfortMode() + { + planmode_enabled = saved_planmodes; + quickfort_mode = false; + } + + bool inQuickFortMode() { return quickfort_mode; } + +private: + map item_for_building_type; + map default_item_filters; + map> available_item_vectors; + map is_relevant_item_type; //Needed for fast check when looping over all items + bool quickfort_mode; + + std::vector planned_buildings; + + void gather_available_items() + { + debug("Gather available items"); + for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++) + { + iter->second.clear(); + } + + // Precompute a bitmask with the bad flags + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); +#undef F + + std::vector &items = world->items.other[items_other_id::IN_PLAY]; + + for (size_t i = 0; i < items.size(); i++) + { + df::item *item = items[i]; + + if (item->flags.whole & bad_flags.whole) + continue; + + df::item_type itype = item->getType(); + if (!is_relevant_item_type[itype]) + continue; + + if (itype == item_type::BOX && item->isBag()) + continue; //Skip bags + + if (item->flags.bits.artifact) + continue; + + if (item->flags.bits.in_job || + item->isAssignedToStockpile() || + item->flags.bits.owned || + item->flags.bits.in_chest) + { + continue; + } + + available_item_vectors[itype].push_back(item); + } + } +}; + +static Planner planner; + +static RoomMonitor roomMonitor; + +#endif \ No newline at end of file diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index d916ba2e3..0e72c0755 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,1124 +1,15 @@ -#include "uicommon.h" - -#include - -// DF data structure definition headers -#include "DataDefs.h" -#include "Types.h" -#include "df/build_req_choice_genst.h" -#include "df/build_req_choice_specst.h" -#include "df/item.h" -#include "df/ui.h" -#include "df/ui_build_selector.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/items_other_id.h" -#include "df/job.h" -#include "df/world.h" -#include "df/building_constructionst.h" -#include "df/building_design.h" -#include "df/entity_position.h" - -#include "modules/Gui.h" -#include "modules/Buildings.h" -#include "modules/Maps.h" -#include "modules/Items.h" -#include "modules/Units.h" - -#include "TileTypes.h" -#include "df/job_item.h" -#include "df/dfhack_material_category.h" -#include "df/general_ref_building_holderst.h" -#include "modules/Job.h" -#include "df/building_design.h" -#include "df/buildings_other_id.h" -#include "modules/World.h" -#include "df/building.h" -#include "df/building_doorst.h" - -using df::global::ui; -using df::global::ui_build_selector; -using df::global::world; +#include "buildingplan-lib.h" DFHACK_PLUGIN("buildingplan"); #define PLUGIN_VERSION 0.14 -struct MaterialDescriptor -{ - df::item_type item_type; - int16_t item_subtype; - int16_t type; - int32_t index; - bool valid; - - bool matches(const MaterialDescriptor &a) const - { - return a.valid && valid && - a.type == type && - a.index == index && - a.item_type == item_type && - a.item_subtype == item_subtype; - } -}; - DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; } -#define MAX_MASK 10 -#define MAX_MATERIAL 21 -#define SIDEBAR_WIDTH 30 - -static bool show_debugging = false; -static bool show_help = false; - -static void debug(const string &msg) -{ - if (!show_debugging) - return; - - color_ostream_proxy out(Core::getInstance().getConsole()); - out << "DEBUG (buildingplan): " << msg << endl; -} - -/* - * Material Choice Screen - */ - -static string material_to_string_fn(MaterialInfo m) { return m.toString(); } - -struct ItemFilter -{ - df::dfhack_material_category mat_mask; - vector materials; - df::item_quality min_quality; - bool decorated_only; - - ItemFilter() : min_quality(item_quality::Ordinary), decorated_only(false), valid(true) - { } - - bool matchesMask(MaterialInfo &mat) - { - return (mat_mask.whole) ? mat.matches(mat_mask) : true; - } - - bool matches(const df::dfhack_material_category mask) const - { - return mask.whole & mat_mask.whole; - } - - bool matches(MaterialInfo &material) const - { - for (auto it = materials.begin(); it != materials.end(); ++it) - if (material.matches(*it)) - return true; - return false; - } - - bool matches(df::item *item) - { - if (item->getQuality() < min_quality) - return false; - - if (decorated_only && !item->hasImprovements()) - return false; - - auto imattype = item->getActualMaterial(); - auto imatindex = item->getActualMaterialIndex(); - auto item_mat = MaterialInfo(imattype, imatindex); - - return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); - } - - vector getMaterialFilterAsVector() - { - vector descriptions; - - transform_(materials, descriptions, material_to_string_fn); - - if (descriptions.size() == 0) - bitfield_to_string(&descriptions, mat_mask); - - if (descriptions.size() == 0) - descriptions.push_back("any"); - - return descriptions; - } - - string getMaterialFilterAsSerial() - { - string str; - - str.append(bitfield_to_string(mat_mask, ",")); - str.append("/"); - if (materials.size() > 0) - { - for (size_t i = 0; i < materials.size(); i++) - str.append(materials[i].getToken() + ","); - - if (str[str.size()-1] == ',') - str.resize(str.size () - 1); - } - - return str; - } - - bool parseSerializedMaterialTokens(string str) - { - valid = false; - vector tokens; - split_string(&tokens, str, "/"); - - if (tokens.size() > 0 && !tokens[0].empty()) - { - if (!parseJobMaterialCategory(&mat_mask, tokens[0])) - return false; - } - - if (tokens.size() > 1 && !tokens[1].empty()) - { - vector mat_names; - split_string(&mat_names, tokens[1], ","); - for (auto m = mat_names.begin(); m != mat_names.end(); m++) - { - MaterialInfo material; - if (!material.find(*m) || !material.isValid()) - return false; - - materials.push_back(material); - } - } - - valid = true; - return true; - } - - string getMinQuality() - { - return ENUM_KEY_STR(item_quality, min_quality); - } - - bool isValid() - { - return valid; - } - - void clear() - { - mat_mask.whole = 0; - materials.clear(); - } - -private: - bool valid; -}; - -static MaterialInfo &material_info_identity_fn(MaterialInfo &m) { return m; } - -class ViewscreenChooseMaterial : public dfhack_viewscreen -{ -public: - ViewscreenChooseMaterial(ItemFilter *filter) - { - selected_column = 0; - masks_column.setTitle("Type"); - masks_column.multiselect = true; - masks_column.allow_search = false; - masks_column.left_margin = 2; - materials_column.left_margin = MAX_MASK + 3; - materials_column.setTitle("Material"); - materials_column.multiselect = true; - this->filter = filter; - - masks_column.changeHighlight(0); - - populateMasks(); - populateMaterials(); - - masks_column.selectDefaultEntry(); - materials_column.selectDefaultEntry(); - materials_column.changeHighlight(0); - } - - void feed(set *input) - { - bool key_processed = false; - switch (selected_column) - { - case 0: - key_processed = masks_column.feed(input); - if (input->count(interface_key::SELECT)) - populateMaterials(); // Redo materials lists based on category selection - break; - case 1: - key_processed = materials_column.feed(input); - break; - } - - if (key_processed) - return; - - if (input->count(interface_key::LEAVESCREEN)) - { - input->clear(); - Screen::dismiss(this); - return; - } - if (input->count(interface_key::CUSTOM_SHIFT_C)) - { - filter->clear(); - masks_column.clearSelection(); - materials_column.clearSelection(); - populateMaterials(); - } - else if (input->count(interface_key::SEC_SELECT)) - { - // Convert list selections to material filters - - - filter->mat_mask.whole = 0; - filter->materials.clear(); - - // Category masks - auto masks = masks_column.getSelectedElems(); - for (auto it = masks.begin(); it != masks.end(); ++it) - filter->mat_mask.whole |= it->whole; - - // Specific materials - auto materials = materials_column.getSelectedElems(); - transform_(materials, filter->materials, material_info_identity_fn); - - Screen::dismiss(this); - } - else if (input->count(interface_key::CURSOR_LEFT)) - { - --selected_column; - validateColumn(); - } - else if (input->count(interface_key::CURSOR_RIGHT)) - { - selected_column++; - validateColumn(); - } - else if (enabler->tracking_on && enabler->mouse_lbut) - { - if (masks_column.setHighlightByMouse()) - selected_column = 0; - else if (materials_column.setHighlightByMouse()) - selected_column = 1; - - enabler->mouse_lbut = enabler->mouse_rbut = 0; - } - } - - void render() - { - if (Screen::isDismissed(this)) - return; - - dfhack_viewscreen::render(); - - Screen::clear(); - Screen::drawBorder(" Building Material "); - - masks_column.display(selected_column == 0); - materials_column.display(selected_column == 1); - - int32_t y = gps->dimy - 3; - int32_t x = 2; - OutputHotkeyString(x, y, "Toggle", "Enter"); - x += 3; - OutputHotkeyString(x, y, "Save", "Shift-Enter"); - x += 3; - OutputHotkeyString(x, y, "Clear", "C"); - x += 3; - OutputHotkeyString(x, y, "Cancel", "Esc"); - } - - std::string getFocusString() { return "buildingplan_choosemat"; } - -private: - ListColumn masks_column; - ListColumn materials_column; - int selected_column; - ItemFilter *filter; - - df::building_type btype; - - void addMaskEntry(df::dfhack_material_category &mask, const string &text) - { - auto entry = ListEntry(pad_string(text, MAX_MASK, false), mask); - if (filter->matches(mask)) - entry.selected = true; - - masks_column.add(entry); - } - - void populateMasks() - { - masks_column.clear(); - df::dfhack_material_category mask; - - mask.whole = 0; - mask.bits.stone = true; - addMaskEntry(mask, "Stone"); - - mask.whole = 0; - mask.bits.wood = true; - addMaskEntry(mask, "Wood"); - - mask.whole = 0; - mask.bits.metal = true; - addMaskEntry(mask, "Metal"); - - mask.whole = 0; - mask.bits.soap = true; - addMaskEntry(mask, "Soap"); - - masks_column.filterDisplay(); - } - - void populateMaterials() - { - materials_column.clear(); - df::dfhack_material_category selected_category; - vector selected_masks = masks_column.getSelectedElems(); - if (selected_masks.size() == 1) - selected_category = selected_masks[0]; - else if (selected_masks.size() > 1) - return; - - df::world_raws &raws = world->raws; - for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; i++) - { - auto obj = raws.mat_table.builtin[i]; - if (obj) - { - MaterialInfo material; - material.decode(i, -1); - addMaterialEntry(selected_category, material, material.toString()); - } - } - - for (size_t i = 0; i < raws.inorganics.size(); i++) - { - df::inorganic_raw *p = raws.inorganics[i]; - MaterialInfo material; - material.decode(0, i); - addMaterialEntry(selected_category, material, material.toString()); - } - - decltype(selected_category) wood_flag; - wood_flag.bits.wood = true; - if (!selected_category.whole || selected_category.bits.wood) - { - for (size_t i = 0; i < raws.plants.all.size(); i++) - { - df::plant_raw *p = raws.plants.all[i]; - for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++) - { - auto t = p->material[j]; - if (p->material[j]->id != "WOOD") - continue; - - MaterialInfo material; - material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i); - auto name = material.toString(); - ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); - if (filter->matches(material)) - entry.selected = true; - - materials_column.add(entry); - } - } - } - materials_column.sort(); - } - - void addMaterialEntry(df::dfhack_material_category &selected_category, - MaterialInfo &material, string name) - { - if (!selected_category.whole || material.matches(selected_category)) - { - ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); - if (filter->matches(material)) - entry.selected = true; - - materials_column.add(entry); - } - } - - void validateColumn() - { - set_to_limit(selected_column, 1); - } - - void resize(int32_t x, int32_t y) - { - dfhack_viewscreen::resize(x, y); - masks_column.resize(); - materials_column.resize(); - } -}; - -static void delete_item_fn(df::job_item *x) { delete x; } - -// START Room Reservation -static bool canReserveRoom(df::building *building) -{ - if (!building) - return false; - - if (building->jobs.size() > 0 && building->jobs[0]->job_type == job_type::DestroyBuilding) - return false; - - return building->is_room; -} - -static std::vector getUniqueNoblePositions(df::unit *unit) -{ - std::vector np; - Units::getNoblePositions(&np, unit); - for (auto iter = np.begin(); iter != np.end(); iter++) - { - if (iter->position->code == "MILITIA_CAPTAIN") - { - np.erase(iter); - break; - } - } - - return np; -} - -class ReservedRoom -{ -public: - ReservedRoom(df::building *building, string noble_code) - { - this->building = building; - config = DFHack::World::AddPersistentData("buildingplan/reservedroom"); - config.val() = noble_code; - config.ival(1) = building->id; - pos = df::coord(building->centerx, building->centery, building->z); - } - - ReservedRoom(PersistentDataItem &config, color_ostream &out) - { - this->config = config; - - building = df::building::find(config.ival(1)); - if (!building) - return; - pos = df::coord(building->centerx, building->centery, building->z); - } - - void remove() - { - DFHack::World::DeletePersistentData(config); - } - - bool isValid() - { - if (!building) - return false; - - if (Buildings::findAtTile(pos) != building) - return false; - - return canReserveRoom(building); - } - - int32_t getId() - { - if (!isValid()) - return 0; - - return building->id; - } - - string getCode() - { - return config.val(); - } - - void setCode(const string &noble_code) - { - config.val() = noble_code; - } - - bool checkRoomAssignment() - { - if (!isValid()) - return false; - - auto np = getOwnersNobleCode(); - bool correctOwner = false; - for (auto iter = np.begin(); iter != np.end(); iter++) - { - if (iter->position->code == getCode()) - { - correctOwner = true; - break; - } - } - - if (correctOwner) - return true; - - for (auto iter = world->units.active.begin(); iter != world->units.active.end(); iter++) - { - df::unit* unit = *iter; - if (!Units::isCitizen(unit)) - continue; - - if (DFHack::Units::isDead(unit)) - continue; - - np = getUniqueNoblePositions(unit); - for (auto iter = np.begin(); iter != np.end(); iter++) - { - if (iter->position->code == getCode()) - { - Buildings::setOwner(building, unit); - break; - } - } - } - - return true; - } - -private: - df::building *building; - PersistentDataItem config; - df::coord pos; - - std::vector getOwnersNobleCode() - { - if (!building->owner) - return std::vector (); - - return getUniqueNoblePositions(building->owner); - } -}; - -class RoomMonitor -{ -public: - RoomMonitor() - { - - } - - string getReservedNobleCode(int32_t buildingId) - { - for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) - { - if (buildingId == iter->getId()) - return iter->getCode(); - } - - return ""; - } - - void toggleRoomForPosition(int32_t buildingId, string noble_code) - { - bool found = false; - for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) - { - if (buildingId != iter->getId()) - { - continue; - } - else - { - if (noble_code == iter->getCode()) - { - iter->remove(); - reservedRooms.erase(iter); - } - else - { - iter->setCode(noble_code); - } - found = true; - break; - } - } - - if (!found) - { - ReservedRoom room(df::building::find(buildingId), noble_code); - reservedRooms.push_back(room); - } - } - - void doCycle() - { - for (auto iter = reservedRooms.begin(); iter != reservedRooms.end();) - { - if (iter->checkRoomAssignment()) - { - ++iter; - } - else - { - iter->remove(); - iter = reservedRooms.erase(iter); - } - } - } - - void reset(color_ostream &out) - { - reservedRooms.clear(); - std::vector items; - DFHack::World::GetPersistentData(&items, "buildingplan/reservedroom"); - - for (auto i = items.begin(); i != items.end(); i++) - { - ReservedRoom rr(*i, out); - if (rr.isValid()) - addRoom(rr); - } - } - -private: - vector reservedRooms; - - void addRoom(ReservedRoom &rr) - { - for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) - { - if (iter->getId() == rr.getId()) - return; - } - - reservedRooms.push_back(rr); - } -}; - -static RoomMonitor roomMonitor; - - -// START Planning -class PlannedBuilding -{ -public: - PlannedBuilding(df::building *building, ItemFilter *filter) - { - this->building = building; - this->filter = *filter; - pos = df::coord(building->centerx, building->centery, building->z); - config = DFHack::World::AddPersistentData("buildingplan/constraints"); - config.val() = filter->getMaterialFilterAsSerial(); - config.ival(1) = building->id; - config.ival(2) = filter->min_quality + 1; - config.ival(3) = static_cast(filter->decorated_only) + 1; - } - - PlannedBuilding(PersistentDataItem &config, color_ostream &out) - { - this->config = config; - - if (!filter.parseSerializedMaterialTokens(config.val())) - { - out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str()); - return; - } - - building = df::building::find(config.ival(1)); - if (!building) - return; - - pos = df::coord(building->centerx, building->centery, building->z); - filter.min_quality = static_cast(config.ival(2) - 1); - filter.decorated_only = config.ival(3) - 1; - } - - df::building_type getType() - { - return building->getType(); - } - - bool assignClosestItem(vector *items_vector) - { - decltype(items_vector->begin()) closest_item; - int32_t closest_distance = -1; - for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++) - { - auto item = *item_iter; - if (!filter.matches(item)) - continue; - - auto pos = item->pos; - auto distance = abs(pos.x - building->centerx) + - abs(pos.y - building->centery) + - abs(pos.z - building->z) * 50; - - if (closest_distance > -1 && distance >= closest_distance) - continue; - - closest_distance = distance; - closest_item = item_iter; - } - - if (closest_distance > -1 && assignItem(*closest_item)) - { - debug("Item assigned"); - items_vector->erase(closest_item); - remove(); - return true; - } - - return false; - } - - bool assignItem(df::item *item) - { - auto ref = df::allocate(); - if (!ref) - { - Core::printerr("Could not allocate general_ref_building_holderst\n"); - return false; - } - - ref->building_id = building->id; - - if (building->jobs.size() != 1) - return false; - - auto job = building->jobs[0]; - - for_each_(job->job_items, delete_item_fn); - job->job_items.clear(); - job->flags.bits.suspend = false; - - bool rough = false; - Job::attachJobItem(job, item, df::job_item_ref::Hauled); - if (item->getType() == item_type::BOULDER) - rough = true; - building->mat_type = item->getMaterial(); - building->mat_index = item->getMaterialIndex(); - - job->mat_type = building->mat_type; - job->mat_index = building->mat_index; - - if (building->needsDesign()) - { - auto act = (df::building_actual *) building; - act->design = new df::building_design(); - act->design->flags.bits.rough = rough; - } - - return true; - } - - bool isValid() - { - bool valid = filter.isValid() && - building && Buildings::findAtTile(pos) == building && - building->getBuildStage() == 0; - - if (!valid) - remove(); - - return valid; - } - - bool isCurrentlySelectedBuilding() - { - return isValid() && (building == world->selected_building); - } - - ItemFilter *getFilter() - { - return &filter; - } - - void remove() - { - DFHack::World::DeletePersistentData(config); - } - -private: - df::building *building; - PersistentDataItem config; - df::coord pos; - ItemFilter filter; -}; - -static map planmode_enabled, saved_planmodes; - static void enable_quickfort_fn(pair& pair) { pair.second = true; } -class Planner -{ -public: - bool in_dummmy_screen; - - Planner() : quickfort_mode(false), in_dummmy_screen(false) - { - - } - - bool isPlanableBuilding(const df::building_type type) const - { - return item_for_building_type.find(type) != item_for_building_type.end(); - } - - void reset(color_ostream &out) - { - planned_buildings.clear(); - std::vector items; - DFHack::World::GetPersistentData(&items, "buildingplan/constraints"); - - for (auto i = items.begin(); i != items.end(); i++) - { - PlannedBuilding pb(*i, out); - if (pb.isValid()) - planned_buildings.push_back(pb); - } - } - - void initialize() - { - vector item_names; - typedef df::enum_traits item_types; - int size = item_types::last_item_value - item_types::first_item_value+1; - for (size_t i = 1; i < size; i++) - { - is_relevant_item_type[(df::item_type) (i-1)] = false; - string item_name = toLower(item_types::key_table[i]); - string item_name_clean; - for (auto c = item_name.begin(); c != item_name.end(); c++) - { - if (*c == '_') - continue; - item_name_clean += *c; - } - item_names.push_back(item_name_clean); - } - - typedef df::enum_traits building_types; - size = building_types::last_item_value - building_types::first_item_value+1; - for (size_t i = 1; i < size; i++) - { - auto building_type = (df::building_type) (i-1); - if (building_type == building_type::Weapon || building_type == building_type::Floodgate) - continue; - - string building_name = toLower(building_types::key_table[i]); - for (size_t j = 0; j < item_names.size(); j++) - { - if (building_name == item_names[j]) - { - auto btype = (df::building_type) (i-1); - auto itype = (df::item_type) j; - - item_for_building_type[btype] = itype; - default_item_filters[btype] = ItemFilter(); - available_item_vectors[itype] = vector(); - is_relevant_item_type[itype] = true; - - if (planmode_enabled.find(btype) == planmode_enabled.end()) - { - planmode_enabled[btype] = false; - } - } - } - } - } - - void addPlannedBuilding(df::building *bld) - { - PlannedBuilding pb(bld, &default_item_filters[bld->getType()]); - planned_buildings.push_back(pb); - } - - void doCycle() - { - debug("Running Cycle"); - if (planned_buildings.size() == 0) - return; - - debug("Planned count: " + int_to_string(planned_buildings.size())); - - gather_available_items(); - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) - { - if (building_iter->isValid()) - { - if (show_debugging) - debug(string("Trying to allocate ") + enum_item_key_str(building_iter->getType())); - - auto required_item_type = item_for_building_type[building_iter->getType()]; - auto items_vector = &available_item_vectors[required_item_type]; - if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector)) - { - debug("Unable to allocate an item"); - ++building_iter; - continue; - } - } - debug("Removing building plan"); - building_iter = planned_buildings.erase(building_iter); - } - } - - bool allocatePlannedBuilding(df::building_type type) - { - coord32_t cursor; - if (!Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) - return false; - - auto newinst = Buildings::allocInstance(cursor.get_coord16(), type); - if (!newinst) - return false; - - df::job_item *filter = new df::job_item(); - filter->item_type = item_type::NONE; - filter->mat_index = 0; - filter->flags2.bits.building_material = true; - std::vector filters; - filters.push_back(filter); - - if (!Buildings::constructWithFilters(newinst, filters)) - { - delete newinst; - return false; - } - - for (auto iter = newinst->jobs.begin(); iter != newinst->jobs.end(); iter++) - { - (*iter)->flags.bits.suspend = true; - } - - if (type == building_type::Door) - { - auto door = virtual_cast(newinst); - if (door) - door->door_flags.bits.pet_passable = true; - } - - addPlannedBuilding(newinst); - - return true; - } - - PlannedBuilding *getSelectedPlannedBuilding() - { - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++) - { - if (building_iter->isCurrentlySelectedBuilding()) - { - return &(*building_iter); - } - } - - return nullptr; - } - - void removeSelectedPlannedBuilding() - { - getSelectedPlannedBuilding()->remove(); - } - - ItemFilter *getDefaultItemFilterForType(df::building_type type) - { - return &default_item_filters[type]; - } - - void cycleDefaultQuality(df::building_type type) - { - auto quality = &getDefaultItemFilterForType(type)->min_quality; - *quality = static_cast(*quality + 1); - if (*quality == item_quality::Artifact) - (*quality) = item_quality::Ordinary; - } - - void enableQuickfortMode() - { - saved_planmodes = planmode_enabled; - for_each_(planmode_enabled, enable_quickfort_fn); - - quickfort_mode = true; - } - - void disableQuickfortMode() - { - planmode_enabled = saved_planmodes; - quickfort_mode = false; - } - - bool inQuickFortMode() - { - return quickfort_mode; - } - -private: - map item_for_building_type; - map default_item_filters; - map> available_item_vectors; - map is_relevant_item_type; //Needed for fast check when looping over all items - bool quickfort_mode; - - vector planned_buildings; - - void gather_available_items() - { - debug("Gather available items"); - for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++) - { - iter->second.clear(); - } - - // Precompute a bitmask with the bad flags - df::item_flags bad_flags; - bad_flags.whole = 0; - -#define F(x) bad_flags.bits.x = true; - F(dump); F(forbid); F(garbage_collect); - F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact); -#undef F - - std::vector &items = world->items.other[items_other_id::IN_PLAY]; - - for (size_t i = 0; i < items.size(); i++) - { - df::item *item = items[i]; - - if (item->flags.whole & bad_flags.whole) - continue; - - df::item_type itype = item->getType(); - if (!is_relevant_item_type[itype]) - continue; - - if (itype == item_type::BOX && item->isBag()) - continue; //Skip bags - - if (item->flags.bits.artifact) - continue; - - if (item->flags.bits.in_job || - item->isAssignedToStockpile() || - item->flags.bits.owned || - item->flags.bits.in_chest) - { - continue; - } - - available_item_vectors[itype].push_back(item); - } - } -}; - -static Planner planner; - - static bool is_planmode_enabled(df::building_type type) { if (planmode_enabled.find(type) == planmode_enabled.end()) diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp new file mode 100644 index 000000000..71119972b --- /dev/null +++ b/plugins/fortplan.cpp @@ -0,0 +1,188 @@ +#include "buildingplan-lib.h" +#include +#include + +DFHACK_PLUGIN("fortplan"); +#define PLUGIN_VERSION 0.10 + +command_result fortplan(color_ostream &out, vector & params); + +struct BuildingInfo { + std::string code; + df::building_type type; + std::string name; + + BuildingInfo(std::string theCode, df::building_type theType, std::string theName) { + code = theCode; + type = theType; + name = theName; + } +}; + +class MatchesCode +{ + std::string _code; + +public: + MatchesCode(const std::string &code) : _code(code) {} + + bool operator()(const BuildingInfo &check) const + { + return check.code == _code; + } +}; + +std::vector buildings; + +DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { + commands.push_back(PluginCommand("fortplan","Lay out buildings in your fortress based on a Quickfort-style CSV input file.",fortplan,false, + "Lay out buildings in your fortress based on a Quickfort-style CSV input file.\n" + "Usage: fortplan [filename]\n")); + + buildings.push_back(BuildingInfo("a",df::building_type::Armorstand,"Armor Stand")); + buildings.push_back(BuildingInfo("r",df::building_type::Weaponrack,"Weapon Rack")); + buildings.push_back(BuildingInfo("b",df::building_type::Bed,"Bed")); + buildings.push_back(BuildingInfo("f",df::building_type::Cabinet,"Cabinet")); + buildings.push_back(BuildingInfo("h",df::building_type::Box,"Box")); + buildings.push_back(BuildingInfo("d",df::building_type::Door,"Door")); + buildings.push_back(BuildingInfo("n",df::building_type::Coffin,"Coffin")); + buildings.push_back(BuildingInfo("c",df::building_type::Chair,"Chair")); + buildings.push_back(BuildingInfo("t",df::building_type::Table,"Table")); + + out << "Loaded fortplan version " << PLUGIN_VERSION << endl; + return CR_OK; +} + +#define DAY_TICKS 1200 +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + static decltype(world->frame_counter) last_frame_count = 0; + if ((world->frame_counter - last_frame_count) >= DAY_TICKS/2) + { + last_frame_count = world->frame_counter; + planner.doCycle(); + } + + return CR_OK; +} + +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + if (!gps) + return CR_FAILURE; + + if (enable != is_enabled) + { + planner.reset(out); + + is_enabled = enable; + } + + return CR_OK; +} +std::string get_working_path() +{ + char temp[MAXPATHLEN]; + return ( getcwd(temp, MAXPATHLEN) ? std::string( temp ) : std::string("") ); +} + +command_result fortplan(color_ostream &out, vector & params) { + + auto & con = out; + if (params.size()) { + coord32_t cursor; + coord32_t startCursor; + if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) { + con.print("You must have an active in-game cursor.\n"); + return CR_FAILURE; + } + DFHack::Gui::getCursorCoords(startCursor.x, startCursor.y, startCursor.z); + + std::string cwd = get_working_path(); + std::string filename = cwd+"/"+params[0]; + con.print("Loading file '%s'...\n",filename.c_str()); + std::ifstream infile(filename.c_str()); + if (!infile.good()) { + con.print("Could not open the file.\n"); + return CR_FAILURE; + } + std::string line; + int lineNum = 0; + while (std::getline(infile, line)) { + lineNum++; + if (lineNum==1) { + auto hashBuild = line.find("#build"); + if (hashBuild >= 0) { + auto startLoc = line.find("start("); + if (startLoc != line.npos) { + startLoc += 6; + auto nextDelimiter = line.find(";",startLoc); + std::string startXStr = line.substr(startLoc,nextDelimiter-startLoc); + int startXOffset = std::stoi(startXStr); + startLoc = nextDelimiter+1; + nextDelimiter = line.find(";",startLoc); + std::string startYStr = line.substr(startLoc,nextDelimiter-startLoc); + int startYOffset = std::stoi(startYStr); + startCursor.x -= startXOffset; + startCursor.y -= startYOffset; + DFHack::Gui::setCursorCoords(startCursor.x,startCursor.y,startCursor.z); + + auto startEnd = line.find(")",nextDelimiter); + + con.print("Starting at (%d,%d,%d) which is described as: %s\n",startCursor.x,startCursor.y,startCursor.z,line.substr(nextDelimiter+1,startEnd-nextDelimiter).c_str()); + std::string desc = line.substr(startEnd+1); + if (desc.size()>0) { + con.print("Description of this plan: %s\n",desc.c_str()); + } + continue; + } else { + con.print("No start location found for this block\n"); + } + } else { + con.print("Not a build file: %s\n",line.c_str()); + break; + } + } + + int start = 0; + auto nextInd = line.find(','); + std::string curCell = line.substr(start,nextInd-start); + if (strcmp(curCell.substr(0,1).c_str(),"#")==0) { + continue; + } + do { + + if (strcmp(curCell.c_str(),"`")!=0) { + con.print("Found a cell with '%s' in it (line %d:%d-%d)\n",curCell.c_str(),lineNum,start,nextInd); + auto buildingIndex = std::find_if(buildings.begin(), buildings.end(), MatchesCode(curCell.c_str())); + + // = std::find(validInstructions.begin(), validInstructions.end(), curCell); + if(buildingIndex == buildings.end()) { + con.print("That is not a valid code.\n"); + } else { + //con.print("I can build that!\n"); + BuildingInfo buildingInfo = *buildingIndex; + con.print("Building a(n) %s.\n",buildingInfo.name.c_str()); + planner.allocatePlannedBuilding(buildingInfo.type); + } + } + cursor.x++; + DFHack::Gui::setCursorCoords(cursor.x, cursor.y, cursor.z); + start = nextInd+1; + nextInd = line.find(',',start); + curCell = line.substr(start,nextInd-start); + } while (nextInd != line.npos); + + cursor.y++; + cursor.x = startCursor.x; + + } + con.print("Done with file.\n"); + } else { + con.print("You must supply a filename to read.\n"); + } + + return CR_OK; +} \ No newline at end of file diff --git a/plugins/uicommon.h b/plugins/uicommon.h index c900a585e..733d29d61 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -92,7 +94,7 @@ static void transform_(vector &src, vector &dst, Fn func) typedef int8_t UIColor; -void OutputString(UIColor color, int &x, int &y, const std::string &text, +static void OutputString(UIColor color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0, const UIColor bg_color = 0) { Screen::paintString(Screen::Pen(' ', color, bg_color), x, y, text); @@ -105,7 +107,7 @@ void OutputString(UIColor color, int &x, int &y, const std::string &text, x += text.length(); } -void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, +static void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) { OutputString(hotkey_color, x, y, hotkey); @@ -114,7 +116,7 @@ void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bo OutputString(text_color, x, y, display, newline, left_margin); } -void OutputLabelString(int &x, int &y, const char *text, const char *hotkey, const string &label, bool newline = false, +static void OutputLabelString(int &x, int &y, const char *text, const char *hotkey, const string &label, bool newline = false, int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) { OutputString(hotkey_color, x, y, hotkey); @@ -125,7 +127,7 @@ void OutputLabelString(int &x, int &y, const char *text, const char *hotkey, con OutputString(hotkey_color, x, y, label, newline, left_margin); } -void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false, +static void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false, int left_margin = 0, int8_t hotkey_color = COLOR_LIGHTGREEN) { OutputString(hotkey_color, x, y, hotkey); @@ -133,7 +135,7 @@ void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bo OutputString((state) ? COLOR_WHITE : COLOR_GREY, x, y, text, newline, left_margin); } -void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, +static void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) { OutputHotkeyString(x, y, text, hotkey, false, 0, color, hotkey_color); @@ -243,13 +245,13 @@ static bool is_metal_item(df::item *item) return (mat.getCraftClass() == craft_material_class::Metal); } -bool is_set_to_melt(df::item* item) +static bool is_set_to_melt(df::item* item) { return item->flags.bits.melt; } // Copied from Kelly Martin's code -bool can_melt(df::item* item) +static bool can_melt(df::item* item) { df::item_flags bad_flags; From 088f3715cb14964ca4625cdae974a59096dbd10f Mon Sep 17 00:00:00 2001 From: Timothy Collett Date: Tue, 23 Dec 2014 17:09:35 -0500 Subject: [PATCH 047/115] Finish up the bare-bones fortplan plugin with support for furniture that is made from an item of the same name --- plugins/fortplan.cpp | 323 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 257 insertions(+), 66 deletions(-) diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index 71119972b..2776064ae 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -3,19 +3,34 @@ #include DFHACK_PLUGIN("fortplan"); -#define PLUGIN_VERSION 0.10 +#define PLUGIN_VERSION 0.15 command_result fortplan(color_ostream &out, vector & params); struct BuildingInfo { std::string code; df::building_type type; + df::furnace_type furnaceType; + df::workshop_type workshopType; + df::trap_type trapType; std::string name; + bool variableSize; + int defaultHeight; + int defaultWidth; + bool hasCustomOptions; - BuildingInfo(std::string theCode, df::building_type theType, std::string theName) { + BuildingInfo(std::string theCode, df::building_type theType, std::string theName, int height, int width) { code = theCode; type = theType; name = theName; + variableSize = false; + defaultHeight = height; + defaultWidth = width; + hasCustomOptions = false; + } + + bool allocate() { + return planner.allocatePlannedBuilding(type); } }; @@ -39,17 +54,33 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector > tokenizeFile(std::string filename) { + std::ifstream infile(filename.c_str()); + std::vector> fileTokens(128, std::vector(128)); + std::vector>::size_type x, y; + if (!infile.good()) { + throw -1; + } + std::string line; + y = 0; + while (std::getline(infile, line)) { + x = 0; + if (strcmp(line.substr(0,1).c_str(),"#")==0) { + fileTokens[y++][0] = line; + continue; + } + int start = 0; + auto nextInd = line.find(','); + std::string curCell = line.substr(start,nextInd-start); + do { + fileTokens[y][x] = curCell; + start = nextInd+1; + nextInd = line.find(',',start); + curCell = line.substr(start,nextInd-start); + x++; + } while (nextInd != line.npos); + y++; + } + return fileTokens; +} + command_result fortplan(color_ostream &out, vector & params) { auto & con = out; + std::vector> layout(128, std::vector(128)); if (params.size()) { coord32_t cursor; + coord32_t userCursor; coord32_t startCursor; if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) { con.print("You must have an active in-game cursor.\n"); return CR_FAILURE; } DFHack::Gui::getCursorCoords(startCursor.x, startCursor.y, startCursor.z); + userCursor = startCursor; std::string cwd = get_working_path(); std::string filename = cwd+"/"+params[0]; con.print("Loading file '%s'...\n",filename.c_str()); - std::ifstream infile(filename.c_str()); - if (!infile.good()) { + try { + layout = tokenizeFile(filename); + } catch (int e) { con.print("Could not open the file.\n"); return CR_FAILURE; } - std::string line; - int lineNum = 0; - while (std::getline(infile, line)) { - lineNum++; - if (lineNum==1) { - auto hashBuild = line.find("#build"); - if (hashBuild >= 0) { - auto startLoc = line.find("start("); - if (startLoc != line.npos) { - startLoc += 6; - auto nextDelimiter = line.find(";",startLoc); - std::string startXStr = line.substr(startLoc,nextDelimiter-startLoc); - int startXOffset = std::stoi(startXStr); - startLoc = nextDelimiter+1; - nextDelimiter = line.find(";",startLoc); - std::string startYStr = line.substr(startLoc,nextDelimiter-startLoc); - int startYOffset = std::stoi(startYStr); - startCursor.x -= startXOffset; - startCursor.y -= startYOffset; - DFHack::Gui::setCursorCoords(startCursor.x,startCursor.y,startCursor.z); - - auto startEnd = line.find(")",nextDelimiter); - - con.print("Starting at (%d,%d,%d) which is described as: %s\n",startCursor.x,startCursor.y,startCursor.z,line.substr(nextDelimiter+1,startEnd-nextDelimiter).c_str()); - std::string desc = line.substr(startEnd+1); - if (desc.size()>0) { - con.print("Description of this plan: %s\n",desc.c_str()); - } - continue; - } else { - con.print("No start location found for this block\n"); + if (!is_enabled) { + plugin_enable(out, true); + } + con.print("Loaded.\n"); + std::vector>::size_type x, y; + bool started = false; + for (y = 0; y < layout.size(); y++) { + x = 0; + auto hashBuild = layout[y][x].find("#build"); + if (hashBuild != layout[y][x].npos) { + auto startLoc = layout[y][x].find("start("); + if (startLoc != layout[y][x].npos) { + startLoc += 6; + auto nextDelimiter = layout[y][x].find(";",startLoc); + std::string startXStr = layout[y][x].substr(startLoc,nextDelimiter-startLoc); + int startXOffset = std::stoi(startXStr); + startLoc = nextDelimiter+1; + nextDelimiter = layout[y][x].find(";",startLoc); + std::string startYStr = layout[y][x].substr(startLoc,nextDelimiter-startLoc); + int startYOffset = std::stoi(startYStr); + startCursor.x -= startXOffset; + startCursor.y -= startYOffset; + DFHack::Gui::setCursorCoords(startCursor.x,startCursor.y,startCursor.z); + started = true; + + auto startEnd = layout[y][x].find(")",nextDelimiter); + + con.print("Starting at (%d,%d,%d) which is described as: %s\n",startCursor.x,startCursor.y,startCursor.z,layout[y][x].substr(nextDelimiter+1,startEnd-nextDelimiter).c_str()); + std::string desc = layout[y][x].substr(startEnd+1); + if (desc.size()>0) { + con.print("Description of this plan: %s\n",desc.c_str()); } + continue; } else { - con.print("Not a build file: %s\n",line.c_str()); - break; + con.print("No start location found for this block\n"); } + } else if (!started) { + con.print("Not a build file: %s\n",layout[y][x].c_str()); + break; } - int start = 0; - auto nextInd = line.find(','); - std::string curCell = line.substr(start,nextInd-start); - if (strcmp(curCell.substr(0,1).c_str(),"#")==0) { - continue; - } - do { + for (x = 0; x < layout[y].size(); x++) { + if (strcmp(layout[y][x].substr(0,1).c_str(),"#")==0) { + continue; + } - if (strcmp(curCell.c_str(),"`")!=0) { - con.print("Found a cell with '%s' in it (line %d:%d-%d)\n",curCell.c_str(),lineNum,start,nextInd); - auto buildingIndex = std::find_if(buildings.begin(), buildings.end(), MatchesCode(curCell.c_str())); + if (strcmp(layout[y][x].c_str(),"`")!=0) { + auto dataIndex = layout[y][x].find("("); + std::string curCode; + std::vector curData; + if (dataIndex != layout[y][x].npos) { + curCode = layout[y][x].substr(0,dataIndex); + int dataStart = dataIndex+1; + auto nextDataStart = layout[y][x].find(",",dataStart); + while (nextDataStart!=layout[y][x].npos) { + std::string nextData = layout[y][x].substr(dataStart,nextDataStart); + if (strcmp(nextData.substr(nextData.size()-1,1).c_str(),")")==0) { + nextData = nextData.substr(0,nextData.size()-1); + } + curData.push_back(nextData); + dataStart = nextDataStart+1; + nextDataStart = layout[y][x].find(",",dataStart); + } + } else { + curCode = layout[y][x]; + } + //con.print("Found a cell with '%s' in it (layout[y][x] %d:%d-%d)\n",layout[y][x].c_str(),lineNum,start,nextInd); + auto buildingIndex = std::find_if(buildings.begin(), buildings.end(), MatchesCode(curCode.c_str())); - // = std::find(validInstructions.begin(), validInstructions.end(), curCell); + // = std::find(validInstructions.begin(), validInstructions.end(), layout[y][x]); if(buildingIndex == buildings.end()) { - con.print("That is not a valid code.\n"); + //con.print("That is not a valid code.\n"); } else { //con.print("I can build that!\n"); BuildingInfo buildingInfo = *buildingIndex; - con.print("Building a(n) %s.\n",buildingInfo.name.c_str()); - planner.allocatePlannedBuilding(buildingInfo.type); + if (buildingInfo.variableSize || buildingInfo.defaultHeight > 1 || buildingInfo.defaultWidth > 1) { + //con.print("Found a building at (%d,%d) called %s, which has a size %dx%d or variable size\n",x,y,buildingInfo.name.c_str(),buildingInfo.defaultWidth, buildingInfo.defaultHeight); + // TODO: Make this function smarter, able to determine the exact shape + // and location of the building + // For now, we just assume that we are always looking at the top left + // corner of where it is located in the input file + coord32_t buildingSize; + if (!buildingInfo.variableSize) { + bool single = true; + bool block = true; + std::vector>::size_type checkX, checkY; + int yOffset = ((buildingInfo.defaultHeight-1)/2); + int xOffset = ((buildingInfo.defaultWidth-1)/2); + //con.print(" - Checking to see if it's a single, with %d<=x<%d and %d<=y<%d...\n",x-xOffset,x+xOffset,y-yOffset,y+yOffset); + // First, check to see if this is in the center of an empty square of its default size + for (checkY = y-yOffset; checkY <= (y+yOffset); checkY++) { + for (checkX = x-xOffset; checkX <= (x+xOffset); checkX++) { + if (checkX==x && checkY==y) { + continue; + } + auto checkDataIndex = layout[checkY][checkX].find("("); + std::string checkCode; + if (checkDataIndex != layout[checkY][checkX].npos) { + checkCode = layout[checkY][checkX].substr(0,checkDataIndex); + } else { + checkCode = layout[checkY][checkX]; + } + + con.print(" - Code at (%d,%d) is '%s': ",checkX,checkY,checkCode.c_str()); + auto checkIndex = std::find_if(buildings.begin(), buildings.end(), MatchesCode(checkCode.c_str())); + //if (checkIndex == buildings.end()) { + // con.print("this is not a valid code, so we keep going.\n"); + // continue; + //} + //if (curCode.compare(layout[checkY][checkX]) != 0) { + if (checkIndex != buildings.end()) { + //con.print("this is a building, so we break.\n"); + single = false; + break; + } else { + //con.print("this is not a building, so we keep going.\n"); + } + } + if (!single) { + //con.print("Not a single. Moving forward.\n"); + break; + } + } + if (!single) { + // If that's not the case, check to see if this is the top-left corner of + // a square of its default size + //con.print(" - It's not single; checking to see if it's a block...\n"); + for (checkY = y; checkY < (y+buildingInfo.defaultHeight); checkY++) { + for (checkX = x; checkX < (x+buildingInfo.defaultWidth); checkX++) { + if (checkX==x && checkY==y) { + continue; + } + auto checkDataIndex = layout[checkY][checkX].find("("); + std::string checkCode; + if (checkDataIndex != layout[checkY][checkX].npos) { + checkCode = layout[checkY][checkX].substr(0,checkDataIndex); + } else { + checkCode = layout[checkY][checkX]; + } + + //con.print(" - Code at (%d,%d) is '%s': ",checkX,checkY,checkCode.c_str()); + if (curCode.compare(checkCode) != 0) { + //con.print("this is not the same as '%s', so we break.\n",curCode.c_str()); + block = false; + break; + } else { + //con.print("this is the same as '%s', so we erase it and move on.\n",curCode.c_str()); + layout[checkY][checkX] = "``"; + } + } + if (!block) { + //con.print("Not a block. Moving forward.\n"); + break; + } + } + } + + if (single) { + //con.print("Placing a building with code '%s' centered at (%d,%d) and default size %dx%d.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight); + coord32_t offsetCursor = cursor; + offsetCursor.x -= xOffset; + offsetCursor.y -= yOffset; + DFHack::Gui::setCursorCoords(offsetCursor.x, offsetCursor.y, offsetCursor.z); + if (!buildingInfo.allocate()) { + con.print("*** There was an error placing building with code '%s' centered at (%d,%d).\n",curCode.c_str(),x,y); + } + DFHack::Gui::setCursorCoords(cursor.x, cursor.y, cursor.z); + } else if (block) { + //con.print("Placing a building with code '%s' with corner at (%d,%d) and default size %dx%d.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight); + if (!buildingInfo.allocate()) { + con.print("*** There was an error placing building with code '%s' with corner at (%d,%d).\n",curCode.c_str(),x,y); + } + } else { + con.print("*** Found a code '%s' at (%d,%d) for a building with default size %dx%d with an invalid size designation.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight); + } + } else { + //buildingSize = findBuildingExtent(layout, x, y, -1, -1, out); + //con.print(" - The building has variable size %dx%d\n",buildingSize.x, buildingSize.y); + //con.print(" - The building has a variable size, which is not yet handled.\n"); + } + } else { + //con.print("Building a(n) %s.\n",buildingInfo.name.c_str()); + if (!buildingInfo.allocate()) { + con.print("*** There was an error placing the %s at (%d,%d).\n",buildingInfo.name.c_str(),x,y); + } + } } } cursor.x++; DFHack::Gui::setCursorCoords(cursor.x, cursor.y, cursor.z); - start = nextInd+1; - nextInd = line.find(',',start); - curCell = line.substr(start,nextInd-start); - } while (nextInd != line.npos); + } cursor.y++; cursor.x = startCursor.x; } + DFHack::Gui::setCursorCoords(userCursor.x, userCursor.y, userCursor.z); con.print("Done with file.\n"); } else { con.print("You must supply a filename to read.\n"); From 89bd741fa403c778b36e4adcc29f52bcd99a1736 Mon Sep 17 00:00:00 2001 From: Timothy Collett Date: Tue, 23 Dec 2014 17:22:51 -0500 Subject: [PATCH 048/115] Add fortplan to the README file --- Readme.rst | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Readme.rst b/Readme.rst index 15933e0f6..8c59d6b59 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2316,6 +2316,39 @@ To purify all elves on the map with fire (may have side-effects):: exterminate elve magma +fortplan +======== +Usage: fortplan [filename] + +Designates furniture for building according to a .csv file with +quickfort-style syntax. Companion to digfort. + +The first line of the file must contain the following: + + #build start(X; Y; ) + +...where X and Y are the offset from the top-left corner of the file's area +where the in-game cursor should be located, and +is an optional description of where that is. You may also leave a description +of the contents of the file itself following the closing parenthesis on the +same line. + +The syntax of the file itself is similar to digfort or quickfort. At present, +only buildings constructed of an item with the same name as the building +are supported. All other characters are ignored. For example: + + `,`,d,`,` + `,f,`,t,` + `,s,b,c,` + +This section of a file would designate for construction a door and some +furniture inside a bedroom: specifically, clockwise from top left, a cabinet, +a table, a chair, a bed, and a statue. + +All of the building designation uses Planning Mode, so you do not need to +have the items available to construct all the buildings when you run +fortplan with the .csv file. + growcrops ========= Instantly grow seeds inside farming plots. From 86cb328bc88a3a0138ea605b1288c0789dfe4269 Mon Sep 17 00:00:00 2001 From: Timothy Collett Date: Tue, 23 Dec 2014 17:48:44 -0500 Subject: [PATCH 049/115] Update NEWS for fortplan --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 1b9ec3f8b..340d5e12a 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ DFHack Future Internals Fixes New Plugins + fortplan: designate construction of (limited) buildings from .csv file, quickfort-style New Scripts Misc Improvements From e71408b40bc0aedf18fb2cc7ae583e9aeb2587b8 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Wed, 24 Dec 2014 18:24:52 +1100 Subject: [PATCH 050/115] replace hellhole.cpp with hfs-pit.lua A port/update. May not have great argument handling, and there's a few magic numbers, but it's a huge improvement over a non-compiling plugin. --- NEWS | 1 + Readme.rst | 14 + needs_porting/_notes.txt | 4 - needs_porting/hellhole.cpp | 1281 ------------------------------------ scripts/hfs-pit.lua | 88 +++ 5 files changed, 103 insertions(+), 1285 deletions(-) delete mode 100644 needs_porting/hellhole.cpp create mode 100644 scripts/hfs-pit.lua diff --git a/NEWS b/NEWS index a05e75465..93e93f5a6 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ DFHack Future New Scripts position: Reports the current date, time, month, and season, plus some location info. Port/update of position.py + hfs-pit: Digs a hole to hell under the cursor. Replaces needs_porting/hellhole.cpp Misc Improvements further work on needs_porting diff --git a/Readme.rst b/Readme.rst index 465037cea..872295a2f 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2329,6 +2329,20 @@ For example, to grow 40 plump helmet spawn:: growcrops plump 40 +hfs-pit +======= +Creates a pit to the underworld at the cursor. + +Takes three arguments: diameter of the pit in tiles, whether to wall off +the pit, and whether to insert stairs. If no arguments are given, the default +is "hfs-pit 1 0 0", ie single-tile wide with no walls or stairs. + + hfs-pit 4 0 1 + hfs-pit 2 1 0 + +First example is a four-across pit with stairs but no walls; second is a +two-across pit with stairs but no walls. + lever ===== Allow manipulation of in-game levers from the dfhack console. diff --git a/needs_porting/_notes.txt b/needs_porting/_notes.txt index 35a7ef5bf..cdd364d12 100644 --- a/needs_porting/_notes.txt +++ b/needs_porting/_notes.txt @@ -3,10 +3,6 @@ TODO: high value target - a proof of concept plugin to allow copy-pasting in DF; does both terrain and buildings/constructions creaturemanager.cpp modify skills and labors of creatures, kill creatures, etc; impressive but I suspect most functions implemented elsewhere - dfbauxtite.cpp - changes material of mechanisms to bauxtite (ie magma-safe) - hellhole.cpp - instantly creates a hole to the HFS In progress: hotkey-notes.lua diff --git a/needs_porting/hellhole.cpp b/needs_porting/hellhole.cpp deleted file mode 100644 index a38d14cbf..000000000 --- a/needs_porting/hellhole.cpp +++ /dev/null @@ -1,1281 +0,0 @@ -// Burn a hole straight to hell! - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -using namespace std; - -#include -#include -#include -using namespace DFHack; - - -#ifdef LINUX_BUILD -#include -void waitmsec (int delay) -{ - usleep(delay); -} -#else -#include -void waitmsec (int delay) -{ - Sleep(delay); -} -#endif - -#define minmax(MinV,V,MaxV) (max((MinV),min((MaxV),(V)))) - -//User interaction enums. -//Pit Type (these only have meaning within hellhole, btw) -#define PITTYPEMACRO \ - X(pitTypeChasm,"Bottomless Chasm" ) \ - X(pitTypeEerie,"Bottomless Eerie Pit" ) \ - X(pitTypeFloor,"Pit with floor" ) \ - X(pitTypeSolid,"Solid Pillar" ) \ - X(pitTypeOasis,"Oasis Pit (ends at magma, no hell access)" ) \ - X(pitTypeOPool,"Oasis Pool, with partial aquifer (default 5 z-levels)" ) \ - X(pitTypeMagma,"Magma Pit (similar to volcano, no hell access)" ) \ - X(pitTypeMPool,"Magma Pool (default 5 z-levels)" ) -//end PITTYPEMACRO - -#define X(name,desc) name, -enum e_pitType -{ - pitTypeInvalid=-1, - PITTYPEMACRO - pitTypeCount, -}; -#undef X - - -#define X(name,desc) desc, -const char * pitTypeDesc[pitTypeCount+1] = -{ - PITTYPEMACRO - "" -}; -#undef X - - - - -int getyesno( const char * msg , int default_value ) -{ - const int bufferlen=4; - static char buf[bufferlen]; - memset(buf,0,bufferlen); - while (-1) - { - if (msg) printf("\n%s (default=%s)\n:" , msg , (default_value?"yes":"no") ); - fflush(stdin); - fgets(buf,bufferlen,stdin); - switch (buf[0]) - { - case 0: - case 0x0d: - case 0x0a: - return default_value; - case 'y': - case 'Y': - case 'T': - case 't': - case '1': - return -1; - case 'n': - case 'N': - case 'F': - case 'f': - case '0': - return 0; - } - } - return 0; -} - -int getint( const char * msg , int min, int max, int default_value ) { - const int bufferlen=16; - static char buf[bufferlen]; - int n=0; - memset(buf,0,bufferlen); - while (-1) - { - if (msg) printf("\n%s (default=%d)\n:" , msg , default_value); - fflush(stdin); - fgets(buf,bufferlen,stdin); - if ( !buf[0] || 0x0a==buf[0] || 0x0d==buf[0] ) - { - return default_value; - } - if ( sscanf(buf,"%d", &n) ) - { - if (n>=min && n<=max ) - { - return n; - } - } - } -} - -int getint( const char * msg , int min, int max ) -{ - const int bufferlen=16; - static char buf[bufferlen]; - int n=0; - memset(buf,0,bufferlen); - while (-1) - { - if (msg) - { - printf("\n%s \n:" , msg ); - } - fflush(stdin); - fgets(buf,bufferlen,stdin); - - if ( !buf[0] || 0x0a==buf[0] || 0x0d==buf[0] ) - { - continue; - } - if ( sscanf(buf,"%d", &n) ) - { - if (n>=min && n<=max ) - { - return n; - } - } - } -} - - - -//Interactive, get pit type from user -e_pitType selectPitType() -{ - while ( -1 ) - { - printf("Enter the type of hole to dig:\n" ); - for (int n=0;n=0, replace with v. -//Returns number of neighbors found. -int checkneighbors(unsigned char pattern[16][16], int x, int y, unsigned char n , char v ) -{ - int r=0; - if ( x>0 && y>0 && n==pattern[x-1][y-1] ) - { - ++r; - if (v>-1) pattern[x][y]=v; - } - if ( x>0 && n==pattern[x-1][y ] ) - { - ++r; - if (v>-1) pattern[x][y]=v; - } - if ( y>0 && n==pattern[x ][y-1] ) - { - ++r; - if (v>-1) pattern[x][y]=v; - } - if ( x<15 && n==pattern[x+1][y ] ) - { - ++r; - if (v>-1) pattern[x][y]=v; - } - if ( x<15 && y>0 && n==pattern[x+1][y-1] ) - { - ++r; - if (v>-1) pattern[x][y]=v; - } - if ( x<15 && y<15 && n==pattern[x+1][y+1] ) - { - ++r; - if (v>-1) pattern[x][y]=v; - } - if ( y<15 && n==pattern[x ][y+1] ) - { - ++r; - if (v>-1) pattern[x][y]=v; - } - if ( x>0 && y<15 && n==pattern[x-1][y+1] ) - { - ++r; - if (v>-1) pattern[x][y]=v; - } - return r; -} -//convenience -int checkneighbors(unsigned char pattern[16][16], int x, int y, unsigned char n ) -{ - return checkneighbors(pattern,x,y,n,-1); -} - -void settileat(unsigned char pattern[16][16], const unsigned char needle, const unsigned char v, const int index ) -{ - int ok=0; - int safety=256*256; - int y,x,i=0; - //Scan for sequential index - while ( !ok && --safety ) - { - for (y=0 ; !ok && y<16 ; ++y ) - { - for (x=0 ; !ok && x<16 ; ++x ) - { - if ( needle==pattern[x][y] ) - { - ++i; - if ( index==i ) - { - //Got it! - pattern[x][y]=v; - ok=-1; - } - } - } - } - } -} - - -//FIXME: good candidate for adding to dfhack. Maybe the Maps should have those cached so they can be queried? -//Is a given feature present at the given tile? -int isfeature( - vector global_features, - std::map > local_features, - const mapblock40d &block, const DFCoord &pc, const int x, const int y, const e_feature Feat -) -{ - //const TileRow * tp; - //tp = getTileTypeP(block.tiletypes[x][y]); - const t_designation * d; - d = &block.designation[x][y]; - - if ( block.local_feature > -1 && d->bits.feature_local ) { - if ( Feat==local_features[pc][block.local_feature]->type ) return Feat; - } - if ( block.global_feature > -1 && d->bits.feature_global ) { - if ( Feat==global_features[block.global_feature].type ) return Feat; - } - - return 0; -} - -// FIXME: use block cache, break into manageable bits -int main (void) -{ - srand ( (unsigned int)time(NULL) ); - - //Message of intent - cout << - "DF Hole" << endl << - "This tool will instantly dig a chasm, pit, pipe, etc through hell, wherever your cursor is." << endl << - "This can not be undone! End program now if you don't want hellish fun." << endl - ; - - //User selection of settings should have it own routine, a structure for settings, I know - //sloppy mess, but this is just a demo utility. - - //Pit Types. - e_pitType pittype = selectPitType(); - - //Here are all the settings. - //Default values are set here. - int pitdepth=0; - int roof=-1; - int holeradius=6; - int wallthickness=1; - int wallpillar=1; - int holepillar=1; - int exposehell = 0; - int fillmagma=0; - int fillwater=0; - int stopatmagma=0; - int exposemagma=0; - int aquify=0; - - //The Tile Type to use for the walls lining the hole - //263 is semi-molten rock, 331 is obsidian - uint32_t whell=263, wmolten=263, wmagma=331, wcave=331; - //The Tile Type to use for the hole's floor at bottom of the map - //35 is chasm, 42 is eerie pit , 340 is obsidian floor, 344 is featstone floor, 264 is 'magma flow' floor - uint32_t floor=35, cap=340; - int floorvar=0; - - - //Modify default settings based on pit type. - switch ( pittype ) - { - case pitTypeChasm: - floor=35; - break; - case pitTypeEerie: - floor=42; - break; - case pitTypeFloor: - floor=344; - floorvar=3; - break; - case pitTypeSolid: - holeradius=0; - wallthickness=7; - wallpillar=4; - break; - case pitTypeOasis: - stopatmagma=-1; - fillwater=-1; - holeradius=5; - wallthickness=2; - //aquify=-1; - floor=340; - floorvar=3; - break; - case pitTypeOPool: - pitdepth=5; - fillwater=-1; - holeradius=5; - wallthickness=2; - //aquify=-1; - floor=340; - floorvar=3; - break; - case pitTypeMagma: - stopatmagma=-1; - exposemagma=-1; - wallthickness=2; - fillmagma=-1; - floor=264; - break; - case pitTypeMPool: - pitdepth=5; - wallthickness=2; - fillmagma=-1; - floor=340; - floorvar=3; - break; - } - - - //Should tiles be revealed? - int reveal=0; - - - int accept = getyesno("Use default settings?",1); - - while ( !accept ) - { - //Pit Depth - pitdepth = getint( "Enter max depth (0 for bottom of map)", 0, INT_MAX, pitdepth ); - - //Hole Size - holeradius = getint( "Enter hole radius, 0 to 16", 0, 16, holeradius ); - - //Wall thickness - wallthickness = getint( "Enter wall thickness, 0 to 16", 0, 16, wallthickness ); - - //Obsidian Pillars - holepillar = getint( "Number of Obsidian Pillars in hole, 0 to 255", 0, 255, holepillar ); - wallpillar = getint( "Number of Obsidian Pillars in wall, 0 to 255", 0, 255, wallpillar ); - - //Open Hell? - exposehell=getyesno("Expose the pit to hell (no walls in hell)?",exposehell); - - //Stop when magma sea is hit? - stopatmagma=getyesno("Stop at magma sea?",stopatmagma); - exposemagma=getyesno("Expose magma sea (no walls in magma)?",exposemagma); - - //Fill? - fillmagma=getyesno("Fill with magma?",fillmagma); - if (fillmagma) aquify=fillwater=0; - fillwater=getyesno("Fill with water?",fillwater); - //aquify=getyesno("Aquifer?",aquify); - - - /////////////////////////////////////////////////////////////////////////////////////////////// - //Print settings. - //If a settings struct existed, this could be in a routine - printf("Using Settings:\n"); - printf("Pit Type......: %d = %s\n", pittype, pitTypeDesc[pittype]); - printf("Depth.........: %d\n", pitdepth); - printf("Hole Radius...: %d\n", holeradius); - printf("Wall Thickness: %d\n", wallthickness); - printf("Pillars, Hole.: %d\n", holepillar); - printf("Pillars, Wall.: %d\n", wallpillar); - printf("Expose Hell...: %c\n", (exposehell?'Y':'N') ); - printf("Stop at Magma.: %c\n", (stopatmagma?'Y':'N') ); - printf("Expose Magma..: %c\n", (exposemagma?'Y':'N') ); - printf("Magma Fill....: %c\n", (fillmagma?'Y':'N') ); - printf("Water Fill....: %c\n", (fillwater?'Y':'N') ); - printf("Aquifer.......: %c\n", (aquify?'Y':'N') ); - - accept = getyesno("Accept these settings?",1); - } - - - int64_t n; - uint32_t x_max,y_max,z_max; - - - //Pattern to dig - unsigned char pattern[16][16]; - - - for (int regen=1;regen; ) - { - regen=0; - - memset(pattern,0,sizeof(pattern)); - - //Calculate a randomized circle. - //These values found through experimentation. - int x=0, y=0, n=0; - - //Two concentric irregular circles - //Outer circle, solid. - if ( wallthickness ) - { - drawcircle(holeradius+wallthickness, pattern, 2); - } - //Inner circle, hole. - if ( holeradius ) - { - drawcircle(holeradius, pattern, 1); - } - - - //Post-process to be certain the wall totally encloses hole. - if (wallthickness) - { - for (y=0;y<16;++y) - { - for (x=0;x<16;++x) - { - if ( 1==pattern[x][y] ) - { - //No hole at edges. - if ( x<1 || x>14 || y<1 || y>14 ) - { - pattern[x][y]=2; - } - } - else if ( 0==pattern[x][y] ) - { - //check neighbors - checkneighbors( pattern , x,y, 1, 2); - } - } - } - } - - //Makes sure that somewhere random gets a vertical pillar of rock which is safe - //to dig stairs down, to permit access to anywhere within the pit from the top. - for (n=holepillar; n ; --n) - { - settileat( pattern , 1 , 3 , rand()&255 ); - } - for (n=wallpillar; n ; --n) - { - settileat( pattern , 2 , 3 , rand()&255 ); - } - - //Note: - //At this point, the pattern holds: - //0 for all tiles which will be ignored. - //1 for all tiles set to empty pit space. - //2 for all normal walls. - //3 for the straight obsidian top-to-bottom wall. - //4 is randomized between wall or floor (!not implemented!) - - printf("\nPattern:\n"); - const char patternkey[] = ".cW!?567890123"; - - //Print the pattern - for (y=0;y<16;++y) - { - for (x=0;x<16;++x) - { - cout << patternkey[ pattern[x][y] ]; - } - cout << endl; - } - cout << endl; - - regen = !getyesno("Acceptable Pattern?",1); - } - - //Post-process settings to fix problems here - if (pitdepth<1) - { - pitdepth=INT_MAX; - } - - - /////////////////////////////////////////////////////////////////////////////////////////////// - - - cerr << "Loading memory map..." << endl; - - //Connect to DF! - DFHack::ContextManager DFMgr("Memory.xml"); - DFHack::Context *DF = DFMgr.getSingleContext(); - - - - //Init - cerr << "Attaching to DF..." << endl; - try - { - DF->Attach(); - } - catch (exception& e) - { - cerr << e.what() << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - return 1; - } - - // init the map - DFHack::Maps *Mapz = DF->getMaps(); - if (!Mapz->Start()) - { - cerr << "Can't init map. Exiting." << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - return 1; - } - - Mapz->getSize(x_max,y_max,z_max); - - - //Get cursor - int32_t cursorX, cursorY, cursorZ; - DFHack::Gui *Gui = DF->getGui(); - Gui->getCursorCoords(cursorX,cursorY,cursorZ); - if (-30000==cursorX) - { - cout << "No cursor position found. Exiting." << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - return 1; - } - - //Block coordinates - int32_t bx=cursorX/16, by=cursorY/16, bz=cursorZ; - //Tile coordinates within block - int32_t tx=cursorX%16, ty=cursorY%16, tz=cursorZ; - - /* - //Access the DF interface to pause the game. - //Copied from the reveal tool. - DFHack::Gui *Gui =DF->getGui(); - cout << "Pausing..." << endl; - Gui->SetPauseState(true); - DF->Resume(); - waitmsec(1000); - DF->Suspend(); - */ - - //Verify that every z-level at this location exists. - for (int32_t Z = 0; Z<= bz ;Z++) - { - if ( ! Mapz->isValidBlock(bx,by,Z) ) - { - cout << "This block does't exist! Exiting." << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - return 1; - } - } - - //Get all the map features. - vector global_features; - if (!Mapz->ReadGlobalFeatures(global_features)) - { - cout << "Couldn't load global features! Probably a version problem." << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - return 1; - } - - std::map > local_features; - if (!Mapz->ReadLocalFeatures(local_features)) - { - cout << "Couldn't load local features! Probably a version problem." << endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - return 1; - } - - //Get info on current tile, to determine how to generate the pit - mapblock40d topblock; - Mapz->ReadBlock40d( bx, by, bz , &topblock ); - //Related block info - DFCoord pc(bx,by); - mapblock40d block; - const TileRow * tp; - t_designation * d; - - ////////////////////////////////////// - //From top to bottom, dig this thing. - ////////////////////////////////////// - - //Top level, cap. - //Might make this an option in the future - //For now, no wall means no cap. - if (wallthickness) - { - Mapz->ReadBlock40d( bx, by, bz , &block ); - for (uint32_t x=0;x<16;++x) - { - for (uint32_t y=0;y<16;++y) - { - if ( (pattern[x][y]>1) || (roof && pattern[x][y]) ) - { - tp = getTileRow(block.tiletypes[x][y]); - d = &block.designation[x][y]; - //Only modify this level if it's 'empty' - if ( EMPTY != tp->shape && RAMP_TOP != tp->shape && STAIR_DOWN != tp->shape && DFHack::BROOK_TOP != tp->shape) - { - continue; - } - - //Need a floor for empty space. - if (reveal) - { - d->bits.hidden = 0; //topblock.designation[x][y].bits.hidden; - } - //Always clear the dig designation. - d->bits.dig = designation_no; - //unlock fluids, so they fall down the pit. - d->bits.flow_forbid = d->bits.liquid_static=0; - block.blockflags.bits.liquid_1 = block.blockflags.bits.liquid_2 = 1; - //Remove aquifer, to prevent bugginess - d->bits.water_table=0; - //Set the tile. - block.tiletypes[x][y] = cap + rand()%4; - } - } - } - //Write the block. - Mapz->WriteBlockFlags(bx,by,bz, block.blockflags ); - Mapz->WriteDesignations(bx,by,bz, &block.designation ); - Mapz->WriteTileTypes(bx,by,bz, &block.tiletypes ); - Mapz->WriteDirtyBit(bx,by,bz,1); - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - //All levels in between. - int done=0; - uint32_t t,v; - int32_t z = bz-1; - int32_t bottom = max(0,bz-pitdepth-1); - assert( bottom>=0 && bottom<=bz ); - for ( ; !done && z>=bottom ; --z) - { - int watercount=0; - int magmacount=0; - int moltencount=0; - int rockcount=0; - int veincount=0; - int emptycount=0; - int hellcount=0; - int templecount=0; - int adamcount=0; - int featcount=0; - int tpat; - - cout << z << endl; - assert( Mapz->isValidBlock(bx,by,z) ); - if (!Mapz->ReadBlock40d( bx, by, z , &block )) - { - cout << "Bad block! " << bx << "," << by << "," << z << endl; - } - - //Pre-process this z-level, to get some tile statistics. - for (int32_t x=0;x<16;++x) - { - for (int32_t y=0;y<16;++y) - { - t=0; - tp = getTileRow(block.tiletypes[x][y]); - d = &block.designation[x][y]; - tpat=pattern[x][y]; - - //Tile type material categories - switch ( tp->material ) - { - case AIR: - ++emptycount; - break; - case MAGMA: - ++moltencount; - break; - case VEIN: - ++veincount; - break; - case FEATSTONE: - case HFS: - case OBSIDIAN: - //basicly, ignored. - break; - default: - if ( EMPTY == tp->shape || RAMP_TOP == tp->shape || STAIR_DOWN == tp->shape ) - { - ++emptycount; - } - else - { - ++rockcount; - } - break; - } - - //Magma and water - if ( d->bits.flow_size ) - { - if (d->bits.liquid_type) - { - ++magmacount; - } - else - { - ++watercount; - } - } - - - //Check for Features - if ( block.local_feature > -1 || block.global_feature > -1 ) - { - //Count tiles which actually are in the feature. - //It is possible for a block to have a feature, but no tiles to be feature. - if ( d->bits.feature_global || d->bits.feature_local ) - { - //All features - ++featcount; - - if ( d->bits.feature_global && d->bits.feature_local ) - { - cout << "warn:tile is global and local at same time!" << endl; - } - - n=0; - if ( block.global_feature > -1 && d->bits.feature_global ) - { - n=global_features[block.global_feature].type; - switch ( n ) - { - case feature_Other: - //no count - break; - case feature_Adamantine_Tube: - ++adamcount; - break; - case feature_Underworld: - ++hellcount; - break; - case feature_Hell_Temple: - ++templecount; - break; - default: - //something here. for debugging, it may be interesting to know. - if (n) cout << '(' << n << ')'; - } - } - - n=0; - if ( block.local_feature > -1 && d->bits.feature_local ) - { - n=local_features[pc][block.local_feature]->type; - switch ( n ) - { - case feature_Other: - //no count - break; - case feature_Adamantine_Tube: - ++adamcount; - break; - case feature_Underworld: - ++hellcount; - break; - case feature_Hell_Temple: - ++templecount; - break; - default: - //something here. for debugging, it may be interesting to know. - if (n) cout << '[' << n << ']'; - } - } - } - } - } - } - - - //If stopping at magma, and no no non-feature stone in this layer, and magma found, then we're either at - //or below the magma sea / molten rock. - if ( stopatmagma && (moltencount || magmacount) && (!exposemagma || !rockcount) ) - { - //If not exposing magma, quit at the first sign of magma. - //If exposing magma, quite once magma is exposed. - done=-1; - } - - - ///////////////////////////////////////////////////////////////////////////////////////////////// - //Some checks, based on settings and stats collected - //First check, are we at illegal depth? - if ( !done && hellcount && stopatmagma ) - { - //Panic! - done=-1; - tpat=0; - cout << "error: illegal breach of hell!" << endl; - } - - ///////////////////////////////////////////////////////////////////////////////////////////////// - //Actually process the current z-level. - //These loops do the work. - for (int32_t x=0;!done && x<16;++x) - { - for (int32_t y=0;!done && y<16;++y) - { - t=0; - tp = getTileRow(block.tiletypes[x][y]); - d = &block.designation[x][y]; - tpat=pattern[x][y]; - - //Up front, remove aquifer, to prevent bugginess - //It may be added back if aquify is set. - d->bits.water_table=0; - - //Change behaviour based on settings and stats from this z-level - - //In hell? - if ( tpat && tpat!=3 && isfeature(global_features, local_features,block,pc,x,y,feature_Underworld ) ) - { - if ( exposehell ) - { - tpat=0; - } - } - - //Expose magma? - if ( tpat && tpat!=3 && exposemagma ) - { - //Leave certain tiles unchanged. - switch ( tp->material ) - { - case HFS: - case FEATSTONE: - case MAGMA: - tpat=0; - default: - break; - } - //Adamantine may be left unchanged... - if ( isfeature(global_features, local_features,block,pc,x,y,feature_Adamantine_Tube ) ) - { - tpat=0; - } - //Leave magma sea unchanged. - if ( d->bits.flow_size && d->bits.liquid_type) - { - tpat=0; - } - } - - - //For all situations... - //Special modification for walls, always for adamantine. - if ( isfeature(global_features, local_features,block,pc,x,y,feature_Adamantine_Tube ) ) - { - if ( 2==pattern[x][y] || 3==pattern[x][y] ) - { - tpat=2; - } - } - - - //Border or space? - switch (tpat) - { - case 0: - continue; - break; - case 1: - //Empty Space - t=32; - //d->bits.light = topblock.designation[x][y].bits.light; - //d->bits.skyview = topblock.designation[x][y].bits.skyview; - //d->bits.subterranean = topblock.designation[x][y].bits.subterranean; - - //Erase special markers? - //d->bits.feature_global = d->bits.feature_local = 0; - - //Water? Magma? - if (fillmagma || fillwater) - { - d->bits.flow_size=7; - d->bits.water_stagnant = false; - d->bits.water_salt = false; - if (fillmagma) - { - d->bits.liquid_type=liquid_magma; - } - else - { - d->bits.liquid_type=liquid_water; - } - } - else - { - //Otherwise, remove all liquids. - d->bits.flow_size=0; - d->bits.water_stagnant = false; - d->bits.water_salt = false; - d->bits.liquid_type = liquid_water; - } - - break; - case 2: - //Wall. - //First guess based on current material - switch ( tp->material ) - { - case OBSIDIAN: - t=wmagma; - break; - case MAGMA: - t=wmolten; - break; - case HFS: - //t=whell; - break; - case VEIN: - t=440; //Solid vein block - break; - case FEATSTONE: - t=335; //Solid feature stone block - break; - default: - t=wcave; - } - //Adamantine (a local feature) trumps veins. - { - //Local Feature? - if ( block.local_feature > -1 ) - { - switch ( n=local_features[pc][block.local_feature]->type ) - { - case feature_Underworld: - case feature_Hell_Temple: - //Only adopt these if there is no global feature present - if ( block.global_feature >-1 ) - { - break; - } - case feature_Adamantine_Tube: - //Always for adamantine, sometimes for others - //Whatever the feature is made of. "featstone wall" - d->bits.feature_global = 0; - d->bits.feature_local = 1; - t=335; - break; - } - } - //Global Feature? - else if (block.global_feature > -1 && !d->bits.feature_local ) - { - switch ( n=global_features[block.global_feature].type ) - { - case feature_Adamantine_Tube: - case feature_Underworld: - case feature_Hell_Temple: - //Whatever the feature is made of. "featstone wall" - d->bits.feature_global = 1; - t=335; - break; - } - } - } - - //Erase any liquids, as they cause problems. - d->bits.flow_size=0; - d->bits.water_stagnant = false; - d->bits.water_salt = false; - d->bits.liquid_type=liquid_water; - - //Placing an aquifer? - //(bugged, these aquifers don't generate water!) - if ( aquify ) - { - //Only normal stone types can be aquified - if ( tp->material!=MAGMA && tp->material!=FEATSTONE && tp->material!=HFS ) - { - //Only place next to the hole. - //If no hole, place in middle. - if ( checkneighbors(pattern,x,y,1) || (7==x && 7==y) ) - { - d->bits.water_table = 1; - //t=265; //soil wall - } - } - } - break; - case 3: - ////No obsidian walls on bottom of map! - //if(z<1 && (d->bits.feature_global || d->bits.feature_local) ) { - // t=335; - //} - - //Special wall, always sets to obsidian, to give a stairway - t=331; - - //Erase special markers - d->bits.feature_global = d->bits.feature_local = 0; - - //Erase any liquids, as they cause problems. - d->bits.flow_size=0; - d->bits.water_stagnant = false; - d->bits.water_salt = false; - d->bits.liquid_type=liquid_water; - break; - default: - cout << ".error,bad pattern."; - } - - //For all tiles. - if (reveal) - { - d->bits.hidden = 0; //topblock.designation[x][y].bits.hidden; - } - //Always clear the dig designation. - d->bits.dig=designation_no; - //Make it underground, because it is capped - d->bits.subterranean=1; - d->bits.light=0; - d->bits.skyview=0; - //unlock fluids, so they fall down the pit. - d->bits.flow_forbid = d->bits.liquid_static=0; - block.blockflags.bits.liquid_1 = block.blockflags.bits.liquid_2 = 1; - //Set the tile. - block.tiletypes[x][y] = t; - - } - } - - //Write the block. - Mapz->WriteBlockFlags(bx,by,z, block.blockflags ); - Mapz->WriteDesignations(bx,by,z, &block.designation ); - Mapz->WriteTileTypes(bx,by,z, &block.tiletypes ); - Mapz->WriteDirtyBit(bx,by,z,1); - - } - - //Re-process the last z-level handled above. - z++; - assert( z>=0 ); - - - /////////////////////////////////////////////////////////////////////////////////////////////// - //The bottom level is special. - if (-1) - { - if (!Mapz->ReadBlock40d( bx, by, z , &block )) - { - cout << "Bad block! " << bx << "," << by << "," << z << endl; - } - for (uint32_t x=0;x<16;++x) - { - for (uint32_t y=0;y<16;++y) - { - t=floor; - v=floorvar; - tp = getTileRow(block.tiletypes[x][y]); - d = &block.designation[x][y]; - - if ( exposehell ) - { - //Leave hell tiles unchanged when exposing hell. - if ( isfeature(global_features,local_features,block,pc,x,y,feature_Underworld) ) - { - continue; - } - } - - //Does expose magma need anything at this level? - if ( exposemagma && stopatmagma ) - { - continue; - } - - switch (pattern[x][y]) - { - case 0: - continue; - break; - case 1: - //Empty becomes floor. - - //Base floor type on the z-level first, features, then tile type. - if (!z) { - //Bottom of map, use the floor specified, always. - break; - } - - ////Only place floor where ground is already solid when exposing - //if( EMPTY == tp->c || RAMP_TOP == tp->c || STAIR_DOWN == tp->c ){ - // continue; - //} - - if ( d->bits.feature_global || d->bits.feature_global ) { - //Feature Floor! - t=344; - break; - } - - //Tile material check. - switch ( tp->material ) - { - case OBSIDIAN: - t=340; - v=3; - break; - case MAGMA: - v=0; - t=264; //magma flow - break; - case HFS: - //should only happen at bottom of map - break; - case VEIN: - t=441; //vein floor - v=3; - break; - case FEATSTONE: - t=344; - v=3; - break; - } - - break; - case 2: - case 3: - //Walls already drawn. - //Ignore. - continue; - break; - } - - //For all tiles. - if (reveal) d->bits.hidden = 0; //topblock.designation[x][y].bits.hidden; - //Always clear the dig designation. - d->bits.dig=designation_no; - //unlock fluids - d->bits.flow_forbid = d->bits.liquid_static=0; - block.blockflags.bits.liquid_1 = block.blockflags.bits.liquid_2 = 1; - - //Set the tile. - block.tiletypes[x][y] = t + ( v ? rand()&v : 0 ); - } - } - //Write the block. - Mapz->WriteBlockFlags(bx,by,z, block.blockflags ); - Mapz->WriteDesignations(bx,by,z, &block.designation ); - Mapz->WriteTileTypes(bx,by,z, &block.tiletypes ); - Mapz->WriteDirtyBit(bx,by,z,1); - } - - DF->Detach(); -#ifndef LINUX_BUILD - cout << "Done. Press any key to continue" << endl; - cin.ignore(); -#endif - return 0; -} diff --git a/scripts/hfs-pit.lua b/scripts/hfs-pit.lua new file mode 100644 index 000000000..9592d0a31 --- /dev/null +++ b/scripts/hfs-pit.lua @@ -0,0 +1,88 @@ +-- Creates a pit under the target leading straight to the Underworld. Type '?' for help. +-- Based on script by IndigoFenix, @ https://gist.github.com/IndigoFenix/8776696 + +args={...} + +if args[1] == '?' then + print("Example usage: 'hfs-pit 2 1 1'") + print("First parameter is size of the pit in all directions.") + print("Second parameter is 1 to wall off the sides of the pit on all layers except the underworld, or anything else to leave them open.") + print("Third parameter is 1 to add stairs. Stairs are buggy; they will not reveal the bottom until you dig somewhere, but underworld creatures will path in.") + print("If no arguments are given, the default is 'hfs-pit 1 0 0', ie single-tile wide with no walls or stairs.") + return +end + +pos = copyall(df.global.cursor) +size = tonumber(args[1]) +if size == nil or size < 1 then size = 1 end + +wallOff = tonumber(args[2]) +stairs = tonumber(args[3]) + +--Get the layer of the underworld +for index,value in ipairs(df.global.world.cur_savegame.map_features) do + local featureType=value:getType() + if featureType==9 then --Underworld + underworldLayer = value.layer + end +end + +if pos.x==-30000 then + qerror("Select a location by placing the cursor") +end +local x = 0 +local y = 0 +for x=pos.x-size,pos.x+size,1 do + for y=pos.y-size,pos.y+size,1 do + z=1 + local hitAir = false + local hitCeiling = false + while z <= pos.z do + local block = dfhack.maps.ensureTileBlock(x,y,z) + if block then + if block.tiletype[x%16][y%16] ~= 335 then + hitAir = true + end + if hitAir == true then + if not hitCeiling then + if block.global_feature ~= underworldLayer or z > 10 then hitCeiling = true end + if stairs == 1 and x == pos.x and y == pos.y then + if block.tiletype[x%16][y%16] == 32 then + if z == pos.z then + block.tiletype[x%16][y%16] = 56 + else + block.tiletype[x%16][y%16] = 55 + end + else + block.tiletype[x%16][y%16] = 57 + end + end + end + if hitCeiling == true then + if block.designation[x%16][y%16].flow_size > 0 or wallOff == 1 then needsWall = true else needsWall = false end + if (x == pos.x-size or x == pos.x+size or y == pos.y-size or y == pos.y+size) and z==pos.z then + --Do nothing, this is the lip of the hole + elseif x == pos.x-size and y == pos.y-size then if needsWall == true then block.tiletype[x%16][y%16]=320 end + elseif x == pos.x-size and y == pos.y+size then if needsWall == true then block.tiletype[x%16][y%16]=321 end + elseif x == pos.x+size and y == pos.y+size then if needsWall == true then block.tiletype[x%16][y%16]=322 end + elseif x == pos.x+size and y == pos.y-size then if needsWall == true then block.tiletype[x%16][y%16]=323 end + elseif x == pos.x-size or x == pos.x+size then if needsWall == true then block.tiletype[x%16][y%16]=324 end + elseif y == pos.y-size or y == pos.y+size then if needsWall == true then block.tiletype[x%16][y%16]=325 end + elseif stairs == 1 and x == pos.x and y == pos.y then + if z == pos.z then block.tiletype[x%16][y%16]=56 + else block.tiletype[x%16][y%16]=55 end + else block.tiletype[x%16][y%16]=32 + end + block.designation[x%16][y%16].hidden = false + --block.designation[x%16][y%16].liquid_type = true -- if true, magma. if false, water. + block.designation[x%16][y%16].flow_size = 0 + dfhack.maps.enableBlockUpdates(block) + block.designation[x%16][y%16].flow_forbid = false + end + end + block.designation[x%16][y%16].hidden = false + end + z = z+1 + end + end +end \ No newline at end of file From d95c0878e10498e1e91daec68ae41c1e48c24fe4 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Wed, 24 Dec 2014 22:10:04 +1100 Subject: [PATCH 051/115] fix -info option to export info instead of detailed maps (oops!) --- scripts/exportlegends.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua index 03f7aa6fb..ccfa09735 100644 --- a/scripts/exportlegends.lua +++ b/scripts/exportlegends.lua @@ -81,7 +81,7 @@ if dfhack.gui.getCurFocus() == "legends" then export_site_maps() wait_for_legends_vs() elseif args[1] == "info" then - wait_for_legends_vs() + export_legends_info() elseif args[1] == "maps" then wait_for_legends_vs() elseif args[1] == "sites" then From 5a404333cad34caeabb39e30784eb0b930c36bea Mon Sep 17 00:00:00 2001 From: Lethosor Date: Fri, 26 Dec 2014 12:39:05 -0500 Subject: [PATCH 052/115] Update devel/export-dt-ini for 0.40.21+ https://github.com/splintermind/Dwarf-Therapist/commit/356ae80 --- scripts/devel/export-dt-ini.lua | 41 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/scripts/devel/export-dt-ini.lua b/scripts/devel/export-dt-ini.lua index 723726150..41a2457ce 100644 --- a/scripts/devel/export-dt-ini.lua +++ b/scripts/devel/export-dt-ini.lua @@ -269,6 +269,7 @@ address('shape_name_plural',df.descriptor_shape,'name_plural') header('health_offsets') address('parent_id',df.body_part_raw,'con_part_id') +address('body_part_flags',df.body_part_raw,'flags') address('layers_vector',df.body_part_raw,'layers') address('number',df.body_part_raw,'number') address('names_vector',df.body_part_raw,'name_singular') @@ -428,27 +429,25 @@ out:write[[ size=0 [invalid_flags_1] -size=10 -1\name=a zombie -1\value=0x00001000 -2\name=a skeleton -2\value=0x00002000 -3\name=a merchant -3\value=0x00000040 -4\name=outpost liason or diplomat -4\value=0x00000800 +size=9 +1\name=a skeleton +1\value=0x00002000 +2\name=a merchant +2\value=0x00000040 +3\name=outpost liason or diplomat +3\value=0x00000800 +4\name=an invader or hostile +4\value=0x00020000 5\name=an invader or hostile -5\value=0x00020000 -6\name=an invader or hostile -6\value=0x00080000 -7\name=resident, invader or ambusher -7\value=0x00600000 -8\name=part of a merchant caravan -8\value=0x00000080 -9\name="Dead, Jim." -9\value=0x00000002 -10\name=marauder -10\value=0x00000010 +5\value=0x00080000 +6\name=resident, invader or ambusher +6\value=0x00600000 +7\name=part of a merchant caravan +7\value=0x00000080 +8\name="Dead, Jim." +8\value=0x00000002 +9\name=marauder +9\value=0x00000010 [invalid_flags_2] size=5 @@ -469,4 +468,4 @@ size=1 1\value=0x00001000 ]] -out:close() \ No newline at end of file +out:close() From efc42c253eb75b6cd3bfd02728a58bdb37d93ab6 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Sat, 27 Dec 2014 11:03:06 -0500 Subject: [PATCH 053/115] Fix remaining issues with mousequery --- plugins/mousequery.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 4f27b504b..47b774d1a 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -445,7 +445,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest sendKey(interface_key::CURSOR_DOWN_FAST); } } - else if (input->count(interface_key::CUSTOM_M) && isInDesignationMenu()) + else if (input->count(interface_key::CUSTOM_ALT_M) && isInDesignationMenu()) { box_designation_enabled = !box_designation_enabled; } @@ -580,9 +580,9 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (isInDesignationMenu()) { int x = left_margin; - int y = 24; - OutputString(COLOR_BROWN, x, y, "DFHack MouseQuery", true, left_margin); - OutputToggleString(x, y, "Box Select", "m", box_designation_enabled, true, left_margin); + int y = gps->dimy - 2; + OutputToggleString(x, y, "Box Select", "Alt+M", box_designation_enabled, + true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); } //Display selection dimensions From 3c0ac70615240879c083b6e611675d7576c2ba91 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Sat, 27 Dec 2014 15:05:43 -0500 Subject: [PATCH 054/115] Remove output from filetype() Not sure how this made it in, but it's causing problems with PRINT_MODE:TEXT (not to mention filling up stdout.log) --- library/modules/Filesystem.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index be1668bcc..4d15400d9 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -130,7 +130,6 @@ _filetype DFHack::Filesystem::filetype (std::string path) { STAT_STRUCT info; DFHack::Filesystem::stat(path, info); - std::cout << info.st_mode << std::endl; return mode2type(info.st_mode); } From 9473f3b28ee04c86efcbf470819a7e5e465f0419 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Sun, 28 Dec 2014 21:46:17 -0500 Subject: [PATCH 055/115] Point Homebrew users to a GCC 4.5 formula that works on 10.10 --- Compile.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Compile.rst b/Compile.rst index 9e301239e..9a6d2f8a2 100644 --- a/Compile.rst +++ b/Compile.rst @@ -106,9 +106,10 @@ If you are building on 10.6, please read the subsection below titled "Snow Leopa Option 2: Using Homebrew: * `Install Homebrew `_ and run: + * ``brew tap lethosor/gcc`` * ``brew install git`` * ``brew install cmake`` - * ``brew install gcc45 --enable-multilib`` + * ``brew install lethosor/gcc/gcc45 --enable-multilib`` 5. Install perl dependencies From a1c9f85a64ed82d2dad7e914169d44731a89354b Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 28 Dec 2014 21:43:48 -0700 Subject: [PATCH 056/115] Repairing the stuck Alt key SDL has been reporting the modifier key state incorrectly after alt-tabbing between the DF and DFHack windows. Fixes issue #448, though more testing is warranted. --- library/Core.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index 29aba234e..19e2c4345 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1579,6 +1579,8 @@ int UnicodeAwareSym(const SDL::KeyboardEvent& ke) //MEMO: return false if event is consumed int Core::DFH_SDL_Event(SDL::Event* ev) { + static bool alt = 0; + // do NOT process events before we are ready. if(!started) return true; if(!ev) @@ -1587,6 +1589,11 @@ int Core::DFH_SDL_Event(SDL::Event* ev) { auto ke = (SDL::KeyboardEvent *)ev; + if (ke->ksym.sym == SDL::K_LALT || ke->ksym.sym == SDL::K_RALT) + { + alt = (ev->type == SDL::ET_KEYDOWN); + } + else if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym]) { hotkey_states[ke->ksym.sym] = true; @@ -1594,7 +1601,7 @@ int Core::DFH_SDL_Event(SDL::Event* ev) int mod = 0; if (ke->ksym.mod & SDL::KMOD_SHIFT) mod |= 1; if (ke->ksym.mod & SDL::KMOD_CTRL) mod |= 2; - if (ke->ksym.mod & SDL::KMOD_ALT) mod |= 4; + if (alt) mod |= 4; // Use unicode so Windows gives the correct value for the // user's Input Language From e0a8cc8537796343032df4bcc0c6a1ec8c020378 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 29 Dec 2014 10:31:12 -0500 Subject: [PATCH 057/115] Merge PlugLoad-linux.cpp and PlugLoad-darwin.cpp --- library/CMakeLists.txt | 6 +-- library/PlugLoad-linux.cpp | 44 ------------------- ...PlugLoad-darwin.cpp => PlugLoad-posix.cpp} | 4 +- 3 files changed, 5 insertions(+), 49 deletions(-) delete mode 100644 library/PlugLoad-linux.cpp rename library/{PlugLoad-darwin.cpp => PlugLoad-posix.cpp} (93%) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index e0ad3606b..242950700 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -87,15 +87,15 @@ ENDIF() SET(MAIN_SOURCES_LINUX Console-posix.cpp Hooks-linux.cpp -PlugLoad-linux.cpp +PlugLoad-posix.cpp Process-linux.cpp ) SET(MAIN_SOURCES_DARWIN Console-posix.cpp -PlugLoad-darwin.cpp -Process-darwin.cpp Hooks-darwin.cpp +PlugLoad-posix.cpp +Process-darwin.cpp ) SET(MAIN_SOURCES_LINUX_EGGY diff --git a/library/PlugLoad-linux.cpp b/library/PlugLoad-linux.cpp deleted file mode 100644 index 69945c6f4..000000000 --- a/library/PlugLoad-linux.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "DFHack.h" -#include "PluginManager.h" -#include "Hooks.h" -#include - -/* - * Plugin loading functions - */ -namespace DFHack -{ - DFLibrary * OpenPlugin (const char * filename) - { - dlerror(); - DFLibrary * ret = (DFLibrary *) dlopen(filename, RTLD_NOW); - if(!ret) - { - std::cerr << dlerror() << std::endl; - } - return ret; - } - void * LookupPlugin (DFLibrary * plugin ,const char * function) - { - return (DFLibrary *) dlsym((void *)plugin, function); - } - void ClosePlugin (DFLibrary * plugin) - { - dlclose((void *) plugin); - } -} \ No newline at end of file diff --git a/library/PlugLoad-darwin.cpp b/library/PlugLoad-posix.cpp similarity index 93% rename from library/PlugLoad-darwin.cpp rename to library/PlugLoad-posix.cpp index 69945c6f4..25bb4170a 100644 --- a/library/PlugLoad-darwin.cpp +++ b/library/PlugLoad-posix.cpp @@ -35,10 +35,10 @@ namespace DFHack } void * LookupPlugin (DFLibrary * plugin ,const char * function) { - return (DFLibrary *) dlsym((void *)plugin, function); + return (void *) dlsym((void *)plugin, function); } void ClosePlugin (DFLibrary * plugin) { dlclose((void *) plugin); } -} \ No newline at end of file +} From cade0d9723cfa980dacb401f07153aa9ffb15333 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 31 Dec 2014 20:39:24 -0500 Subject: [PATCH 058/115] Add local area mouse controls to embark-tools --- plugins/embark-tools.cpp | 386 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 360 insertions(+), 26 deletions(-) diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 44a79f2be..13884dc2d 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -3,6 +3,7 @@ #include "DataDefs.h" #include "Export.h" #include "PluginManager.h" +#include "MiscUtils.h" #include "modules/Screen.h" #include "modules/Gui.h" @@ -16,6 +17,8 @@ #include "df/interface_key.h" using namespace DFHack; +using df::global::enabler; +using df::global::gps; #define FOR_ITER_TOOLS(iter) for(auto iter = tools.begin(); iter != tools.end(); iter++) @@ -34,17 +37,36 @@ void update_embark_sidebar (df::viewscreen_choose_start_sitest * screen) } } +void get_embark_pos (df::viewscreen_choose_start_sitest * screen, + int& x1, int& x2, int& y1, int& y2, int& w, int& h) +{ + x1 = screen->location.embark_pos_min.x, + x2 = screen->location.embark_pos_max.x, + y1 = screen->location.embark_pos_min.y, + y2 = screen->location.embark_pos_max.y, + w = x2 - x1 + 1, + h = y2 - y1 + 1; +} + +void set_embark_pos (df::viewscreen_choose_start_sitest * screen, + int x1, int x2, int y1, int y2) +{ + screen->location.embark_pos_min.x = x1; + screen->location.embark_pos_max.x = x2; + screen->location.embark_pos_min.y = y1; + screen->location.embark_pos_max.y = y2; +} + +#define GET_EMBARK_POS(screen, a, b, c, d, e, f) \ + int a, b, c, d, e, f; \ + get_embark_pos(screen, a, b, c, d, e, f); + void resize_embark (df::viewscreen_choose_start_sitest * screen, int dx, int dy) { /* Reproduces DF's embark resizing functionality * Local area resizes up and to the right, unless it's already touching the edge */ - int x1 = screen->location.embark_pos_min.x, - x2 = screen->location.embark_pos_max.x, - y1 = screen->location.embark_pos_min.y, - y2 = screen->location.embark_pos_max.y, - width = x2 - x1 + dx, - height = y2 - y1 + dy; + GET_EMBARK_POS(screen, x1, x2, y1, y2, width, height); if (x1 == x2 && dx == -1) dx = 0; if (y1 == y2 && dy == -1) @@ -66,11 +88,7 @@ void resize_embark (df::viewscreen_choose_start_sitest * screen, int dx, int dy) } y2 = std::min(15, y2); - screen->location.embark_pos_min.x = x1; - screen->location.embark_pos_max.x = x2; - screen->location.embark_pos_min.y = y1; - screen->location.embark_pos_max.y = y2; - + set_embark_pos(screen, x1, x2, y1, y2); update_embark_sidebar(screen); } @@ -96,6 +114,7 @@ public: virtual void after_render(start_sitest* screen) { }; virtual void before_feed(start_sitest* screen, ikey_set* input, bool &cancel) { }; virtual void after_feed(start_sitest* screen, ikey_set* input) { }; + virtual void after_mouse_event(start_sitest* screen) { }; }; std::vector tools; @@ -320,6 +339,294 @@ public: }; }; +class MouseControl : public EmbarkTool +{ +protected: + // Used for event handling + int prev_x; + int prev_y; + bool prev_lbut; + // Used for controls + bool base_max_x; + bool base_max_y; + bool in_local_move; + bool in_local_edge_resize_x; + bool in_local_edge_resize_y; + bool in_local_corner_resize; + // These keep track of where the embark location would be (i.e. if + // the mouse is dragged out of the local embark area), to prevent + // the mouse from moving the actual area when it's 20+ tiles away + int local_overshoot_x1; + int local_overshoot_x2; + int local_overshoot_y1; + int local_overshoot_y2; + inline bool in_local_adjust() + { + return in_local_move || in_local_edge_resize_x || in_local_edge_resize_y || + in_local_corner_resize; + } + void lbut_press(start_sitest* screen, bool pressed, int x, int y) + { + GET_EMBARK_POS(screen, x1, x2, y1, y2, width, height); + in_local_move = in_local_edge_resize_x = in_local_edge_resize_y = + in_local_corner_resize = false; + if (pressed) + { + if (x >= 1 && x <= 16 && y >= 2 && y <= 17) + { + // Local embark - translate to local map coordinates + x -= 1; + y -= 2; + if ((x == x1 || x == x2) && (y == y1 || y == y2)) + { + in_local_corner_resize = true; + base_max_x = (x == x2); + base_max_y = (y == y2); + } + else if (x == x1 || x == x2) + { + in_local_edge_resize_x = true; + base_max_x = (x == x2); + base_max_y = false; + } + else if (y == y1 || y == y2) + { + in_local_edge_resize_y = true; + base_max_x = false; + base_max_y = (y == y2); + } + else if (x > x1 && x < x2 && y > y1 && y < y2) + { + in_local_move = true; + base_max_x = base_max_y = false; + local_overshoot_x1 = x1; + local_overshoot_x2 = x2; + local_overshoot_y1 = y1; + local_overshoot_y2 = y2; + } + } + } + update_embark_sidebar(screen); + } + void mouse_move(start_sitest* screen, int x, int y) + { + GET_EMBARK_POS(screen, x1, x2, y1, y2, width, height); + if (x == -1 && prev_x > (2 + 16)) + { + x = gps->dimx; + gps->mouse_x = x - 1; + } + if (y == -1 && prev_y > (1 + 16)) + { + y = gps->dimy; + gps->mouse_y = y - 1; + } + if (in_local_corner_resize || in_local_edge_resize_x || in_local_edge_resize_y) + { + x -= 1; + y -= 2; + } + if (in_local_corner_resize) + { + x = std::max(0, std::min(15, x)); + y = std::max(0, std::min(15, y)); + if (base_max_x) + x2 = x; + else + x1 = x; + if (base_max_y) + y2 = y; + else + y1 = y; + if (x1 > x2) + { + std::swap(x1, x2); + base_max_x = !base_max_x; + } + if (y1 > y2) + { + std::swap(y1, y2); + base_max_y = !base_max_y; + } + } + else if (in_local_edge_resize_x) + { + x = std::max(0, std::min(15, x)); + if (base_max_x) + x2 = x; + else + x1 = x; + if (x1 > x2) + { + std::swap(x1, x2); + base_max_x = !base_max_x; + } + } + else if (in_local_edge_resize_y) + { + y = std::max(0, std::min(15, y)); + if (base_max_y) + y2 = y; + else + y1 = y; + if (y1 > y2) + { + std::swap(y1, y2); + base_max_y = !base_max_y; + } + } + else if (in_local_move) + { + int dx = x - prev_x; + int dy = y - prev_y; + local_overshoot_x1 += dx; + local_overshoot_x2 += dx; + local_overshoot_y1 += dy; + local_overshoot_y2 += dy; + if (local_overshoot_x1 < 0) + { + x1 = 0; + x2 = width - 1; + } + else if (local_overshoot_x2 > 15) + { + x1 = 15 - (width - 1); + x2 = 15; + } + else + { + x1 = local_overshoot_x1; + x2 = local_overshoot_x2; + } + if (local_overshoot_y1 < 0) + { + y1 = 0; + y2 = height - 1; + } + else if (local_overshoot_y2 > 15) + { + y1 = 15 - (height - 1); + y2 = 15; + } + else + { + y1 = local_overshoot_y1; + y2 = local_overshoot_y2; + } + } + set_embark_pos(screen, x1, x2, y1, y2); + } +public: + MouseControl() + :EmbarkTool(), + prev_x(0), + prev_y(0), + prev_lbut(false), + base_max_x(false), + base_max_y(false), + in_local_move(false), + in_local_edge_resize_x(false), + in_local_edge_resize_y(false), + in_local_corner_resize(false), + local_overshoot_x1(0), + local_overshoot_x2(0), + local_overshoot_y1(0), + local_overshoot_y2(0) + { } + virtual std::string getId() { return "mouse"; } + virtual std::string getName() { return "Mouse control"; } + virtual std::string getDesc() { return "Implements mouse controls on the embark screen"; } + virtual df::interface_key getToggleKey() { return df::interface_key::CUSTOM_M; } + virtual void after_render(start_sitest* screen) + { + GET_EMBARK_POS(screen, x1, x2, y1, y2, width, height); + int mouse_x = gps->mouse_x, mouse_y = gps->mouse_y; + int local_x = prev_x - 1; + int local_y = prev_y - 2; + if (local_x >= x1 && local_x <= x2 && local_y >= y1 && local_y <= y2) + { + int screen_x1 = x1 + 1; + int screen_x2 = x2 + 1; + int screen_y1 = y1 + 2; + int screen_y2 = y2 + 2; + UIColor fg = in_local_adjust() ? COLOR_GREY : COLOR_DARKGREY; + Screen::Pen corner_ul = Screen::Pen((char)201, fg, COLOR_BLACK); + Screen::Pen corner_ur = Screen::Pen((char)187, fg, COLOR_BLACK); + Screen::Pen corner_dl = Screen::Pen((char)200, fg, COLOR_BLACK); + Screen::Pen corner_dr = Screen::Pen((char)188, fg, COLOR_BLACK); + Screen::Pen border_ud = Screen::Pen((char)205, fg, COLOR_BLACK); + Screen::Pen border_lr = Screen::Pen((char)186, fg, COLOR_BLACK); + if (in_local_corner_resize || + ((local_x == x1 || local_x == x2) && (local_y == y1 || local_y == y2))) + { + if (local_x == x1 && local_y == y1) + Screen::paintTile(corner_ul, screen_x1, screen_y1); + else if (local_x == x2 && local_y == y1) + Screen::paintTile(corner_ur, screen_x2, screen_y1); + else if (local_x == x1 && local_y == y2) + Screen::paintTile(corner_dl, screen_x1, screen_y2); + else if (local_x == x2 && local_y == y2) + Screen::paintTile(corner_dr, screen_x2, screen_y2); + } + else if (in_local_edge_resize_x || local_x == x1 || local_x == x2) + { + if ((in_local_edge_resize_x && !base_max_x) || local_x == x1) + { + Screen::paintTile(corner_ul, screen_x1, screen_y1); + for (int i = screen_y1 + 1; i <= screen_y2 - 1; ++i) + Screen::paintTile(border_lr, screen_x1, i); + Screen::paintTile(corner_dl, screen_x1, screen_y2); + } + else + { + Screen::paintTile(corner_ur, screen_x2, screen_y1); + for (int i = screen_y1 + 1; i <= screen_y2 - 1; ++i) + Screen::paintTile(border_lr, screen_x2, i); + Screen::paintTile(corner_dr, screen_x2, screen_y2); + } + } + else if (in_local_edge_resize_y || local_y == y1 || local_y == y2) + { + if ((in_local_edge_resize_y && !base_max_y) || local_y == y1) + { + Screen::paintTile(corner_ul, screen_x1, screen_y1); + for (int i = screen_x1 + 1; i <= screen_x2 - 1; ++i) + Screen::paintTile(border_ud, i, screen_y1); + Screen::paintTile(corner_ur, screen_x2, screen_y1); + } + else + { + Screen::paintTile(corner_dl, screen_x1, screen_y2); + for (int i = screen_x1 + 1; i <= screen_x2 - 1; ++i) + Screen::paintTile(border_ud, i, screen_y2); + Screen::paintTile(corner_dr, screen_x2, screen_y2); + } + } + else + { + Screen::paintTile(corner_ul, screen_x1, screen_y1); + Screen::paintTile(corner_ur, screen_x2, screen_y1); + Screen::paintTile(corner_dl, screen_x1, screen_y2); + Screen::paintTile(corner_dr, screen_x2, screen_y2); + } + } + } + virtual void after_mouse_event(start_sitest* screen) + { + if (enabler->mouse_lbut != prev_lbut) + { + lbut_press(screen, enabler->mouse_lbut, gps->mouse_x, gps->mouse_y); + } + if (gps->mouse_x != prev_x || gps->mouse_y != prev_y) + { + mouse_move(screen, gps->mouse_x, gps->mouse_y); + } + prev_lbut = enabler->mouse_lbut; + prev_x = gps->mouse_x; + prev_y = gps->mouse_y; + }; +}; + class embark_tools_settings : public dfhack_viewscreen { public: @@ -352,8 +659,9 @@ public: EmbarkTool* t = *iter; x = min_x + 2; OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(t->getToggleKey())); - OutputString(COLOR_WHITE, x, y, ": " + t->getName() + - (t->getEnabled() ? ": Enabled" : ": Disabled")); + OutputString(COLOR_WHITE, x, y, ": " + t->getName() + ": "); + OutputString(t->getEnabled() ? COLOR_GREEN : COLOR_RED, x, y, + t->getEnabled() ? "Enabled" : "Disabled"); y++; } }; @@ -427,28 +735,24 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest y = dim.y - 5; OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_S)); OutputString(COLOR_WHITE, x, y, ": Enabled: "); - std::list parts; - FOR_ITER_TOOLS(iter) + std::vector parts; + FOR_ITER_TOOLS(it) { - EmbarkTool* tool = *iter; - if (tool->getEnabled()) - { - parts.push_back(tool->getName()); - parts.push_back(", "); - } + if ((*it)->getEnabled()) + parts.push_back((*it)->getName()); } if (parts.size()) { - parts.pop_back(); // Remove trailing comma - for (auto iter = parts.begin(); iter != parts.end(); iter++) + std::string label = join_strings(", ", parts); + if (label.size() > dim.x - x - 1) { - OutputString(COLOR_LIGHTMAGENTA, x, y, *iter); + label.resize(dim.x - x - 1 - 3); + label.append("..."); } + OutputString(COLOR_LIGHTMAGENTA, x, y, label); } else - { OutputString(COLOR_LIGHTMAGENTA, x, y, "(none)"); - } } void display_settings() @@ -511,6 +815,7 @@ command_result embark_tools_cmd (color_ostream &out, std::vector & DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { tools.push_back(new EmbarkAnywhere); + tools.push_back(new MouseControl); tools.push_back(new NanoEmbark); tools.push_back(new SandIndicator); tools.push_back(new StablePosition); @@ -550,6 +855,35 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) return CR_OK; } +DFhackCExport command_result plugin_onupdate (color_ostream &out) +{ + static int8_t mask = 0; + static decltype(gps->mouse_x) prev_x = -1; + static decltype(gps->mouse_y) prev_y = -1; + df::viewscreen* parent = DFHack::Gui::getCurViewscreen(); + VIRTUAL_CAST_VAR(screen, df::viewscreen_choose_start_sitest, parent); + if (!screen) + return CR_OK; + int8_t new_mask = (enabler->mouse_lbut << 1) | + (enabler->mouse_rbut << 2) | + (enabler->mouse_lbut_down << 3) | + (enabler->mouse_rbut_down << 4) | + (enabler->mouse_lbut_lift << 5) | + (enabler->mouse_rbut_lift << 6); + if (mask != new_mask || prev_x != gps->mouse_x || prev_y != gps->mouse_y) + { + FOR_ITER_TOOLS(iter) + { + if ((*iter)->getEnabled()) + (*iter)->after_mouse_event(screen); + } + } + mask = new_mask; + prev_x = gps->mouse_x; + prev_y = gps->mouse_y; + return CR_OK; +} + command_result embark_tools_cmd (color_ostream &out, std::vector & parameters) { CoreSuspender suspend; From ffac2f179589cd32fdef9a2c41c608ad0b3d8c80 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 1 Jan 2015 13:47:47 -0500 Subject: [PATCH 059/115] New tweak: "eggs-fertile" --- Readme.html | 5 ++- Readme.rst | 1 + plugins/tweak/tweak.cpp | 5 +++ plugins/tweak/tweaks/eggs-fertile.h | 66 +++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 plugins/tweak/tweaks/eggs-fertile.h diff --git a/Readme.html b/Readme.html index a4ef98057..6ec8651c2 100644 --- a/Readme.html +++ b/Readme.html @@ -2049,7 +2049,10 @@ category when discussing an import agreement with the liaison

 

Fixes overlapping text on the "view agreement" screen

-nestbox-color:

Fixes the color of built nestboxes

+nestbox-color:

Fixes the color of built nestboxes

+ + +eggs-fertile:

Displays a fertility indicator on nestboxes

diff --git a/Readme.rst b/Readme.rst index 15933e0f6..f6ff47c7b 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1329,6 +1329,7 @@ Subcommands that persist until disabled or DF quit: :manager-quantity: Removes the limit of 30 jobs per manager order :civ-view-agreement: Fixes overlapping text on the "view agreement" screen :nestbox-color: Fixes the color of built nestboxes +:eggs-fertile: Displays a fertility indicator on nestboxes fix-armory ---------- diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index d49fbe2da..709a1f484 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -76,6 +76,7 @@ #include "tweaks/advmode-contained.h" #include "tweaks/civ-agreement-ui.h" #include "tweaks/craft-age-wear.h" +#include "tweaks/eggs-fertile.h" #include "tweaks/farm-plot-select.h" #include "tweaks/fast-heat.h" #include "tweaks/fast-trade.h" @@ -147,6 +148,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector \n" @@ -186,6 +189,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector main.mode != ui_sidebar_mode::QueryBuilding && + ui->main.mode != ui_sidebar_mode::BuildingItems) + return NULL; + return virtual_cast(world->selected_building); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + df::building_nest_boxst* nest_box = getNestBox(); + if (nest_box) + { + auto dims = Gui::getDwarfmodeViewDims(); + bool has_eggs = false; + bool fertile = false; + int idx = 0; + for (auto iter = nest_box->contained_items.begin(); + iter != nest_box->contained_items.end(); ++iter) + { + df::item_eggst* egg = virtual_cast((*iter)->item); + if (egg) + { + has_eggs = true; + if (egg->egg_flags.bits.fertile) + fertile = true; + if (ui->main.mode == ui_sidebar_mode::BuildingItems) + { + Screen::paintString( + Screen::Pen(' ', fertile ? COLOR_LIGHTGREEN : COLOR_LIGHTRED), + dims.menu_x2 - (fertile ? 4 : 6), + dims.y1 + idx + 3, + fertile ? "Fert" : "N.Fert" + ); + } + } + ++idx; + } + if (has_eggs && ui->main.mode == ui_sidebar_mode::QueryBuilding) + { + Screen::paintString( + Screen::Pen(' ', fertile ? COLOR_LIGHTGREEN : COLOR_LIGHTRED), + dims.menu_x1 + 1, + dims.y1 + 5, + fertile ? "Eggs Fertile" : "Eggs infertile" + ); + } + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(egg_fertile_hook, render); From 47d6e111c8c2dbcab9a51c6a3b2b235e10a133b3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 1 Jan 2015 14:36:30 -0500 Subject: [PATCH 060/115] New tweak: "max-wheelbarrow" --- Readme.html | 6 +- Readme.rst | 1 + plugins/tweak/tweak.cpp | 9 ++- plugins/tweak/tweaks/max-wheelbarrow.h | 96 ++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 plugins/tweak/tweaks/max-wheelbarrow.h diff --git a/Readme.html b/Readme.html index 6ec8651c2..6da852c3d 100644 --- a/Readme.html +++ b/Readme.html @@ -2052,7 +2052,11 @@ category when discussing an import agreement with the liaison

nestbox-color:

Fixes the color of built nestboxes

-eggs-fertile:

Displays a fertility indicator on nestboxes

+eggs-fertile:

Displays a fertility indicator on nestboxes

+ + +max-wheelbarrow: + 

Allows assigning more than 3 wheelbarrows to a stockpile

diff --git a/Readme.rst b/Readme.rst index f6ff47c7b..187b0fc88 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1330,6 +1330,7 @@ Subcommands that persist until disabled or DF quit: :civ-view-agreement: Fixes overlapping text on the "view agreement" screen :nestbox-color: Fixes the color of built nestboxes :eggs-fertile: Displays a fertility indicator on nestboxes +:max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile fix-armory ---------- diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 709a1f484..3ecd81299 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -13,6 +13,7 @@ #include "modules/Job.h" #include "modules/Materials.h" #include "modules/MapCache.h" +#include "modules/Buildings.h" #include "MiscUtils.h" @@ -82,6 +83,7 @@ #include "tweaks/fast-trade.h" #include "tweaks/import-priority-category.h" #include "tweaks/manager-quantity.h" +#include "tweaks/max-wheelbarrow.h" #include "tweaks/military-assign.h" #include "tweaks/nestbox-color.h" #include "tweaks/stable-cursor.h" @@ -148,7 +150,7 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector > ret; + return ret; + } + + df::building_stockpilest* getStockpile() + { + if (ui->main.mode != ui_sidebar_mode::QueryBuilding) + return NULL; + return virtual_cast(world->selected_building); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + df::building_stockpilest* stockpile = getStockpile(); + if (stockpile && in_wheelbarrow_entry) + { + auto dims = Gui::getDwarfmodeViewDims(); + Screen::paintString(Screen::Pen(' ', COLOR_LIGHTCYAN), + dims.menu_x1 + 22, dims.y1 + 6, wheelbarrow_entry + "_"); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set* input)) + { + df::building_stockpilest* stockpile = getStockpile(); + bool handled = false; + if (stockpile) + { + auto dims = Gui::getDwarfmodeViewDims(); + handled = true; + if (!in_wheelbarrow_entry && + input->count(df::interface_key::BUILDJOB_STOCKPILE_WHEELBARROW)) + { + in_wheelbarrow_entry = true; + std::stringstream tmp; + tmp << stockpile->max_wheelbarrows; + tmp >> wheelbarrow_entry; + } + else if (in_wheelbarrow_entry) + { + if (input->count(df::interface_key::SELECT) || + input->count(df::interface_key::LEAVESCREEN) || + input->count(df::interface_key::LEAVESCREEN_ALL) || + input->count(df::interface_key::BUILDJOB_STOCKPILE_WHEELBARROW)) + { + in_wheelbarrow_entry = false; + stockpile->max_wheelbarrows = std::min(wheelbarrow_count(), + Buildings::countExtentTiles(&stockpile->room)); + } + else if (input->count(df::interface_key::STRING_A000) && + wheelbarrow_entry.size()) + { + wheelbarrow_entry.resize(wheelbarrow_entry.size() - 1); + } + else + { + for (auto iter = input->begin(); iter != input->end(); ++iter) + { + df::interface_key key = *iter; + if (key >= Screen::charToKey('0') && key <= Screen::charToKey('9') && + wheelbarrow_entry.size() < 3) + { + wheelbarrow_entry.push_back(Screen::keyToChar(key)); + } + } + } + } + else + handled = false; + } + if (!handled) + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(max_wheelbarrow_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(max_wheelbarrow_hook, feed); From cc22c034abd10dca365116a8a9c2f2a4ceaa5af0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 1 Jan 2015 14:38:14 -0500 Subject: [PATCH 061/115] Update NEWS --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 1b9ec3f8b..8c1ef4a91 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,9 @@ DFHack Future Fixes New Plugins New Scripts + New tweaks: + eggs-fertile: Displays an egg fertility indicator on nestboxes + max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile Misc Improvements DFHack 0.40.19-r1 From cea32a335c1580c086f9f6f6d5a350d74a090d19 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Thu, 1 Jan 2015 20:20:11 -0500 Subject: [PATCH 062/115] Add `tweak max-wheelbarrow` to dfhack.init-example --- dfhack.init-example | 1 + 1 file changed, 1 insertion(+) diff --git a/dfhack.init-example b/dfhack.init-example index c1cd1ac86..32a84e067 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -174,6 +174,7 @@ tweak import-priority-category # Misc. UI tweaks tweak civ-view-agreement +tweak max-wheelbarrow ########################### # Globally acting plugins # From b4f224fb3f2e4e558caebcfdfe28892eddd6c809 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Fri, 2 Jan 2015 22:01:48 +1100 Subject: [PATCH 063/115] minor console output tweaks Also remove unused sample file from code, change handling of offset. --- scripts/digfort.rb | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/scripts/digfort.rb b/scripts/digfort.rb index e20d64f23..0d41649b3 100644 --- a/scripts/digfort.rb +++ b/scripts/digfort.rb @@ -1,7 +1,6 @@ # designate an area for digging according to a plan in csv format fname = $script_args[0].to_s -print(fname) if not $script_args[0] then puts " Usage: digfort " @@ -19,29 +18,10 @@ end planfile = File.read(fname) if df.cursor.x == -30000 - puts "place the game cursor to the top-left corner of the design" + puts "place the game cursor to the top-left corner of the design and retry" throw :script_finished end -# a sample CSV file -# empty lines are ignored -# a special comment with start(dx, dy) means the actual patterns starts at cursor.x-dx, cursor.y-dy -# the CSV file should be saved in the main DF directory, alongside of Dwarf Fortress.exe -sample_csv = < Date: Fri, 2 Jan 2015 15:50:26 -0500 Subject: [PATCH 064/115] Remove `embark` references from readme --- Readme.html | 221 +++++++++++++++++++++++++--------------------------- Readme.rst | 4 - 2 files changed, 108 insertions(+), 117 deletions(-) diff --git a/Readme.html b/Readme.html index a4ef98057..4d3cc58ee 100644 --- a/Readme.html +++ b/Readme.html @@ -533,69 +533,68 @@ access DF memory and allow for easier development of new tools.

  • deathcause
  • dfstatus
  • -
  • embark
  • -
  • exterminate
  • -
  • growcrops
  • -
  • lever
  • -
  • locate-ore
  • -
  • lua
  • -
  • masspit
  • -
  • multicmd
  • -
  • quicksave
  • -
  • remove-stress
  • -
  • setfps
  • -
  • siren
  • -
  • soundsense-season
  • -
  • source
  • -
  • superdwarf
  • -
  • stripcaged
  • -
  • teleport
  • -
  • undump-buildings
  • +
  • exterminate
  • +
  • growcrops
  • +
  • lever
  • +
  • locate-ore
  • +
  • lua
  • +
  • masspit
  • +
  • multicmd
  • +
  • quicksave
  • +
  • remove-stress
  • +
  • setfps
  • +
  • siren
  • +
  • soundsense-season
  • +
  • source
  • +
  • superdwarf
  • +
  • stripcaged
  • +
  • teleport
  • +
  • undump-buildings
  • -
  • modtools
  • -
  • In-game interface tools
      -
    • Dwarf Manipulator
    • -
    • Search
    • -
    • AutoMaterial
    • -
    • Stockpile Automation
    • -
    • Track Stop Menu
    • -
    • gui/advfort
    • -
    • gui/assign-rack
    • -
    • gui/choose-weapons
    • -
    • gui/clone-uniform
    • -
    • gui/companion-order
    • -
    • gui/gm-editor
    • -
    • Hotkeys
    • -
    • Hotkeys
    • -
    • Stockpile Automation
    • -
    • Stockpile Automation
    • -
    • gui/liquids
    • -
    • gui/mechanisms
    • -
    • gui/mod-manager
    • -
    • gui/rename
    • -
    • gui/room-list
    • -
    • gui/guide-path
    • -
    • gui/workflow
    • -
    • gui/workshop-job
    • +
    • modtools
    • +
    • In-game interface tools
    • -
    • Behavior Mods
        -
      • Siege Engine
          -
        • Rationale
        • -
        • Configuration UI
        • +
        • Behavior Mods @@ -3076,12 +3075,8 @@ Also works when selecting units from the 'u'nitlist viewscreen.

          dfstatus

          Show a quick overview of critical stock quantities, including food, dirnks, wood, and various bars.

          -
          -

          embark

          -

          Allows to embark anywhere. Currently windows only.

          -
          -

          exterminate

          +

          exterminate

          Kills any unit of a given race.

          With no argument, lists the available races and count eligible targets.

          With the special argument him, targets only the selected creature.

          @@ -3113,7 +3108,7 @@ exterminate elve magma
          -

          growcrops

          +

          growcrops

          Instantly grow seeds inside farming plots.

          With no argument, this command list the various seed types currently in use in your farming plots. @@ -3125,7 +3120,7 @@ growcrops plump 40

          -

          lever

          +

          lever

          Allow manipulation of in-game levers from the dfhack console.

          Can list levers, including state and links, with:

          @@ -3139,7 +3134,7 @@ lever pull 42 --now
           
          -

          locate-ore

          +

          locate-ore

          Scan the map for metal ores.

          Finds and designate for digging one tile of a specific metal ore. Only works for native metal ores, does not handle reaction stuff (eg STEEL).

          @@ -3152,7 +3147,7 @@ locate-ore iron
          -

          lua

          +

          lua

          There are the following ways to invoke this command:

          1. lua (without any parameters)

            @@ -3171,14 +3166,14 @@ directory. If the filename is not supplied, it loads "dfhack.lua".

          -

          masspit

          +

          masspit

          Designate all creatures in cages on top of a pit/pond activity zone for pitting. Works best with an animal stockpile on top of the zone.

          Works with a zone number as argument (eg Activity Zone #6 -> masspit 6) or with the game cursor on top of the area.

          -

          multicmd

          +

          multicmd

          Run multiple dfhack commands. The argument is split around the character ; and all parts are run sequencially as independent dfhack commands. Useful for hotkeys.

          @@ -3188,21 +3183,21 @@ multicmd locate-ore iron ; digv
          -

          quicksave

          +

          quicksave

          If called in dwarf mode, makes DF immediately auto-save the game by setting a flag normally used in seasonal auto-save.

          -

          remove-stress

          +

          remove-stress

          Sets stress to -1,000,000; the normal range is 0 to 500,000 with very stable or very stressed dwarves taking on negative or greater values respectively. Applies to the selected unit, or use "remove-stress -all" to apply to all units.

          -

          setfps

          +

          setfps

          Run setfps <number> to set the FPS cap at runtime, in case you want to watch combat in slow motion or something :)

          -

          siren

          +

          siren

          Wakes up sleeping units, cancels breaks and stops parties either everywhere, or in the burrows given as arguments. In return, adds bad thoughts about noise, tiredness and lack of protection. Also, the units with interrupted @@ -3210,7 +3205,7 @@ breaks will go on break again a lot sooner. The script is intended for emergencies, e.g. when a siege appears, and all your military is partying.

          -

          soundsense-season

          +

          soundsense-season

          It is a well known issue that Soundsense cannot detect the correct current season when a savegame is loaded and has to play random season music until a season switch occurs.

          @@ -3219,7 +3214,7 @@ to gamelog.txt on every map load to fix this. For best results call the script from dfhack.init.

          -

          source

          +

          source

          Create an infinite magma or water source or drain on a tile.

          This script registers a map tile as a liquid source, and every 12 game ticks that tile receives or remove 1 new unit of flow based on the configuration.

          @@ -3241,7 +3236,7 @@ source add water 0 - water drain
          -

          superdwarf

          +

          superdwarf

          Similar to fastdwarf, per-creature.

          To make any creature superfast, target it ingame using 'v' and:

          @@ -3251,7 +3246,7 @@ superdwarf add
           

          This plugin also shortens the 'sleeping' and 'on break' periods of targets.

          -

          stripcaged

          +

          stripcaged

          For dumping items inside cages. Will mark selected items for dumping, then a dwarf may come and actually dump it. See also autodump.

          With the items argument, only dumps items laying in the cage, excluding @@ -3269,7 +3264,7 @@ stripcaged weapons 25321 34228

          -

          teleport

          +

          teleport

          Teleports a unit to given coordinates.

          Examples:

          @@ -3279,12 +3274,12 @@ teleport -unit 1234 -x 56 -y 115 -z 26  - teleports unit 1234 to 56,115,26
           
          -

          undump-buildings

          +

          undump-buildings

          Undesignates building base materials for dumping.

          -

          modtools

          +

          modtools

          These scripts are mostly useful for raw modders and scripters. They all have standard arguments: arguments are of the form tool -argName1 argVal1 -argName2 argVal2. This is equivalent to tool -argName2 argVal2 -argName1 argVal1. It is not necessary to provide a value to an argument name: tool -argName3 is fine. Supplying the same argument name multiple times will result in an error. Argument names are preceded with a dash. The -help argument will print a descriptive usage string describing the nature of the arguments. For multiple word argument values, brackets must be used: tool -argName4 [ sadf1 sadf2 sadf3 ]. In order to allow passing literal braces as part of the argument, backslashes are used: tool -argName4 [ \] asdf \foo ] sets argName4 to \] asdf foo. The *-trigger scripts have a similar policy with backslashes.

          • add-syndrome

            @@ -3341,7 +3336,7 @@ teleport -unit 1234 -x 56 -y 115 -z 26 - teleports unit 1234 to 56,115,26
          -

          In-game interface tools

          +

          In-game interface tools

          These tools work by displaying dialogs or overlays in the game window, and are mostly implemented by lua scripts.

          @@ -3356,7 +3351,7 @@ guideline because it arguably just fixes small usability bugs in the game UI.

          -

          Dwarf Manipulator

          +

          Dwarf Manipulator

          Implemented by the 'manipulator' plugin.

          To activate, open the unit screen and press 'l'.

          images/manipulator.png @@ -3397,7 +3392,7 @@ cursor onto that cell instead of toggling it.
        • directly to the main dwarf mode screen.

          -

          AutoMaterial

          +

          AutoMaterial

          Implemented by the 'automaterial' plugin.

          This makes building constructions (walls, floors, fortifications, etc) a little bit easier by saving you from having to trawl through long lists of materials each time @@ -3456,7 +3451,7 @@ materials, it returns you back to this screen. If you use this along with severa enabled materials, you should be able to place complex constructions more conveniently.

          -

          Stockpile Automation

          +

          Stockpile Automation

          Enable the automelt or autotrade plugins in your dfhack.init with:

           enable automelt
          @@ -3467,7 +3462,7 @@ When automelt is enabled for a stockpile, any meltable items placed in it will b
           When autotrade is enabled for a stockpile, any items placed in it will be designated to be taken to the Trade Depot whenever merchants are on the map.

          -

          Track Stop Menu

          +

          Track Stop Menu

          The q menu of track stops is completely blank by default. To enable one:

           enable trackstop
          @@ -3481,7 +3476,7 @@ It re-uses the keybindings from the track stop building interface:

        -

        gui/advfort

        +

        gui/advfort

        This script allows to perform jobs in adventure mode. For more complete help press '?' while script is running. It's most confortable to use this as a keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:

        @@ -3500,7 +3495,7 @@ implies -a
      • -

        gui/assign-rack

        +

        gui/assign-rack

        Bind to a key (the example config uses P), and activate when viewing a weapon rack in the 'q' mode.

        images/assign-rack.png @@ -3524,7 +3519,7 @@ the intended user. In order to aid in the choice, it shows the number of currently assigned racks for every valid squad.

        -

        gui/choose-weapons

        +

        gui/choose-weapons

        Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize page of the military screen.

        Depending on the cursor location, it rewrites all 'individual choice weapon' entries @@ -3535,14 +3530,14 @@ only that entry, and does it even if it is not 'individual choice'.

        and may lead to inappropriate weapons being selected.

        -

        gui/clone-uniform

        +

        gui/clone-uniform

        Bind to a key (the example config uses Ctrl-C), and activate in the Uniforms page of the military screen with the cursor in the leftmost list.

        When invoked, the script duplicates the currently selected uniform template, and selects the newly created copy.

        -

        gui/companion-order

        +

        gui/companion-order

        A script to issue orders for companions. Select companions with lower case chars, issue orders with upper case. Must be in look or talk mode to issue command on tile.

        images/companion-order.png @@ -3558,7 +3553,7 @@ case. Must be in look or talk mode to issue command on tile.

      -

      gui/gm-editor

      +

      gui/gm-editor

      There are three ways to open this editor:

      • using gui/gm-editor command/keybinding - opens editor on what is selected @@ -3573,7 +3568,7 @@ the same as version above.
      • in-game help.

      -

      Hotkeys

      +

      Hotkeys

      Opens an in-game screen showing DFHack keybindings that are valid in the current mode.

      images/hotkeys.png

      Type hotkeys into the DFHack console to open the screen, or bind the command to a @@ -3583,7 +3578,7 @@ keybinding add Ctrl-F1 hotkeys

      -

      Hotkeys

      +

      Hotkeys

      Opens an in-game screen showing DFHack keybindings that are valid in the current mode.

      images/hotkeys.png

      Type hotkeys into the DFHack console to open the screen, or bind the command to a @@ -3592,7 +3587,7 @@ globally active hotkey in dfhack.init, e.g.:

      keybinding add Ctrl-F1 hotkeys
      -

      Stockpile Automation

      +

      Stockpile Automation

      Enable the autodump plugin in your dfhack.init with
      enable autodump
      @@ -3601,7 +3596,7 @@ globally active hotkey in dfhack.init, e.g.:

      Any items placed in this stockpile will be designated to be dumped.

      -

      Stockpile Automation

      +

      Stockpile Automation

      Enable the automelt plugin in your dfhack.init with
      enable automelt
      @@ -3610,7 +3605,7 @@ Any items placed in this stockpile will be designated to be dumped.

      Any items placed in this stockpile will be designated to be melted.

      -

      gui/liquids

      +

      gui/liquids

      To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.

      images/liquids.png

      This script is a gui front-end to the liquids plugin and works similar to it, @@ -3630,7 +3625,7 @@ rivers power water wheels even when full and technically not flowing.

      After setting up the desired operations using the described keys, use Enter to apply them.

      -

      gui/mechanisms

      +

      gui/mechanisms

      To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.

      images/mechanisms.png

      Lists mechanisms connected to the building, and their links. Navigating the list centers @@ -3640,13 +3635,13 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui.

      -

      gui/mod-manager

      +

      gui/mod-manager

      A way to simply install and remove small mods. It looks for specially formatted mods in df subfolder 'mods'. Mods are not included, for example mods see: github mini mod repository

      images/mod-manager.png
      -

      gui/rename

      +

      gui/rename

      Backed by the rename plugin, this script allows entering the desired name via a simple dialog in the game ui.

        @@ -3669,7 +3664,7 @@ their species string.

        unit profession change to Ctrl-Shift-T.

      -

      gui/room-list

      +

      gui/room-list

      To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, either immediately or after opening the assign owner page.

      images/room-list.png @@ -3677,7 +3672,7 @@ either immediately or after opening the assign owner page.

      list, and allows unassigning them.

      -

      gui/guide-path

      +

      gui/guide-path

      Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with the cursor over a Guide order.

      images/guide-path.png @@ -3685,7 +3680,7 @@ the cursor over a Guide order.

      computes it when the order is executed for the first time.

      -

      gui/workflow

      +

      gui/workflow

      Bind to a key (the example config uses Alt-W), and activate with a job selected in a workshop in the 'q' mode.

      images/workflow.png @@ -3732,7 +3727,7 @@ the current stock value. The bright green dashed line is the target limit (maximum) and the dark green line is that minus the gap (minimum).

      -

      gui/workshop-job

      +

      gui/workshop-job

      Bind to a key (the example config uses Alt-A), and activate with a job selected in a workshop in the 'q' mode.

      images/workshop-job.png @@ -3769,7 +3764,7 @@ you have to unset the material first.

      -

      Behavior Mods

      +

      Behavior Mods

      These plugins, when activated via configuration UI or by detecting certain structures in RAWs, modify the game engine behavior concerning the target objects to add features not otherwise present.

      @@ -3780,20 +3775,20 @@ technical challenge, and do not represent any long-term plans to produce more similar modifications of the game.

      -

      Siege Engine

      +

      Siege Engine

      The siege-engine plugin enables siege engines to be linked to stockpiles, and aimed at an arbitrary rectangular area across Z levels, instead of the original four directions. Also, catapults can be ordered to load arbitrary objects, not just stones.

      -

      Rationale

      +

      Rationale

      Siege engines are a very interesting feature, but sadly almost useless in the current state because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do.

      -

      Configuration UI

      +

      Configuration UI

      The configuration front-end to the plugin is implemented by the gui/siege-engine script. Bind it to a key (the example config uses Alt-A) and activate after selecting a siege engine in 'q' mode.

      @@ -3816,7 +3811,7 @@ menu.

      -

      Power Meter

      +

      Power Meter

      The power-meter plugin implements a modified pressure plate that detects power being supplied to gear boxes built in the four adjacent N/S/W/E tiles.

      The configuration front-end is implemented by the gui/power-meter script. Bind it to a @@ -3827,11 +3822,11 @@ in the build menu.

      configuration page, but configures parameters relevant to the modded power meter building.

      -

      Steam Engine

      +

      Steam Engine

      The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

      -

      Rationale

      +

      Rationale

      The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -3842,7 +3837,7 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

      -

      Construction

      +

      Construction

      The workshop needs water as its input, which it takes via a passable floor tile below it, like usual magma workshops do. The magma version also needs magma.

      @@ -3866,7 +3861,7 @@ short axles that can be built later than both of the engines.

      -

      Operation

      +

      Operation

      In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear @@ -3897,7 +3892,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

      -

      Explosions

      +

      Explosions

      The engine must be constructed using barrel, pipe and piston from fire-safe, or in the magma version magma-safe metals.

      During operation weak parts get gradually worn out, and @@ -3906,7 +3901,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

      -

      Save files

      +

      Save files

      It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -3917,7 +3912,7 @@ being generated.

      -

      Add Spatter

      +

      Add Spatter

      This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements. The produced contaminants are immune to being washed away by water or destroyed by diff --git a/Readme.rst b/Readme.rst index 15933e0f6..d5c1e026b 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2273,10 +2273,6 @@ dfstatus ======== Show a quick overview of critical stock quantities, including food, dirnks, wood, and various bars. -embark -====== -Allows to embark anywhere. Currently windows only. - exterminate =========== Kills any unit of a given race. From 4abcaf79ef58e6dbe487e9448f39ef2c80b53ccc Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 2 Jan 2015 15:52:45 -0500 Subject: [PATCH 065/115] Update readme --- Readme.html | 1 + Readme.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/Readme.html b/Readme.html index a4ef98057..eab2e32ec 100644 --- a/Readme.html +++ b/Readme.html @@ -2874,6 +2874,7 @@ embark-tools enable/disable tool [tool]...

      Tools:

      • anywhere: Allows embarking anywhere (including sites, mountain-only biomes, and oceans). Use with caution.
      • +
      • mouse: Implements mouse controls (currently in the local embark region only)
      • nano: An implementation of nano embark - allows resizing below 2x2 when enabled.
      • sand: Displays an indicator when sand is present in the currently-selected area, similar to the default clay/stone indicators.
      • sticky: Maintains the selected local area while navigating the world map
      • diff --git a/Readme.rst b/Readme.rst index 15933e0f6..a6638340e 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2066,6 +2066,7 @@ Usage:: Tools: * ``anywhere``: Allows embarking anywhere (including sites, mountain-only biomes, and oceans). Use with caution. +* ``mouse``: Implements mouse controls (currently in the local embark region only) * ``nano``: An implementation of nano embark - allows resizing below 2x2 when enabled. * ``sand``: Displays an indicator when sand is present in the currently-selected area, similar to the default clay/stone indicators. * ``sticky``: Maintains the selected local area while navigating the world map From 9b429eb9444535226e45d60af8344abc3aebde29 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sun, 4 Jan 2015 12:04:12 -0700 Subject: [PATCH 066/115] Update NEWS --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 1b9ec3f8b..5858165c4 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ DFHack Future Internals Fixes + Alt should no longer get stuck on Windows (and perhaps OS X) New Plugins New Scripts Misc Improvements From 294fe0b30c5b2bdafa3334aec9415c79ffff8d4c Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Mon, 5 Jan 2015 08:33:10 -0500 Subject: [PATCH 067/115] Update siren.lua for emotions Runs now without exception/error and adds emotions correctly. Seems to work as intended. --- scripts/siren.lua | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/scripts/siren.lua b/scripts/siren.lua index 4fc574ed0..3a799c3e2 100644 --- a/scripts/siren.lua +++ b/scripts/siren.lua @@ -32,15 +32,18 @@ function is_in_burrows(pos) end end -function add_thought(unit, code) - for _,v in ipairs(unit.status.recent_events) do - if v.type == code then - v.age = 0 - return - end - end - - unit.status.recent_events:insert('#', { new = true, type = code }) +function add_thought(unit, emotion, thought) + unit.status.current_soul.personality.emotions:insert('#', { new = true, + type = emotion, + unk2=1, + strength=1, + thought=thought, + subthought=0, + severity=0, + flags=0, + unk7=0, + year=df.global.cur_year, + year_tick=df.global.cur_year_tick}) end function wake_unit(unit) @@ -51,9 +54,9 @@ function wake_unit(unit) if job.completion_timer > 0 then unit.counters.unconscious = 0 - add_thought(unit, df.unit_thought_type.SleepNoiseWake) + add_thought(unit, df.emotion_type.Grouchiness, df.unit_thought_type.Drowsy) elseif job.completion_timer < 0 then - add_thought(unit, df.unit_thought_type.Tired) + add_thought(unit, df.emotion_type.Grumpiness, df.unit_thought_type.Drowsy) end job.pos:assign(unit.pos) @@ -73,7 +76,7 @@ function stop_break(unit) if counter then counter.id = df.misc_trait_type.TimeSinceBreak counter.value = 100800 - 30*1200 - add_thought(unit, df.unit_thought_type.Tired) + add_thought(unit, df.emotion_type.Grumpiness, df.unit_thought_type.Drowsy) end end @@ -90,7 +93,7 @@ for _,v in ipairs(df.global.world.units.active) do local x,y,z = dfhack.units.getPosition(v) if x and dfhack.units.isCitizen(v) and is_in_burrows(xyz2pos(x,y,z)) then if not in_siege and v.military.squad_id < 0 then - add_thought(v, df.unit_thought_type.LackProtection) + add_thought(v, df.emotion_type.Nervousness, df.unit_thought_type.LackProtection) end wake_unit(v) stop_break(v) @@ -103,7 +106,7 @@ for _,v in ipairs(df.global.ui.parties) do if is_in_burrows(pos) then v.timer = 0 for _, u in ipairs(v.units) do - add_thought(unit, df.unit_thought_type.Tired) + add_thought(unit, df.emotion_type.Grumpiness, df.unit_thought_type.Drowsy) end end end From 084bbc316233297dd279cf1eb9db9e36d4f06bb2 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Mon, 5 Jan 2015 13:54:17 -0500 Subject: [PATCH 068/115] Add isGay(df::unit*) method --- plugins/zone.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c88e82154..c7dfd3b81 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -67,6 +67,7 @@ using namespace std; #include "df/general_ref_building_civzone_assignedst.h" #include #include +#include "df/unit_soul.h" #include "df/viewscreen_dwarfmodest.h" #include "modules/Translation.h" @@ -345,6 +346,7 @@ bool isHunter(df::unit* unit); bool isOwnCiv(df::unit* unit); bool isMerchant(df::unit* unit); bool isForest(df::unit* unit); +bool isGay(df::unit* unit); bool isActivityZone(df::building * building); bool isPenPasture(df::building * building); @@ -705,6 +707,11 @@ int getUnitIndexFromId(df::unit* unit_) return -1; } +bool isGay(df::unit* unit) +{ + return isFemale(unit) && unit->status.current_soul->orientation_flags.bits.romance_female || unit->status.current_soul->orientation_flags.bits.romance_male; +} + // dump some unit info void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) { From 1cdf61908ea81578944ba6811c697a5a69464b65 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Mon, 5 Jan 2015 14:22:07 -0500 Subject: [PATCH 069/115] Collapse ProcessUnits_* methods. --- plugins/zone.cpp | 60 ++++++++---------------------------------------- 1 file changed, 10 insertions(+), 50 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c7dfd3b81..cf51dfa6f 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -709,7 +709,7 @@ int getUnitIndexFromId(df::unit* unit_) bool isGay(df::unit* unit) { - return isFemale(unit) && unit->status.current_soul->orientation_flags.bits.romance_female || unit->status.current_soul->orientation_flags.bits.romance_male; + return isFemale(unit) && unit->status.current_soul->orientation_flags.bits.romance_female || unit->status.current_soul->orientation_flags.bits.romance_male; } // dump some unit info @@ -2803,8 +2803,7 @@ public: int mk_prot; int ma_prot; - // bah, this should better be an array of 4 vectors - // that way there's no need for the 4 ugly process methods + // butcherable units vector fk_ptr; vector mk_ptr; vector fa_ptr; @@ -2910,53 +2909,14 @@ public: ma_ptr.clear(); } - int ProcessUnits_fk() + int ProcessUnits(vector& unit_ptr, int prot, int goal) { int subcount = 0; - while(fk_ptr.size() && (fk_ptr.size() + fk_prot > fk) ) + while(unit_ptr.size() && (unit_ptr.size() + prot > goal) ) { - df::unit* unit = fk_ptr.back(); + df::unit* unit = unit_ptr.back(); doMarkForSlaughter(unit); - fk_ptr.pop_back(); - subcount++; - } - return subcount; - } - - int ProcessUnits_mk() - { - int subcount = 0; - while(mk_ptr.size() && (mk_ptr.size() + mk_prot > 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_ptr.size() + fa_prot > 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_ptr.size() + ma_prot > ma) ) - { - df::unit* unit = ma_ptr.back(); - doMarkForSlaughter(unit); - ma_ptr.pop_back(); + unit_ptr.pop_back(); subcount++; } return subcount; @@ -2966,10 +2926,10 @@ public: { SortUnitsByAge(); int slaughter_count = 0; - slaughter_count += ProcessUnits_fk(); - slaughter_count += ProcessUnits_mk(); - slaughter_count += ProcessUnits_fa(); - slaughter_count += ProcessUnits_ma(); + slaughter_count += ProcessUnits(fk_ptr, fk_prot, fk); + slaughter_count += ProcessUnits(mk_ptr, mk_prot, mk); + slaughter_count += ProcessUnits(fa_ptr, fa_prot, fa); + slaughter_count += ProcessUnits(ma_ptr, ma_prot, ma); ClearUnits(); return slaughter_count; } From c069a87b47334c490aa0d4f6c5878ab3c6054159 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Mon, 5 Jan 2015 14:59:32 -0500 Subject: [PATCH 070/115] Update isGay(df::unit) with better determination --- plugins/zone.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index cf51dfa6f..594840361 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -709,7 +709,9 @@ int getUnitIndexFromId(df::unit* unit_) bool isGay(df::unit* unit) { - return isFemale(unit) && unit->status.current_soul->orientation_flags.bits.romance_female || unit->status.current_soul->orientation_flags.bits.romance_male; + df::orientation_flags orientation = unit->status.current_soul->orientation_flags; + return isFemale(unit) && ! (orientation.whole & (orientation.mask_marry_male | orientation.mask_romance_male)) + || ! isFemale(unit) && ! (orientation.whole & (orientation.mask_marry_female | orientation.mask_romance_female)); } // dump some unit info @@ -2809,6 +2811,12 @@ public: vector fa_ptr; vector ma_ptr; + // priority butcherable units + vector fk_pri_ptr; + vector mk_pri_ptr; + vector fa_pri_ptr; + vector ma_pri_ptr; + WatchedRace(bool watch, int id, int _fk, int _mk, int _fa, int _ma) { isWatched = watch; @@ -2862,6 +2870,10 @@ public: sort(mk_ptr.begin(), mk_ptr.end(), compareUnitAgesOlder); sort(fa_ptr.begin(), fa_ptr.end(), compareUnitAgesYounger); sort(ma_ptr.begin(), ma_ptr.end(), compareUnitAgesYounger); + sort(fk_pri_ptr.begin(), fk_ptr.end(), compareUnitAgesOlder); + sort(mk_pri_ptr.begin(), mk_ptr.end(), compareUnitAgesOlder); + sort(fa_pri_ptr.begin(), fa_ptr.end(), compareUnitAgesYounger); + sort(ma_pri_ptr.begin(), ma_ptr.end(), compareUnitAgesYounger); } void PushUnit(df::unit * unit) @@ -2882,6 +2894,24 @@ public: } } + void PushPriorityUnit(df::unit * unit) + { + if(isFemale(unit)) + { + if(isBaby(unit) || isChild(unit)) + fk_pri_ptr.push_back(unit); + else + fa_pri_ptr.push_back(unit); + } + else + { + if(isBaby(unit) || isChild(unit)) + mk_pri_ptr.push_back(unit); + else + ma_pri_ptr.push_back(unit); + } + } + void PushProtectedUnit(df::unit * unit) { if(isFemale(unit)) @@ -2907,11 +2937,22 @@ public: mk_ptr.clear(); fa_ptr.clear(); ma_ptr.clear(); + fk_pri_ptr.clear(); + mk_pri_ptr.clear(); + fa_pri_ptr.clear(); + ma_pri_ptr.clear(); } - int ProcessUnits(vector& unit_ptr, int prot, int goal) + int ProcessUnits(vector& unit_ptr, vector& unit_pri_ptr, int prot, int goal) { int subcount = 0; + while(unit_pri_ptr.size() && (unit_ptr.size() + unit_pri_ptr.size() + prot > goal) ) + { + df::unit* unit = unit_pri_ptr.back(); + doMarkForSlaughter(unit); + unit_pri_ptr.pop_back(); + subcount++; + } while(unit_ptr.size() && (unit_ptr.size() + prot > goal) ) { df::unit* unit = unit_ptr.back(); @@ -2926,10 +2967,10 @@ public: { SortUnitsByAge(); int slaughter_count = 0; - slaughter_count += ProcessUnits(fk_ptr, fk_prot, fk); - slaughter_count += ProcessUnits(mk_ptr, mk_prot, mk); - slaughter_count += ProcessUnits(fa_ptr, fa_prot, fa); - slaughter_count += ProcessUnits(ma_ptr, ma_prot, ma); + slaughter_count += ProcessUnits(fk_ptr, fk_pri_ptr, fk_prot, fk); + slaughter_count += ProcessUnits(mk_ptr, mk_pri_ptr, mk_prot, mk); + slaughter_count += ProcessUnits(fa_ptr, fa_pri_ptr, fa_prot, fa); + slaughter_count += ProcessUnits(ma_ptr, ma_pri_ptr, ma_prot, ma); ClearUnits(); return slaughter_count; } @@ -3440,6 +3481,8 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) || isAvailableForAdoption(unit) || unit->name.has_name ) w->PushProtectedUnit(unit); + else if (isGay(unit)) + w->PushPriorityUnit(unit); else w->PushUnit(unit); } From dd9cb70ad3a372a267adda15c998acfe179f3285 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 15:55:12 -0500 Subject: [PATCH 071/115] Update CMakeLists to 0.40.23-r1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b24c93267..984bb09e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.40.19") +set(DF_VERSION "0.40.23") SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.") set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From e7ee29b1e378ce862b28c6070cbb0b2aaf6040ae Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 15:57:34 -0500 Subject: [PATCH 072/115] Clean up whitespace in Hooks-darwin.cpp --- library/Hooks-darwin.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/library/Hooks-darwin.cpp b/library/Hooks-darwin.cpp index ac9640d80..a5c9b5e13 100644 --- a/library/Hooks-darwin.cpp +++ b/library/Hooks-darwin.cpp @@ -49,16 +49,19 @@ typedef struct interpose_s #include "Hooks.h" #include -/*static const interpose_t interposers[] __attribute__ ((section("__DATA, __interpose"))) = +/*static const interpose_t interposers[] __attribute__ ((section("__DATA, __interpose"))) = { { (void *)DFH_SDL_Init, (void *)SDL_Init }, { (void *)DFH_SDL_PollEvent, (void *)SDL_PollEvent }, { (void *)DFH_SDL_Quit, (void *)SDL_Quit }, { (void *)DFH_SDL_NumJoysticks, (void *)SDL_NumJoysticks }, - + };*/ -#define DYLD_INTERPOSE(_replacment,_replacee) __attribute__((used)) static struct{ const void* replacment; const void* replacee; } _interpose_##_replacee __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacment, (const void*)(unsigned long)&_replacee }; +#define DYLD_INTERPOSE(_replacment,_replacee) \ + __attribute__((used)) static struct{ const void* replacment; const void* replacee; } \ + _interpose_##_replacee __attribute__ ((section ("__DATA,__interpose"))) = \ + { (const void*)(unsigned long)&_replacment, (const void*)(unsigned long)&_replacee }; DYLD_INTERPOSE(DFH_SDL_Init,SDL_Init); DYLD_INTERPOSE(DFH_SDL_PollEvent,SDL_PollEvent); @@ -94,7 +97,7 @@ DFhackCExport void DFH_SDL_Quit(void) { _SDL_Quit(); }*/ - + SDL_Quit(); } @@ -170,10 +173,10 @@ DFhackCExport int DFH_SDL_Init(uint32_t flags) fprintf(stderr,"dfhack: something went horribly wrong\n"); exit(1); } - + DFHack::Core & c = DFHack::Core::getInstance(); //c.Init(); - + //int ret = _SDL_Init(flags); int ret = SDL_Init(flags); return ret; @@ -270,4 +273,4 @@ DFhackCExport int DFH_SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Re } return SDL_UpperBlit(src, srcrect, dst, dstrect); -} \ No newline at end of file +} From 929f63ee2e95e1c2f35f2c3ff9816a3400bfe063 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Mon, 5 Jan 2015 15:57:34 -0500 Subject: [PATCH 073/115] Fix error in WatchedRace::SortUnitsByAge --- plugins/zone.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 594840361..571eb6bba 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -2870,10 +2870,10 @@ public: sort(mk_ptr.begin(), mk_ptr.end(), compareUnitAgesOlder); sort(fa_ptr.begin(), fa_ptr.end(), compareUnitAgesYounger); sort(ma_ptr.begin(), ma_ptr.end(), compareUnitAgesYounger); - sort(fk_pri_ptr.begin(), fk_ptr.end(), compareUnitAgesOlder); - sort(mk_pri_ptr.begin(), mk_ptr.end(), compareUnitAgesOlder); - sort(fa_pri_ptr.begin(), fa_ptr.end(), compareUnitAgesYounger); - sort(ma_pri_ptr.begin(), ma_ptr.end(), compareUnitAgesYounger); + sort(fk_pri_ptr.begin(), fk_pri_ptr.end(), compareUnitAgesOlder); + sort(mk_pri_ptr.begin(), mk_pri_ptr.end(), compareUnitAgesOlder); + sort(fa_pri_ptr.begin(), fa_pri_ptr.end(), compareUnitAgesYounger); + sort(ma_pri_ptr.begin(), ma_pri_ptr.end(), compareUnitAgesYounger); } void PushUnit(df::unit * unit) From 9f2bdc2bdc5c21d184b2751e35b794479cd4c58d Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 15:58:02 -0500 Subject: [PATCH 074/115] Disable excessive logging in Process-darwin.cpp --- library/Process-darwin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 69ccefcea..3aee24eba 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -188,7 +188,7 @@ void Process::getMemRanges( vector & ranges ) temp.valid = true; ranges.push_back(temp); - fprintf(stderr, + /*fprintf(stderr, "%08x-%08x %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", address, (address + vmsize), (vmsize >> 10), (info.protection & VM_PROT_READ) ? 'r' : '-', @@ -202,7 +202,7 @@ void Process::getMemRanges( vector & ranges ) behavior_strings[info.behavior], info.user_wired_count, info.reserved, - dlinfo.dli_fname); + dlinfo.dli_fname);*/ address += vmsize; } else if (kr != KERN_INVALID_ADDRESS) { From fba77a6dfdc6c1fb58e87c0e35ff22053cefbb71 Mon Sep 17 00:00:00 2001 From: Matthew Townsend Date: Mon, 5 Jan 2015 16:02:13 -0500 Subject: [PATCH 075/115] Bash script to build dfhack according to the instructions in Compile.rst. Closes #410 --- build/build-osx.sh | 123 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 build/build-osx.sh diff --git a/build/build-osx.sh b/build/build-osx.sh new file mode 100644 index 000000000..8e3410159 --- /dev/null +++ b/build/build-osx.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +# This implements steps 7 and 8 of the OSX compilation procedure described in Compile.rst +# If build-osx does not exist in the parent directory, it will be created. + +LUA_PATCH=1 +ME=$PWD/`basename $0` + +usage() { + echo "Usage: $0 [options] {DF_OSX_PATH}" + echo -e "\told\t- use on pre-Snow Leopard OSX installations" + echo -e "\tbrew\t- if GCC 4.5 was installed with homebrew" + echo -e "\tport\t- if GCC 4.5 was insalled with macports" + echo -e "\tclean\t- delete ../build-osx before compiling" + echo "Example:" + echo -e "\t$0 old brew ../../../personal/df_osx" + echo -e "\t$0 port clean /Users/dfplayer/df_osx" + exit $1 +} + +options() { + case $1 in + brew) + echo "Using homebrew gcc." + export CC=/usr/local/bin/gcc-4.5 + export CXX=/usr/local/bin/g++-4.5 + targetted=1 + ;; + port) + echo "Using macports gcc." + export CC=/opt/local/bin/gcc-mp-4.5 + export CXX=/opt/local/bin/g++-mp-4.5 + targetted=1 + ;; + old) + LUA_PATCH=0 + ;; + clean) + echo "Deleting ../build-osx" + rm -rf ../build-osx + ;; + *) + ;; + esac +} + +# sanity checks +if [[ $# -lt 1 ]] +then + echo "Not enough arguments." + usage 0 +fi +if [[ $# -gt 4 ]] +then + echo "Too many arguments." + usage 1 +fi + +# run through the arguments +for last +do + options $last +done +# last keeps the last argument + +if [[ $targetted -eq 0 ]] +then + echo "You did not specify whether you intalled GCC 4.5 from brew or ports." + echo "If you continue, your default compiler will be used." + read -p "Are you sure you want to continue? [y/N] " -n 1 -r + echo # (optional) move to a new line + if [[ ! $REPLY =~ ^[Yy]$ ]] + then + exit 0 + fi +fi + +# check for build folder and start working there +if [[ ! -d ../build-osx ]] +then + mkdir ../build-osx +fi +cd ../build-osx + +# patch if necessary +if [[ $LUA_PATCH -ne 0 ]] +then + cd .. + echo "$PWD" + sed -e '1,/'"PATCH""CODE"'/d' "$ME" | patch -p0 + cd - +fi + +echo "Generate" +cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX="$last" +echo "Build" +make +echo "Install" +make install + +# unpatch if /libarary/luaTypes.cpp was patched +if [[ $LUA_PATCH -ne 0 ]] +then + cd .. + echo -n "un" + sed -e '1,/'"PATCH""CODE"'/d' "$ME" | patch -p0 -R + cd - +fi + +exit 0 + +# PATCHCODE - everything below this line is fed into patch +--- library/LuaTypes.cpp 2014-08-20 00:13:17.000000000 -0700 ++++ library/LuaTypes.cpp 2014-08-31 23:31:00.000000000 -0700 +@@ -464,7 +464,7 @@ + { + case struct_field_info::STATIC_STRING: + { +- int len = strnlen((char*)ptr, field->count); ++ int len = strlen((char*)ptr); + lua_pushlstring(state, (char*)ptr, len); + return; + } From 2bc5fe26eed18da18faed8fbc985b761aa86f254 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 16:29:03 -0500 Subject: [PATCH 076/115] Update NEWS --- NEWS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS b/NEWS index ee9cf23bc..52c53c6ca 100644 --- a/NEWS +++ b/NEWS @@ -1,9 +1,16 @@ DFHack Future Internals + plugins will not be loaded if globals they specify as required are not located (should prevent some crashes) Fixes + advfort works again + exportlegends: Fixed map export + full-heal: Fixed a problem with selecting units in the GUI + gui/hack-wish: Fixed restrictive material filters plugins/dwarfmonitor: correct date display (month index, separator) scripts/putontable: added to the readme + stderr.log: removed excessive debug output on OS X trackstop: No longer prevents cancelling the removal of a track stop or roller. + Fixed a symbol error (MapExtras::BiomeInfo::MAX_LAYERS) when compiling DFHack in Debug mode New Plugins New Scripts gui/stockpiles: an in-game interface for saving and loading stockpile From a28fde3f865f32ece5475cc476fc0b1047217580 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 17:00:28 -0500 Subject: [PATCH 077/115] Run fixTexts.sh --- Compile.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Compile.html b/Compile.html index 623c087ac..07b128db5 100644 --- a/Compile.html +++ b/Compile.html @@ -469,9 +469,10 @@ This will take some time—maybe hours, depending on your machine.
        • Install Homebrew and run:
        • +
        • brew tap lethosor/gcc
        • brew install git
        • brew install cmake
        • -
        • brew install gcc45 --enable-multilib
        • +
        • brew install lethosor/gcc/gcc45 --enable-multilib
        From 5de5d4420a54d2e0b6519a1fbc11ae151bbfab9d Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 17:01:03 -0500 Subject: [PATCH 078/115] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 55b60e3aa..d03ad8b43 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 55b60e3aa99aece7e9a239b041dc4f95a58dcef3 +Subproject commit d03ad8b43ffd7fb71db60e0272c2fbeb158074e2 From 318fb5213ab47dc863d3f4de3ea0500b4d3c6da5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 17:03:52 -0500 Subject: [PATCH 079/115] Update contributors list --- Contributors.html | 7 +++++++ Contributors.rst | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/Contributors.html b/Contributors.html index cfdf78775..702f40b1a 100644 --- a/Contributors.html +++ b/Contributors.html @@ -403,6 +403,13 @@ ul.auto-toc {
      • Angus Mezick <amezick@gmail.com>
      • PeridexisErrant <PeridexisErrant@gmail.com>
      • Putnam
      • +
      • Danaris
      • +
      • Lethosor
      • +
      • Eswald
      • +
      • Ramblurr
      • +
      • MithrilTuxedo
      • +
      • AndreasPK
      • +
      • cdombroski

      And those are the cool people who made stonesense.

        diff --git a/Contributors.rst b/Contributors.rst index 37de797c9..6f079f4ed 100644 --- a/Contributors.rst +++ b/Contributors.rst @@ -63,6 +63,13 @@ The following is a list of people who have contributed to **DFHack**. - Angus Mezick - PeridexisErrant - Putnam +- Danaris +- Lethosor +- Eswald +- Ramblurr +- MithrilTuxedo +- AndreasPK +- cdombroski And those are the cool people who made **stonesense**. From f4b75787be47b64c01d3606a7a871a2d124fccd3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 17:07:13 -0500 Subject: [PATCH 080/115] Update version in NEWS --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 67da3563b..817ca1392 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -DFHack Future +DFHack 0.40.23-r1 Internals plugins will not be loaded if globals they specify as required are not located (should prevent some crashes) Fixes From d932c762426f8892ae906463694865dacdae75c7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 17:28:36 -0500 Subject: [PATCH 081/115] Fix MapCache compiling error on Windows --- library/modules/MapCache.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index 404626e00..2ebfd9644 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -74,7 +74,9 @@ using df::global::world; extern bool GetLocalFeature(t_feature &feature, df::coord2d rgn_pos, int32_t index); +#ifdef LINUX_BUILD const unsigned MapExtras::BiomeInfo::MAX_LAYERS; +#endif const BiomeInfo MapCache::biome_stub = { df::coord2d(), From cd64fb84b7ef64ec2f0d9eedd83f4c514b11773e Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 17:37:41 -0500 Subject: [PATCH 082/115] Make fortplan use Filesystem::getcwd() --- plugins/fortplan.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index 2776064ae..cbd4bd346 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -1,6 +1,7 @@ #include "buildingplan-lib.h" #include #include +#include "modules/Filesystem.h" DFHACK_PLUGIN("fortplan"); #define PLUGIN_VERSION 0.15 @@ -113,11 +114,6 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) return CR_OK; } -std::string get_working_path() -{ - char temp[MAXPATHLEN]; - return ( getcwd(temp, MAXPATHLEN) ? std::string( temp ) : std::string("") ); -} std::vector> tokenizeFile(std::string filename) { std::ifstream infile(filename.c_str()); @@ -164,7 +160,7 @@ command_result fortplan(color_ostream &out, vector & params) { DFHack::Gui::getCursorCoords(startCursor.x, startCursor.y, startCursor.z); userCursor = startCursor; - std::string cwd = get_working_path(); + std::string cwd = Filesystem::getcwd(); std::string filename = cwd+"/"+params[0]; con.print("Loading file '%s'...\n",filename.c_str()); try { @@ -376,4 +372,4 @@ command_result fortplan(color_ostream &out, vector & params) { } return CR_OK; -} \ No newline at end of file +} From 7e11ad821fbe59477a439b14e032039ebfaa92f3 Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 5 Jan 2015 19:33:39 -0500 Subject: [PATCH 083/115] Update xml. --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index d03ad8b43..20014d2f5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d03ad8b43ffd7fb71db60e0272c2fbeb158074e2 +Subproject commit 20014d2f501b76b697e594d56e594ea8f143ea24 From 1519d8e9eaf9d7fb284cce6d85f299d84ef93e8c Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 5 Jan 2015 19:34:01 -0500 Subject: [PATCH 084/115] Disable fortplan and buildingplan for now because it doesn't compile on Windows. --- plugins/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b68fe61bc..131345949 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -93,7 +93,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(autotrade autotrade.cpp) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua) - DFHACK_PLUGIN(buildingplan buildingplan-lib.cpp buildingplan.cpp) + #DFHACK_PLUGIN(buildingplan buildingplan-lib.cpp buildingplan.cpp) DFHACK_PLUGIN(catsplosion catsplosion.cpp) DFHACK_PLUGIN(changeitem changeitem.cpp) DFHACK_PLUGIN(changelayer changelayer.cpp) @@ -122,7 +122,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(flows flows.cpp) DFHACK_PLUGIN(follow follow.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp) - DFHACK_PLUGIN(fortplan buildingplan-lib.cpp fortplan.cpp) + #DFHACK_PLUGIN(fortplan buildingplan-lib.cpp fortplan.cpp) DFHACK_PLUGIN(getplants getplants.cpp) DFHACK_PLUGIN(hotkeys hotkeys.cpp) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) From 448404a4650964fec1cae0867707483514629d44 Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 5 Jan 2015 20:02:54 -0500 Subject: [PATCH 085/115] Fix buildingplan stuff on Windows. --- plugins/CMakeLists.txt | 4 ++-- plugins/buildingplan-lib.cpp | 4 +++- plugins/buildingplan-lib.h | 6 +++--- plugins/buildingplan.cpp | 1 - 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 131345949..b68fe61bc 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -93,7 +93,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(autotrade autotrade.cpp) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua) - #DFHACK_PLUGIN(buildingplan buildingplan-lib.cpp buildingplan.cpp) + DFHACK_PLUGIN(buildingplan buildingplan-lib.cpp buildingplan.cpp) DFHACK_PLUGIN(catsplosion catsplosion.cpp) DFHACK_PLUGIN(changeitem changeitem.cpp) DFHACK_PLUGIN(changelayer changelayer.cpp) @@ -122,7 +122,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(flows flows.cpp) DFHACK_PLUGIN(follow follow.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp) - #DFHACK_PLUGIN(fortplan buildingplan-lib.cpp fortplan.cpp) + DFHACK_PLUGIN(fortplan buildingplan-lib.cpp fortplan.cpp) DFHACK_PLUGIN(getplants getplants.cpp) DFHACK_PLUGIN(hotkeys hotkeys.cpp) DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index 949571cf4..58c4b8aca 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -1,7 +1,7 @@ #include "buildingplan-lib.h" #define PLUGIN_VERSION 0.00 -static void debug(const string &msg) +void debug(const string &msg) { if (!show_debugging) return; @@ -10,6 +10,8 @@ static void debug(const string &msg) out << "DEBUG (" << PLUGIN_VERSION << "): " << msg << endl; } +void enable_quickfort_fn(pair& pair) { pair.second = true; } + /* * Material Choice Screen */ diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index 48f0a8d7b..76f123cd8 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -97,12 +97,12 @@ static MaterialInfo &material_info_identity_fn(MaterialInfo &m); static map planmode_enabled, saved_planmodes; -static void enable_quickfort_fn(pair& pair); +void enable_quickfort_fn(pair& pair); -static void debug(const std::string &msg); +void debug(const std::string &msg); static std::string material_to_string_fn(MaterialInfo m); -static bool show_debugging = true; +static bool show_debugging = false; static bool show_help = false; struct ItemFilter diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 36f7b220f..5a5b9cf0b 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -11,7 +11,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -static void enable_quickfort_fn(pair& pair) { pair.second = true; } static bool is_planmode_enabled(df::building_type type) { From 2db6c0c7357ebd18bb84deb403d6a6a387857a0c Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 5 Jan 2015 20:34:09 -0500 Subject: [PATCH 086/115] Update NEWS. --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 817ca1392..04819a438 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +DFHack Future + + + DFHack 0.40.23-r1 Internals plugins will not be loaded if globals they specify as required are not located (should prevent some crashes) From c39cf1fb49aee87bd7d2e812a26062b7fe4ee334 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Jan 2015 19:36:06 -0500 Subject: [PATCH 087/115] Add room assignment search --- plugins/search.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index e25343cf7..929cd653c 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -43,6 +43,9 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(gview); REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(ui_building_assign_units); +REQUIRE_GLOBAL(ui_building_in_assign); +REQUIRE_GLOBAL(ui_building_item_cursor); /* Search Plugin @@ -1671,6 +1674,68 @@ IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, burrow_search); // +// +// START: Room assignment search +// + +typedef search_generic room_assign_search_base; +class room_assign_search : public room_assign_search_base +{ +public: + bool can_init(df::viewscreen_dwarfmodest *screen) + { + if (ui->main.mode == df::ui_sidebar_mode::QueryBuilding && *ui_building_in_assign) + { + return room_assign_search_base::can_init(screen); + } + + return false; + } + + string get_element_description(df::unit *element) const + { + return element ? get_unit_description(element) : "Nobody"; + } + + void render() const + { + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 19; + + print_search_option(x, y); + } + + vector *get_primary_list() + { + return ui_building_assign_units; + } + + virtual int32_t * get_viewscreen_cursor() + { + return ui_building_item_cursor; + } + + bool should_check_input(set *input) + { + if (input->count(interface_key::SECONDSCROLL_UP) || input->count(interface_key::SECONDSCROLL_DOWN) + || input->count(interface_key::SECONDSCROLL_PAGEUP) || input->count(interface_key::SECONDSCROLL_PAGEDOWN)) + { + end_entry_mode(); + return false; + } + + return true; + } +}; + +IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, room_assign_search); + +// +// END: Room assignment search +// + #define SEARCH_HOOKS \ HOOK_ACTION(unitlist_search_hook) \ HOOK_ACTION(roomlist_search_hook) \ @@ -1684,7 +1749,8 @@ IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, burrow_search); HOOK_ACTION(annoucnement_search_hook) \ HOOK_ACTION(joblist_search_hook) \ HOOK_ACTION(burrow_search_hook) \ - HOOK_ACTION(stockpile_search_hook) + HOOK_ACTION(stockpile_search_hook) \ + HOOK_ACTION(room_assign_search_hook) DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) { From d833005832470727fb56271cac83442f4366d44d Mon Sep 17 00:00:00 2001 From: Peridexis Errant Date: Tue, 6 Jan 2015 22:43:09 +1100 Subject: [PATCH 088/115] delete un-portable plugins copypaste needs to be redone, not ported (see issue #479). creaturemanager is obsolete due to the manipulator plugin, vjeck's scripts for the cheaty bits, and changes to DF itself. --- needs_porting/copypaste.cpp | 479 ---------- needs_porting/creaturemanager.cpp | 1428 ----------------------------- 2 files changed, 1907 deletions(-) delete mode 100644 needs_porting/copypaste.cpp delete mode 100644 needs_porting/creaturemanager.cpp diff --git a/needs_porting/copypaste.cpp b/needs_porting/copypaste.cpp deleted file mode 100644 index 4f584241e..000000000 --- a/needs_porting/copypaste.cpp +++ /dev/null @@ -1,479 +0,0 @@ -// Console version of DF copy paste, proof of concept -// By belal - -#include -#include -#include -#include -#include -#include -#include -#include - -#define DFHACK_WANT_MISCUTILS -#define DFHACK_WANT_TILETYPES -#include -#include "modules/WindowIO.h" - -using namespace DFHack; -//bool waitTillCursorState(DFHack::Context *DF, bool On); -//bool waitTillCursorPositionState(DFHack::Context *DF, int32_t x,int32_t y, int32_t z); - -//change this if you are having problems getting correct results, lower if you would like to go faster -//const int WAIT_AMT = 25; - -void sort(uint32_t &a,uint32_t &b) -{ - if(a > b){ - uint32_t c = b; - b = a; - a = c; - } -} -void sort(int32_t &a,int32_t &b) -{ - if(a > b){ - int16_t c = b; - b = a; - a = c; - } -} -void printVecOfVec(ostream &out, vector > >vec,char sep) -{ - for(size_t k=0;k buildCommands; - buildCommands["building_stockpilest"]=""; - buildCommands["building_zonest"]=""; - buildCommands["building_construction_blueprintst"]=""; - buildCommands["building_wagonst"]=""; - buildCommands["building_armor_standst"]="a"; - buildCommands["building_bedst"]="b"; - buildCommands["building_seatst"]="c"; - buildCommands["building_burial_receptaclest"]="n"; - buildCommands["building_doorst"]="d"; - buildCommands["building_floodgatest"]="x"; - buildCommands["building_floor_hatchst"]="H"; - buildCommands["building_wall_gratest"]="W"; - buildCommands["building_floor_gratest"]="G"; - buildCommands["building_vertical_barsst"]="B"; - buildCommands["building_floor_barsst"]="alt-b"; - buildCommands["building_cabinetst"]="f"; - buildCommands["building_containerst"]="h"; - buildCommands["building_shopst"]=""; - buildCommands["building_workshopst"]=""; - buildCommands["building_alchemists_laboratoryst"]="wa"; - buildCommands["building_carpenters_workshopst"]="wc"; - buildCommands["building_farmers_workshopst"]="ww"; - buildCommands["building_masons_workshopst"]="wm"; - buildCommands["building_craftdwarfs_workshopst"]="wr"; - buildCommands["building_jewelers_workshopst"]="wj"; - buildCommands["building_metalsmiths_workshopst"]="wf"; - buildCommands["building_magma_forgest"]=""; - buildCommands["building_bowyers_workshopst"]="wb"; - buildCommands["building_mechanics_workshopst"]="wt"; - buildCommands["building_siege_workshopst"]="ws"; - buildCommands["building_butchers_shopst"]="wU"; - buildCommands["building_leather_worksst"]="we"; - buildCommands["building_tanners_shopst"]="wn"; - buildCommands["building_clothiers_shopst"]="wk"; - buildCommands["building_fisheryst"]="wh"; - buildCommands["building_stillst"]="wl"; - buildCommands["building_loomst"]="wo"; - buildCommands["building_quernst"]="wq"; - buildCommands["building_kennelsst"]="k"; - buildCommands["building_kitchenst"]="wz"; - buildCommands["building_asheryst"]="wy"; - buildCommands["building_dyers_shopst"]="wd"; - buildCommands["building_millstonest"]="wM"; - buildCommands["building_farm_plotst"]="p"; - buildCommands["building_weapon_rackst"]="r"; - buildCommands["building_statuest"]="s"; - buildCommands["building_tablest"]="t"; - buildCommands["building_paved_roadst"]="o"; - buildCommands["building_bridgest"]="g"; - buildCommands["building_wellst"]="l"; - buildCommands["building_siege enginest"]="i"; - buildCommands["building_catapultst"]="ic"; - buildCommands["building_ballistast"]="ib"; - buildCommands["building_furnacest"]=""; - buildCommands["building_wood_furnacest"]="ew"; - buildCommands["building_smelterst"]="es"; - buildCommands["building_glass_furnacest"]="ek"; - buildCommands["building_kilnst"]="ek"; - buildCommands["building_magma_smelterst"]="es"; - buildCommands["building_magma_glass_furnacest"]="ek"; - buildCommands["building_magma_kilnst"]="ek"; - buildCommands["building_glass_windowst"]="y"; - buildCommands["building_gem_windowst"]="Y"; - buildCommands["building_tradedepotst"]="D"; - buildCommands["building_mechanismst"]=""; - buildCommands["building_leverst"]="Tl"; - buildCommands["building_pressure_platest"]="Tp"; - buildCommands["building_cage_trapst"]="Tc"; - buildCommands["building_stonefall_trapst"]="Ts"; - buildCommands["building_weapon_trapst"]="Tw"; - buildCommands["building_spikest"]=""; - buildCommands["building_animal_trapst"]="m"; - buildCommands["building_screw_pumpst"]="Ms"; - buildCommands["building_water_wheelst"]="Mw"; - buildCommands["building_windmillst"]="Mm"; - buildCommands["building_gear_assemblyst"]="Mg"; - buildCommands["building_horizontal_axlest"]="Mh"; - buildCommands["building_vertical_axlest"]="Mv"; - buildCommands["building_supportst"]="S"; - buildCommands["building_cagest"]="j"; - buildCommands["building_archery_targetst"]="A"; - buildCommands["building_restraintst"]="v"; - - DFHack::ContextManager DFMgr("Memory.xml"); - DFHack::Context *DF = DFMgr.getSingleContext(); - - try - { - DF->Attach(); - } - catch (std::exception& e) - { - std::cerr << e.what() << std::endl; - #ifndef LINUX_BUILD - cin.ignore(); - #endif - return 1; - } - - DFHack::Gui *Gui = DF->getGui(); - DFHack::VersionInfo* mem = DF->getMemoryInfo(); - DFHack::Process * p = DF->getProcess(); - OffsetGroup * OG_Maps = mem->getGroup("Maps"); - OffsetGroup * OG_MapBlock = OG_Maps->getGroup("block"); - OffsetGroup * OG_LocalFt = OG_Maps->getGroup("features")->getGroup("local"); - uint32_t designations = OG_MapBlock->getOffset("designation"); - uint32_t block_feature1 = OG_MapBlock->getOffset("feature_local"); - uint32_t block_feature2 = OG_MapBlock->getOffset("feature_global"); - uint32_t region_x_offset = OG_Maps->getAddress("region_x"); - uint32_t region_y_offset = OG_Maps->getAddress("region_y"); - uint32_t region_z_offset = OG_Maps->getAddress("region_z"); - uint32_t feature1_start_ptr = OG_LocalFt->getAddress("start_ptr"); - int32_t regionX, regionY, regionZ; - - // read position of the region inside DF world - p->readDWord (region_x_offset, (uint32_t &)regionX); - p->readDWord (region_y_offset, (uint32_t &)regionY); - p->readDWord (region_z_offset, (uint32_t &)regionZ); - while(1){ - int32_t cx1,cy1,cz1; - cx1 = -30000; - while(cx1 == -30000) - { - DF->ForceResume(); - cout << "Set cursor at first position, then press any key"; - cin.ignore(); - DF->Suspend(); - Gui->getCursorCoords(cx1,cy1,cz1); - } - - uint32_t tx1,ty1,tz1; - tx1 = cx1/16; - ty1 = cy1/16; - tz1 = cz1; - - int32_t cx2,cy2,cz2; - cx2 = -30000; - while(cx2 == -30000) - { - DF->Resume(); - cout << "Set cursor at second position, then press any key"; - cin.ignore(); - DF->Suspend(); - Gui->getCursorCoords(cx2,cy2,cz2); - } - uint32_t tx2,ty2,tz2; - tx2 = cx2/16; - ty2 = cy2/16; - tz2 = cz2; - sort(tx1,tx2); - sort(ty1,ty2); - sort(tz1,tz2); - sort(cx1,cx2); - sort(cy1,cy2); - sort(cz1,cz2); - - vector > >dig(cz2-cz1+1,vector >(cy2-cy1+1,vector(cx2-cx1+1))); - vector > >build(cz2-cz1+1,vector >(cy2-cy1+1,vector(cx2-cx1+1))); - mapblock40d block; - DFHack::Maps *Maps = DF->getMaps(); - Maps->Start(); - for(uint32_t y = ty1;y<=ty2;y++) - { - for(uint32_t x = tx1;x<=tx2;x++) - { - for(uint32_t z = tz1;z<=tz2;z++) - { - if(Maps->isValidBlock(x,y,z)) - { - if(Maps->ReadBlock40d(x,y,z,&block)) - { - int ystart,yend,xstart,xend; - ystart=xstart=0; - yend=xend=15; - if(y == ty2) - { - yend = cy2 % 16; - } - if(y == ty1) - { - ystart = cy1 % 16; - } - if(x == tx2) - { - xend = cx2 % 16; - } - if(x == tx1) - { - xstart = cx1 % 16; - } - int zidx = z-tz1; - for(int yy = ystart; yy <= yend;yy++) - { - int yidx = yy+(16*(y-ty1)-(cy1%16)); - for(int xx = xstart; xx <= xend;xx++) - { - int xidx = xx+(16*(x-tx1)-(cx1%16)); - int16_t tt = block.tiletypes[xx][yy]; - DFHack::TileShape ts = DFHack::tileShape(tt); - if(DFHack::isOpenTerrain(tt) || DFHack::isFloorTerrain(tt)) - { - dig[zidx][yidx][xidx] = "d"; - } - else if(DFHack::STAIR_DOWN == ts) - { - dig [zidx][yidx][xidx] = "j"; - build [zidx][yidx][xidx] = "Cd"; - } - else if(DFHack::STAIR_UP == ts) - { - dig [zidx][yidx][xidx] = "u"; - build [zidx][yidx][xidx] = "Cu"; - } - else if(DFHack::STAIR_UPDOWN == ts) - { - dig [zidx][yidx][xidx] = "i"; - build [zidx][yidx][xidx] = "Cx"; - } - else if(DFHack::isRampTerrain(tt)) - { - dig [zidx][yidx][xidx] = "r"; - build [zidx][yidx][xidx] = "Cr"; - } - else if(DFHack::isWallTerrain(tt)) - { - build [zidx][yidx][xidx] = "Cw"; - } - } - yidx++; - } - } - } - } - } - } - DFHack::Buildings * Bld = DF->getBuildings(); - std::map custom_workshop_types; - uint32_t numBuildings; - if(Bld->Start(numBuildings)) - { - Bld->ReadCustomWorkshopTypes(custom_workshop_types); - for(uint32_t i = 0; i < numBuildings; i++) - { - DFHack::t_building temp; - Bld->Read(i, temp); - if(temp.type != 0xFFFFFFFF) // check if type isn't invalid - { - std::string typestr; - mem->resolveClassIDToClassname(temp.type, typestr); - if(temp.z == cz1 && cx1 <= temp.x1 && cx2 >= temp.x2 && cy1 <= temp.y1 && cy2 >= temp.y2) - { - string currStr = build[temp.z-cz1][temp.y1-cy1][temp.x1-cx1]; - stringstream stream; - string newStr = buildCommands[typestr]; - if(temp.x1 != temp.x2) - { - stream << "(" << temp.x2-temp.x1+1 << "x" << temp.y2-temp.y1+1 << ")"; - newStr += stream.str(); - } - build[temp.z-cz1][temp.y1-cy1][temp.x1-cx1] = newStr + currStr; - } - } - } - } -// for testing purposes - //ofstream outfile("test.txt"); -// printVecOfVec(outfile, dig,'\t'); -// outfile << endl; -// printVecOfVec(outfile, build,'\t'); -// outfile << endl; -// outfile.close(); - - int32_t cx3,cy3,cz3,cx4,cy4,cz4; - uint32_t tx3,ty3,tz3,tx4,ty4,tz4; - char result; - while(1){ - cx3 = -30000; - while(cx3 == -30000){ - DF->Resume(); - cout << "Set cursor at new position, then press any key:"; - result = cin.get(); - DF->Suspend(); - Gui->getCursorCoords(cx3,cy3,cz3); - } - if(result == 'q'){ - break; - } - cx4 = cx3+cx2-cx1; - cy4 = cy3+cy2-cy1; - cz4 = cz3+cz2-cz1; - tx3=cx3/16; - ty3=cy3/16; - tz3=cz3; - tx4=cx4/16; - ty4=cy4/16; - tz4=cz4; - DFHack::WindowIO * Win = DF->getWindowIO(); - designations40d designationBlock; - for(uint32_t y = ty3;y<=ty4;y++) - { - for(uint32_t x = tx3;x<=tx4;x++) - { - for(uint32_t z = tz3;z<=tz4;z++) - { - Maps->Start(); - Maps->ReadBlock40d(x,y,z,&block); - Maps->ReadDesignations(x,y,z,&designationBlock); - int ystart,yend,xstart,xend; - ystart=xstart=0; - yend=xend=15; - if(y == ty4){ - yend = cy4 % 16; - } - if(y == ty3){ - ystart = cy3 % 16; - } - if(x == tx4){ - xend = cx4 % 16; - } - if(x == tx3){ - xstart = cx3 % 16; - } - int zidx = z-tz3; - for(int yy = ystart; yy <= yend;yy++){ - int yidx = yy+(16*(y-ty3)-(cy3%16)); - for(int xx = xstart; xx <= xend;xx++){ - int xidx = xx+(16*(x-tx3)-(cx3%16)); - if(dig[zidx][yidx][xidx] != ""){ - char test = dig[zidx][yidx][xidx].c_str()[0]; - switch (test){ - case 'd': - designationBlock[xx][yy].bits.dig = DFHack::designation_default; - break; - case 'i': - designationBlock[xx][yy].bits.dig = DFHack::designation_ud_stair; - break; - case 'u': - designationBlock[xx][yy].bits.dig = DFHack::designation_u_stair; - break; - case 'j': - designationBlock[xx][yy].bits.dig = DFHack::designation_d_stair; - break; - case 'r': - designationBlock[xx][yy].bits.dig = DFHack::designation_ramp; - break; - } - - } - } - yidx++; - } - Maps->Start(); - Maps->WriteDesignations(x,y,z,&designationBlock); - } - } - } - } - } - DF->Detach(); - #ifndef LINUX_BUILD - std::cout << "Done. Press any key to continue" << std::endl; - cin.ignore(); - #endif - return 0; -} -/* -bool waitTillCursorState(DFHack::Context *DF, bool On) -{ - DFHack::WindowIO * w = DF->getWindowIO(); - DFHack::Position * p = DF->getPosition(); - int32_t x,y,z; - int tryCount = 0; - DF->Suspend(); - bool cursorResult = p->getCursorCoords(x,y,z); - while(tryCount < 50 && On && !cursorResult || !On && cursorResult) - { - DF->Resume(); - w->TypeSpecial(DFHack::WAIT,1,WAIT_AMT); - tryCount++; - DF->Suspend(); - cursorResult = p->getCursorCoords(x,y,z); - } - if(tryCount >= 50) - { - cerr << "Something went wrong, cursor at x: " << x << " y: " << y << " z: " << z << endl; - return false; - } - DF->Resume(); - return true; -} -bool waitTillCursorPositionState(DFHack::Context *DF, int32_t x,int32_t y, int32_t z) -{ - DFHack::WindowIO * w = DF->getWindowIO(); - DFHack::Position * p = DF->getPosition(); - int32_t x2,y2,z2; - int tryCount = 0; - DF->Suspend(); - bool cursorResult = p->getCursorCoords(x2,y2,z2); - while(tryCount < 50 && (x != x2 || y != y2 || z != z2)) - { - DF->Resume(); - w->TypeSpecial(DFHack::WAIT,1,WAIT_AMT); - tryCount++; - DF->Suspend(); - cursorResult = p->getCursorCoords(x2,y2,z2); - } - if(tryCount >= 50) - { - cerr << "Something went wrong, cursor at x: " << x2 << " y: " << y2 << " z: " << z2 << endl; - return false; - } - DF->Resume(); - return true; -}*/ \ No newline at end of file diff --git a/needs_porting/creaturemanager.cpp b/needs_porting/creaturemanager.cpp deleted file mode 100644 index 0d65e9f6e..000000000 --- a/needs_porting/creaturemanager.cpp +++ /dev/null @@ -1,1428 +0,0 @@ -/********************************************* - * Purpose: - * - * - Display creatures - * - Modify skills and labors of creatures - * - Kill creatures - * - Etc. - * - * Version: 0.1.1 - * Date: 2011-04-07 - * Author: raoulxq (based on creaturedump.cpp from peterix) - - * Todo: - * - Option to add/remove single skills - * - Ghosts/Merchants/etc. should be tagged as not own creatures - * - Filter by nickname with -n - * - Filter by first name with -fn - * - Filter by last name with -ln - * - Add pattern matching (or at least matching) to -n/-fn/-ln - * - Set nickname with --setnick (only if -i is given) - * - Show skills/labors only when -ss/-sl/-v is given or a skill/labor is changed - * - Make -1 the default for everything but -i - * - Imply -i if first argument is a number - * - Search for nick/profession if first argument is a string without - (i.e. no switch) - * - Switch --showhappy (show dwarf's experiences which make her un-/happy) - * - Switch --makefriendly - * - Switch --listskills, showing first 3 important skills - - * Done: - * - More space for "Current Job" - * - Attempted to revive creature(s) with --revive, but it doesn't work (flag is there but invisible) - * - Switch -rcs, remove civil skills - * - Switch -rms, remove military skills (who would want that?) - * - Allow comma separated list of IDs for -i - * - '-c all' shows all creatures - * - Rename from skillmodify.cpp to creature.cpp - * - Kill creature(s) with --kill - * - Hide skills with level 0 and 0 experience points - * - Add --showallflags flag to display all flags (default: display a few important ones) - * - Add --showdead flag to also display dead creatures - * - Display more creature flags - * - Show creature type (again) - * - Add switch -1/--summary to only display one line for every creature. Good for an overview. - * - Display current job (has been there all the time, but not shown in Windows due to missing memory offsets) - * - Remove magic numbers - * - Show social skills only when -ss is given - * - Hide hauler labors when +sh is given - * - Add -v for verbose - * - Override forbidden mass-designation with -f - * - Option to add/remove single labors - * - Switches -ras and rl should only be possible with -nn or -i - * - Switch -rh removes hauler jobs - * - Dead creatures should not be displayed - * - Childs should not get labors assigned to - * - Babies should not get labors assigned to - * - Switch -al adds labor number n - * - Switch -rl removes labor number n - * - Switch -ral removes all labors - * - Switch -ll lists all available labors - ********************************************* -*/ - -#include -#include -#include -#include -#include -#include -using namespace std; - -#define DFHACK_WANT_MISCUTILS -#include -#include - -/* Note about magic numbers: - * If you have an idea how to better solve this, tell me. Currently I'd be - * either dependent on Toady One's implementation (#defining numbers) or - * Memory.xml (#defining text). I voted for Toady One's numbers to be more - * stable, but could be wrong. - * - * Ideally there would be a flag "is_military" or "is_social" in Memory.xml. - */ - -/* Social skills */ -#define SKILL_PERSUASION 72 -#define SKILL_NEGOTIATION 73 -#define SKILL_JUDGING_INTENT 74 -#define SKILL_INTIMIDATION 79 -#define SKILL_CONVERSATION 80 -#define SKILL_COMEDY 81 -#define SKILL_FLATTERY 82 -#define SKILL_CONSOLING 83 -#define SKILL_PACIFICATION 84 - -/* Misc skills */ -#define SKILL_WEAPONSMITHING 27 -#define SKILL_ARMORSMITHING 28 -#define SKILL_RECORD_KEEPING 77 -#define SKILL_WAX_WORKING 115 - -/* Some military skills */ -#define SKILL_COORDINATION 95 -#define SKILL_BALANCE 96 -#define SKILL_LEADERSHIP 97 -#define SKILL_TEACHING 98 -#define SKILL_FIGHTING 99 -#define SKILL_ARCHERY 100 -#define SKILL_WRESTLING 101 -#define SKILL_BITING 102 -#define SKILL_STRIKING 103 -#define SKILL_KICKING 104 -#define SKILL_DODGING 105 - -#define LABOR_STONE_HAULING 1 -#define LABOR_WOOD_HAULING 2 -#define LABOR_BURIAL 3 -#define LABOR_FOOD_HAULING 4 -#define LABOR_REFUSE_HAULING 5 -#define LABOR_ITEM_HAULING 6 -#define LABOR_FURNITURE_HAULING 7 -#define LABOR_ANIMAL_HAULING 8 -#define LABOR_CLEANING 9 -#define LABOR_FEED_PATIENTS_PRISONERS 22 -#define LABOR_RECOVERING_WOUNDED 23 - -#define PROFESSION_CHILD 96 -#define PROFESSION_BABY 97 - -#define NOT_SET INT_MIN -#define MAX_MOOD 4 -#define NO_MOOD -1 - -bool quiet=true; -bool verbose = false; -bool showhauler = true; -bool showsocial = false; -bool showfirstlineonly = false; -bool showdead = false; -bool showallflags = false; - -int hauler_labors[] = { - LABOR_STONE_HAULING - ,LABOR_WOOD_HAULING - ,LABOR_BURIAL - ,LABOR_FOOD_HAULING - ,LABOR_REFUSE_HAULING - ,LABOR_ITEM_HAULING - ,LABOR_FURNITURE_HAULING - ,LABOR_ANIMAL_HAULING - ,LABOR_CLEANING - ,LABOR_FEED_PATIENTS_PRISONERS - ,LABOR_RECOVERING_WOUNDED -}; - -int social_skills[] = -{ - SKILL_PERSUASION - ,SKILL_NEGOTIATION - ,SKILL_JUDGING_INTENT - ,SKILL_INTIMIDATION - ,SKILL_CONVERSATION - ,SKILL_COMEDY - ,SKILL_FLATTERY - ,SKILL_CONSOLING - ,SKILL_PACIFICATION -}; - -int military_skills[] = -{ - SKILL_COORDINATION - ,SKILL_BALANCE - ,SKILL_LEADERSHIP - ,SKILL_TEACHING - ,SKILL_FIGHTING - ,SKILL_ARCHERY - ,SKILL_WRESTLING - ,SKILL_BITING - ,SKILL_STRIKING - ,SKILL_KICKING - ,SKILL_DODGING -}; - -void usage(int argc, const char * argv[]) -{ - cout - << "Usage:" << endl - << argv[0] << " [option 1] [option 2] [...]" << endl - << endl - << "Display options:" << endl - << "-q : Suppress \"Press any key to continue\" at program termination" << endl - << "-v : Increase verbosity" << endl - << endl - - << "Choosing which creatures to display and/or modify " - << "(note that all criteria" << endl << "must match, so adding " - << " more narrows things down):" << endl - << "-i id1[,id2,...]: Only show/modify creature with this id" << endl - << "-c creature : Show/modify this creature type instead of dwarves" << endl - << " ('all' to show all creatures)" << endl - << "-nn/--nonicks : Only show/modify creatures with no custom nickname (migrants)" << endl - << "--nicks : Only show/modify creatures with custom nickname" << endl - << "--showdead : Also show/modify dead creatures" << endl - << "--type : Show/modify all creatures of given type" << endl - << " : Can be used multiple times" << endl - << " types:" << endl - << " * dead: all dead creatures" << endl - << " * demon: all demons" << endl - << " * diplomat: all diplomats" << endl - << " * FB: all forgotten beasts" << endl - << " * female: all female creatures" << endl - << " * ghost: all ghosts" << endl - << " * male: all male creatures" << endl - << " * merchants: all merchants (including pack animals)" << endl - << " * neuter: all neuter creatuers" << endl - << " * pregnant: all pregnant creatures" << endl - << " * tame: all tame creatues" << endl - << " * wild: all wild creatures" << endl - - << endl - - << "What information to display:" << endl - << "-saf : Show all flags of a creature" << endl - << "--showallflags : Show all flags of a creature" << endl - << "-ll/--listlabors: List available labors" << endl - << "-ss : Show social skills" << endl - << "+sh : Hide hauler labors" << endl - << "-1/--summary : Only display one line per creature" << endl - - << endl - - << "Options to modify selected creatures:" << endl - << "-al : Add labor to creature" << endl - << "-rl : Remove labor from creature" << endl - << "-ras : Remove all skills from creature (i.e. set them to zero)" << endl - << "-rcs : Remove civil skills from creature (i.e. set them to zero)" << endl - << "-rms : Remove military skills from creature (i.e. set them to zero)" << endl - << "-ral : Remove all labors from creature" << endl - << "-ah : Add hauler labors (stone hauling, etc.) to creature" << endl - << "-rh : Remove hauler labors (stone hauling, etc.) from creature" << endl - // Disabling mood doesn't work as intented - << "--setmood : Set mood to n (-1 = no mood, max=4, buggy!)" << endl - << "--kill : Kill creature(s) (leaves behind corpses)" << endl - << "--erase : Remove creature(s) from game without killing" << endl - << "--tame : Tames animals, recruits intelligent creatures." << endl - << "--slaugher : Mark a creature for slaughter, even sentients" << endl - << "--butcher : Same as --slaugher" << endl - // Doesn't seem to work - //<< "--revive : Attempt to revive creature(s) (remove dead and killed flag)" << endl - // Setting happiness doesn't work really, because hapiness is recalculated - //<< "--sethappiness : Set happiness to n" << endl - << "-f : Force an action" << endl - << endl - << "Examples:" << endl - << endl - << "Show all dwarfs:" << endl - << argv[0] << " -c Dwarf" << endl - << endl - << "Show summary of all creatures (spoiler: includes unknown creatures):" << endl - << argv[0] << " -1 -c all" << endl - << endl - << "Kill that nasty ogre" << endl - << argv[0] << " -i 52 --kill" << endl - << endl - << "Check that the ogre is really dead" << endl - << argv[0] << " -c ogre --showdead" << endl - << endl - << "Remove all skills from dwarfs 15 and 32:" << endl - << argv[0] << " -i 15,32 -ras" << endl - << endl - << "Remove all skills and labors from dwarfs with no custom nickname:" << endl - << argv[0] << " -c DWARF -nn -ras -ral" << endl - << endl - << "Add hauling labors to all dwarfs without nickname (e.g. migrants):" << endl - << argv[0] << " -c DWARF -nn -ah" << endl - << endl - << "Show list of labor ids:" << endl - << argv[0] << " -c DWARF -ll" << endl - << endl - << "Add engraving labor to all dwarfs without nickname (get the labor id from the list above):" << endl - << argv[0] << " -c DWARF -nn -al 13" << endl - << endl - << "Make Urist, Stodir and Ingish miners:" << endl - << argv[0] << " -i 31,42,77 -al 0" << endl - << endl - << "Make all demons friendly:" << endl - << argv[0] << " --type demon --tame" << endl - ; - if (quiet == false) { - cout << "Press any key to continue" << endl; - cin.ignore(); - } -} - -DFHack::Materials * Materials; -DFHack::VersionInfo *mem; -DFHack::Creatures * Creatures = NULL; - -// Note that toCaps() changes the string itself and I'm using it a few times in -// an unsafe way below. Didn't crash yet however. -std::string toCaps(std::string s) -{ - const int length = s.length(); - std::locale loc(""); - bool caps=true; - if (length == 0) { - return s; - } - for(int i=0; i!=length ; ++i) - { - if (caps) - { - s[i] = std::toupper(s[i],loc); - caps = false; - } - else if (s[i] == '_' || s[i] == ' ') - { - s[i] = ' '; - caps = true; - } - else - { - s[i] = std::tolower(s[i],loc); - } - } - return s; -} - -int strtoint(const string &str) -{ - stringstream ss(str); - int result; - return ss >> result ? result : -1; -} - - -// A C++ standard library function should be used instead -bool is_in(int m, int set[], int set_size) -{ - for (int i=0; i v, int comp) -{ - for (size_t i=0; igetTranslation(); - DFHack::VersionInfo *mem = DF->getMemoryInfo(); - - string type="(no type)"; - if (Materials->raceEx[creature.race].rawname[0]) - { - type = toCaps(Materials->raceEx[creature.race].rawname); - } - - string name="(no name)"; - if(creature.name.nickname[0]) - { - name = creature.name.nickname; - } - else - { - if(creature.name.first_name[0]) - { - name = toCaps(creature.name.first_name); - - string transName = Tran->TranslateName(creature.name,false); - if(!transName.empty()) - { - name += " " + toCaps(transName); - } - } - } - - string profession=""; - try { - profession = mem->getProfession(creature.profession); - } - catch (exception& e) - { - cout << "Error retrieving creature profession: " << e.what() << endl; - } - if(creature.custom_profession[0]) - { - profession = creature.custom_profession; - } - - - string jobid; - stringstream ss; - ss << "(" << creature.current_job.jobId << ")"; - jobid = ss.str(); - - string job="No Job/On Break" + (creature.current_job.jobId == 0 ? "" : jobid); - if(creature.current_job.active) - { - job=mem->getJob(creature.current_job.jobId); - - int p=job.size(); - while (p>0 && (job[p]==' ' || job[p]=='\t')) - p--; - if (p <= 1) // Display numeric jobID if unknown job - { - job = jobid; - } - } - - if (showfirstlineonly) - { - printf("%3d", index); - printf(" %-17s", type.c_str()); - printf(" %-24s", name.c_str()); - printf(" %-16s", toCaps(profession).c_str()); - printf(" %-38s", job.c_str()); - printf(" %5d", creature.happiness); - if (showdead) - { - printf(" %-5s", creature.flags1.bits.dead ? "Dead" : "Alive"); - } - - printf("\n"); - - return; - } - else - { - printf("ID: %d", index); - printf(", %s", type.c_str()); - printf(", %s", name.c_str()); - printf(", %s", toCaps(profession).c_str()); - printf(", Job: %s", job.c_str()); - printf(", Happiness: %d", creature.happiness); - printf("\n"); - printf("Origin: %p\n", creature.origin); - printf("Civ #: %d\n", creature.civ); - } - - if((creature.mood != NO_MOOD) && (creature.mood<=MAX_MOOD)) - { - cout << "Creature is in a strange mood (mood=" << creature.mood << "), skill: " << mem->getSkill(creature.mood_skill) << endl; - vector mymat; - if(Creatures->ReadJob(&creature, mymat)) - { - for(unsigned int i = 0; i < mymat.size(); i++) - { - printf("\t%s(%d)\t%d %d %d - %.8x\n", Materials->getDescription(mymat[i]).c_str(), mymat[i].itemType, mymat[i].subType, mymat[i].subIndex, mymat[i].index, mymat[i].flags); - } - } - } - - if(creature.has_default_soul) - { - // Print out skills - int skillid; - int skillrating; - int skillexperience; - string skillname; - - cout << setiosflags(ios::left); - - for(unsigned int i = 0; i < creature.defaultSoul.numSkills;i++) - { - skillid = creature.defaultSoul.skills[i].id; - bool is_social = is_in(skillid, social_skills, sizeof(social_skills)/sizeof(social_skills[0])); - if (!is_social || (is_social && showsocial)) - { - skillrating = creature.defaultSoul.skills[i].rating; - skillexperience = creature.defaultSoul.skills[i].experience; - try - { - skillname = mem->getSkill(skillid); - } - catch(DFHack::Error::AllMemdef &e) - { - skillname = "Unknown skill"; - cout << e.what() << endl; - } - if (skillrating > 0 || skillexperience > 0) - { - cout << "(Skill " << int(skillid) << ") " << setw(16) << skillname << ": " - << skillrating << "/" << skillexperience << endl; - } - } - } - - for(unsigned int i = 0; i < NUM_CREATURE_LABORS;i++) - { - if(!creature.labors[i]) - continue; - string laborname; - try - { - laborname = mem->getLabor(i); - } - catch(exception &) - { - laborname = "(Undefined)"; - } - bool is_labor = is_in(i, hauler_labors, sizeof(hauler_labors)/sizeof(hauler_labors[0])); - if (!is_labor || (is_labor && showhauler)) - cout << "(Labor " << i << ") " << setw(16) << laborname << endl; - } - } - - if (creature.pregnancy_timer > 0) - cout << "Pregnant: " << creature.pregnancy_timer << " ticks to " - << "birth." << endl; - - if (showallflags) - { - DFHack::t_creaturflags1 f1 = creature.flags1; - DFHack::t_creaturflags2 f2 = creature.flags2; - DFHack::t_creaturflags3 f3 = creature.flags3; - - if(f1.bits.dead){cout << "Flag: dead" << endl; } - if(f1.bits.had_mood){cout<TranslateName(creature.artifact_name,false); - cout << "Artifact: " << artifact_name << endl; - } - } - cout << endl; -} - -class creature_filter -{ -public: - - enum sex_filter - { - SEX_FEMALE = 0, - SEX_MALE = 1, - SEX_ANY = 254, // Our magin number for ignoring sex. - SEX_NEUTER = 255 - }; - - bool dead; - bool demon; - bool diplomat; - bool find_nonicks; - bool find_nicks; - bool forgotten_beast; - bool ghost; - bool merchant; - bool pregnant; - bool tame; - bool wild; - - sex_filter sex; - - string creature_type; - std::vector creature_id; - - #define DEFAULT_CREATURE_STR "Default" - - creature_filter() - { - // By default we only select dwarves, except that if we use the - // --type option we want to default to everyone. So we start out - // with a special string, and if remains unchanged after all - // the options have been processed we turn it to DWARF. - creature_type = DEFAULT_CREATURE_STR; - - dead = false; - demon = false; - diplomat = false; - find_nonicks = false; - find_nicks = false; - forgotten_beast = false; - ghost = false; - merchant = false; - pregnant = false; - sex = SEX_ANY; - tame = false; - wild = false; - } - - // If the creature type is still the default, then change it to allow - // for all creatures. If the creature type has been explicitly set, - // then don't alter it. - void defaultTypeToAll() - { - if (creature_type == DEFAULT_CREATURE_STR) - creature_type = ""; - } - - // If the creature type is still the default, change it to DWARF - void defaultTypeToDwarf() - { - if (creature_type == DEFAULT_CREATURE_STR) - creature_type = "Dwarf"; - } - - void process_type(string type) - { - type = toCaps(type); - - // If we're going by type, then by default all species are - // permitted. - defaultTypeToAll(); - - if (type == "Dead") - { - dead = true; - showdead = true; - } - else if (type == "Demon") - demon = true; - else if (type == "Diplomat") - diplomat = true; - else if (type == "Fb" || type == "Beast") - forgotten_beast = true; - else if (type == "Ghost") - ghost = true; - else if (type == "Merchant") - merchant = true; - else if (type == "Pregnant") - pregnant = true; - else if (type == "Tame") - tame = true; - else if (type == "Wild") - wild = true; - else if (type == "Male") - sex = SEX_MALE; - else if (type == "Female") - sex = SEX_FEMALE; - else if (type == "Neuter") - sex = SEX_NEUTER; - else - { - cerr << "ERROR: Unknown type '" << type << "'" << endl; - } - } - - void doneProcessingOptions() - { - string temp = toCaps(creature_type); - creature_type = temp; - - defaultTypeToDwarf(); - } - - bool creatureMatches(const DFHack::t_creature & creature, - uint32_t creature_idx) - { - // A list of ids overrides everything else. - if (creature_id.size() > 0) - return (find_int(creature_id, creature_idx)); - - // If it's not a list of ids, it has not match all given criteria. - - const DFHack::t_creaturflags1 &f1 = creature.flags1; - const DFHack::t_creaturflags2 &f2 = creature.flags2; - const DFHack::t_creaturflags3 &f3 = creature.flags3; - - if(f1.bits.dead && !showdead) - return false; - - bool hasnick = (creature.name.nickname[0] != '\0'); - if(hasnick && find_nonicks) - return false; - if(!hasnick && find_nicks) - return false; - - string race_name = string(Materials->raceEx[creature.race].rawname); - - if(!creature_type.empty() && creature_type != toCaps(race_name)) - return false; - - if(dead && !f1.bits.dead) - return false; - if(demon && !f2.bits.underworld) - return false; - if(diplomat && !f1.bits.diplomat) - return false; - if(forgotten_beast && !f2.bits.visitor_uninvited) - return false; - if(ghost && !f3.bits.ghostly) - return false; - if(merchant && !f1.bits.merchant) - return false; - if(pregnant && creature.pregnancy_timer == 0) - return false; - if (sex != SEX_ANY && creature.sex != (uint8_t) sex) - return false; - if(tame && !f1.bits.tame) - return false; - - if(wild && !f2.bits.roaming_wilderness_population_source && - !f2.bits.roaming_wilderness_population_source_not_a_map_feature) - { - return false; - } - - return true; - } -}; - -int main (int argc, const char* argv[]) -{ - // let's be more useful when double-clicked on windows -#ifndef LINUX_BUILD - quiet = false; -#endif - creature_filter filter; - - bool remove_skills = false; - bool remove_civil_skills = false; - bool remove_military_skills = false; - bool remove_labors = false; - bool kill_creature = false; - bool erase_creature = false; - bool revive_creature = false; - bool make_hauler = false; - bool remove_hauler = false; - bool add_labor = false; - int add_labor_n = NOT_SET; - bool remove_labor = false; - int remove_labor_n = NOT_SET; - bool set_happiness = false; - int set_happiness_n = NOT_SET; - bool set_mood = false; - int set_mood_n = NOT_SET; - bool list_labors = false; - bool force_massdesignation = false; - bool tame_creature = false; - bool slaughter_creature = false; - - if (argc == 1) { - usage(argc, argv); - return 1; - } - - for(int i = 1; i < argc; i++) - { - string arg_cur = argv[i]; - string arg_next = ""; - int arg_next_int = NOT_SET; - /* Check if argv[i+1] is a number >= 0 */ - if (i < argc-1) { - arg_next = argv[i+1]; - arg_next_int = strtoint(arg_next); - if (arg_next != "0" && arg_next_int == 0) { - arg_next_int = NOT_SET; - } - } - - if(arg_cur == "-q") - { - quiet = true; - } - else if(arg_cur == "+q") - { - quiet = false; - } - else if(arg_cur == "-v") - { - verbose = true; - } - else if(arg_cur == "-1" || arg_cur == "--summary") - { - showfirstlineonly = true; - } - else if(arg_cur == "-ss" || arg_cur == "--showsocial") - { - showsocial = true; - } - else if(arg_cur == "+sh" || arg_cur == "-nosh" || arg_cur == "--noshowhauler") - { - showhauler = false; - } - else if(arg_cur == "--showdead") - { - showdead = true; - } - else if(arg_cur == "--showallflags" || arg_cur == "-saf") - { - showallflags = true; - } - else if(arg_cur == "-ras") - { - remove_skills = true; - } - else if(arg_cur == "-rcs") - { - remove_civil_skills = true; - } - else if(arg_cur == "-rms") - { - remove_military_skills = true; - } - else if(arg_cur == "-f") - { - force_massdesignation = true; - } - // list labors - else if(arg_cur == "-ll" || arg_cur == "--listlabors") - { - list_labors = true; - } - // add single labor - else if(arg_cur == "-al" && i < argc-1) - { - if (arg_next_int == NOT_SET || arg_next_int >= NUM_CREATURE_LABORS) { - usage(argc, argv); - return 1; - } - add_labor = true; - add_labor_n = arg_next_int; - i++; - } - // remove single labor - else if(arg_cur == "-rl" && i < argc-1) - { - if (arg_next_int == NOT_SET || arg_next_int >= NUM_CREATURE_LABORS) { - usage(argc, argv); - return 1; - } - remove_labor = true; - remove_labor_n = arg_next_int; - i++; - } - else if(arg_cur == "--setmood" && i < argc-1) - { - if (arg_next_int < NO_MOOD || arg_next_int > MAX_MOOD) { - usage(argc, argv); - return 1; - } - set_mood = true; - set_mood_n = arg_next_int; - i++; - } - else if(arg_cur == "--sethappiness" && i < argc-1) - { - if (arg_next_int < 1 || arg_next_int >= 2000) { - usage(argc, argv); - return 1; - } - set_happiness = true; - set_happiness_n = arg_next_int; - i++; - } - else if(arg_cur == "--kill") - { - kill_creature = true; - showallflags = true; - showdead = true; - } - else if(arg_cur == "--erase") - { - erase_creature = true; - showallflags = true; - showdead = true; - } - else if(arg_cur == "--revive") - { - revive_creature = true; - showdead = true; - showallflags = true; - } - else if(arg_cur == "-ral") - { - remove_labors = true; - } - else if(arg_cur == "-ah") - { - make_hauler = true; - } - else if(arg_cur == "-rh") - { - remove_hauler = true; - } - else if(arg_cur == "-nn" || arg_cur == "--nonicks") - { - filter.find_nonicks = true; - } - else if(arg_cur == "--nicks") - { - filter.find_nicks = true; - } - else if(arg_cur == "-c" && i < argc-1) - { - filter.creature_type = argv[i+1]; - i++; - } - else if(arg_cur == "-i" && i < argc-1) - { - std::stringstream ss(argv[i+1]); - int num; - while (ss >> num) { - filter.creature_id.push_back(num); - ss.ignore(1); - } - - filter.creature_type = ""; // if -i is given, match all creatures - showdead = true; - i++; - } - else if(arg_cur == "--type" && i < argc-1) - { - filter.process_type(arg_next); - i++; - } - else if (arg_cur == "--tame") - tame_creature = true; - else if (arg_cur == "--slaugher" || arg_cur == "--butcher") - slaughter_creature = true; - else - { - if (arg_cur != "-h") { - cout << "Unknown option '" << arg_cur << "'" << endl; - cout << endl; - } - usage(argc, argv); - return 1; - } - } - - filter.doneProcessingOptions(); - - DFHack::ContextManager DFMgr("Memory.xml"); - DFHack::Context* DF; - try - { - DF = DFMgr.getSingleContext(); - DF->Attach(); - } - catch (exception& e) - { - cerr << e.what() << endl; - if (quiet == false) - { - cin.ignore(); - } - return 1; - } - - Creatures = DF->getCreatures(); - Materials = DF->getMaterials(); - DFHack::Translation * Tran = DF->getTranslation(); - - uint32_t numCreatures; - if(!Creatures->Start(numCreatures)) - { - cerr << "Can't get creatures" << endl; - if (quiet == false) - { - cin.ignore(); - } - return 1; - } - if(!numCreatures) - { - cerr << "No creatures to print" << endl; - if (quiet == false) - { - cin.ignore(); - } - return 1; - } - - mem = DF->getMemoryInfo(); - Materials->ReadInorganicMaterials(); - Materials->ReadOrganicMaterials(); - Materials->ReadWoodMaterials(); - Materials->ReadPlantMaterials(); - Materials->ReadCreatureTypes(); - Materials->ReadCreatureTypesEx(); - Materials->ReadDescriptorColors(); - - if(!Tran->Start()) - { - cerr << "Can't get name tables" << endl; - return 1; - } - - // List all available labors (reproduces contents of Memory.xml) - if (list_labors == true) { - string laborname; - for (int i=0; i < NUM_CREATURE_LABORS; i++) { - try { - laborname = mem->getLabor(i); - cout << "Labor " << int(i) << ": " << laborname << endl; - } - catch (exception&) { - if (verbose) - { - laborname = "Unknown"; - cout << "Labor " << int(i) << ": " << laborname << endl; - } - } - } - } - else - { - if (showfirstlineonly) - { - printf("ID Type Name/nickname Job title Current job Happy%s\n", showdead?" Dead ":""); - printf("--- ----------------- ------------------------ ---------------- -------------------------------------- -----%s\n", showdead?" -----":""); - } - - vector addrs; - for(uint32_t creature_idx = 0; creature_idx < numCreatures; creature_idx++) - { - DFHack::t_creature creature; - Creatures->ReadCreature(creature_idx,creature); - /* Check if we want to display/change this creature or skip it */ - if(filter.creatureMatches(creature, creature_idx)) - { - printCreature(DF,creature,creature_idx); - addrs.push_back(creature.origin); - - bool dochange = ( - remove_skills || remove_civil_skills || remove_military_skills - || remove_labors || add_labor || remove_labor - || make_hauler || remove_hauler - || kill_creature || erase_creature - || revive_creature - || set_happiness - || set_mood - || tame_creature || slaughter_creature - ); - - if (toCaps(filter.creature_type) == "Dwarf" - && (creature.profession == PROFESSION_CHILD || creature.profession == PROFESSION_BABY)) - { - dochange = false; - } - - bool allow_massdesignation = - filter.creature_id.size()==0 || - toCaps(filter.creature_type) != "Dwarf" || - filter.find_nonicks == true || - force_massdesignation; - if (dochange == true && allow_massdesignation == false) - { - cout - << "Not changing creature because none of -c (other than dwarf), -i or -nn was" << endl - << "selected. Add -f (force) to override this safety measure." << endl; - dochange = false; - } - - if (dochange) - { - if(creature.has_default_soul) - { - if (kill_creature && !creature.flags1.bits.dead) - { - DFHack::t_creaturflags1 f1 = creature.flags1; - DFHack::t_creaturflags2 f2 = creature.flags2; - DFHack::t_creaturflags3 f3 = creature.flags3; - - f3.bits.scuttle = true; - - cout << "Writing flags..." << endl; - if (!Creatures->WriteFlags(creature_idx, f1.whole, - f2.whole, f3.whole)) - { - cout << "Error writing creature flags!" << endl; - } - // We want the flags to be shown after our - // modification, but they are not read back - creature.flags1 = f1; - creature.flags2 = f2; - creature.flags3 = f3; - } - - if (erase_creature && !creature.flags1.bits.dead) - { - /* - [quote author=Eldrick Tobin link=topic=58809.msg2178545#msg2178545 date=1302638055] - - After extensive testing that just ate itself -.-; - - Runesmith does not unset the following: - - Active Invader (sets if they are just about the invade, as Currently - Invading removes this one) - - Hidden Ambusher (Just in Case, however it is still set when an Active Invader) - - Hidden in Ambush (Just in Case, however it is still set when an Active Invader, - until discovery) - - Incoming (Sets if something is here yet... wave X of a siege here) - - Invader -Fleeing/Leaving - - Currently Invading - - When it nukes something it basically just sets them to 'dead'. It does not also - set them to 'killed'. Show dead will show everything (short of 'vanished'/'deleted' - I'd suspect) so one CAN go through the intensive process to revive a broken siege. These - particular flags are not visible at the same exact time so multiple passes -even through - a narrow segment- are advised. - - Problem I ran into (last thing before I mention something more DFHack related): - I set the Killed Flag (but not dead), and I got mortally wounded siegers that refused to - just pift in Magma. [color=purple]Likely missing upper torsoes on examination[/color]. - - */ - /* This is from an invading creature's flags: - - ID: 560, Crocodile Cave, Nako, Standard, Job: No Job, Happiness: 100 - Flag: Marauder - Flag: Can Swap - Flag: Active Invader - Flag: Invader Origin - Flag: Coward - Flag: Hidden Ambusher - Flag: Invades - Flag: Ridden - Flag: Calculated Nerves - Flag: Calculated Bodyparts - Flag: Calculated Insulation - Flag: Vision Good - Flag: Breathing Good - - */ - - DFHack::t_creaturflags1 f1 = creature.flags1; - DFHack::t_creaturflags2 f2 = creature.flags2; - - f1.bits.dead = 1; - f2.bits.killed = 1; - f1.bits.active_invader = 0; /*!< 17: Active invader (for organized ones) */ - f1.bits.hidden_ambusher = 0; /*!< 21: Active marauder/invader moving inward? */ - f1.bits.hidden_in_ambush = 0; - f1.bits.invades = 0; /*!< 22: Marauder resident/invader moving in all the way */ - - cout << "Writing flags..." << endl; - if (!Creatures->WriteFlags(creature_idx, f1.whole, f2.whole)) - { - cout << "Error writing creature flags!" << endl; - } - // We want the flags to be shown after our modification, but they are not read back - creature.flags1 = f1; - creature.flags2 = f2; - } - - - if (revive_creature && creature.flags1.bits.dead) - { - DFHack::t_creaturflags1 f1 = creature.flags1; - DFHack::t_creaturflags2 f2 = creature.flags2; - - f1.bits.dead = 0; - f2.bits.killed = 0; - f1.bits.active_invader = 1; /*!< 17: Active invader (for organized ones) */ - f1.bits.hidden_ambusher = 1; /*!< 21: Active marauder/invader moving inward? */ - f1.bits.hidden_in_ambush = 1; - f1.bits.invades = 1; /*!< 22: Marauder resident/invader moving in all the way */ - - cout << "Writing flags..." << endl; - if (!Creatures->WriteFlags(creature_idx, f1.whole, f2.whole)) - { - cout << "Error writing creature flags!" << endl; - } - // We want the flags to be shown after our modification, but they are not read back - creature.flags1 = f1; - creature.flags2 = f2; - } - - if (set_mood) - { - /* Doesn't really work to disable a mood */ - cout << "Setting mood to " << set_mood_n << "..." << endl; - Creatures->WriteMood(creature_idx, set_mood_n); - DFHack::t_creaturflags1 f1 = creature.flags1; - DFHack::t_creaturflags2 f2 = creature.flags2; - f1.bits.has_mood = (set_mood_n == NO_MOOD ? 0 : 1); - if (!Creatures->WriteFlags(creature_idx, f1.whole, f2.whole)) - { - cout << "Error writing creature flags!" << endl; - } - creature.flags1 = f1; - creature.flags2 = f2; - } - - if (set_happiness) - { - cout << "Setting happiness to " << set_happiness_n << "..." << endl; - Creatures->WriteHappiness(creature_idx, set_happiness_n); - } - - if (remove_skills || remove_civil_skills || remove_military_skills) - { - DFHack::t_soul & soul = creature.defaultSoul; - - cout << "Removing skills..." << endl; - - for(unsigned int sk = 0; sk < soul.numSkills;sk++) - { - bool is_military = is_in(soul.skills[sk].id, military_skills, sizeof(military_skills)/sizeof(military_skills[0])); - if (remove_skills - || (remove_civil_skills && !is_military) - || (remove_military_skills && is_military)) - { - soul.skills[sk].rating=0; - soul.skills[sk].experience=0; - } - } - - // Doesn't work anyways, so better leave it alone - //soul.numSkills=0; - if (Creatures->WriteSkills(creature_idx, soul) == true) { - cout << "Success writing skills." << endl; - } else { - cout << "Error writing skills." << endl; - } - } - - if (add_labor || remove_labor || remove_labors || make_hauler || remove_hauler) - { - if (add_labor) { - cout << "Adding labor " << add_labor_n << "..." << endl; - creature.labors[add_labor_n] = 1; - } - - if (remove_labor) { - cout << "Removing labor " << remove_labor_n << "..." << endl; - creature.labors[remove_labor_n] = 0; - } - - if (remove_labors) { - cout << "Removing labors..." << endl; - for(unsigned int lab = 0; lab < NUM_CREATURE_LABORS; lab++) { - creature.labors[lab] = 0; - } - } - - if (remove_hauler) { - for (int labs=0; - labs < sizeof(hauler_labors)/sizeof(hauler_labors[0]); - labs++) - { - creature.labors[hauler_labors[labs]] = 0; - } - } - - if (make_hauler) { - cout << "Setting hauler labors..." << endl; - for (int labs=0; - labs < sizeof(hauler_labors)/sizeof(hauler_labors[0]); - labs++) - { - creature.labors[hauler_labors[labs]] = 1; - } - } - if (Creatures->WriteLabors(creature_idx, creature.labors) == true) { - cout << "Success writing labors." << endl; - } else { - cout << "Error writing labors." << endl; - } - } - - if (tame_creature) - { - bool tame = true; - - DFHack::t_creaturflags1 f1 = creature.flags1; - DFHack::t_creaturflags2 f2 = creature.flags2; - - // Site residents are intelligent, so don't - // tame them. - if (f2.bits.resident) - tame = false; - - f1.bits.diplomat = false; - f1.bits.merchant = false; - f2.bits.resident = false; - f2.bits.underworld = false; - f2.bits.visitor_uninvited = false; - f2.bits.roaming_wilderness_population_source = false; - f2.bits.roaming_wilderness_population_source_not_a_map_feature = false; - - // Creatures which already belong to a civ might - // be intelligent, so don't tame them. - if (creature.civ == -1) - f1.bits.tame = tame; - - if (!Creatures->WriteFlags(creature_idx, - f1.whole, f2.whole)) - { - cout << "Error writing creature flags!" << endl; - } - - int32_t civ = Creatures->GetDwarfCivId(); - if (!Creatures->WriteCiv(creature_idx, civ)) - { - cout << "Error writing creature civ!" << endl; - } - creature.flags1 = f1; - creature.flags2 = f2; - } - - if (slaughter_creature) - { - DFHack::t_creaturflags1 f1 = creature.flags1; - DFHack::t_creaturflags2 f2 = creature.flags2; - - f2.bits.slaughter = true; - - if (!Creatures->WriteFlags(creature_idx, - f1.whole, f2.whole)) - { - cout << "Error writing creature flags!" << endl; - } - creature.flags1 = f1; - creature.flags2 = f2; - } - } - else - { - cout << "Error removing skills: Creature has no default soul." << endl; - } - printCreature(DF,creature,creature_idx); - } /* End remove skills/labors */ - } /* if (print creature) */ - } /* End for(all creatures) */ - } /* End if (we need to walk creatures) */ - - Creatures->Finish(); - DF->Detach(); - if (quiet == false) - { - cout << "Done. Press any key to continue" << endl; - cin.ignore(); - } - return 0; -} From 5b54909886bdd58087506d4d35c43edc073b3a14 Mon Sep 17 00:00:00 2001 From: Peridexis Errant Date: Tue, 6 Jan 2015 22:48:24 +1100 Subject: [PATCH 089/115] Move hotkey-notes to scripts, delete needs_porting! The notes script is unfinished, but that's the last thing to do - since everything else is obsolete, ported, or the subject of a github issue to make a replacement. --- needs_porting/_notes.txt | 9 ------- {needs_porting => scripts}/hotkey-notes.lua | 30 ++++----------------- 2 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 needs_porting/_notes.txt rename {needs_porting => scripts}/hotkey-notes.lua (54%) diff --git a/needs_porting/_notes.txt b/needs_porting/_notes.txt deleted file mode 100644 index cdd364d12..000000000 --- a/needs_porting/_notes.txt +++ /dev/null @@ -1,9 +0,0 @@ -TODO: - copypaste.cpp - high value target - a proof of concept plugin to allow copy-pasting in DF; does both terrain and buildings/constructions - creaturemanager.cpp - modify skills and labors of creatures, kill creatures, etc; impressive but I suspect most functions implemented elsewhere - -In progress: - hotkey-notes.lua - prints list of hotkeys with name and jump position diff --git a/needs_porting/hotkey-notes.lua b/scripts/hotkey-notes.lua similarity index 54% rename from needs_porting/hotkey-notes.lua rename to scripts/hotkey-notes.lua index a84506512..30654911e 100644 --- a/needs_porting/hotkey-notes.lua +++ b/scripts/hotkey-notes.lua @@ -1,5 +1,9 @@ -- prints info on assigned hotkeys to the console +-- A work in progress, hoping for some help! +-- A different approach might be needed depending on internal structures, +-- but this gets the idea across. + local hotkeys = {'F1 ', 'F2 ', 'F3 ', 'F4 ', 'F5 ', 'F6 ', 'F7 ', 'F8 ', 'F9 ', 'F10', 'F11', 'F12'} @@ -13,28 +17,4 @@ for i=1, #hotkeys do hk.z = df.global.window_z print(hk.id..' '..hk.name..' X= '..hk.x..', Y= '..hk.y..', Z= '..hk.z) -end - ---[[ -# the (very) old Python version... -from context import Context, ContextManager - -cm = ContextManager("Memory.xml") -df = cm.get_single_context() - -df.attach() - -gui = df.gui - -print "Hotkeys" - -hotkeys = gui.read_hotkeys() - -for key in hotkeys: - print "x: %d\ny: %d\tz: %d\ttext: %s" % (key.x, key.y, key.z, key.name) - -df.detach() - -print "Done. Press any key to continue" -raw_input() -]]-- +end \ No newline at end of file From 49990868462bdcdae0ca4080c7d772390a597af6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 7 Jan 2015 16:03:24 -0500 Subject: [PATCH 090/115] Use "OSX" in package names on OS X instead of "Darwin" --- CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 984bb09e7..a01129418 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,5 +188,10 @@ ELSEIF(WIN32) SET(CPACK_GENERATOR "ZIP") ENDIF() set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0) -set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${CMAKE_SYSTEM_NAME}") +IF(APPLE) + set(DFHACK_PACKAGE_PLATFORM_NAME OSX) +ELSE() + set(DFHACK_PACKAGE_PLATFORM_NAME ${CMAKE_SYSTEM_NAME}) +ENDIF() +set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_PACKAGE_PLATFORM_NAME}") INCLUDE(CPack) From b030bb08fec87da89717a20cf999c0284654064f Mon Sep 17 00:00:00 2001 From: Peridexis Errant Date: Thu, 8 Jan 2015 11:11:09 +1100 Subject: [PATCH 091/115] Finish and document hotkey-notes All working correctly - thus ends the long story of the needs_porting folder. --- NEWS | 4 ++++ Readme.rst | 4 ++++ scripts/hotkey-notes.lua | 28 ++++++++++------------------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/NEWS b/NEWS index 04819a438..065c08aa8 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,8 @@ DFHack Future + New Scripts + hotkey-notes: print key, name, and jump position of hotkeys + Removed + needs_porting/* diff --git a/Readme.rst b/Readme.rst index 609a98dcd..5d2d86dee 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2394,6 +2394,10 @@ is "hfs-pit 1 0 0", ie single-tile wide with no walls or stairs. First example is a four-across pit with stairs but no walls; second is a two-across pit with stairs but no walls. +hotkey-notes +============ +Lists the key, name, and jump position of your hotkeys. + lever ===== Allow manipulation of in-game levers from the dfhack console. diff --git a/scripts/hotkey-notes.lua b/scripts/hotkey-notes.lua index 30654911e..c8b9b9606 100644 --- a/scripts/hotkey-notes.lua +++ b/scripts/hotkey-notes.lua @@ -1,20 +1,12 @@ -- prints info on assigned hotkeys to the console --- A work in progress, hoping for some help! --- A different approach might be needed depending on internal structures, --- but this gets the idea across. - -local hotkeys = {'F1 ', 'F2 ', 'F3 ', 'F4 ', 'F5 ', 'F6 ', - 'F7 ', 'F8 ', 'F9 ', 'F10', 'F11', 'F12'} - -for i=1, #hotkeys do - local hk = hotkeys[i] - hk = {id=hk} - -- PLACEHOLDER PROPERTIES ONLY! - hk.name = '_name' - hk.x = df.global.window_x - hk.y = df.global.window_y - hk.z = df.global.window_z - - print(hk.id..' '..hk.name..' X= '..hk.x..', Y= '..hk.y..', Z= '..hk.z) -end \ No newline at end of file +local h_list = {'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', + 'Shift+F1', 'Shift+F2', 'Shift+F3', 'Shift+F4', + 'Shift+F5', 'Shift+F6', 'Shift+F7', 'Shift+F8'} + +for i=1, #df.global.ui.main.hotkeys do + local hk = df.global.ui.main.hotkeys[i-1] + if hk.cmd ~= -1 then + print(h_list[i]..': '..hk.name..': x='..hk.x..' y='..hk.y..' z='..hk.z) + end +end From 4ab233694f734241c15030ad57a947da4e0ae386 Mon Sep 17 00:00:00 2001 From: Peridexis Errant Date: Thu, 8 Jan 2015 13:02:07 +1100 Subject: [PATCH 092/115] tabs -> space in NEWS --- NEWS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 065c08aa8..238a321a3 100644 --- a/NEWS +++ b/NEWS @@ -1,8 +1,8 @@ DFHack Future New Scripts - hotkey-notes: print key, name, and jump position of hotkeys + hotkey-notes: print key, name, and jump position of hotkeys Removed - needs_porting/* + needs_porting/* @@ -28,7 +28,7 @@ DFHack 0.40.23-r1 Fixed a display issue with PRINT_MODE:TEXT Fixed a symbol error (MapExtras::BiomeInfo::MAX_LAYERS) when compiling DFHack in Debug mode New Plugins - fortplan: designate construction of (limited) buildings from .csv file, quickfort-style + fortplan: designate construction of (limited) buildings from .csv file, quickfort-style New Scripts gui/stockpiles: an in-game interface for saving and loading stockpile settings files. From 9f0558b5471dd364bb3ab3f8ea38baf118ee1444 Mon Sep 17 00:00:00 2001 From: Peridexis Errant Date: Thu, 8 Jan 2015 13:10:11 +1100 Subject: [PATCH 093/115] Allow for other hotkeys keybinds Using this method is more compact and more flexible. Also tabs to spaces... --- scripts/hotkey-notes.lua | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts/hotkey-notes.lua b/scripts/hotkey-notes.lua index c8b9b9606..2b713e6bb 100644 --- a/scripts/hotkey-notes.lua +++ b/scripts/hotkey-notes.lua @@ -1,12 +1,9 @@ -- prints info on assigned hotkeys to the console -local h_list = {'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', - 'Shift+F1', 'Shift+F2', 'Shift+F3', 'Shift+F4', - 'Shift+F5', 'Shift+F6', 'Shift+F7', 'Shift+F8'} - for i=1, #df.global.ui.main.hotkeys do local hk = df.global.ui.main.hotkeys[i-1] - if hk.cmd ~= -1 then - print(h_list[i]..': '..hk.name..': x='..hk.x..' y='..hk.y..' z='..hk.z) - end + local key = dfhack.screen.getKeyDisplay(df.interface_key.D_HOTKEY1 + i - 1) + if hk.cmd ~= -1 then + print(key..': '..hk.name..': x='..hk.x..' y='..hk.y..' z='..hk.z) + end end From 819327348b3c8f3274de22c52972c4f5d46a8486 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Wed, 7 Jan 2015 16:27:48 -0500 Subject: [PATCH 094/115] Blueprint plugin basic structure It should be able to load and make empty blueprint files. --- plugins/CMakeLists.txt | 1 + plugins/blueprint.cpp | 172 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 plugins/blueprint.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b68fe61bc..f58c0182d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -91,6 +91,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(automaterial automaterial.cpp) DFHACK_PLUGIN(automelt automelt.cpp) DFHACK_PLUGIN(autotrade autotrade.cpp) + DFHACK_PLUGIN(blueprint blueprint.cpp) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(buildingplan buildingplan-lib.cpp buildingplan.cpp) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp new file mode 100644 index 000000000..987d6d0d8 --- /dev/null +++ b/plugins/blueprint.cpp @@ -0,0 +1,172 @@ +//Blueprint +//By cdombroski +//Translates a region of tiles specified by the cursor and arguments/prompts into a series of blueprint files suitable for digfort/buildingplan/quickfort + +#include +#include + +#include "modules/Gui.h" +#include "modules/MapCache.h" + +using std::string; +using std::endl; +using std::vector; +using std::ofstream; +using namespace DFHack; + +DFHACK_PLUGIN("blueprint"); + +#define swap(x, y)\ + x += y;\ + y = x - y;\ + x -= x + +enum phase {DIG, BUILD, PLACE, QUERY}; + +command_result blueprint(color_ostream &out, vector ¶meters); + +DFhackCExport command_result plugin_init(color_ostream &out, vector &commands) +{ + commands.push_back(PluginCommand("blueprint", "Convert map tiles into a blueprint", blueprint, false)); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) +{ + return CR_OK; +} + +command_result help(color_ostream &out) +{ + out << "blueprint width height depth name [dig [build [place [query]]]]" << endl + << " width, height, depth: area to translate in tiles" << endl + << " name: base name for blueprint files" << endl + << " dig: generate blueprints for digging" << endl + << " build: generate blueprints for building" << endl + << " place: generate blueprints for stockpiles" << endl + << " query: generate blueprints for querying (room designations)" << endl + << " defaults to generating all blueprints" << endl + << endl + << "blueprint translates a portion of your fortress into blueprints suitable for" << endl + << " digfort/fortplan/quickfort. Blueprints are created in the DF folder with names" << endl + << " following a \"name-phase.csv\" pattern. Translation starts at the current" << endl + << " cursor location and includes all tiles in the range specified." << endl; + return CR_OK; +} + + +command_result do_transform(DFCoord start, DFCoord range, string name, phase last_phase) +{ + ofstream dig, build, place, query; + switch (last_phase) + { + case QUERY: + query = ofstream(name + "-query.csv", ofstream::trunc); + query << "#query" << endl; + case PLACE: + place = ofstream(name + "-place.csv", ofstream::trunc); + place << "#place" << endl; + case BUILD: + build = ofstream(name + "-build.csv", ofstream::trunc); + build << "#build" << endl; + case DIG: + dig = ofstream(name + "-dig.csv", ofstream::trunc); + dig << "#dig" << endl; + } + DFCoord end = start + range; + if (start.x > end.x) + { + swap(start.x, end.x); + } + if (start.y > end.y) + { + swap(start.y, end.y); + } + if (start.z > end.z) + { + swap(start.z, end.z); + } + + MapExtras::MapCache mc; + for (auto z = start.z; z < end.z; z++) + { + for (auto y = start.y; y < end.y; y++) + { + for (auto x = start.x; x < end.x; x++) + { + switch (last_phase) { + case QUERY: + case PLACE: + case BUILD: + case DIG: + } + } + switch (last_phase) { + case QUERY: + query << "#" << endl; + case PLACE: + place << "#" << endl; + case BUILD: + place << "#" << endl; + case DIG: + dig << "#" << endl; + } + } + if (z < end.z - 1) + switch (last_phase) { + case QUERY: + query << "#>" << endl; + case PLACE: + place << "#>" << endl; + case BUILD: + place << "#>" << endl; + case DIG: + dig << "#>" << endl; + } + } + switch (last_phase) { + case QUERY: + query.close(); + case PLACE: + place.close(); + case BUILD: + place.close(); + case DIG: + dig.close(); + } + return CR_OK; +} + +command_result blueprint(color_ostream &out, vector ¶meters) +{ + if (parameters.size() < 4 || parameters.size() > 8) + return help(out); + CoreSuspender suspend; + if (!Maps::IsValid()) + { + out.printerr("Map is not available!\n"); + return CR_FAILURE; + } + int32_t x, y, z; + if (!Gui::getCursorCoords(x, y, z)) + { + out.printerr("Can't get cursor coords! Make sure you have an active cursor in DF.\n"); + return CR_FAILURE; + } + DFCoord start (x, y, z); + DFCoord range (stoi(parameters[0]), stoi(parameters[1]), stoi(parameters[2])); + switch(parameters.size()) + { + case 4: + case 8: + return do_transform(start, range, parameters[3], QUERY); + case 5: + return do_transform(start, range, parameters[3], DIG); + case 6: + return do_transform(start, range, parameters[3], BUILD); + case 7: + return do_transform(start, range, parameters[3], PLACE); + default: //wtf? + return CR_FAILURE; + } +} \ No newline at end of file From b627240943002087eb3dcd918a4d7ea59cc60187 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Thu, 8 Jan 2015 12:17:18 -0500 Subject: [PATCH 095/115] Dig mode complete --- plugins/blueprint.cpp | 71 ++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 987d6d0d8..eea748697 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -19,7 +19,7 @@ DFHACK_PLUGIN("blueprint"); #define swap(x, y)\ x += y;\ y = x - y;\ - x -= x + x -= y enum phase {DIG, BUILD, PLACE, QUERY}; @@ -54,8 +54,39 @@ command_result help(color_ostream &out) return CR_OK; } +char get_tile_dig(MapExtras::MapCache mc, int32_t x, int32_t y, int32_t z) +{ + df::tiletype tt = mc.tiletypeAt(DFCoord(x, y, z)); + df::enum_traits et; + df::tiletype_shape ts = et.attr_table[tt].shape; + switch (ts) + { + case df::tiletype_shape::EMPTY: + case df::tiletype_shape::RAMP_TOP: + return 'h'; + case df::tiletype_shape::FLOOR: + case df::tiletype_shape::BOULDER: + case df::tiletype_shape::PEBBLES: + case df::tiletype_shape::BROOK_TOP: + return 'd'; + case df::tiletype_shape::FORTIFICATION: + return 'a'; //or possibly 'F' with new keybindings? + case df::tiletype_shape::STAIR_UP: + return 'u'; + case df::tiletype_shape::STAIR_DOWN: + return 'j'; + case df::tiletype_shape::STAIR_UPDOWN: + return 'i'; + case df::tiletype_shape::RAMP: + return 'r'; + default: + return ' '; + + } +} -command_result do_transform(DFCoord start, DFCoord range, string name, phase last_phase) + +command_result do_transform(DFCoord start, DFCoord end, string name, phase last_phase) { ofstream dig, build, place, query; switch (last_phase) @@ -73,32 +104,38 @@ command_result do_transform(DFCoord start, DFCoord range, string name, phase las dig = ofstream(name + "-dig.csv", ofstream::trunc); dig << "#dig" << endl; } - DFCoord end = start + range; if (start.x > end.x) { swap(start.x, end.x); + start.x++; + end.x++; } if (start.y > end.y) { swap(start.y, end.y); + start.y++; + end.y++; } if (start.z > end.z) { swap(start.z, end.z); + start.z++; + end.z++; } MapExtras::MapCache mc; - for (auto z = start.z; z < end.z; z++) + for (int32_t z = start.z; z < end.z; z++) { - for (auto y = start.y; y < end.y; y++) + for (int32_t y = start.y; y < end.y; y++) { - for (auto x = start.x; x < end.x; x++) + for (int32_t x = start.x; x < end.x; x++) { switch (last_phase) { case QUERY: case PLACE: case BUILD: case DIG: + dig << get_tile_dig(mc, x, y, z) << ','; } } switch (last_phase) { @@ -107,7 +144,7 @@ command_result do_transform(DFCoord start, DFCoord range, string name, phase las case PLACE: place << "#" << endl; case BUILD: - place << "#" << endl; + build << "#" << endl; case DIG: dig << "#" << endl; } @@ -115,13 +152,13 @@ command_result do_transform(DFCoord start, DFCoord range, string name, phase las if (z < end.z - 1) switch (last_phase) { case QUERY: - query << "#>" << endl; + query << "#<" << endl; case PLACE: - place << "#>" << endl; + place << "#<" << endl; case BUILD: - place << "#>" << endl; + build << "#<" << endl; case DIG: - dig << "#>" << endl; + dig << "#<" << endl; } } switch (last_phase) { @@ -130,7 +167,7 @@ command_result do_transform(DFCoord start, DFCoord range, string name, phase las case PLACE: place.close(); case BUILD: - place.close(); + build.close(); case DIG: dig.close(); } @@ -154,18 +191,18 @@ command_result blueprint(color_ostream &out, vector ¶meters) return CR_FAILURE; } DFCoord start (x, y, z); - DFCoord range (stoi(parameters[0]), stoi(parameters[1]), stoi(parameters[2])); + DFCoord end (x + stoi(parameters[0]), y + stoi(parameters[1]), z + stoi(parameters[2])); switch(parameters.size()) { case 4: case 8: - return do_transform(start, range, parameters[3], QUERY); + return do_transform(start, end, parameters[3], QUERY); case 5: - return do_transform(start, range, parameters[3], DIG); + return do_transform(start, end, parameters[3], DIG); case 6: - return do_transform(start, range, parameters[3], BUILD); + return do_transform(start, end, parameters[3], BUILD); case 7: - return do_transform(start, range, parameters[3], PLACE); + return do_transform(start, end, parameters[3], PLACE); default: //wtf? return CR_FAILURE; } From 859fdd60d6c058ae77420c97118543109b5004ba Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 8 Jan 2015 16:59:25 -0500 Subject: [PATCH 096/115] annoucnement -> announcement --- plugins/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 929cd653c..a1ad442bd 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1382,7 +1382,7 @@ IMPLEMENT_HOOKS(df::viewscreen_buildinglistst, roomlist_search); // // START: Announcement list search // -class annoucnement_search : public search_generic +class announcement_search : public search_generic { public: void render() const @@ -1410,7 +1410,7 @@ private: }; -IMPLEMENT_HOOKS(df::viewscreen_announcelistst, annoucnement_search); +IMPLEMENT_HOOKS(df::viewscreen_announcelistst, announcement_search); // // END: Announcement list search @@ -1746,7 +1746,7 @@ IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, room_assign_search); HOOK_ACTION(military_search_hook) \ HOOK_ACTION(nobles_search_hook) \ HOOK_ACTION(profiles_search_hook) \ - HOOK_ACTION(annoucnement_search_hook) \ + HOOK_ACTION(announcement_search_hook) \ HOOK_ACTION(joblist_search_hook) \ HOOK_ACTION(burrow_search_hook) \ HOOK_ACTION(stockpile_search_hook) \ From aa332e18e28a803098d74dffc078dfb32593461d Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 8 Jan 2015 16:59:37 -0500 Subject: [PATCH 097/115] Remove stray whitespace --- plugins/search.cpp | 52 +++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index a1ad442bd..b4428f049 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -984,9 +984,9 @@ private: return desc; } - bool should_check_input(set *input) + bool should_check_input(set *input) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || (!in_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) { if (!in_entry_mode()) @@ -1009,17 +1009,17 @@ private: return 'q'; } - vector *get_secondary_list() + vector *get_secondary_list() { return &viewscreen->jobs[viewscreen->page]; } - int32_t *get_viewscreen_cursor() + int32_t *get_viewscreen_cursor() { return &viewscreen->cursor_pos[viewscreen->page]; } - vector *get_primary_list() + vector *get_primary_list() { return &viewscreen->units[viewscreen->page]; } @@ -1092,7 +1092,7 @@ public: // but the native hotkeys are where we normally write. return; } - + print_search_option(2, -1); if (!search_string.empty()) @@ -1121,7 +1121,7 @@ private: return &viewscreen->trader_items; } - char get_search_select_key() + char get_search_select_key() { return 'q'; } @@ -1142,7 +1142,7 @@ public: // but the native hotkeys are where we normally write. return; } - + int32_t x = gps->dimx / 2 + 2; print_search_option(x, -1); @@ -1171,7 +1171,7 @@ private: return &viewscreen->broker_items; } - char get_search_select_key() + char get_search_select_key() { return 'w'; } @@ -1206,12 +1206,12 @@ public: print_search_option(51, 23); } - vector *get_primary_list() + vector *get_primary_list() { return &viewscreen->item_names; } - vector *get_secondary_list() + vector *get_secondary_list() { return &viewscreen->item_status; } @@ -1260,7 +1260,7 @@ public: return true; } - vector *get_primary_list() + vector *get_primary_list() { return &viewscreen->positions.candidates; } @@ -1273,7 +1273,7 @@ public: int32_t *cursor = get_viewscreen_cursor(); df::unit *selected_unit = get_primary_list()->at(*cursor); clear_search(); - + for (*cursor = 0; *cursor < get_primary_list()->size(); (*cursor)++) { if (get_primary_list()->at(*cursor) == selected_unit) @@ -1351,21 +1351,21 @@ private: desc += name; } } - + return desc; } - vector *get_secondary_list() + vector *get_secondary_list() { return &viewscreen->room_value; } - int32_t *get_viewscreen_cursor() + int32_t *get_viewscreen_cursor() { return &viewscreen->cursor; } - vector *get_primary_list() + vector *get_primary_list() { return &viewscreen->buildings; } @@ -1452,7 +1452,7 @@ public: return nobles_search_base::can_init(screen); } - vector *get_primary_list() + vector *get_primary_list() { return &viewscreen->candidates; } @@ -1482,7 +1482,7 @@ public: print_search_option(2, 23); } - vector *get_primary_list() + vector *get_primary_list() { return &viewscreen->workers; } @@ -1509,7 +1509,7 @@ void get_job_details(string &desc, df::job *job) desc += c; } desc += "."; - + df::item_type itype = ENUM_ATTR(job_type, item, job->job_type); MaterialInfo mat(job); @@ -1582,17 +1582,17 @@ private: return 'q'; } - vector *get_secondary_list() + vector *get_secondary_list() { return &viewscreen->units; } - int32_t *get_viewscreen_cursor() + int32_t *get_viewscreen_cursor() { return &viewscreen->cursor_pos; } - vector *get_primary_list() + vector *get_primary_list() { return &viewscreen->jobs; } @@ -1638,17 +1638,17 @@ public: print_search_option(x, y); } - vector *get_primary_list() + vector *get_primary_list() { return &ui->burrows.list_units; } - vector *get_secondary_list() + vector *get_secondary_list() { return &ui->burrows.sel_units; } - virtual int32_t * get_viewscreen_cursor() + virtual int32_t * get_viewscreen_cursor() { return &ui->burrows.unit_cursor_pos; } From ea313b6bf5b2e27cd217a02069746e8143b838ee Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Fri, 9 Jan 2015 16:00:47 -0500 Subject: [PATCH 098/115] plugins/blueprint.cpp: Buildings hopefully --- plugins/blueprint.cpp | 381 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 359 insertions(+), 22 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index eea748697..2689f4c69 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -5,22 +5,31 @@ #include #include +#include "df/building_axle_horizontalst.h" +#include "df/building_bridgest.h" +#include "df/building_constructionst.h" +#include "df/building_furnacest.h" +#include "df/building_rollersst.h" +#include "df/building_screw_pumpst.h" +#include "df/building_siegeenginest.h" +#include "df/building_trapst.h" +#include "df/building_water_wheelst.h" +#include "df/building_workshopst.h" + #include "modules/Gui.h" #include "modules/MapCache.h" +#include "modules/Buildings.h" using std::string; using std::endl; using std::vector; using std::ofstream; +using std::swap; using namespace DFHack; +using namespace df::enums; DFHACK_PLUGIN("blueprint"); -#define swap(x, y)\ - x += y;\ - y = x - y;\ - x -= y - enum phase {DIG, BUILD, PLACE, QUERY}; command_result blueprint(color_ostream &out, vector ¶meters); @@ -56,35 +65,361 @@ command_result help(color_ostream &out) char get_tile_dig(MapExtras::MapCache mc, int32_t x, int32_t y, int32_t z) { - df::tiletype tt = mc.tiletypeAt(DFCoord(x, y, z)); - df::enum_traits et; - df::tiletype_shape ts = et.attr_table[tt].shape; + df::tiletype tt = mc.tiletypeAt(DFCoord(x, y, z)); + df::tiletype_shape ts = tileShape(tt); switch (ts) { - case df::tiletype_shape::EMPTY: - case df::tiletype_shape::RAMP_TOP: + case tiletype_shape::EMPTY: + case tiletype_shape::RAMP_TOP: return 'h'; - case df::tiletype_shape::FLOOR: - case df::tiletype_shape::BOULDER: - case df::tiletype_shape::PEBBLES: - case df::tiletype_shape::BROOK_TOP: + case tiletype_shape::FLOOR: + case tiletype_shape::BOULDER: + case tiletype_shape::PEBBLES: + case tiletype_shape::BROOK_TOP: return 'd'; - case df::tiletype_shape::FORTIFICATION: - return 'a'; //or possibly 'F' with new keybindings? - case df::tiletype_shape::STAIR_UP: + case tiletype_shape::FORTIFICATION: + return 'F'; + case tiletype_shape::STAIR_UP: return 'u'; - case df::tiletype_shape::STAIR_DOWN: + case tiletype_shape::STAIR_DOWN: return 'j'; - case df::tiletype_shape::STAIR_UPDOWN: + case tiletype_shape::STAIR_UPDOWN: return 'i'; - case df::tiletype_shape::RAMP: + case tiletype_shape::RAMP: return 'r'; default: return ' '; - + } } +string get_tile_build(df::building* b) +{ + switch(b->getType()) + { + case building_type::Armorstand: + return "a"; + case building_type::Bed: + return "b"; + case building_type::Chair: + return "c"; + case building_type::Door: + return "d"; + case building_type::Floodgate: + return "x"; + case building_type::Cabinet: + return "f"; + case building_type::Box: + return "h"; + //case building_type::Kennel is missing + case building_type::FarmPlot: + return "p"; + case building_type::Weaponrack: + return "r"; + case building_type::Statue: + return "s"; + case building_type::Table: + return "t"; + case building_type::RoadPaved: + return "o"; + case building_type::RoadDirt: + return "O"; + case building_type::Bridge: + switch(((df::building_bridgest*) b)->direction) + { + case df::building_bridgest::T_direction::Down: + return "gx"; + case df::building_bridgest::T_direction::Left: + return "ga"; + case df::building_bridgest::T_direction::Up: + return "gw"; + case df::building_bridgest::T_direction::Right: + return "gd"; + case df::building_bridgest::T_direction::Retracting: + return "gs"; + } + case building_type::Well: + return "l"; + case building_type::SiegeEngine: + return "i" + ((df::building_siegeenginest*) b)->type == df::siegeengine_type::Ballista ? "b" : "c"; + case building_type::Workshop: + switch (((df::building_workshopst*) b)->type) + { + case workshop_type::Leatherworks: + return "we"; + case workshop_type::Quern: + return "wq"; + case workshop_type::Millstone: + return "wM"; + case workshop_type::Loom: + return "wo"; + case workshop_type::Clothiers: + return "wk"; + case workshop_type::Bowyers: + return "wb"; + case workshop_type::Carpenters: + return "wc"; + case workshop_type::MetalsmithsForge: + return "wf"; + case workshop_type::MagmaForge: + return "wv"; + case workshop_type::Jewelers: + return "wj"; + case workshop_type::Masons: + return "wm"; + case workshop_type::Butchers: + return "wu"; + case workshop_type::Tanners: + return "wn"; + case workshop_type::Craftsdwarfs: + return "wr"; + case workshop_type::Siege: + return "ws"; + case workshop_type::Mechanics: + return "wt"; + case workshop_type::Still: + return "wl"; + case workshop_type::Farmers: + return "ww"; + case workshop_type::Kitchen: + return "wz"; + case workshop_type::Fishery: + return "wh"; + case workshop_type::Ashery: + return "wy"; + case workshop_type::Dyers: + return "wd"; + case workshop_type::Custom: + //can't do anything with custom workshop + return " "; + } + case building_type::Furnace: + switch (((df::building_furnacest*) b)->type) + { + case furnace_type::WoodFurnace: + return "ew"; + case furnace_type::Smelter: + return "es"; + case furnace_type::GlassFurnace: + return "eg"; + case furnace_type::Kiln: + return "ek"; + case furnace_type::MagmaSmelter: + return "el"; + case furnace_type::MagmaGlassFurnace: + return "ea"; + case furnace_type::MagmaKiln: + return "en"; + case furnace_type::Custom: + //can't do anything with custom furnace + return " "; + } + case building_type::WindowGlass: + return "y"; + case building_type::WindowGem: + return "Y"; + case building_type::Construction: + switch (((df::building_constructionst*) b)->type) + { + case construction_type::Fortification: + return "CF"; + case construction_type::Wall: + return "CW"; + case construction_type::Floor: + return "Cf"; + case construction_type::UpStair: + return "Cu"; + case construction_type::DownStair: + return "Cj"; + case construction_type::UpDownStair: + return "Cx"; + case construction_type::Ramp: + return "Cr"; + case construction_type::TrackN: + return "trackN"; + case construction_type::TrackS: + return "trackS"; + case construction_type::TrackE: + return "trackE"; + case construction_type::TrackW: + return "trackW"; + case construction_type::TrackNS: + return "trackNS"; + case construction_type::TrackNE: + return "trackNE"; + case construction_type::TrackNW: + return "trackNW"; + case construction_type::TrackSE: + return "trackSE"; + case construction_type::TrackSW: + return "trackSW"; + case construction_type::TrackEW: + return "trackEW"; + case construction_type::TrackNSE: + return "trackNSE"; + case construction_type::TrackNSW: + return "trackNSW"; + case construction_type::TrackNEW: + return "trackNEW"; + case construction_type::TrackSEW: + return "trackSEW"; + case construction_type::TrackNSEW: + return "trackNSEW"; + case construction_type::TrackRampN: + return "trackrampN"; + case construction_type::TrackRampS: + return "trackrampS"; + case construction_type::TrackRampE: + return "trackrampE"; + case construction_type::TrackRampW: + return "trackrampW"; + case construction_type::TrackRampNS: + return "trackrampNS"; + case construction_type::TrackRampNE: + return "trackrampNE"; + case construction_type::TrackRampNW: + return "trackrampNW"; + case construction_type::TrackRampSE: + return "trackrampSE"; + case construction_type::TrackRampSW: + return "trackrampSW"; + case construction_type::TrackRampEW: + return "trackrampEW"; + case construction_type::TrackRampNSE: + return "trackrampNSE"; + case construction_type::TrackRampNSW: + return "trackrampNSW"; + case construction_type::TrackRampNEW: + return "trackrampNEW"; + case construction_type::TrackRampSEW: + return "trackrampSEW"; + case construction_type::TrackRampNSEW: + return "trackrampNSEW"; + } + case building_type::Shop: + return "z"; + case building_type::AnimalTrap: + return "m"; + case building_type::Chain: + return "v"; + case building_type::Cage: + return "j"; + case building_type::TradeDepot: + return "D"; + case building_type::Trap: + switch (((df::building_trapst*) b)->trap_type) + { + case trap_type::StoneFallTrap: + return "Ts"; + case trap_type::WeaponTrap: + return "Tw"; + case trap_type::Lever: + return "Tl"; + case trap_type::PressurePlate: + return "Tp"; + case trap_type::CageTrap: + return "Tc"; + case trap_type::TrackStop: + df::building_trapst* ts = (df::building_trapst*) b; + string ret = "CS"; + if (ts->use_dump) + { + if (ts->dump_x_shift == 0) + { + if (ts->dump_y_shift > 0) + ret += "dd"; + else + ret += "d"; + } + else + { + if (ts->dump_x_shift > 0) + ret += "ddd"; + else + ret += "dddd"; + } + } + switch (ts->friction) + { + case 10: + ret += "a"; + case 50: + ret += "a"; + case 500: + ret += "a"; + case 10000: + ret += "a"; + } + return ret; + } + case building_type::ScrewPump: + switch (((df::building_screw_pumpst*) b)->direction) + { + case screw_pump_direction::FromNorth: + return "Msu"; + case screw_pump_direction::FromEast: + return "Msk"; + case screw_pump_direction::FromSouth: + return "Msm"; + case screw_pump_direction::FromWest: + return "Msh"; + } + case building_type::WaterWheel: + //s swaps orientation which defaults to vertical + return "Mw" + ((df::building_water_wheelst*) b)->is_vertical ? "" : "s"; + case building_type::Windmill: + return "Mm"; + case building_type::GearAssembly: + return "Mg"; + case building_type::AxleHorizontal: + //same as water wheel but reversed + return "Mh" + ((df::building_axle_horizontalst*) b)->is_vertical ? "s" : ""; + case building_type::AxleVertical: + return "Mv"; + case building_type::Rollers: + string ret = "Mr"; + switch (((df::building_rollersst*) b)->direction) + { + case screw_pump_direction::FromNorth: + break; + case screw_pump_direction::FromEast: + ret += "s"; + case screw_pump_direction::FromSouth: + ret += "s"; + case screw_pump_direction::FromWest: + ret += "s"; + } + return ret; + case building_type::Support: + return "S"; + case building_type::ArcheryTarget: + return "A"; + case building_type::TractionBench: + return "R"; + case building_type::Hatch: + return "H"; + case building_type::Slab: + //how to mine alt key?!? + //alt+s + return " "; + case building_type::NestBox: + return "N"; + case building_type::Hive: + //alt+h + return " "; + case building_type::GrateWall: + return "W"; + case building_type::GrateFloor: + return "G"; + case building_type::BarsVertical: + return "B"; + case building_type::BarsFloor: + //alt+b + return " "; + default: + return " "; + } +} command_result do_transform(DFCoord start, DFCoord end, string name, phase last_phase) { @@ -130,10 +465,12 @@ command_result do_transform(DFCoord start, DFCoord end, string name, phase last_ { for (int32_t x = start.x; x < end.x; x++) { + df::building* b = DFHack::Buildings::findAtTile(DFCoord(x, y, z)); switch (last_phase) { case QUERY: case PLACE: case BUILD: + build << get_tile_build(b) << ','; case DIG: dig << get_tile_dig(mc, x, y, z) << ','; } @@ -159,7 +496,7 @@ command_result do_transform(DFCoord start, DFCoord end, string name, phase last_ build << "#<" << endl; case DIG: dig << "#<" << endl; - } + } } switch (last_phase) { case QUERY: From 077d149d645c249f0317fe2b2e42de7f3783e9ee Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 11 Jan 2015 12:48:52 -0500 Subject: [PATCH 099/115] Expose key modifier state to C++/Lua --- library/Core.cpp | 31 ++++++++++++++----------------- library/LuaApi.cpp | 19 +++++++++++++++++++ library/include/Core.h | 6 ++++++ 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 19e2c4345..452c294e3 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -714,7 +714,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve std::string keystr = parts[1]; if (parts[0] == "set") ClearKeyBindings(keystr); - for (int i = parts.size()-1; i >= 2; i--) + for (int i = parts.size()-1; i >= 2; i--) { if (!AddKeyBinding(keystr, parts[i])) { con.printerr("Invalid key spec: %s\n", keystr.c_str()); @@ -910,7 +910,7 @@ void fIOthread(void * iodata) main_history.add(command); main_history.save("dfhack.history"); } - + auto rv = core->runCommand(con, command); if (rv == CR_NOT_IMPLEMENTED) @@ -1091,6 +1091,7 @@ bool Core::Init() screen_window = new Windows::top_level_window(); screen_window->addChild(new Windows::dfhack_dummy(5,10)); started = true; + modstate = 0; cerr << "Starting the TCP listener.\n"; server = new ServerMain(); @@ -1579,7 +1580,7 @@ int UnicodeAwareSym(const SDL::KeyboardEvent& ke) //MEMO: return false if event is consumed int Core::DFH_SDL_Event(SDL::Event* ev) { - static bool alt = 0; + //static bool alt = 0; // do NOT process events before we are ready. if(!started) return true; @@ -1589,31 +1590,27 @@ int Core::DFH_SDL_Event(SDL::Event* ev) { auto ke = (SDL::KeyboardEvent *)ev; - if (ke->ksym.sym == SDL::K_LALT || ke->ksym.sym == SDL::K_RALT) - { - alt = (ev->type == SDL::ET_KEYDOWN); - } - else - if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym]) + if (ke->ksym.sym == SDL::K_LSHIFT || ke->ksym.sym == SDL::K_RSHIFT) + modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | MOD_SHIFT : modstate & ~MOD_SHIFT; + else if (ke->ksym.sym == SDL::K_LCTRL || ke->ksym.sym == SDL::K_RCTRL) + modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | MOD_CTRL : modstate & ~MOD_CTRL; + else if (ke->ksym.sym == SDL::K_LALT || ke->ksym.sym == SDL::K_RALT) + modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | MOD_ALT : modstate & ~MOD_ALT; + else if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym]) { hotkey_states[ke->ksym.sym] = true; - int mod = 0; - if (ke->ksym.mod & SDL::KMOD_SHIFT) mod |= 1; - if (ke->ksym.mod & SDL::KMOD_CTRL) mod |= 2; - if (alt) mod |= 4; - // Use unicode so Windows gives the correct value for the // user's Input Language if((ke->ksym.unicode & 0xff80) == 0) { int key = UnicodeAwareSym(*ke); - SelectHotkey(key, mod); + SelectHotkey(key, modstate); } else { // Pretend non-ascii characters don't happen: - SelectHotkey(ke->ksym.sym, mod); + SelectHotkey(ke->ksym.sym, modstate); } } else if(ke->state == SDL::BTN_RELEASED) @@ -1710,7 +1707,7 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string } else if (keyspec.size() > 4 && keyspec.substr(0, 4) == "Alt-") { *pmod |= 4; keyspec = keyspec.substr(4); - } else + } else break; } diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index f2693363a..f9b19b102 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2008,10 +2008,12 @@ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) static uint32_t getImageBase() { return Core::getInstance().p->getBase(); } static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); } +static int8_t getModstate() { return Core::getInstance().getModstate(); } static const LuaWrapper::FunctionReg dfhack_internal_module[] = { WRAP(getImageBase), WRAP(getRebaseDelta), + WRAP(getModstate), { NULL, NULL } }; @@ -2351,6 +2353,22 @@ static int internal_runCommand(lua_State *L) return 1; } +static int internal_getModifiers(lua_State *L) +{ + int8_t modstate = Core::getInstance().getModstate(); + lua_newtable(L); + lua_pushstring(L, "shift"); + lua_pushboolean(L, modstate & MOD_SHIFT); + lua_settable(L, -3); + lua_pushstring(L, "ctrl"); + lua_pushboolean(L, modstate & MOD_CTRL); + lua_settable(L, -3); + lua_pushstring(L, "alt"); + lua_pushboolean(L, modstate & MOD_ALT); + lua_settable(L, -3); + return 1; +} + static const luaL_Reg dfhack_internal_funcs[] = { { "getAddress", internal_getAddress }, { "setAddress", internal_setAddress }, @@ -2365,6 +2383,7 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "diffscan", internal_diffscan }, { "getDir", internal_getDir }, { "runCommand", internal_runCommand }, + { "getModifiers", internal_getModifiers }, { NULL, NULL } }; diff --git a/library/include/Core.h b/library/include/Core.h index b3db50c74..f3166496a 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -36,6 +36,10 @@ distribution. #include "RemoteClient.h" +#define MOD_SHIFT 1 +#define MOD_CTRL 2 +#define MOD_ALT 4 + struct WINDOW; namespace tthread @@ -142,6 +146,7 @@ namespace DFHack bool ClearKeyBindings(std::string keyspec); bool AddKeyBinding(std::string keyspec, std::string cmdline); std::vector ListKeyBindings(std::string keyspec); + int8_t getModstate() { return modstate; } std::string getHackPath(); @@ -216,6 +221,7 @@ namespace DFHack std::string cmdline; std::string focus; }; + int8_t modstate; std::map > key_bindings; std::map hotkey_states; From e20808b6f6b4bc4bd2f6e80e2a84dc2039961643 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Sun, 11 Jan 2015 13:53:51 -0500 Subject: [PATCH 100/115] Allow `dfhack` script to be run from other directories on OS X --- package/darwin/dfhack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/darwin/dfhack b/package/darwin/dfhack index 55aa81a98..8040a837a 100755 --- a/package/darwin/dfhack +++ b/package/darwin/dfhack @@ -1,5 +1,6 @@ #!/bin/sh PWD=`dirname "${0}"` +cd "${PWD}" #thanks to Iriel for figuring this out OSREV=`uname -r | cut -d. -f1` if [ "$OSREV" -ge 11 ] ; then @@ -11,7 +12,6 @@ else fi old_tty_settings=$(stty -g) -cd "${PWD}" DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe "$@" stty "$old_tty_settings" echo "" From f510cd7c11c34d1a8ded74d207d2e9d369a9a008 Mon Sep 17 00:00:00 2001 From: warmist Date: Tue, 13 Jan 2015 10:30:53 +0200 Subject: [PATCH 101/115] Update advfort.lua Fix bug with building jobs --- scripts/gui/advfort.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 921dd87cf..17aef6a42 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,8 +1,10 @@ -- allows to do jobs in adv. mode. --[==[ - version: 0.011 + version: 0.012 changelog: + *0.012 + - fix for some jobs not finding correct building. *0.011 - fixed crash with building jobs (other jobs might have been crashing too!) - fixed bug with building asking twice to input items @@ -1123,7 +1125,7 @@ function usetool:openShopWindow(building) local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) if filter_pile then - local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z,building=building} + local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building ,screen=self,bld=building,common=filter_pile.common} choices={} for k,v in pairs(filter_pile) do @@ -1455,4 +1457,4 @@ end if not (dfhack.gui.getCurFocus()=="dungeonmode/Look" or dfhack.gui.getCurFocus()=="dungeonmode/Default") then qerror("This script requires an adventurer mode with (l)ook or default mode.") end -usetool():show() \ No newline at end of file +usetool():show() From 3489c2f39a352fca12b1dd01b3af8bc27554cee3 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Mon, 12 Jan 2015 11:52:46 -0500 Subject: [PATCH 102/115] Buildings complete --- plugins/blueprint.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 2689f4c69..092562f5d 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -5,6 +5,10 @@ #include #include +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/MapCache.h" + #include "df/building_axle_horizontalst.h" #include "df/building_bridgest.h" #include "df/building_constructionst.h" @@ -16,10 +20,6 @@ #include "df/building_water_wheelst.h" #include "df/building_workshopst.h" -#include "modules/Gui.h" -#include "modules/MapCache.h" -#include "modules/Buildings.h" - using std::string; using std::endl; using std::vector; @@ -95,6 +95,9 @@ char get_tile_dig(MapExtras::MapCache mc, int32_t x, int32_t y, int32_t z) string get_tile_build(df::building* b) { + if (! b) + return " "; + string ret; switch(b->getType()) { case building_type::Armorstand: @@ -141,7 +144,7 @@ string get_tile_build(df::building* b) case building_type::Well: return "l"; case building_type::SiegeEngine: - return "i" + ((df::building_siegeenginest*) b)->type == df::siegeengine_type::Ballista ? "b" : "c"; + return ((df::building_siegeenginest*) b)->type == df::siegeengine_type::Ballista ? "ib" : "ic"; case building_type::Workshop: switch (((df::building_workshopst*) b)->type) { @@ -321,7 +324,7 @@ string get_tile_build(df::building* b) return "Tc"; case trap_type::TrackStop: df::building_trapst* ts = (df::building_trapst*) b; - string ret = "CS"; + ret = "CS"; if (ts->use_dump) { if (ts->dump_x_shift == 0) @@ -377,7 +380,7 @@ string get_tile_build(df::building* b) case building_type::AxleVertical: return "Mv"; case building_type::Rollers: - string ret = "Mr"; + ret = "Mr"; switch (((df::building_rollersst*) b)->direction) { case screw_pump_direction::FromNorth: From f315ee43a4a6481e06a818e6c3a71f109ab8d991 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Tue, 13 Jan 2015 14:28:28 -0500 Subject: [PATCH 103/115] Added stockpiles --- plugins/blueprint.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 092562f5d..eb4802525 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -424,6 +424,50 @@ string get_tile_build(df::building* b) } } +char get_tile_place(df::building* b) +{ + if (! b || b->getType() != building_type::Stockpile) + return ' '; + df::building_stockpilest* sp = (df::building_stockpilest*) b; + switch (sp->settings.flags.whole) + { + case df::stockpile_group_set::mask_animals: + return 'a'; + case df::stockpile_group_set::mask_food: + return 'f'; + case df::stockpile_group_set::mask_furniture: + return 'u'; + case df::stockpile_group_set::mask_corpses: + return 'y'; + case df::stockpile_group_set::mask_refuse: + return 'r'; + case df::stockpile_group_set::mask_wood: + return 'w'; + case df::stockpile_group_set::mask_stone: + return 's'; + case df::stockpile_group_set::mask_gems: + return 'e'; + case df::stockpile_group_set::mask_bars_blocks: + return 'b'; + case df::stockpile_group_set::mask_cloth: + return 'h'; + case df::stockpile_group_set::mask_leather: + return 'l'; + case df::stockpile_group_set::mask_ammo: + return 'z'; + case df::stockpile_group_set::mask_coins: + return 'n'; + case df::stockpile_group_set::mask_finished_goods: + return 'g'; + case df::stockpile_group_set::mask_weapons: + return 'p'; + case df::stockpile_group_set::mask_armor: + return 'd'; + default: //multiple stockpile type + return ' '; + } +} + command_result do_transform(DFCoord start, DFCoord end, string name, phase last_phase) { ofstream dig, build, place, query; @@ -472,6 +516,7 @@ command_result do_transform(DFCoord start, DFCoord end, string name, phase last_ switch (last_phase) { case QUERY: case PLACE: + place << get_tile_place(b) << ','; case BUILD: build << get_tile_build(b) << ','; case DIG: From d0273b8eff3cfeeefcd7f01b02906a9a9727e7df Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Tue, 13 Jan 2015 16:15:54 -0500 Subject: [PATCH 104/115] Now puts room designations in the query blueprint --- plugins/blueprint.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index eb4802525..4bd534d8a 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -468,6 +468,13 @@ char get_tile_place(df::building* b) } } +string get_tile_query(df::building* b) +{ + if (b && b->is_room) + return "r+"; + return " "; +} + command_result do_transform(DFCoord start, DFCoord end, string name, phase last_phase) { ofstream dig, build, place, query; @@ -515,6 +522,7 @@ command_result do_transform(DFCoord start, DFCoord end, string name, phase last_ df::building* b = DFHack::Buildings::findAtTile(DFCoord(x, y, z)); switch (last_phase) { case QUERY: + query << get_tile_query(b) << ','; case PLACE: place << get_tile_place(b) << ','; case BUILD: From 106612f3868b15e0f3e91faa2150e16063b6d3d7 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Wed, 14 Jan 2015 14:08:54 -0500 Subject: [PATCH 105/115] Use alternate qf syntax for output. --- plugins/blueprint.cpp | 173 ++++++++++++++++++++++++++++++------------ 1 file changed, 123 insertions(+), 50 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 4bd534d8a..c6611cef5 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -25,6 +25,7 @@ using std::endl; using std::vector; using std::ofstream; using std::swap; +using std::pair; using namespace DFHack; using namespace df::enums; @@ -63,6 +64,11 @@ command_result help(color_ostream &out) return CR_OK; } +pair get_building_size(df::building* b) +{ + return pair(b->x2 - b->x1 + 1, b->y2 - b->y1 + 1); +} + char get_tile_dig(MapExtras::MapCache mc, int32_t x, int32_t y, int32_t z) { df::tiletype tt = mc.tiletypeAt(DFCoord(x, y, z)); @@ -93,11 +99,15 @@ char get_tile_dig(MapExtras::MapCache mc, int32_t x, int32_t y, int32_t z) } } -string get_tile_build(df::building* b) +string get_tile_build(uint32_t x, uint32_t y, df::building* b) { if (! b) return " "; - string ret; + bool at_nw_corner = x == b->x1 && y == b->y1; + bool at_se_corner = x == b->x2 && y == b->y2; + bool at_center = x == b->centerx && y == b->centery; + pair size = get_building_size(b); + stringstream out = stringstream(); switch(b->getType()) { case building_type::Armorstand: @@ -116,7 +126,10 @@ string get_tile_build(df::building* b) return "h"; //case building_type::Kennel is missing case building_type::FarmPlot: - return "p"; + if(!at_nw_corner) + return "`"; + out << "p(" << size.first << "x" << size.second << ")"; + return out.str(); case building_type::Weaponrack: return "r"; case building_type::Statue: @@ -124,28 +137,47 @@ string get_tile_build(df::building* b) case building_type::Table: return "t"; case building_type::RoadPaved: - return "o"; + if(! at_nw_corner) + return "`"; + out << "o(" << size.first << "x" << size.second << ")"; + return out.str(); case building_type::RoadDirt: - return "O"; + if(! at_nw_corner) + return "`"; + out << "O(" << size.first << "x" << size.second << ")"; + return out.str(); case building_type::Bridge: + if(! at_nw_corner) + return "`"; switch(((df::building_bridgest*) b)->direction) { case df::building_bridgest::T_direction::Down: - return "gx"; + out << "gx"; + break; case df::building_bridgest::T_direction::Left: - return "ga"; + out << "ga"; + break; case df::building_bridgest::T_direction::Up: - return "gw"; + out << "gw"; + break; case df::building_bridgest::T_direction::Right: - return "gd"; + out << "gd"; + break; case df::building_bridgest::T_direction::Retracting: - return "gs"; + out << "gs"; + break; } + out << "(" << size.first << "x" << size.second << ")"; + return out.str(); case building_type::Well: return "l"; case building_type::SiegeEngine: + if (! at_center) + return "`"; return ((df::building_siegeenginest*) b)->type == df::siegeengine_type::Ballista ? "ib" : "ic"; case building_type::Workshop: + if (! at_center) + return "`"; switch (((df::building_workshopst*) b)->type) { case workshop_type::Leatherworks: @@ -194,9 +226,11 @@ string get_tile_build(df::building* b) return "wd"; case workshop_type::Custom: //can't do anything with custom workshop - return " "; + return "`"; } case building_type::Furnace: + if (! at_center) + return "`"; switch (((df::building_furnacest*) b)->type) { case furnace_type::WoodFurnace: @@ -215,7 +249,7 @@ string get_tile_build(df::building* b) return "en"; case furnace_type::Custom: //can't do anything with custom furnace - return " "; + return "`"; } case building_type::WindowGlass: return "y"; @@ -300,6 +334,8 @@ string get_tile_build(df::building* b) return "trackrampNSEW"; } case building_type::Shop: + if (! at_center) + return "`"; return "z"; case building_type::AnimalTrap: return "m"; @@ -308,6 +344,8 @@ string get_tile_build(df::building* b) case building_type::Cage: return "j"; case building_type::TradeDepot: + if (! at_center) + return "`"; return "D"; case building_type::Trap: switch (((df::building_trapst*) b)->trap_type) @@ -324,38 +362,40 @@ string get_tile_build(df::building* b) return "Tc"; case trap_type::TrackStop: df::building_trapst* ts = (df::building_trapst*) b; - ret = "CS"; + out << "CS"; if (ts->use_dump) { if (ts->dump_x_shift == 0) { if (ts->dump_y_shift > 0) - ret += "dd"; + out << "dd"; else - ret += "d"; + out << "d"; } else { if (ts->dump_x_shift > 0) - ret += "ddd"; + out << "ddd"; else - ret += "dddd"; + out << "dddd"; } } switch (ts->friction) { case 10: - ret += "a"; + out << "a"; case 50: - ret += "a"; + out << "a"; case 500: - ret += "a"; + out << "a"; case 10000: - ret += "a"; + out << "a"; } - return ret; + return out.str(); } case building_type::ScrewPump: + if (! at_se_corner) //screw pumps anchor at bottom/right + return "`"; switch (((df::building_screw_pumpst*) b)->direction) { case screw_pump_direction::FromNorth: @@ -368,31 +408,42 @@ string get_tile_build(df::building* b) return "Msh"; } case building_type::WaterWheel: + if (! at_center) + return "`"; //s swaps orientation which defaults to vertical - return "Mw" + ((df::building_water_wheelst*) b)->is_vertical ? "" : "s"; + return ((df::building_water_wheelst*) b)->is_vertical ? "Mw" : "Mws"; case building_type::Windmill: + if (! at_center) + return "`"; return "Mm"; case building_type::GearAssembly: return "Mg"; case building_type::AxleHorizontal: + if (! at_nw_corner) //a guess based on how constructions work + return "`"; //same as water wheel but reversed - return "Mh" + ((df::building_axle_horizontalst*) b)->is_vertical ? "s" : ""; + out << "Mh" << (((df::building_axle_horizontalst*) b)->is_vertical ? "s" : "") + << "(" << size.first << "x" << size.second << ")"; + return out.str(); case building_type::AxleVertical: return "Mv"; case building_type::Rollers: - ret = "Mr"; + if (! at_nw_corner) + return "`"; + out << "Mr"; switch (((df::building_rollersst*) b)->direction) { case screw_pump_direction::FromNorth: break; case screw_pump_direction::FromEast: - ret += "s"; + out << "s"; case screw_pump_direction::FromSouth: - ret += "s"; + out << "s"; case screw_pump_direction::FromWest: - ret += "s"; + out << "s"; } - return ret; + out << "(" << size.first << "x" << size.second << ")"; + return out.str(); case building_type::Support: return "S"; case building_type::ArcheryTarget: @@ -424,48 +475,70 @@ string get_tile_build(df::building* b) } } -char get_tile_place(df::building* b) +string get_tile_place(uint32_t x, uint32_t y, df::building* b) { if (! b || b->getType() != building_type::Stockpile) - return ' '; + return " "; + if (b->x1 != x || b->y1 != y) + return "`"; + pair size = get_building_size(b); df::building_stockpilest* sp = (df::building_stockpilest*) b; + stringstream out = stringstream(); switch (sp->settings.flags.whole) { case df::stockpile_group_set::mask_animals: - return 'a'; + out << "a"; + break; case df::stockpile_group_set::mask_food: - return 'f'; + out << "f"; + break; case df::stockpile_group_set::mask_furniture: - return 'u'; + out << "u"; + break; case df::stockpile_group_set::mask_corpses: - return 'y'; + out << "y"; + break; case df::stockpile_group_set::mask_refuse: - return 'r'; + out << "r"; + break; case df::stockpile_group_set::mask_wood: - return 'w'; + out << "w"; + break; case df::stockpile_group_set::mask_stone: - return 's'; + out << "s"; + break; case df::stockpile_group_set::mask_gems: - return 'e'; + out << "e"; + break; case df::stockpile_group_set::mask_bars_blocks: - return 'b'; + out << "b"; + break; case df::stockpile_group_set::mask_cloth: - return 'h'; + out << "h"; + break; case df::stockpile_group_set::mask_leather: - return 'l'; + out << "l"; + break; case df::stockpile_group_set::mask_ammo: - return 'z'; + out << "z"; + break; case df::stockpile_group_set::mask_coins: - return 'n'; + out << "n"; + break; case df::stockpile_group_set::mask_finished_goods: - return 'g'; + out << "g"; + break; case df::stockpile_group_set::mask_weapons: - return 'p'; + out << "p"; + break; case df::stockpile_group_set::mask_armor: - return 'd'; + out << "d"; + break; default: //multiple stockpile type - return ' '; + return "`"; } + out << "("<< size.first << "x" << size.second << ")"; + return out.str(); } string get_tile_query(df::building* b) @@ -524,9 +597,9 @@ command_result do_transform(DFCoord start, DFCoord end, string name, phase last_ case QUERY: query << get_tile_query(b) << ','; case PLACE: - place << get_tile_place(b) << ','; + place << get_tile_place(x, y, b) << ','; case BUILD: - build << get_tile_build(b) << ','; + build << get_tile_build(x, y, b) << ','; case DIG: dig << get_tile_dig(mc, x, y, z) << ','; } From da2224b0badfb6d8a4c95ab701192ba9f3f2f1a7 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Wed, 14 Jan 2015 14:58:15 -0500 Subject: [PATCH 106/115] Changed how parameters are parsed. --- plugins/blueprint.cpp | 95 ++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index c6611cef5..60cf8dec7 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -25,13 +25,14 @@ using std::endl; using std::vector; using std::ofstream; using std::swap; +using std::find; using std::pair; using namespace DFHack; using namespace df::enums; DFHACK_PLUGIN("blueprint"); -enum phase {DIG, BUILD, PLACE, QUERY}; +enum phase {DIG=1, BUILD=2, PLACE=4, QUERY=8}; command_result blueprint(color_ostream &out, vector ¶meters); @@ -48,7 +49,7 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) command_result help(color_ostream &out) { - out << "blueprint width height depth name [dig [build [place [query]]]]" << endl + out << "blueprint width height depth name [dig] [build] [place] [query]" << endl << " width, height, depth: area to translate in tiles" << endl << " name: base name for blueprint files" << endl << " dig: generate blueprints for digging" << endl @@ -548,21 +549,26 @@ string get_tile_query(df::building* b) return " "; } -command_result do_transform(DFCoord start, DFCoord end, string name, phase last_phase) +command_result do_transform(DFCoord start, DFCoord end, string name, uint32_t phases) { ofstream dig, build, place, query; - switch (last_phase) + if (phases & QUERY) { - case QUERY: query = ofstream(name + "-query.csv", ofstream::trunc); query << "#query" << endl; - case PLACE: + } + if (phases & PLACE) + { place = ofstream(name + "-place.csv", ofstream::trunc); place << "#place" << endl; - case BUILD: + } + if (phases & BUILD) + { build = ofstream(name + "-build.csv", ofstream::trunc); build << "#build" << endl; - case DIG: + } + if (phases & DIG) + { dig = ofstream(name + "-dig.csv", ofstream::trunc); dig << "#dig" << endl; } @@ -593,54 +599,53 @@ command_result do_transform(DFCoord start, DFCoord end, string name, phase last_ for (int32_t x = start.x; x < end.x; x++) { df::building* b = DFHack::Buildings::findAtTile(DFCoord(x, y, z)); - switch (last_phase) { - case QUERY: + if (phases & QUERY) query << get_tile_query(b) << ','; - case PLACE: + if (phases & PLACE) place << get_tile_place(x, y, b) << ','; - case BUILD: + if (phases & BUILD) build << get_tile_build(x, y, b) << ','; - case DIG: + if (phases & DIG) dig << get_tile_dig(mc, x, y, z) << ','; - } } - switch (last_phase) { - case QUERY: + if (phases & QUERY) query << "#" << endl; - case PLACE: + if (phases & PLACE) place << "#" << endl; - case BUILD: + if (phases & BUILD) build << "#" << endl; - case DIG: + if (phases & DIG) dig << "#" << endl; - } } if (z < end.z - 1) - switch (last_phase) { - case QUERY: + { + if (phases & QUERY) query << "#<" << endl; - case PLACE: + if (phases & PLACE) place << "#<" << endl; - case BUILD: + if (phases & BUILD) build << "#<" << endl; - case DIG: + if (phases & DIG) dig << "#<" << endl; } } - switch (last_phase) { - case QUERY: + if (phases & QUERY) query.close(); - case PLACE: + if (phases & PLACE) place.close(); - case BUILD: + if (phases & BUILD) build.close(); - case DIG: + if (phases & DIG) dig.close(); - } return CR_OK; } -command_result blueprint(color_ostream &out, vector ¶meters) +bool cmd_option_exists(vector& parameters, const string& option) +{ + return find(parameters.begin(), parameters.end(), option) != parameters.end(); +} + +command_result blueprint(color_ostream &out, vector ¶meters) { if (parameters.size() < 4 || parameters.size() > 8) return help(out); @@ -658,18 +663,16 @@ command_result blueprint(color_ostream &out, vector ¶meters) } DFCoord start (x, y, z); DFCoord end (x + stoi(parameters[0]), y + stoi(parameters[1]), z + stoi(parameters[2])); - switch(parameters.size()) - { - case 4: - case 8: - return do_transform(start, end, parameters[3], QUERY); - case 5: - return do_transform(start, end, parameters[3], DIG); - case 6: - return do_transform(start, end, parameters[3], BUILD); - case 7: - return do_transform(start, end, parameters[3], PLACE); - default: //wtf? - return CR_FAILURE; - } + if (parameters.size() == 4) + return do_transform(start, end, parameters[3], DIG | BUILD | PLACE | QUERY); + uint32_t option = 0; + if (cmd_option_exists(parameters, "dig")) + option |= DIG; + if (cmd_option_exists(parameters, "build")) + option |= BUILD; + if (cmd_option_exists(parameters, "place")) + option |= PLACE; + if (cmd_option_exists(parameters, "query")) + option |= QUERY; + return do_transform(start, end, parameters[3], option); } \ No newline at end of file From bc30d7cdda48126e3cda7a4b409f87782322be9f Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Wed, 14 Jan 2015 15:34:14 -0500 Subject: [PATCH 107/115] Update documentation for new plugin --- Readme.html | 471 +++++++++++++++++++++++++++++++--------------------- Readme.rst | 17 ++ 2 files changed, 302 insertions(+), 186 deletions(-) diff --git a/Readme.html b/Readme.html index 558c2a662..47f25f636 100644 --- a/Readme.html +++ b/Readme.html @@ -481,121 +481,126 @@ access DF memory and allow for easier development of new tools.

      • mapexport
      • dwarfexport
      • exportlegends
      • +
      • blueprint
    • -
    • Job management
        -
      • job
      • -
      • job-material
      • -
      • job-duplicate
      • -
      • stockflow
      • -
      • workflow
          -
        • Function
        • -
        • Constraint format
        • -
        • Constraint examples
        • +
        • Job management
        • -
        • Fortress activity management
            -
          • seedwatch
          • -
          • zone
              -
            • Usage with single units
            • -
            • Usage with filters
            • -
            • Mass-renaming
            • -
            • Cage zones
            • -
            • Examples
            • +
            • Fortress activity management
            • -
            • Other
            • -
            • Scripts
                -
              • fix/*
              • -
              • gui/*
              • -
              • binpatch
              • -
              • create-items
              • -
              • digfort
              • -
              • drain-aquifer
              • -
              • deathcause
              • -
              • dfstatus
              • -
              • embark
              • -
              • exterminate
              • -
              • growcrops
              • -
              • lever
              • -
              • locate-ore
              • -
              • lua
              • -
              • masspit
              • -
              • multicmd
              • -
              • quicksave
              • -
              • remove-stress
              • -
              • setfps
              • -
              • siren
              • -
              • soundsense-season
              • -
              • source
              • -
              • superdwarf
              • -
              • stripcaged
              • -
              • teleport
              • -
              • undump-buildings
              • +
              • Scripts
              • -
              • modtools
              • -
              • In-game interface tools
                  -
                • Dwarf Manipulator
                • -
                • Search
                • -
                • AutoMaterial
                • -
                • Stockpile Automation
                • -
                • Track Stop Menu
                • -
                • gui/advfort
                • -
                • gui/assign-rack
                • -
                • gui/choose-weapons
                • -
                • gui/clone-uniform
                • -
                • gui/companion-order
                • -
                • gui/gm-editor
                • -
                • Hotkeys
                • -
                • Hotkeys
                • -
                • Stockpile Automation
                • -
                • Stockpile Automation
                • -
                • gui/liquids
                • -
                • gui/mechanisms
                • -
                • gui/mod-manager
                • -
                • gui/rename
                • -
                • gui/room-list
                • -
                • gui/guide-path
                • -
                • gui/workflow
                • -
                • gui/workshop-job
                • +
                • modtools
                • +
                • In-game interface tools
                • -
                • Behavior Mods
                    -
                  • Siege Engine
                    -

                    job-duplicate

                    +

                    job-duplicate

                    Duplicate the selected job in a workshop:

                    • In 'q' mode, when a job is highlighted within a workshop or furnace building, @@ -2244,7 +2278,7 @@ instantly duplicates the job.
                    -

                    stockflow

                    +

                    stockflow

                    Allows the fortress bookkeeper to queue jobs through the manager, based on space or items available in stockpiles.

                    Usage:

                    @@ -2272,7 +2306,7 @@ number of identical orders already in the queue.

                    waiting for the bookkeeper.

                    -

                    workflow

                    +

                    workflow

                    Manage control of repeat jobs.

                    Usage:

                    @@ -2302,7 +2336,7 @@ this list can be copied to a file, and then reloaded using the
                    Delete all constraints.
                    -

                    Function

                    +

                    Function

                    When the plugin is enabled, it protects all repeat jobs from removal. If they do disappear due to any cause, they are immediately re-added to their workshop and suspended.

                    @@ -2315,7 +2349,7 @@ the frequency of jobs being toggled.

                    in the game UI.

                    -

                    Constraint format

                    +

                    Constraint format

                    The contstraint spec consists of 4 parts, separated with '/' characters:

                     ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,<quality>]
                    @@ -2344,7 +2378,7 @@ be used to ignore imported items or items below a certain quality.

                  -

                  Constraint examples

                  +

                  Constraint examples

                  Keep metal bolts within 900-1000, and wood/bone within 150-200:

                   workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
                  @@ -2393,9 +2427,9 @@ workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
                   
                  -

                  Fortress activity management

                  +

                  Fortress activity management

                  -

                  seedwatch

                  +

                  seedwatch

                  Watches the numbers of seeds available and enables/disables seed and plant cooking.

                  Each plant type can be assigned a limit. If their number falls below that limit, the plants and seeds of that type will be excluded from cookery. @@ -2430,7 +2464,7 @@ You have to reactivate with 'seedwatch start' after you load the game.

                  -

                  zone

                  +

                  zone

                  Helps a bit with managing activity zones (pens, pastures and pits) and cages.

                  Options:

                  @@ -2525,7 +2559,7 @@ for war/hunt). Negatable.
                  -

                  Usage with single units

                  +

                  Usage with single units

                  One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units @@ -2534,7 +2568,7 @@ and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way.

                  -

                  Usage with filters

                  +

                  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. @@ -2552,14 +2586,14 @@ 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

                  +

                  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

                  +

                  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 @@ -2570,7 +2604,7 @@ would make no sense, but can be used together with 'nick' or 'remnick' and all the usual filters.

                  -

                  Examples

                  +

                  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 @@ -2596,7 +2630,7 @@ on the current default zone.
                  -

                  autonestbox

                  +

                  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 @@ -2623,7 +2657,7 @@ frames between runs.

                  -

                  autobutcher

                  +

                  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 (to protect specific animals from @@ -2729,7 +2763,7 @@ autobutcher.bat

                  -

                  autochop

                  +

                  autochop

                  Automatically manage tree cutting designation to keep available logs withing given quotas.

                  Open the dashboard by running:

                  @@ -2747,7 +2781,7 @@ enable getplants menu.

                  -

                  autolabor

                  +

                  autolabor

                  Automatically manage dwarf labors to efficiently complete jobs. Autolabor tries to keep as many dwarves as possible busy but also tries to have dwarves specialize in specific skills.

                  @@ -2843,14 +2877,14 @@ and then assign additional dwarfs that meet any of these conditions:

                  -

                  Other

                  +

                  Other

                  -

                  catsplosion

                  +

                  catsplosion

                  Makes cats just multiply. It is not a good idea to run this more than once or twice.

                  -

                  dfusion

                  +

                  dfusion

                  This is the DFusion lua plugin system by Warmist, running as a DFHack plugin. There are two parts to this plugin: an interactive script that shows a text based menu and lua modules. Some of the functionality of is intentionaly left out of the menu:

                  @@ -2872,7 +2906,7 @@ twice.

                  -

                  embark-tools

                  +

                  embark-tools

                  A collection of embark-related tools.

                  Usage:

                  @@ -2888,7 +2922,7 @@ embark-tools enable/disable tool [tool]...
                   
                   
                  -

                  petcapRemover

                  +

                  petcapRemover

                  This plugin allows you to remove or raise the pet population cap. In vanilla DF, pets will not reproduce unless the population is below 50 and the number of children of that species is below a certain percentage. This plugin allows removing the second restriction and removing or raising the first. Pets still require PET or PET_EXOTIC tags in order to reproduce. Type help petcapRemover for exact usage. In order to make population more stable and avoid sudden population booms as you go below the raised population cap, this plugin counts pregnancies toward the new population cap. It can still go over, but only in the case of multiple births.

                  petcapRemover
                  @@ -2902,7 +2936,7 @@ embark-tools enable/disable tool [tool]...
                  -

                  misery

                  +

                  misery

                  When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).

                  Usage:

                  @@ -2924,7 +2958,7 @@ embark-tools enable/disable tool [tool]...
                  -

                  strangemood

                  +

                  strangemood

                  Creates a strange mood job the same way the game itself normally does it.

                  Options:

                  @@ -2946,13 +2980,13 @@ Valid values are "miner", "carpenter", "engraver",

                  Known limitations: if the selected unit is currently performing a job, the mood will not be started.

                  -

                  log-region

                  +

                  log-region

                  When enabled in dfhack.init, each time a fort is loaded identifying information will be written to the gamelog. Assists in parsing the file if you switch between forts, and adds information for story-building.

                  -

                  Scripts

                  +

                  Scripts

                  Lua or ruby scripts placed in the hack/scripts/ directory are considered for execution as if they were native DFHack commands. They are listed at the end of the 'ls' command output.

                  @@ -2961,7 +2995,7 @@ only be listed by ls if called as 'ls -a'. This is intended as a way to hide scripts that are obscure, developer-oriented, or should be used as keybindings.

                  Some notable scripts:

                  -

                  fix/*

                  +

                  fix/*

                  Scripts in this subdirectory fix various bugs and issues, some of them obscure.

                  • fix/blood-del

                    @@ -3007,17 +3041,33 @@ state however, use tweak stable-t
                  -

                  gui/*

                  +

                  gui/*

                  Scripts that implement dialogs inserted into the main game window are put in this directory.

                  • gui/hack-wish

                    A graphical interface for creating items.

                  • +
                  • gui/stockpiles

                    +

                    Load and save stockpile settings from the 'q' menu. +Usage:

                    +
                    +

                    System Message: ERROR/3 (Readme.rst, line 2225)

                    +

                    Unexpected indentation.

                    +
                    +
                    +

                    gui/stockpiles -save to save the current stockpile +gui/stockpiles -load to load settings into the current stockpile +gui/stockpiles -dir <path> set the default directory to save settings into +gui/stockpiles -help to see this message

                    +
                    +
                  +

                  Don't forget to enable stockpiles and create the stocksettings directory in +the DF folder before trying to use this plugin.

                  -

                  binpatch

                  +

                  binpatch

                  Checks, applies or removes binary patches directly in memory at runtime:

                   binpatch check/apply/remove <patchname>
                  @@ -3027,7 +3077,7 @@ script uses hack/patches/<df-v
                   the version appropriate for the currently loaded executable.

                  -

                  create-items

                  +

                  create-items

                  Spawn arbitrary items under the cursor.

                  The first argument gives the item category, the second gives the material, and the optionnal third gives the number of items to create (defaults to 20).

                  @@ -3049,7 +3099,7 @@ create-items bar adamantine
                  -

                  digfort

                  +

                  digfort

                  A script to designate an area for digging according to a plan in csv format.

                  This script, inspired from quickfort, can designate an area for digging. Your plan should be stored in a .csv file like this:

                  @@ -3071,25 +3121,25 @@ as an offset for the pattern: instead of starting at the cursor, it will start Dwarf Fortress.exe is found).

                  -

                  drain-aquifer

                  +

                  drain-aquifer

                  Remove all 'aquifer' tag from the map blocks. Irreversible.

                  -

                  deathcause

                  +

                  deathcause

                  Focus a body part ingame, and this script will display the cause of death of the creature. Also works when selecting units from the 'u'nitlist viewscreen.

                  -

                  dfstatus

                  +

                  dfstatus

                  Show a quick overview of critical stock quantities, including food, dirnks, wood, and various bars.

                  -

                  embark

                  +

                  embark

                  Allows to embark anywhere. Currently windows only.

                  -

                  exterminate

                  +

                  exterminate

                  Kills any unit of a given race.

                  With no argument, lists the available races and count eligible targets.

                  With the special argument him, targets only the selected creature.

                  @@ -3120,8 +3170,35 @@ exterminate him exterminate elve magma
                  +
                  +

                  fortplan

                  +

                  Usage: fortplan [filename]

                  +

                  Designates furniture for building according to a .csv file with +quickfort-style syntax. Companion to digfort.

                  +

                  The first line of the file must contain the following:

                  +
                  +#build start(X; Y; <start location description>)
                  +

                  ...where X and Y are the offset from the top-left corner of the file's area +where the in-game cursor should be located, and <start location description> +is an optional description of where that is. You may also leave a description +of the contents of the file itself following the closing parenthesis on the +same line.

                  +

                  The syntax of the file itself is similar to digfort or quickfort. At present, +only buildings constructed of an item with the same name as the building +are supported. All other characters are ignored. For example:

                  +
                  +,,d,`,` +,f,,t,` +,s,b,c,
                  +

                  This section of a file would designate for construction a door and some +furniture inside a bedroom: specifically, clockwise from top left, a cabinet, +a table, a chair, a bed, and a statue.

                  +

                  All of the building designation uses Planning Mode, so you do not need to +have the items available to construct all the buildings when you run +fortplan with the .csv file.

                  +
                  -

                  growcrops

                  +

                  growcrops

                  Instantly grow seeds inside farming plots.

                  With no argument, this command list the various seed types currently in use in your farming plots. @@ -3132,8 +3209,20 @@ harvested. You can change the number with a 2nd argument.

                  growcrops plump 40
                  +
                  +

                  hfs-pit

                  +

                  Creates a pit to the underworld at the cursor.

                  +

                  Takes three arguments: diameter of the pit in tiles, whether to wall off +the pit, and whether to insert stairs. If no arguments are given, the default +is "hfs-pit 1 0 0", ie single-tile wide with no walls or stairs.

                  +
                  +hfs-pit 4 0 1 +hfs-pit 2 1 0
                  +

                  First example is a four-across pit with stairs but no walls; second is a +two-across pit with stairs but no walls.

                  +
                  -

                  lever

                  +

                  lever

                  Allow manipulation of in-game levers from the dfhack console.

                  Can list levers, including state and links, with:

                  @@ -3147,7 +3236,7 @@ lever pull 42 --now
                   
                  -

                  locate-ore

                  +

                  locate-ore

                  Scan the map for metal ores.

                  Finds and designate for digging one tile of a specific metal ore. Only works for native metal ores, does not handle reaction stuff (eg STEEL).

                  @@ -3160,7 +3249,7 @@ locate-ore iron
                  -

                  lua

                  +

                  lua

                  There are the following ways to invoke this command:

                  1. lua (without any parameters)

                    @@ -3179,14 +3268,14 @@ directory. If the filename is not supplied, it loads "dfhack.lua".

                  -

                  masspit

                  +

                  masspit

                  Designate all creatures in cages on top of a pit/pond activity zone for pitting. Works best with an animal stockpile on top of the zone.

                  Works with a zone number as argument (eg Activity Zone #6 -> masspit 6) or with the game cursor on top of the area.

                  -

                  multicmd

                  +

                  multicmd

                  Run multiple dfhack commands. The argument is split around the character ; and all parts are run sequencially as independent dfhack commands. Useful for hotkeys.

                  @@ -3195,22 +3284,32 @@ dfhack commands. Useful for hotkeys.

                  multicmd locate-ore iron ; digv
                  +
                  +

                  position

                  +

                  Reports the current time: date, clock time, month, and season. Also reports +location: z-level, cursor position, window size, and mouse location.

                  +
                  +
                  +

                  putontable

                  +

                  Makes item appear on the table, like in adventure mode shops. Arguments: '-a' +or '--all' for all items.

                  +
                  -

                  quicksave

                  +

                  quicksave

                  If called in dwarf mode, makes DF immediately auto-save the game by setting a flag normally used in seasonal auto-save.

                  -

                  remove-stress

                  +

                  remove-stress

                  Sets stress to -1,000,000; the normal range is 0 to 500,000 with very stable or very stressed dwarves taking on negative or greater values respectively. Applies to the selected unit, or use "remove-stress -all" to apply to all units.

                  -

                  setfps

                  +

                  setfps

                  Run setfps <number> to set the FPS cap at runtime, in case you want to watch combat in slow motion or something :)

                  -

                  siren

                  +

                  siren

                  Wakes up sleeping units, cancels breaks and stops parties either everywhere, or in the burrows given as arguments. In return, adds bad thoughts about noise, tiredness and lack of protection. Also, the units with interrupted @@ -3218,7 +3317,7 @@ breaks will go on break again a lot sooner. The script is intended for emergencies, e.g. when a siege appears, and all your military is partying.

                  -

                  soundsense-season

                  +

                  soundsense-season

                  It is a well known issue that Soundsense cannot detect the correct current season when a savegame is loaded and has to play random season music until a season switch occurs.

                  @@ -3227,7 +3326,7 @@ to gamelog.txt on every map load to fix this. For best results call the script from dfhack.init.

                  -

                  source

                  +

                  source

                  Create an infinite magma or water source or drain on a tile.

                  This script registers a map tile as a liquid source, and every 12 game ticks that tile receives or remove 1 new unit of flow based on the configuration.

                  @@ -3249,7 +3348,7 @@ source add water 0 - water drain
                  -

                  superdwarf

                  +

                  superdwarf

                  Similar to fastdwarf, per-creature.

                  To make any creature superfast, target it ingame using 'v' and:

                  @@ -3259,7 +3358,7 @@ superdwarf add
                   

                  This plugin also shortens the 'sleeping' and 'on break' periods of targets.

                  -

                  stripcaged

                  +

                  stripcaged

                  For dumping items inside cages. Will mark selected items for dumping, then a dwarf may come and actually dump it. See also autodump.

                  With the items argument, only dumps items laying in the cage, excluding @@ -3277,7 +3376,7 @@ stripcaged weapons 25321 34228

                  -

                  teleport

                  +

                  teleport

                  Teleports a unit to given coordinates.

                  Examples:

                  @@ -3287,12 +3386,12 @@ teleport -unit 1234 -x 56 -y 115 -z 26  - teleports unit 1234 to 56,115,26
                   
                  -

                  undump-buildings

                  +

                  undump-buildings

                  Undesignates building base materials for dumping.

                  -

                  modtools

                  +

                  modtools

                  These scripts are mostly useful for raw modders and scripters. They all have standard arguments: arguments are of the form tool -argName1 argVal1 -argName2 argVal2. This is equivalent to tool -argName2 argVal2 -argName1 argVal1. It is not necessary to provide a value to an argument name: tool -argName3 is fine. Supplying the same argument name multiple times will result in an error. Argument names are preceded with a dash. The -help argument will print a descriptive usage string describing the nature of the arguments. For multiple word argument values, brackets must be used: tool -argName4 [ sadf1 sadf2 sadf3 ]. In order to allow passing literal braces as part of the argument, backslashes are used: tool -argName4 [ \] asdf \foo ] sets argName4 to \] asdf foo. The *-trigger scripts have a similar policy with backslashes.

                  • add-syndrome

                    @@ -3349,7 +3448,7 @@ teleport -unit 1234 -x 56 -y 115 -z 26 - teleports unit 1234 to 56,115,26
                  -

                  In-game interface tools

                  +

                  In-game interface tools

                  These tools work by displaying dialogs or overlays in the game window, and are mostly implemented by lua scripts.

                  @@ -3364,7 +3463,7 @@ guideline because it arguably just fixes small usability bugs in the game UI.

                  -

                  Dwarf Manipulator

                  +

                  Dwarf Manipulator

                  Implemented by the 'manipulator' plugin.

                  To activate, open the unit screen and press 'l'.

                  images/manipulator.png @@ -3405,7 +3504,7 @@ cursor onto that cell instead of toggling it. directly to the main dwarf mode screen.

                  -

                  AutoMaterial

                  +

                  AutoMaterial

                  Implemented by the 'automaterial' plugin.

                  This makes building constructions (walls, floors, fortifications, etc) a little bit easier by saving you from having to trawl through long lists of materials each time @@ -3464,7 +3563,7 @@ materials, it returns you back to this screen. If you use this along with severa enabled materials, you should be able to place complex constructions more conveniently.

                  -

                  Stockpile Automation

                  +

                  Stockpile Automation

                  Enable the automelt or autotrade plugins in your dfhack.init with:

                   enable automelt
                  @@ -3475,7 +3574,7 @@ When automelt is enabled for a stockpile, any meltable items placed in it will b
                   When autotrade is enabled for a stockpile, any items placed in it will be designated to be taken to the Trade Depot whenever merchants are on the map.

                  -

                  Track Stop Menu

                  +

                  Track Stop Menu

                  The q menu of track stops is completely blank by default. To enable one:

                   enable trackstop
                  @@ -3489,7 +3588,7 @@ It re-uses the keybindings from the track stop building interface:

                  -

                  gui/advfort

                  +

                  gui/advfort

                  This script allows to perform jobs in adventure mode. For more complete help press '?' while script is running. It's most confortable to use this as a keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:

                  @@ -3508,7 +3607,7 @@ implies -a
                  -

                  gui/assign-rack

                  +

                  gui/assign-rack

                  Bind to a key (the example config uses P), and activate when viewing a weapon rack in the 'q' mode.

                  images/assign-rack.png @@ -3532,7 +3631,7 @@ the intended user. In order to aid in the choice, it shows the number of currently assigned racks for every valid squad.

                  -

                  gui/choose-weapons

                  +

                  gui/choose-weapons

                  Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize page of the military screen.

                  Depending on the cursor location, it rewrites all 'individual choice weapon' entries @@ -3543,14 +3642,14 @@ only that entry, and does it even if it is not 'individual choice'.

                  and may lead to inappropriate weapons being selected.

                  -

                  gui/clone-uniform

                  +

                  gui/clone-uniform

                  Bind to a key (the example config uses Ctrl-C), and activate in the Uniforms page of the military screen with the cursor in the leftmost list.

                  When invoked, the script duplicates the currently selected uniform template, and selects the newly created copy.

                  -

                  gui/companion-order

                  +

                  gui/companion-order

                  A script to issue orders for companions. Select companions with lower case chars, issue orders with upper case. Must be in look or talk mode to issue command on tile.

                  images/companion-order.png @@ -3566,7 +3665,7 @@ case. Must be in look or talk mode to issue command on tile.

                  -

                  gui/gm-editor

                  +

                  gui/gm-editor

                  There are three ways to open this editor:

                  • using gui/gm-editor command/keybinding - opens editor on what is selected @@ -3581,7 +3680,7 @@ the same as version above.
                  • in-game help.

                  -

                  Hotkeys

                  +

                  Hotkeys

                  Opens an in-game screen showing DFHack keybindings that are valid in the current mode.

                  images/hotkeys.png

                  Type hotkeys into the DFHack console to open the screen, or bind the command to a @@ -3591,7 +3690,7 @@ keybinding add Ctrl-F1 hotkeys

                  -

                  Hotkeys

                  +

                  Hotkeys

                  Opens an in-game screen showing DFHack keybindings that are valid in the current mode.

                  images/hotkeys.png

                  Type hotkeys into the DFHack console to open the screen, or bind the command to a @@ -3600,7 +3699,7 @@ globally active hotkey in dfhack.init, e.g.:

                  keybinding add Ctrl-F1 hotkeys
                  -

                  Stockpile Automation

                  +

                  Stockpile Automation

                  Enable the autodump plugin in your dfhack.init with
                  enable autodump
                  @@ -3609,7 +3708,7 @@ globally active hotkey in dfhack.init, e.g.:

                  Any items placed in this stockpile will be designated to be dumped.

                  -

                  Stockpile Automation

                  +

                  Stockpile Automation

                  Enable the automelt plugin in your dfhack.init with
                  enable automelt
                  @@ -3618,7 +3717,7 @@ Any items placed in this stockpile will be designated to be dumped.

                  Any items placed in this stockpile will be designated to be melted.

                  -

                  gui/liquids

                  +

                  gui/liquids

                  To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.

                  images/liquids.png

                  This script is a gui front-end to the liquids plugin and works similar to it, @@ -3638,7 +3737,7 @@ rivers power water wheels even when full and technically not flowing.

                  After setting up the desired operations using the described keys, use Enter to apply them.

                  -

                  gui/mechanisms

                  +

                  gui/mechanisms

                  To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.

                  images/mechanisms.png

                  Lists mechanisms connected to the building, and their links. Navigating the list centers @@ -3648,13 +3747,13 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui.

                  -

                  gui/mod-manager

                  +

                  gui/mod-manager

                  A way to simply install and remove small mods. It looks for specially formatted mods in df subfolder 'mods'. Mods are not included, for example mods see: github mini mod repository

                  images/mod-manager.png
                  -

                  gui/rename

                  +

                  gui/rename

                  Backed by the rename plugin, this script allows entering the desired name via a simple dialog in the game ui.

                    @@ -3677,7 +3776,7 @@ their species string.

                    unit profession change to Ctrl-Shift-T.

                  -

                  gui/room-list

                  +

                  gui/room-list

                  To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, either immediately or after opening the assign owner page.

                  images/room-list.png @@ -3685,7 +3784,7 @@ either immediately or after opening the assign owner page.

                  list, and allows unassigning them.

                  -

                  gui/guide-path

                  +

                  gui/guide-path

                  Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with the cursor over a Guide order.

                  images/guide-path.png @@ -3693,7 +3792,7 @@ the cursor over a Guide order.

                  computes it when the order is executed for the first time.

                  -

                  gui/workflow

                  +

                  gui/workflow

                  Bind to a key (the example config uses Alt-W), and activate with a job selected in a workshop in the 'q' mode.

                  images/workflow.png @@ -3740,7 +3839,7 @@ the current stock value. The bright green dashed line is the target limit (maximum) and the dark green line is that minus the gap (minimum).

                  -

                  gui/workshop-job

                  +

                  gui/workshop-job

                  Bind to a key (the example config uses Alt-A), and activate with a job selected in a workshop in the 'q' mode.

                  images/workshop-job.png @@ -3777,7 +3876,7 @@ you have to unset the material first.

                  -

                  Behavior Mods

                  +

                  Behavior Mods

                  These plugins, when activated via configuration UI or by detecting certain structures in RAWs, modify the game engine behavior concerning the target objects to add features not otherwise present.

                  @@ -3788,20 +3887,20 @@ technical challenge, and do not represent any long-term plans to produce more similar modifications of the game.

                  -

                  Siege Engine

                  +

                  Siege Engine

                  The siege-engine plugin enables siege engines to be linked to stockpiles, and aimed at an arbitrary rectangular area across Z levels, instead of the original four directions. Also, catapults can be ordered to load arbitrary objects, not just stones.

                  -

                  Rationale

                  +

                  Rationale

                  Siege engines are a very interesting feature, but sadly almost useless in the current state because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do.

                  -

                  Configuration UI

                  +

                  Configuration UI

                  The configuration front-end to the plugin is implemented by the gui/siege-engine script. Bind it to a key (the example config uses Alt-A) and activate after selecting a siege engine in 'q' mode.

                  @@ -3824,7 +3923,7 @@ menu.

                  -

                  Power Meter

                  +

                  Power Meter

                  The power-meter plugin implements a modified pressure plate that detects power being supplied to gear boxes built in the four adjacent N/S/W/E tiles.

                  The configuration front-end is implemented by the gui/power-meter script. Bind it to a @@ -3835,11 +3934,11 @@ in the build menu.

                  configuration page, but configures parameters relevant to the modded power meter building.

                  -

                  Steam Engine

                  +

                  Steam Engine

                  The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

                  -

                  Rationale

                  +

                  Rationale

                  The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -3850,7 +3949,7 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

                  -

                  Construction

                  +

                  Construction

                  The workshop needs water as its input, which it takes via a passable floor tile below it, like usual magma workshops do. The magma version also needs magma.

                  @@ -3874,7 +3973,7 @@ short axles that can be built later than both of the engines.

                  -

                  Operation

                  +

                  Operation

                  In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear @@ -3905,7 +4004,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

                  -

                  Explosions

                  +

                  Explosions

                  The engine must be constructed using barrel, pipe and piston from fire-safe, or in the magma version magma-safe metals.

                  During operation weak parts get gradually worn out, and @@ -3914,7 +4013,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

                  -

                  Save files

                  +

                  Save files

                  It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -3925,7 +4024,7 @@ being generated.

                  -

                  Add Spatter

                  +

                  Add Spatter

                  This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements. The produced contaminants are immune to being washed away by water or destroyed by diff --git a/Readme.rst b/Readme.rst index 609a98dcd..87cd92126 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1477,6 +1477,23 @@ Options: :maps: Exports all seventeen detailed maps :all: Equivalent to calling all of the above, in that order +blueprint +--------- +Exports a portion of your fortress into QuickFort style blueprint files.:: + + blueprint [dig] [build] [place] [query] + +Options: + +:x,y,z: Size of map area to export +:name: Name of export files +:dig: Export dig commands to "-dig.csv" +:build: Export build commands to "-build.csv" +:place: Export stockpile commands to "-place.csv" +:query: Export query commands to "-query.csv" + +If only region and name are given, all exports are performed. + Job management ============== From 6b3e6a6bdb812764332395e463db4ef7c0f49690 Mon Sep 17 00:00:00 2001 From: Chris Dombroski Date: Wed, 14 Jan 2015 19:29:06 -0500 Subject: [PATCH 108/115] Update NEWS for blueprint plugin --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 817ca1392..1a018c0c0 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +DFHack 0.40.24-r1 + New Plugins + blueprint: export part of your fortress to quickfort .csv files DFHack 0.40.23-r1 Internals plugins will not be loaded if globals they specify as required are not located (should prevent some crashes) From 8f4b76610751d047c14e855debdcf10717387d8e Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 15 Jan 2015 18:13:19 -0500 Subject: [PATCH 109/115] EventManager: Fix a crash bug with EQUIPMENT_CHANGE event. --- library/modules/EventManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 9e356269a..46178d818 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -763,8 +763,6 @@ static void manageEquipmentEvent(color_ostream& out) { handle.eventHandler(out, (void*)&data); } } - if ( !hadEquipment ) - delete temp; //check for dropped items for ( auto b = v.begin(); b != v.end(); b++ ) { InventoryItem i = *b; @@ -777,6 +775,8 @@ static void manageEquipmentEvent(color_ostream& out) { handle.eventHandler(out, (void*)&data); } } + if ( !hadEquipment ) + delete temp; //update equipment vector& equipment = equipmentLog[unit->id]; From aa276b2d0c661f991ab7eaa80261eccf95240465 Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 15 Jan 2015 22:16:13 -0500 Subject: [PATCH 110/115] Update NEWS. --- NEWS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 04819a438..25a240428 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ DFHack Future - + Internals + EventManager: fixed crash error with EQUIPMENT_CHANGE event. DFHack 0.40.23-r1 Internals From 947cc879bd068844cdc3168cb6d1d98ece4a4cd2 Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 15 Jan 2015 22:30:28 -0500 Subject: [PATCH 111/115] Update NEWS. --- NEWS | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/NEWS b/NEWS index 25a240428..1d4b7a129 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,16 @@ DFHack Future Internals EventManager: fixed crash error with EQUIPMENT_CHANGE event. + key modifier state exposed to Lua + Fixes + dfhack script can now be run from other directories on OSX + New Plugins + New Scripts + Removed + embark.lua + New Tweaks + Misc Improvements + added support for searching more lists DFHack 0.40.23-r1 Internals From edefeb8ae01bd4fa6249d2b4ea446ae43e5cc99c Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 15 Jan 2015 22:53:41 -0500 Subject: [PATCH 112/115] Update NEWS. A rare case where there are no merge errors but git gets it wrong. --- NEWS | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index eeb05eb63..2ea15e5c8 100644 --- a/NEWS +++ b/NEWS @@ -1,9 +1,4 @@ DFHack Future - New Scripts - hotkey-notes: print key, name, and jump position of hotkeys - Removed - needs_porting/* - Internals EventManager: fixed crash error with EQUIPMENT_CHANGE event. key modifier state exposed to Lua @@ -12,8 +7,10 @@ DFHack Future New Plugins blueprint: export part of your fortress to quickfort .csv files New Scripts + hotkey-notes: print key, name, and jump position of hotkeys Removed embark.lua + needs_porting/* New Tweaks Misc Improvements added support for searching more lists From 53a8032f8af30ae668bcf27e44474b7e6667180d Mon Sep 17 00:00:00 2001 From: expwnent Date: Fri, 16 Jan 2015 03:27:14 -0500 Subject: [PATCH 113/115] Fix compile of blueprint plugin on Linux. --- plugins/blueprint.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 60cf8dec7..e32d4a20a 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -108,7 +108,7 @@ string get_tile_build(uint32_t x, uint32_t y, df::building* b) bool at_se_corner = x == b->x2 && y == b->y2; bool at_center = x == b->centerx && y == b->centery; pair size = get_building_size(b); - stringstream out = stringstream(); + stringstream out;// = stringstream(); switch(b->getType()) { case building_type::Armorstand: @@ -484,7 +484,7 @@ string get_tile_place(uint32_t x, uint32_t y, df::building* b) return "`"; pair size = get_building_size(b); df::building_stockpilest* sp = (df::building_stockpilest*) b; - stringstream out = stringstream(); + stringstream out;// = stringstream(); switch (sp->settings.flags.whole) { case df::stockpile_group_set::mask_animals: @@ -554,22 +554,26 @@ command_result do_transform(DFCoord start, DFCoord end, string name, uint32_t ph ofstream dig, build, place, query; if (phases & QUERY) { - query = ofstream(name + "-query.csv", ofstream::trunc); + //query = ofstream((name + "-query.csv").c_str(), ofstream::trunc); + query.open(name+"-query.csv", ofstream::trunc); query << "#query" << endl; } if (phases & PLACE) { - place = ofstream(name + "-place.csv", ofstream::trunc); + //place = ofstream(name + "-place.csv", ofstream::trunc); + place.open(name+"-place.csv", ofstream::trunc); place << "#place" << endl; } if (phases & BUILD) { - build = ofstream(name + "-build.csv", ofstream::trunc); + //build = ofstream(name + "-build.csv", ofstream::trunc); + build.open(name+"-build.csv", ofstream::trunc); build << "#build" << endl; } if (phases & DIG) { - dig = ofstream(name + "-dig.csv", ofstream::trunc); + //dig = ofstream(name + "-dig.csv", ofstream::trunc); + dig.open(name+"-dig.csv", ofstream::trunc); dig << "#dig" << endl; } if (start.x > end.x) @@ -675,4 +679,4 @@ command_result blueprint(color_ostream &out, vector ¶meters) if (cmd_option_exists(parameters, "query")) option |= QUERY; return do_transform(start, end, parameters[3], option); -} \ No newline at end of file +} From fd1ba80e1fad8812eca2d231e9f207d69a37cbbd Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 17 Jan 2015 08:56:49 -0500 Subject: [PATCH 114/115] Change MOD_* to DFH_MOD_* MOD_* constants are defined in Winuser.h on Windows --- library/Core.cpp | 6 +++--- library/LuaApi.cpp | 6 +++--- library/include/Core.h | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 452c294e3..e19878b2e 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1591,11 +1591,11 @@ int Core::DFH_SDL_Event(SDL::Event* ev) auto ke = (SDL::KeyboardEvent *)ev; if (ke->ksym.sym == SDL::K_LSHIFT || ke->ksym.sym == SDL::K_RSHIFT) - modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | MOD_SHIFT : modstate & ~MOD_SHIFT; + modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | DFH_MOD_SHIFT : modstate & ~DFH_MOD_SHIFT; else if (ke->ksym.sym == SDL::K_LCTRL || ke->ksym.sym == SDL::K_RCTRL) - modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | MOD_CTRL : modstate & ~MOD_CTRL; + modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | DFH_MOD_CTRL : modstate & ~DFH_MOD_CTRL; else if (ke->ksym.sym == SDL::K_LALT || ke->ksym.sym == SDL::K_RALT) - modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | MOD_ALT : modstate & ~MOD_ALT; + modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | DFH_MOD_ALT : modstate & ~DFH_MOD_ALT; else if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym]) { hotkey_states[ke->ksym.sym] = true; diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index f9b19b102..2d44c04d2 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2358,13 +2358,13 @@ static int internal_getModifiers(lua_State *L) int8_t modstate = Core::getInstance().getModstate(); lua_newtable(L); lua_pushstring(L, "shift"); - lua_pushboolean(L, modstate & MOD_SHIFT); + lua_pushboolean(L, modstate & DFH_MOD_SHIFT); lua_settable(L, -3); lua_pushstring(L, "ctrl"); - lua_pushboolean(L, modstate & MOD_CTRL); + lua_pushboolean(L, modstate & DFH_MOD_CTRL); lua_settable(L, -3); lua_pushstring(L, "alt"); - lua_pushboolean(L, modstate & MOD_ALT); + lua_pushboolean(L, modstate & DFH_MOD_ALT); lua_settable(L, -3); return 1; } diff --git a/library/include/Core.h b/library/include/Core.h index f3166496a..23f39a211 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -36,9 +36,9 @@ distribution. #include "RemoteClient.h" -#define MOD_SHIFT 1 -#define MOD_CTRL 2 -#define MOD_ALT 4 +#define DFH_MOD_SHIFT 1 +#define DFH_MOD_CTRL 2 +#define DFH_MOD_ALT 4 struct WINDOW; From 686647af0e87a6b29b1b309ba6d271b60e8f2b5f Mon Sep 17 00:00:00 2001 From: warmist Date: Sun, 18 Jan 2015 19:53:08 +0200 Subject: [PATCH 115/115] fix skill-change so it would use insert_sorted This is needed because the skills vector is actually sorted. Adding skill with id

  • drain-aquifer