From 37b5be1f357cb27ada067a3847b592fd4cf70904 Mon Sep 17 00:00:00 2001 From: John Cosker Date: Fri, 27 Jan 2023 19:46:56 -0500 Subject: [PATCH 01/40] Implement autoslab engraving feature (#1) * Initial autoslab implementation --- docs/changelog.txt | 1 + docs/plugins/autoslab.rst | 20 +++ plugins/CMakeLists.txt | 1 + plugins/autoslab.cpp | 267 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 docs/plugins/autoslab.rst create mode 100644 plugins/autoslab.cpp diff --git a/docs/changelog.txt b/docs/changelog.txt index 8d84e3cff..d4014c788 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,6 +34,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins +- `autoslab`: Automatically create work orders to engrave slabs for ghostly dwarves. ## Fixes - Fix issues with clicks "passing through" some DFHack window elements, like scrollbars diff --git a/docs/plugins/autoslab.rst b/docs/plugins/autoslab.rst new file mode 100644 index 000000000..e5db146cb --- /dev/null +++ b/docs/plugins/autoslab.rst @@ -0,0 +1,20 @@ +autoslab +======== + +.. dfhack-tool:: + :summary: Automatically engrave slabs for ghostly citizens! + :tags: untested fort auto workorders + :no-command: + +Automatically queue orders to engrave slabs of existing ghosts. Will only queue +an order if there is no existing slab with that unit's memorial engraved and +there is not already an existing work order to engrave a slab for that unit + +Usage +----- + +``enable autoslab`` + Enables the plugin and starts checking for ghosts that need memorializing. + +``disable autoslab`` + Disables the plugin. diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 284d1788e..41b3ad542 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -134,6 +134,7 @@ dfhack_plugin(misery misery.cpp) #dfhack_plugin(mode mode.cpp) #dfhack_plugin(mousequery mousequery.cpp) dfhack_plugin(nestboxes nestboxes.cpp) +dfhack_plugin(autoslab autoslab.cpp) dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static lua) dfhack_plugin(overlay overlay.cpp LINK_LIBRARIES lua) dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua) diff --git a/plugins/autoslab.cpp b/plugins/autoslab.cpp new file mode 100644 index 000000000..4dcfcecff --- /dev/null +++ b/plugins/autoslab.cpp @@ -0,0 +1,267 @@ +/* Simple plugin to check for ghosts and automatically queue jobs to engrave slabs for them. + * + * Enhancement idea: Queue up a ConstructSlab job, then link the engrave slab job to it. Avoids need to have slabs in stockpiles + * Would require argument parsing, specifying materials + * Enhancement idea: Automatically place the slab. This seems like a tricky problem but maybe solveable with named zones? + * Might be made obsolete by people just using buildingplan to pre-place plans for slab? + * Enhancement idea: Optionally enable autoengraving for pets. + * Enhancement idea: Try to get ahead of ghosts by autoengraving for dead dwarves with no remains. + */ + +#include "Core.h" +#include "Debug.h" +#include "PluginManager.h" + +#include "modules/Persistence.h" +#include "modules/World.h" +#include "modules/Translation.h" + +#include "df/manager_order.h" +#include "df/world.h" +#include "df/plotinfost.h" +#include "df/unit.h" +#include "df/item.h" +#include "df/historical_figure.h" + +using namespace DFHack; + +static command_result autoslab(color_ostream &out, std::vector ¶meters); + +DFHACK_PLUGIN("autoslab"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +// logging levels can be dynamically controlled with the `debugfilter` command. +namespace DFHack +{ + // for configuration-related logging + DBG_DECLARE(autoslab, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(autoslab, cycle, DebugCategory::LINFO); +} + +static const auto CONFIG_KEY = std::string(plugin_name) + "/config"; +static PersistentDataItem config; +enum ConfigValues +{ + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, +}; +static int get_config_val(int index) +{ + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool get_config_bool(int index) +{ + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) +{ + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) +{ + set_config_val(index, value ? 1 : 0); +} + +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle + +static command_result do_command(color_ostream &out, std::vector ¶meters); +static void do_cycle(color_ostream &out); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +{ + DEBUG(status, out).print("initializing %s\n", plugin_name); + + // provide a configuration interface for the plugin + commands.push_back(PluginCommand( + plugin_name, + "Automatically engrave slabs of ghostly citizens!", + do_command)); + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + if (!Core::getInstance().isWorldLoaded()) + { + out.printerr("Cannot enable %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } + + if (enable != is_enabled) + { + is_enabled = enable; + DEBUG(status, out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + } + else + { + DEBUG(status, out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) +{ + DEBUG(status, out).print("shutting down %s\n", plugin_name); + + return CR_OK; +} + +DFhackCExport command_result plugin_load_data(color_ostream &out) +{ + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) + { + DEBUG(status, out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + set_config_val(CONFIG_CYCLE_TICKS, 1200); + } + + // we have to copy our enabled flag into the global plugin variable, but + // all the other state we can directly read/modify from the persistent + // data structure. + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status, out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false"); + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + if (event == DFHack::SC_WORLD_UNLOADED) + { + if (is_enabled) + { + DEBUG(status, out).print("world unloaded; disabling %s\n", plugin_name); + is_enabled = false; + } + } + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + CoreSuspender suspend; + if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) + do_cycle(out); + return CR_OK; +} + +static command_result do_command(color_ostream &out, std::vector ¶meters) +{ + // be sure to suspend the core if any DF state is read or modified + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) + { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } + + // TODO: decide what, if any configuration should be here + + return CR_OK; +} + +// Name functions taken from manipulator.cpp +static std::string get_first_name(df::unit *unit) +{ + return Translation::capitalize(unit->name.first_name); +} + +static std::string get_last_name(df::unit *unit) +{ + df::language_name name = unit->name; + std::string ret = ""; + for (int i = 0; i < 2; i++) + { + if (name.words[i] >= 0) + ret += *world->raws.language.translations[name.language]->words[name.words[i]]; + } + return Translation::capitalize(ret); +} + +// Couldn't figure out any other way to do this besides look for the dwarf name in +// the slab item description. +// Ideally, we could get the historical figure id from the slab but I didn't +// see anything like that in the item struct. This seems to work based on testing. +// Confirmed nicknames don't show up in engraved slab names, so this should probably work okay +bool engravedSlabItemExists(df::unit *unit, std::vector slabs) +{ + for (auto slab : slabs) + { + std::string desc = ""; + slab->getItemDescription(&desc, 0); + auto fullName = get_first_name(unit) + " " + get_last_name(unit); + if (desc.find(fullName) != std::string::npos) + return true; + } + + return false; +} + +// Queue up a single order to engrave the slab for the given unit +static void createSlabJob(df::unit *unit) +{ + auto next_id = world->manager_order_next_id++; + auto order = new df::manager_order(); + + order->id = next_id; + order->job_type = df::job_type::EngraveSlab; + order->hist_figure_id = unit->hist_figure_id; + order->amount_left = 1; + order->amount_total = 1; + world->manager_orders.push_back(order); +} + +static void checkslabs(color_ostream &out) +{ + // Get existing orders for slab engraving as map hist_figure_id -> order ID + std::map histToJob; + for (auto order : world->manager_orders) + { + if (order->job_type == df::job_type::EngraveSlab) + histToJob[order->hist_figure_id] = order->id; + } + + // Get list of engraved slab items on map + std::vector engravedSlabs; + std::copy_if(world->items.all.begin(), world->items.all.end(), + std::back_inserter(engravedSlabs), + [](df::item *item) + { return item->getType() == df::item_type::SLAB && item->getSlabEngravingType() == df::slab_engraving_type::Memorial; }); + + // Build list of ghosts + std::vector ghosts; + std::copy_if(world->units.all.begin(), world->units.all.end(), + std::back_inserter(ghosts), + [](const auto &unit) + { return unit->flags3.bits.ghostly; }); + + for (auto ghost : ghosts) + { + // Only create a job is the map has no existing jobs for that historical figure or no existing engraved slabs + if (histToJob.count(ghost->hist_figure_id) == 0 && !engravedSlabItemExists(ghost, engravedSlabs)) + { + createSlabJob(ghost); + auto fullName = get_first_name(ghost) + " " + get_last_name(ghost); + out.print("Added slab order for ghost %s\n", fullName.c_str()); + } + } +} + +static void do_cycle(color_ostream &out) +{ + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + checkslabs(out); +} From b9b8b3665277ed17cc2a4802c67d3e60205888d1 Mon Sep 17 00:00:00 2001 From: John Cosker Date: Fri, 27 Jan 2023 20:08:33 -0500 Subject: [PATCH 02/40] Sort header includes per guidelines --- plugins/autoslab.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/autoslab.cpp b/plugins/autoslab.cpp index 4dcfcecff..5f7f25f6f 100644 --- a/plugins/autoslab.cpp +++ b/plugins/autoslab.cpp @@ -13,15 +13,15 @@ #include "PluginManager.h" #include "modules/Persistence.h" -#include "modules/World.h" #include "modules/Translation.h" +#include "modules/World.h" +#include "df/historical_figure.h" +#include "df/item.h" #include "df/manager_order.h" -#include "df/world.h" #include "df/plotinfost.h" #include "df/unit.h" -#include "df/item.h" -#include "df/historical_figure.h" +#include "df/world.h" using namespace DFHack; From 70b15ffcd1a48e1656f8cdbb1df14fe495ac4d17 Mon Sep 17 00:00:00 2001 From: John Cosker Date: Fri, 27 Jan 2023 19:46:56 -0500 Subject: [PATCH 03/40] Implement autoslab engraving feature (#1) * Initial autoslab implementation --- docs/changelog.txt | 1 + docs/plugins/autoslab.rst | 20 +++ plugins/CMakeLists.txt | 1 + plugins/autoslab.cpp | 267 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 docs/plugins/autoslab.rst create mode 100644 plugins/autoslab.cpp diff --git a/docs/changelog.txt b/docs/changelog.txt index 8d84e3cff..d4014c788 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,6 +34,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins +- `autoslab`: Automatically create work orders to engrave slabs for ghostly dwarves. ## Fixes - Fix issues with clicks "passing through" some DFHack window elements, like scrollbars diff --git a/docs/plugins/autoslab.rst b/docs/plugins/autoslab.rst new file mode 100644 index 000000000..e5db146cb --- /dev/null +++ b/docs/plugins/autoslab.rst @@ -0,0 +1,20 @@ +autoslab +======== + +.. dfhack-tool:: + :summary: Automatically engrave slabs for ghostly citizens! + :tags: untested fort auto workorders + :no-command: + +Automatically queue orders to engrave slabs of existing ghosts. Will only queue +an order if there is no existing slab with that unit's memorial engraved and +there is not already an existing work order to engrave a slab for that unit + +Usage +----- + +``enable autoslab`` + Enables the plugin and starts checking for ghosts that need memorializing. + +``disable autoslab`` + Disables the plugin. diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 284d1788e..41b3ad542 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -134,6 +134,7 @@ dfhack_plugin(misery misery.cpp) #dfhack_plugin(mode mode.cpp) #dfhack_plugin(mousequery mousequery.cpp) dfhack_plugin(nestboxes nestboxes.cpp) +dfhack_plugin(autoslab autoslab.cpp) dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static lua) dfhack_plugin(overlay overlay.cpp LINK_LIBRARIES lua) dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua) diff --git a/plugins/autoslab.cpp b/plugins/autoslab.cpp new file mode 100644 index 000000000..4dcfcecff --- /dev/null +++ b/plugins/autoslab.cpp @@ -0,0 +1,267 @@ +/* Simple plugin to check for ghosts and automatically queue jobs to engrave slabs for them. + * + * Enhancement idea: Queue up a ConstructSlab job, then link the engrave slab job to it. Avoids need to have slabs in stockpiles + * Would require argument parsing, specifying materials + * Enhancement idea: Automatically place the slab. This seems like a tricky problem but maybe solveable with named zones? + * Might be made obsolete by people just using buildingplan to pre-place plans for slab? + * Enhancement idea: Optionally enable autoengraving for pets. + * Enhancement idea: Try to get ahead of ghosts by autoengraving for dead dwarves with no remains. + */ + +#include "Core.h" +#include "Debug.h" +#include "PluginManager.h" + +#include "modules/Persistence.h" +#include "modules/World.h" +#include "modules/Translation.h" + +#include "df/manager_order.h" +#include "df/world.h" +#include "df/plotinfost.h" +#include "df/unit.h" +#include "df/item.h" +#include "df/historical_figure.h" + +using namespace DFHack; + +static command_result autoslab(color_ostream &out, std::vector ¶meters); + +DFHACK_PLUGIN("autoslab"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +// logging levels can be dynamically controlled with the `debugfilter` command. +namespace DFHack +{ + // for configuration-related logging + DBG_DECLARE(autoslab, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(autoslab, cycle, DebugCategory::LINFO); +} + +static const auto CONFIG_KEY = std::string(plugin_name) + "/config"; +static PersistentDataItem config; +enum ConfigValues +{ + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, +}; +static int get_config_val(int index) +{ + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool get_config_bool(int index) +{ + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) +{ + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) +{ + set_config_val(index, value ? 1 : 0); +} + +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle + +static command_result do_command(color_ostream &out, std::vector ¶meters); +static void do_cycle(color_ostream &out); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) +{ + DEBUG(status, out).print("initializing %s\n", plugin_name); + + // provide a configuration interface for the plugin + commands.push_back(PluginCommand( + plugin_name, + "Automatically engrave slabs of ghostly citizens!", + do_command)); + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + if (!Core::getInstance().isWorldLoaded()) + { + out.printerr("Cannot enable %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } + + if (enable != is_enabled) + { + is_enabled = enable; + DEBUG(status, out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + } + else + { + DEBUG(status, out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) +{ + DEBUG(status, out).print("shutting down %s\n", plugin_name); + + return CR_OK; +} + +DFhackCExport command_result plugin_load_data(color_ostream &out) +{ + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) + { + DEBUG(status, out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + set_config_val(CONFIG_CYCLE_TICKS, 1200); + } + + // we have to copy our enabled flag into the global plugin variable, but + // all the other state we can directly read/modify from the persistent + // data structure. + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status, out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false"); + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + if (event == DFHack::SC_WORLD_UNLOADED) + { + if (is_enabled) + { + DEBUG(status, out).print("world unloaded; disabling %s\n", plugin_name); + is_enabled = false; + } + } + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + CoreSuspender suspend; + if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) + do_cycle(out); + return CR_OK; +} + +static command_result do_command(color_ostream &out, std::vector ¶meters) +{ + // be sure to suspend the core if any DF state is read or modified + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) + { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } + + // TODO: decide what, if any configuration should be here + + return CR_OK; +} + +// Name functions taken from manipulator.cpp +static std::string get_first_name(df::unit *unit) +{ + return Translation::capitalize(unit->name.first_name); +} + +static std::string get_last_name(df::unit *unit) +{ + df::language_name name = unit->name; + std::string ret = ""; + for (int i = 0; i < 2; i++) + { + if (name.words[i] >= 0) + ret += *world->raws.language.translations[name.language]->words[name.words[i]]; + } + return Translation::capitalize(ret); +} + +// Couldn't figure out any other way to do this besides look for the dwarf name in +// the slab item description. +// Ideally, we could get the historical figure id from the slab but I didn't +// see anything like that in the item struct. This seems to work based on testing. +// Confirmed nicknames don't show up in engraved slab names, so this should probably work okay +bool engravedSlabItemExists(df::unit *unit, std::vector slabs) +{ + for (auto slab : slabs) + { + std::string desc = ""; + slab->getItemDescription(&desc, 0); + auto fullName = get_first_name(unit) + " " + get_last_name(unit); + if (desc.find(fullName) != std::string::npos) + return true; + } + + return false; +} + +// Queue up a single order to engrave the slab for the given unit +static void createSlabJob(df::unit *unit) +{ + auto next_id = world->manager_order_next_id++; + auto order = new df::manager_order(); + + order->id = next_id; + order->job_type = df::job_type::EngraveSlab; + order->hist_figure_id = unit->hist_figure_id; + order->amount_left = 1; + order->amount_total = 1; + world->manager_orders.push_back(order); +} + +static void checkslabs(color_ostream &out) +{ + // Get existing orders for slab engraving as map hist_figure_id -> order ID + std::map histToJob; + for (auto order : world->manager_orders) + { + if (order->job_type == df::job_type::EngraveSlab) + histToJob[order->hist_figure_id] = order->id; + } + + // Get list of engraved slab items on map + std::vector engravedSlabs; + std::copy_if(world->items.all.begin(), world->items.all.end(), + std::back_inserter(engravedSlabs), + [](df::item *item) + { return item->getType() == df::item_type::SLAB && item->getSlabEngravingType() == df::slab_engraving_type::Memorial; }); + + // Build list of ghosts + std::vector ghosts; + std::copy_if(world->units.all.begin(), world->units.all.end(), + std::back_inserter(ghosts), + [](const auto &unit) + { return unit->flags3.bits.ghostly; }); + + for (auto ghost : ghosts) + { + // Only create a job is the map has no existing jobs for that historical figure or no existing engraved slabs + if (histToJob.count(ghost->hist_figure_id) == 0 && !engravedSlabItemExists(ghost, engravedSlabs)) + { + createSlabJob(ghost); + auto fullName = get_first_name(ghost) + " " + get_last_name(ghost); + out.print("Added slab order for ghost %s\n", fullName.c_str()); + } + } +} + +static void do_cycle(color_ostream &out) +{ + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + checkslabs(out); +} From 1a3d2c0a2d5fcfb42bc5946762844ce4345e1336 Mon Sep 17 00:00:00 2001 From: John Cosker Date: Fri, 27 Jan 2023 20:08:33 -0500 Subject: [PATCH 04/40] Sort header includes per guidelines --- plugins/autoslab.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/autoslab.cpp b/plugins/autoslab.cpp index 4dcfcecff..5f7f25f6f 100644 --- a/plugins/autoslab.cpp +++ b/plugins/autoslab.cpp @@ -13,15 +13,15 @@ #include "PluginManager.h" #include "modules/Persistence.h" -#include "modules/World.h" #include "modules/Translation.h" +#include "modules/World.h" +#include "df/historical_figure.h" +#include "df/item.h" #include "df/manager_order.h" -#include "df/world.h" #include "df/plotinfost.h" #include "df/unit.h" -#include "df/item.h" -#include "df/historical_figure.h" +#include "df/world.h" using namespace DFHack; From 26f6820198eb42abf821c27723b7201eb5471f65 Mon Sep 17 00:00:00 2001 From: John Cosker Date: Sun, 29 Jan 2023 18:16:26 -0500 Subject: [PATCH 05/40] Address review comments, hopefully fix linux build --- docs/plugins/autoslab.rst | 4 ++-- plugins/autoslab.cpp | 35 ++++++----------------------------- 2 files changed, 8 insertions(+), 31 deletions(-) diff --git a/docs/plugins/autoslab.rst b/docs/plugins/autoslab.rst index e5db146cb..5e61e0938 100644 --- a/docs/plugins/autoslab.rst +++ b/docs/plugins/autoslab.rst @@ -2,8 +2,8 @@ autoslab ======== .. dfhack-tool:: - :summary: Automatically engrave slabs for ghostly citizens! - :tags: untested fort auto workorders + :summary: Automatically engrave slabs for ghostly citizens. + :tags: fort auto workorders :no-command: Automatically queue orders to engrave slabs of existing ghosts. Will only queue diff --git a/plugins/autoslab.cpp b/plugins/autoslab.cpp index 5f7f25f6f..4e9220cf2 100644 --- a/plugins/autoslab.cpp +++ b/plugins/autoslab.cpp @@ -5,7 +5,8 @@ * Enhancement idea: Automatically place the slab. This seems like a tricky problem but maybe solveable with named zones? * Might be made obsolete by people just using buildingplan to pre-place plans for slab? * Enhancement idea: Optionally enable autoengraving for pets. - * Enhancement idea: Try to get ahead of ghosts by autoengraving for dead dwarves with no remains. + * Enhancement idea: Try to get ahead of ghosts by autoengraving for dead dwarves with no remains, or dwarves + * whose remains are unreachable. */ #include "Core.h" @@ -25,8 +26,6 @@ using namespace DFHack; -static command_result autoslab(color_ostream &out, std::vector ¶meters); - DFHACK_PLUGIN("autoslab"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); @@ -70,19 +69,12 @@ static void set_config_bool(int index, bool value) static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle -static command_result do_command(color_ostream &out, std::vector ¶meters); static void do_cycle(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { DEBUG(status, out).print("initializing %s\n", plugin_name); - // provide a configuration interface for the plugin - commands.push_back(PluginCommand( - plugin_name, - "Automatically engrave slabs of ghostly citizens!", - do_command)); - return CR_OK; } @@ -123,7 +115,6 @@ DFhackCExport command_result plugin_load_data(color_ostream &out) DEBUG(status, out).print("no config found in this save; initializing\n"); config = World::AddPersistentData(CONFIG_KEY); set_config_bool(CONFIG_IS_ENABLED, is_enabled); - set_config_val(CONFIG_CYCLE_TICKS, 1200); } // we have to copy our enabled flag into the global plugin variable, but @@ -147,30 +138,16 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan return CR_OK; } +static const int32_t CYCLE_TICKS = 1200; + DFhackCExport command_result plugin_onupdate(color_ostream &out) { CoreSuspender suspend; - if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) + if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) do_cycle(out); return CR_OK; } -static command_result do_command(color_ostream &out, std::vector ¶meters) -{ - // be sure to suspend the core if any DF state is read or modified - CoreSuspender suspend; - - if (!Core::getInstance().isWorldLoaded()) - { - out.printerr("Cannot run %s without a loaded world.\n", plugin_name); - return CR_FAILURE; - } - - // TODO: decide what, if any configuration should be here - - return CR_OK; -} - // Name functions taken from manipulator.cpp static std::string get_first_name(df::unit *unit) { @@ -243,7 +220,7 @@ static void checkslabs(color_ostream &out) std::vector ghosts; std::copy_if(world->units.all.begin(), world->units.all.end(), std::back_inserter(ghosts), - [](const auto &unit) + [](const df::unit *unit) { return unit->flags3.bits.ghostly; }); for (auto ghost : ghosts) From 61f33258602ce027d8f3b70f4c8a530cd3c26f9c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 29 Jan 2023 17:35:02 -0800 Subject: [PATCH 06/40] update sample plugin code status -> config don't make cycle ticks configurable. nobody does that --- .../examples/persistent_per_save_example.cpp | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/plugins/examples/persistent_per_save_example.cpp b/plugins/examples/persistent_per_save_example.cpp index 5a7bf5224..55e4cb8b4 100644 --- a/plugins/examples/persistent_per_save_example.cpp +++ b/plugins/examples/persistent_per_save_example.cpp @@ -30,7 +30,7 @@ REQUIRE_GLOBAL(world); // logging levels can be dynamically controlled with the `debugfilter` command. namespace DFHack { // for configuration-related logging - DBG_DECLARE(persistent_per_save_example, status, DebugCategory::LINFO); + DBG_DECLARE(persistent_per_save_example, cycle, DebugCategory::LINFO); // for logging during the periodic scan DBG_DECLARE(persistent_per_save_example, cycle, DebugCategory::LINFO); } @@ -39,7 +39,7 @@ static const string CONFIG_KEY = string(plugin_name) + "/config"; static PersistentDataItem config; enum ConfigValues { CONFIG_IS_ENABLED = 0, - CONFIG_CYCLE_TICKS = 1, + CONFIG_SOMETHING_ELSE = 1, }; static int get_config_val(int index) { if (!config.isValid()) @@ -57,13 +57,14 @@ static void set_config_bool(int index, bool value) { set_config_val(index, value ? 1 : 0); } +static const int32_t CYCLE_TICKS = 1200; // one day static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle static command_result do_command(color_ostream &out, vector ¶meters); static void do_cycle(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(status,out).print("initializing %s\n", plugin_name); + DEBUG(cycle,out).print("initializing %s\n", plugin_name); // provide a configuration interface for the plugin commands.push_back(PluginCommand( @@ -82,11 +83,11 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (enable != is_enabled) { is_enabled = enable; - DEBUG(status,out).print("%s from the API; persisting\n", + DEBUG(cycle,out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); set_config_bool(CONFIG_IS_ENABLED, is_enabled); } else { - DEBUG(status,out).print("%s from the API, but already %s; no action\n", + DEBUG(cycle,out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } @@ -94,7 +95,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { } DFhackCExport command_result plugin_shutdown (color_ostream &out) { - DEBUG(status,out).print("shutting down %s\n", plugin_name); + DEBUG(cycle,out).print("shutting down %s\n", plugin_name); return CR_OK; } @@ -103,17 +104,17 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { config = World::GetPersistentData(CONFIG_KEY); if (!config.isValid()) { - DEBUG(status,out).print("no config found in this save; initializing\n"); + DEBUG(cycle,out).print("no config found in this save; initializing\n"); config = World::AddPersistentData(CONFIG_KEY); set_config_bool(CONFIG_IS_ENABLED, is_enabled); - set_config_val(CONFIG_CYCLE_TICKS, 6000); + set_config_val(CONFIG_SOMETHING_ELSE, 6000); } // we have to copy our enabled flag into the global plugin variable, but // all the other state we can directly read/modify from the persistent // data structure. is_enabled = get_config_bool(CONFIG_IS_ENABLED); - DEBUG(status,out).print("loading persisted enabled state: %s\n", + DEBUG(cycle,out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false"); return CR_OK; } @@ -121,7 +122,7 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (event == DFHack::SC_WORLD_UNLOADED) { if (is_enabled) { - DEBUG(status,out).print("world unloaded; disabling %s\n", + DEBUG(cycle,out).print("world unloaded; disabling %s\n", plugin_name); is_enabled = false; } @@ -130,7 +131,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) + if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) do_cycle(out); return CR_OK; } @@ -162,5 +163,5 @@ static void do_cycle(color_ostream &out) { DEBUG(cycle,out).print("running %s cycle\n", plugin_name); - // TODO: logic that runs every get_config_val(CONFIG_CYCLE_TICKS) ticks + // TODO: logic that runs every CYCLE_TICKS ticks } From 2b3160b1b945bf5bd6113d47bf589ad0c3f838b7 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 29 Jan 2023 17:39:48 -0800 Subject: [PATCH 07/40] fix typos --- .../examples/persistent_per_save_example.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/examples/persistent_per_save_example.cpp b/plugins/examples/persistent_per_save_example.cpp index 55e4cb8b4..603df1a3c 100644 --- a/plugins/examples/persistent_per_save_example.cpp +++ b/plugins/examples/persistent_per_save_example.cpp @@ -28,9 +28,11 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); // logging levels can be dynamically controlled with the `debugfilter` command. +// the names "config" and "cycle" are arbitrary and are just used to categorize +// your log messages. namespace DFHack { // for configuration-related logging - DBG_DECLARE(persistent_per_save_example, cycle, DebugCategory::LINFO); + DBG_DECLARE(persistent_per_save_example, config, DebugCategory::LINFO); // for logging during the periodic scan DBG_DECLARE(persistent_per_save_example, cycle, DebugCategory::LINFO); } @@ -64,7 +66,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) static void do_cycle(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - DEBUG(cycle,out).print("initializing %s\n", plugin_name); + DEBUG(config,out).print("initializing %s\n", plugin_name); // provide a configuration interface for the plugin commands.push_back(PluginCommand( @@ -83,11 +85,11 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (enable != is_enabled) { is_enabled = enable; - DEBUG(cycle,out).print("%s from the API; persisting\n", + DEBUG(config,out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); set_config_bool(CONFIG_IS_ENABLED, is_enabled); } else { - DEBUG(cycle,out).print("%s from the API, but already %s; no action\n", + DEBUG(config,out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled"); } @@ -95,7 +97,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { } DFhackCExport command_result plugin_shutdown (color_ostream &out) { - DEBUG(cycle,out).print("shutting down %s\n", plugin_name); + DEBUG(config,out).print("shutting down %s\n", plugin_name); return CR_OK; } @@ -104,7 +106,7 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { config = World::GetPersistentData(CONFIG_KEY); if (!config.isValid()) { - DEBUG(cycle,out).print("no config found in this save; initializing\n"); + DEBUG(config,out).print("no config found in this save; initializing\n"); config = World::AddPersistentData(CONFIG_KEY); set_config_bool(CONFIG_IS_ENABLED, is_enabled); set_config_val(CONFIG_SOMETHING_ELSE, 6000); @@ -114,7 +116,7 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { // all the other state we can directly read/modify from the persistent // data structure. is_enabled = get_config_bool(CONFIG_IS_ENABLED); - DEBUG(cycle,out).print("loading persisted enabled state: %s\n", + DEBUG(config,out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false"); return CR_OK; } @@ -122,7 +124,7 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { if (event == DFHack::SC_WORLD_UNLOADED) { if (is_enabled) { - DEBUG(cycle,out).print("world unloaded; disabling %s\n", + DEBUG(config,out).print("world unloaded; disabling %s\n", plugin_name); is_enabled = false; } From 8774d3191f76b9f50a63851482720cedb8ced46d Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 29 Jan 2023 17:59:16 -0800 Subject: [PATCH 08/40] sync tags from spreadsheet --- docs/plugins/dig.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst index afe1fac17..a10db97df 100644 --- a/docs/plugins/dig.rst +++ b/docs/plugins/dig.rst @@ -6,7 +6,7 @@ dig .. dfhack-tool:: :summary: Provides commands for designating tiles for digging. - :tags: untested fort design productivity map + :tags: fort design productivity map :no-command: .. dfhack-command:: digv From 57c53409302fa12bcc8aed7561c836274b6971da Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 30 Jan 2023 02:14:05 +0000 Subject: [PATCH 09/40] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index bf914ddd8..fd293eab7 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit bf914ddd8c58011368f72172f9d158d0043c61fc +Subproject commit fd293eab744d4c01db66960fc9e7a858f0b6db1e From 4c17d79198aeaf84b496f79d120c50da13f54d03 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 30 Jan 2023 02:28:17 +0000 Subject: [PATCH 10/40] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index a3b6cd550..518c1a431 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a3b6cd5507753207ce28fe0872c98d6434d58a90 +Subproject commit 518c1a431dcfa0ca23110d94223973853d640a11 From 3f9f7855890add716034a5fc576126d3d8262db7 Mon Sep 17 00:00:00 2001 From: John Cosker Date: Sun, 29 Jan 2023 21:34:47 -0500 Subject: [PATCH 11/40] Update doc and remove unused enum --- docs/plugins/autoslab.rst | 5 ++++- plugins/autoslab.cpp | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/plugins/autoslab.rst b/docs/plugins/autoslab.rst index 5e61e0938..2f14f590d 100644 --- a/docs/plugins/autoslab.rst +++ b/docs/plugins/autoslab.rst @@ -8,7 +8,10 @@ autoslab Automatically queue orders to engrave slabs of existing ghosts. Will only queue an order if there is no existing slab with that unit's memorial engraved and -there is not already an existing work order to engrave a slab for that unit +there is not already an existing work order to engrave a slab for that unit. +Make sure you have spare slabs on hand for engraving! If you run +`orders import library/rockstock `, you'll be sure to always have +some slabs in stock. Usage ----- diff --git a/plugins/autoslab.cpp b/plugins/autoslab.cpp index 4e9220cf2..51c92b0f7 100644 --- a/plugins/autoslab.cpp +++ b/plugins/autoslab.cpp @@ -45,7 +45,6 @@ static PersistentDataItem config; enum ConfigValues { CONFIG_IS_ENABLED = 0, - CONFIG_CYCLE_TICKS = 1, }; static int get_config_val(int index) { From f1c173863c2bf2b4989b2dbc3d6555cd345d3fec Mon Sep 17 00:00:00 2001 From: John Cosker Date: Sun, 29 Jan 2023 21:36:49 -0500 Subject: [PATCH 12/40] Remove trailing whitespace --- docs/plugins/autoslab.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/autoslab.rst b/docs/plugins/autoslab.rst index 2f14f590d..a947f7bbe 100644 --- a/docs/plugins/autoslab.rst +++ b/docs/plugins/autoslab.rst @@ -11,7 +11,7 @@ an order if there is no existing slab with that unit's memorial engraved and there is not already an existing work order to engrave a slab for that unit. Make sure you have spare slabs on hand for engraving! If you run `orders import library/rockstock `, you'll be sure to always have -some slabs in stock. +some slabs in stock. Usage ----- From 40e69bfa3f19b53ffa950fbd8044fe0f5001b0ac Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 29 Jan 2023 20:37:01 -0600 Subject: [PATCH 13/40] remove autohauler autohauler just doesn't make sense in v50 --- docs/changelog.txt | 1 + plugins/autolabor/CMakeLists.txt | 1 - plugins/autolabor/autohauler.cpp | 727 ------------------------------- 3 files changed, 1 insertion(+), 728 deletions(-) delete mode 100644 plugins/autolabor/autohauler.cpp diff --git a/docs/changelog.txt b/docs/changelog.txt index 1174508e2..0f2159c38 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -66,6 +66,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``dfhack.units.getCitizens()``: gets a list of citizens ## Removed +- `autohauler`: no plans to port to v50, as it just doesn't make sense with the new work detail system # 50.05-alpha2 diff --git a/plugins/autolabor/CMakeLists.txt b/plugins/autolabor/CMakeLists.txt index c73e2ad6d..554a02ecf 100644 --- a/plugins/autolabor/CMakeLists.txt +++ b/plugins/autolabor/CMakeLists.txt @@ -12,5 +12,4 @@ list(APPEND COMMON_SRCS ${COMMON_HDRS}) dfhack_plugin(labormanager labormanager.cpp joblabormapper.cpp ${COMMON_SRCS}) -dfhack_plugin(autohauler autohauler.cpp ${COMMON_SRCS}) dfhack_plugin(autolabor autolabor.cpp ${COMMON_SRCS}) diff --git a/plugins/autolabor/autohauler.cpp b/plugins/autolabor/autohauler.cpp deleted file mode 100644 index 3bb2534f8..000000000 --- a/plugins/autolabor/autohauler.cpp +++ /dev/null @@ -1,727 +0,0 @@ -#include "Core.h" -#include -#include -#include -#include - -#include -#include - -#include "modules/Units.h" -#include "modules/World.h" - -// DF data structure definition headers -#include "DataDefs.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "modules/MapCache.h" -#include "modules/Items.h" -#include "modules/Units.h" - -#include "laborstatemap.h" - -using namespace DFHack; -using namespace df::enums; - -DFHACK_PLUGIN("autohauler"); -REQUIRE_GLOBAL(plotinfo); -REQUIRE_GLOBAL(world); - -#define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) - -/* - * Autohauler module for dfhack - * Fork of autolabor, DFHack version 0.40.24-r2 - * - * Rather than the all-of-the-above means of autolabor, autohauler will instead - * only manage hauling labors and leave skilled labors entirely to the user, who - * will probably use Dwarf Therapist to do so. - * Idle dwarves will be assigned the hauling labors; everyone else (including - * those currently hauling) will have the hauling labors removed. This is to - * encourage every dwarf to do their assigned skilled labors whenever possible, - * but resort to hauling when those jobs are not available. This also implies - * that the user will have a very tight skill assignment, with most skilled - * labors only being assigned to just one dwarf, no dwarf having more than two - * active skilled labors, and almost every non-military dwarf having at least - * one skilled labor assigned. - * Autohauler allows skills to be flagged as to prevent hauling labors from - * being assigned when the skill is present. By default this is the unused - * ALCHEMIST labor but can be changed by the user. - * It is noteworthy that, as stated in autolabor.cpp, "for almost all labors, - * once a dwarf begins a job it will finish that job even if the associated - * labor is removed." This is why we can remove hauling labors by default to try - * to force dwarves to do "real" jobs whenever they can. - * This is a standalone plugin. However, it would be wise to delete - * autolabor.plug.dll as this plugin is mutually exclusive with it. - */ - -DFHACK_PLUGIN_IS_ENABLED(enable_autohauler); - -namespace DFHack { - DBG_DECLARE(autohauler, cycle, DebugCategory::LINFO); -} - -static std::vector state_count(NUM_STATE); - -const static int DEFAULT_FRAME_SKIP = 30; - -static PersistentDataItem config; - -command_result autohauler (color_ostream &out, std::vector & parameters); - -static int frame_skip; - -static bool isOptionEnabled(unsigned flag) -{ - return config.isValid() && (config.ival(0) & flag) != 0; -} - -enum ConfigFlags { - CF_ENABLED = 1, -}; - -static void setOptionEnabled(ConfigFlags flag, bool on) -{ - if (!config.isValid()) - return; - - if (on) - config.ival(0) |= flag; - else - config.ival(0) &= ~flag; -} - -enum labor_mode { - ALLOW, - HAULERS, - FORBID -}; - -struct labor_info -{ - PersistentDataItem config; - - int active_dwarfs; - - labor_mode mode() { return (labor_mode) config.ival(0); } - - void set_mode(labor_mode mode) { config.ival(0) = mode; } - - void set_config(PersistentDataItem a) { config = a; } - - }; - -struct labor_default -{ - labor_mode mode; - int active_dwarfs; -}; - -static std::vector labor_infos; - -static const struct labor_default default_labor_infos[] = { - /* MINE */ {ALLOW, 0}, - /* HAUL_STONE */ {HAULERS, 0}, - /* HAUL_WOOD */ {HAULERS, 0}, - /* HAUL_BODY */ {HAULERS, 0}, - /* HAUL_FOOD */ {HAULERS, 0}, - /* HAUL_REFUSE */ {HAULERS, 0}, - /* HAUL_ITEM */ {HAULERS, 0}, - /* HAUL_FURNITURE */ {HAULERS, 0}, - /* HAUL_ANIMAL */ {HAULERS, 0}, - /* CLEAN */ {HAULERS, 0}, - /* CUTWOOD */ {ALLOW, 0}, - /* CARPENTER */ {ALLOW, 0}, - /* DETAIL */ {ALLOW, 0}, - /* MASON */ {ALLOW, 0}, - /* ARCHITECT */ {ALLOW, 0}, - /* ANIMALTRAIN */ {ALLOW, 0}, - /* ANIMALCARE */ {ALLOW, 0}, - /* DIAGNOSE */ {ALLOW, 0}, - /* SURGERY */ {ALLOW, 0}, - /* BONE_SETTING */ {ALLOW, 0}, - /* SUTURING */ {ALLOW, 0}, - /* DRESSING_WOUNDS */ {ALLOW, 0}, - /* FEED_WATER_CIVILIANS */ {HAULERS, 0}, // This could also be ALLOW - /* RECOVER_WOUNDED */ {HAULERS, 0}, - /* BUTCHER */ {ALLOW, 0}, - /* TRAPPER */ {ALLOW, 0}, - /* DISSECT_VERMIN */ {ALLOW, 0}, - /* LEATHER */ {ALLOW, 0}, - /* TANNER */ {ALLOW, 0}, - /* BREWER */ {ALLOW, 0}, - /* ALCHEMIST */ {FORBID, 0}, - /* SOAP_MAKER */ {ALLOW, 0}, - /* WEAVER */ {ALLOW, 0}, - /* CLOTHESMAKER */ {ALLOW, 0}, - /* MILLER */ {ALLOW, 0}, - /* PROCESS_PLANT */ {ALLOW, 0}, - /* MAKE_CHEESE */ {ALLOW, 0}, - /* MILK */ {ALLOW, 0}, - /* COOK */ {ALLOW, 0}, - /* PLANT */ {ALLOW, 0}, - /* HERBALIST */ {ALLOW, 0}, - /* FISH */ {ALLOW, 0}, - /* CLEAN_FISH */ {ALLOW, 0}, - /* DISSECT_FISH */ {ALLOW, 0}, - /* HUNT */ {ALLOW, 0}, - /* SMELT */ {ALLOW, 0}, - /* FORGE_WEAPON */ {ALLOW, 0}, - /* FORGE_ARMOR */ {ALLOW, 0}, - /* FORGE_FURNITURE */ {ALLOW, 0}, - /* METAL_CRAFT */ {ALLOW, 0}, - /* CUT_GEM */ {ALLOW, 0}, - /* ENCRUST_GEM */ {ALLOW, 0}, - /* WOOD_CRAFT */ {ALLOW, 0}, - /* STONE_CRAFT */ {ALLOW, 0}, - /* BONE_CARVE */ {ALLOW, 0}, - /* GLASSMAKER */ {ALLOW, 0}, - /* EXTRACT_STRAND */ {ALLOW, 0}, - /* SIEGECRAFT */ {ALLOW, 0}, - /* SIEGEOPERATE */ {ALLOW, 0}, - /* BOWYER */ {ALLOW, 0}, - /* MECHANIC */ {ALLOW, 0}, - /* POTASH_MAKING */ {ALLOW, 0}, - /* LYE_MAKING */ {ALLOW, 0}, - /* DYER */ {ALLOW, 0}, - /* BURN_WOOD */ {ALLOW, 0}, - /* OPERATE_PUMP */ {ALLOW, 0}, - /* SHEARER */ {ALLOW, 0}, - /* SPINNER */ {ALLOW, 0}, - /* POTTERY */ {ALLOW, 0}, - /* GLAZING */ {ALLOW, 0}, - /* PRESSING */ {ALLOW, 0}, - /* BEEKEEPING */ {ALLOW, 0}, - /* WAX_WORKING */ {ALLOW, 0}, - /* HANDLE_VEHICLES */ {HAULERS, 0}, - /* HAUL_TRADE */ {HAULERS, 0}, - /* PULL_LEVER */ {HAULERS, 0}, - /* REMOVE_CONSTRUCTION */ {HAULERS, 0}, - /* HAUL_WATER */ {HAULERS, 0}, - /* GELD */ {ALLOW, 0}, - /* BUILD_ROAD */ {HAULERS, 0}, - /* BUILD_CONSTRUCTION */ {HAULERS, 0}, - /* PAPERMAKING */ {ALLOW, 0}, - /* BOOKBINDING */ {ALLOW, 0} -}; - -struct dwarf_info_t -{ - dwarf_state state; - - bool haul_exempt; -}; - -static void cleanup_state() -{ - enable_autohauler = false; - labor_infos.clear(); -} - -static void reset_labor(df::unit_labor labor) -{ - labor_infos[labor].set_mode(default_labor_infos[labor].mode); -} - -static void enable_alchemist(color_ostream &out) -{ - if (!Units::setLaborValidity(unit_labor::ALCHEMIST, true)) - { - // informational only; this is a non-fatal error - out.printerr("%s: Could not flag Alchemist as a valid skill; Alchemist will not" - " be settable from DF or DFHack labor management screens.\n", plugin_name); - } -} - -static void init_state(color_ostream &out) -{ - config = World::GetPersistentData("autohauler/config"); - - if (config.isValid() && config.ival(0) == -1) - config.ival(0) = 0; - - enable_autohauler = isOptionEnabled(CF_ENABLED); - - if (!enable_autohauler) - return; - - auto cfg_frameskip = World::GetPersistentData("autohauler/frameskip"); - if (cfg_frameskip.isValid()) - { - frame_skip = cfg_frameskip.ival(0); - } - else - { - cfg_frameskip = World::AddPersistentData("autohauler/frameskip"); - cfg_frameskip.ival(0) = DEFAULT_FRAME_SKIP; - frame_skip = cfg_frameskip.ival(0); - } - labor_infos.resize(ARRAY_COUNT(default_labor_infos)); - - std::vector items; - World::GetPersistentData(&items, "autohauler/labors/", true); - - - for (auto& p : items) - { - std::string key = p.key(); - df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autohauler/labors/")).c_str()); - if (labor >= 0 && size_t(labor) < labor_infos.size()) - { - labor_infos[labor].set_config(p); - labor_infos[labor].active_dwarfs = 0; - } - } - - // Add default labors for those not in save - for (size_t i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { - if (labor_infos[i].config.isValid()) - continue; - - std::stringstream name; - name << "autohauler/labors/" << i; - - labor_infos[i].set_config(World::AddPersistentData(name.str())); - - labor_infos[i].active_dwarfs = 0; - reset_labor((df::unit_labor) i); - } - - enable_alchemist(out); -} - -static void enable_plugin(color_ostream &out) -{ - if (!config.isValid()) - { - config = World::AddPersistentData("autohauler/config"); - config.ival(0) = 0; - } - - setOptionEnabled(CF_ENABLED, true); - enable_autohauler = true; - out << "Enabling the plugin." << std::endl; - - cleanup_state(); - init_state(out); -} - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - if(ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1) - { - out.printerr("autohauler: labor size mismatch\n"); - return CR_FAILURE; - } - - commands.push_back(PluginCommand( - "autohauler", - "Automatically manage hauling labors.", - autohauler)); - - init_state(out); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - cleanup_state(); - - return CR_OK; -} - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_MAP_LOADED: - cleanup_state(); - init_state(out); - break; - case SC_MAP_UNLOADED: - cleanup_state(); - break; - default: - break; - } - - return CR_OK; -} - -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) -{ - static int step_count = 0; - if(!world || !world->map.block_index || !enable_autohauler) - { - return CR_OK; - } - - if (++step_count < frame_skip) - return CR_OK; - step_count = 0; - - std::vector dwarfs; - - for (auto& cre : world->units.active) - { - if (Units::isCitizen(cre)) - { - dwarfs.push_back(cre); - } - } - - int n_dwarfs = dwarfs.size(); - - if (n_dwarfs == 0) - return CR_OK; - - std::vector dwarf_info(n_dwarfs); - - state_count.clear(); - state_count.resize(NUM_STATE); - - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - /* Before determining how to handle employment status, handle - * hauling exemptions first */ - - // Default deny condition of on break for later else-if series - bool is_migrant = false; - - // Scan every labor. If a labor that disallows hauling is present - // for the dwarf, the dwarf is hauling exempt - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (!(labor == unit_labor::NONE)) - { - bool test1 = labor_infos[labor].mode() == FORBID; - bool test2 = dwarfs[dwarf]->status.labors[labor]; - - if(test1 && test2) dwarf_info[dwarf].haul_exempt = true; - } - } - - // Scan a dwarf's miscellaneous traits for on break or migrant status. - // If either of these are present, disable hauling because we want them - // to try to find real jobs first - auto v = dwarfs[dwarf]->status.misc_traits; - auto test_migrant = [](df::unit_misc_trait* t) { return t->id == misc_trait_type::Migrant; }; - is_migrant = std::find_if(v.begin(), v.end(), test_migrant ) != v.end(); - - /* Now determine a dwarf's employment status and decide whether - * to assign hauling */ - - // I don't think you can set the labors for babies and children, but let's - // ignore them anyway - if (Units::isBaby(dwarfs[dwarf]) || Units::isChild(dwarfs[dwarf])) - { - dwarf_info[dwarf].state = CHILD; - } - // Account for any hauling exemptions here - else if (dwarf_info[dwarf].haul_exempt) - { - dwarf_info[dwarf].state = BUSY; - } - // Account for the military - else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession)) - dwarf_info[dwarf].state = MILITARY; - // Account for incoming migrants - else if (is_migrant) - { - dwarf_info[dwarf].state = OTHER; - } - else if (dwarfs[dwarf]->job.current_job == NULL) - { - dwarf_info[dwarf].state = IDLE; - } - else - { - int job = dwarfs[dwarf]->job.current_job->job_type; - if (job >= 0 && size_t(job) < ARRAY_COUNT(dwarf_states)) - dwarf_info[dwarf].state = dwarf_states[job]; - else - { - WARN(cycle, out).print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); - dwarf_info[dwarf].state = OTHER; - } - } - - state_count[dwarf_info[dwarf].state]++; - - TRACE(cycle, out).print("Dwarf %i \"%s\": state %s\n", - dwarf, dwarfs[dwarf]->name.first_name.c_str(), state_names[dwarf_info[dwarf].state]); - } - - // This is a vector of all the labors - std::vector labors; - - // For every labor... - FOR_ENUM_ITEMS(unit_labor, labor) - { - // Ignore all nonexistent labors - if (labor == unit_labor::NONE) - continue; - - // Set number of active dwarves for this job to zero - labor_infos[labor].active_dwarfs = 0; - - // And add the labor to the aforementioned vector of labors - labors.push_back(labor); - } - - // This is a different algorithm than Autolabor. Instead, the intent is to - // have "real" jobs filled first, then if nothing is available the dwarf - // instead resorts to hauling. - - // IDLE - Enable hauling - // BUSY - Disable hauling - // OTHER - Enable hauling - // MILITARY - Enable hauling - - // There was no reason to put potential haulers in an array. All of them are - // covered in the following for loop. - - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - if (labor_infos[labor].mode() != HAULERS) - continue; - - for(size_t dwarf = 0; dwarf < dwarfs.size(); dwarf++) - { - if (!Units::isValidLabor(dwarfs[dwarf], labor)) - continue; - - // Set hauling labors based on employment states - if(dwarf_info[dwarf].state == IDLE) { - dwarfs[dwarf]->status.labors[labor] = true; - } - else if(dwarf_info[dwarf].state == MILITARY) { - dwarfs[dwarf]->status.labors[labor] = true; - } - else if(dwarf_info[dwarf].state == OTHER) { - dwarfs[dwarf]->status.labors[labor] = true; - } - else if(dwarf_info[dwarf].state == BUSY) { - dwarfs[dwarf]->status.labors[labor] = false; - } - // If at the end of this the dwarf has the hauling labor, increment the - // counter - if(dwarfs[dwarf]->status.labors[labor]) - { - labor_infos[labor].active_dwarfs++; - } - // CHILD ignored - } - } - return CR_OK; -} - -void print_labor (df::unit_labor labor, color_ostream &out) -{ - std::string labor_name = ENUM_KEY_STR(unit_labor, labor); - out << labor_name << ": "; - for (int i = 0; i < 20 - (int)labor_name.length(); i++) - out << ' '; - if (labor_infos[labor].mode() == ALLOW) out << "allow" << std::endl; - else if(labor_infos[labor].mode() == FORBID) out << "forbid" << std::endl; - else if(labor_infos[labor].mode() == HAULERS) - { - out << "haulers, currently " << labor_infos[labor].active_dwarfs << " dwarfs" << std::endl; - } - else - { - out << "Warning: Invalid labor mode!" << std::endl; - } -} - -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) -{ - if (!Core::getInstance().isWorldLoaded()) { - out.printerr("World is not loaded: please load a game first.\n"); - return CR_FAILURE; - } - - if (enable && !enable_autohauler) - { - enable_plugin(out); - } - else if(!enable && enable_autohauler) - { - enable_autohauler = false; - setOptionEnabled(CF_ENABLED, false); - - out << "Autohauler is disabled." << std::endl; - } - - return CR_OK; -} - -command_result autohauler (color_ostream &out, std::vector & parameters) -{ - CoreSuspender suspend; - - if (!Core::getInstance().isWorldLoaded()) { - out.printerr("World is not loaded: please load a game first.\n"); - return CR_FAILURE; - } - - if (parameters.size() == 1 && - (parameters[0] == "0" || parameters[0] == "enable" || - parameters[0] == "1" || parameters[0] == "disable")) - { - bool enable = (parameters[0] == "1" || parameters[0] == "enable"); - - return plugin_enable(out, enable); - } - else if (parameters.size() == 2 && parameters[0] == "frameskip") - { - auto cfg_frameskip = World::GetPersistentData("autohauler/frameskip"); - if(cfg_frameskip.isValid()) - { - int newValue = atoi(parameters[1].c_str()); - cfg_frameskip.ival(0) = newValue; - out << "Setting frame skip to " << newValue << std::endl; - frame_skip = cfg_frameskip.ival(0); - return CR_OK; - } - else - { - out << "Warning! No persistent data for frame skip!" << std::endl; - return CR_OK; - } - } - else if (parameters.size() >= 2 && parameters.size() <= 4) - { - if (!enable_autohauler) - { - out << "Error: The plugin is not enabled." << std::endl; - return CR_FAILURE; - } - - df::unit_labor labor = unit_labor::NONE; - - FOR_ENUM_ITEMS(unit_labor, test_labor) - { - if (parameters[0] == ENUM_KEY_STR(unit_labor, test_labor)) - labor = test_labor; - } - - if (labor == unit_labor::NONE) - { - out.printerr("Could not find labor %s.\n", parameters[0].c_str()); - return CR_WRONG_USAGE; - } - - if (parameters[1] == "haulers") - { - labor_infos[labor].set_mode(HAULERS); - print_labor(labor, out); - return CR_OK; - } - if (parameters[1] == "allow") - { - labor_infos[labor].set_mode(ALLOW); - print_labor(labor, out); - return CR_OK; - } - if (parameters[1] == "forbid") - { - labor_infos[labor].set_mode(FORBID); - print_labor(labor, out); - return CR_OK; - } - if (parameters[1] == "reset") - { - reset_labor(labor); - print_labor(labor, out); - return CR_OK; - } - - print_labor(labor, out); - - return CR_OK; - } - else if (parameters.size() == 1 && parameters[0] == "reset-all") - { - if (!enable_autohauler) - { - out << "Error: The plugin is not enabled." << std::endl; - return CR_FAILURE; - } - - for (size_t i = 0; i < labor_infos.size(); i++) - { - reset_labor((df::unit_labor) i); - } - out << "All labors reset." << std::endl; - return CR_OK; - } - else if (parameters.size() == 1 && (parameters[0] == "list" || parameters[0] == "status")) - { - if (!enable_autohauler) - { - out << "Error: The plugin is not enabled." << std::endl; - return CR_FAILURE; - } - - bool need_comma = false; - for (int i = 0; i < NUM_STATE; i++) - { - if (state_count[i] == 0) - continue; - if (need_comma) - out << ", "; - out << state_count[i] << ' ' << state_names[i]; - need_comma = true; - } - out << std::endl; - - out << "Autohauler is running every " << frame_skip << " frames." << std::endl; - - if (parameters[0] == "list") - { - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - print_labor(labor, out); - } - } - - return CR_OK; - } - else - { - out.print("Automatically assigns hauling labors to dwarves.\n" - "Activate with 'enable autohauler', deactivate with 'disable autohauler'.\n" - "Current state: %d.\n", enable_autohauler); - - return CR_OK; - } -} From 93bfbde8d7e1d32468a82185cbf88b1703f0f2bc Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sun, 29 Jan 2023 21:54:02 -0600 Subject: [PATCH 14/40] doc updates for removal of autohauler --- docs/about/Removed.rst | 8 +++++ docs/plugins/autohauler.rst | 58 ------------------------------------- 2 files changed, 8 insertions(+), 58 deletions(-) delete mode 100644 docs/plugins/autohauler.rst diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index d30a00f00..5309372d8 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -10,6 +10,14 @@ work (e.g. links from the `changelog`). :local: :depth: 1 +.. _autohauler: + +autohauler +========== +An automated labor management tool that only addressed hauling labors, leaving the assignment +of skilled labors entirely up to the player. Fundamentally incompatible with the work detail +system of labor management in v50 of Dwarf Fortress. + .. _command-prompt: command-prompt diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst deleted file mode 100644 index a40d50f34..000000000 --- a/docs/plugins/autohauler.rst +++ /dev/null @@ -1,58 +0,0 @@ -autohauler -========== - -.. dfhack-tool:: - :summary: Automatically manage hauling labors. - :tags: untested fort auto labors - -Similar to `autolabor`, but instead of managing all labors, autohauler only -addresses hauling labors, leaving the assignment of skilled labors entirely up -to you. You can use the in-game `manipulator` UI or an external tool like Dwarf -Therapist to do so. - -Idle dwarves who are not on active military duty will be assigned the hauling -labors; everyone else (including those currently hauling) will have the hauling -labors removed. This is to encourage every dwarf to do their assigned skilled -labors whenever possible, but resort to hauling when those jobs are not -available. This also implies that the user will have a very tight skill -assignment, with most skilled labors only being assigned to just a few dwarves -and almost every non-military dwarf having at least one skilled labor assigned. - -Autohauler allows a skill to be used as a flag to exempt a dwarf from -autohauler's effects. By default, this is the unused ALCHEMIST labor, but it -can be changed by the user. - -Autohauler uses DFHack's `debug` functionality to display information about the changes it makes. The amount of -information displayed can be controlled through appropriate use of the ``debugfilter`` command. - -Usage ------ - -``enable autohauler`` - Start managing hauling labors. This is normally all you need to do. - Autohauler works well on default settings. -``autohauler status`` - Show autohauler status and status of fort dwarves. -``autohauler haulers`` - Set whether a particular labor should be assigned to haulers. -``autohauler allow|forbid`` - Set whether a particular labor should mark a dwarf as exempt from hauling. - By default, only the ``ALCHEMIST`` labor is set to ``forbid``. -``autohauler reset-all| reset`` - Reset a particular labor (or all labors) to their default - haulers/allow/forbid state. -``autohauler list`` - Show the active configuration for all labors. -``autohauler frameskip `` - Set the number of frames between runs of autohauler. - -Examples --------- - -``autohauler HAUL_STONE haulers`` - Set stone hauling as a hauling labor (this is already the default). -``autohauler RECOVER_WOUNDED allow`` - Allow the "Recover wounded" labor to be manually assigned by the player. By - default it is automatically given to haulers. -``autohauler MINE forbid`` - Don't assign hauling labors to dwarves with the Mining labor enabled. From 282da701bbd5c54f7104255842ef25a7c3ed301d Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 29 Jan 2023 23:58:01 -0500 Subject: [PATCH 15/40] changelog: add #2689, fix a few typos, add a link --- docs/changelog.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 8bbd3a412..74fc26086 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,15 +40,16 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: -@ DF screens can no longer get "stuck" on transitions when DFHack tool windows are visible. Instead, those DF screens are force-paused while DFHack windows are visible so the player can close them first and not corrupt the screen sequence. The "force pause" indicator will appear on these DFHack windows to indicate what is happening. -@ ``Screen``: allow `gui/launcher` and `gui/quickcmd` to launch themselves without hanging the game -@ Fix issues with clicks "passing through" some DFHack window elements, like scrollbars -- `getplants`: tree are now designated correctly +- `getplants`: trees are now designated correctly +-@ Sample orders: fix orders that create bags ## Misc Improvements -- A new cross-compile build script was added for building the Windows files from a Linux Docker builder (see the Compile instructions in the docs) +- A new cross-compile build script was added for building DFHack for Windows from a Linux Docker builder (see the `compile` instructions in the docs) - You can now configure whether DFHack tool windows should pause the game by default - `hotkeys`: clicking on the DFHack logo no longer closes the popup menu - `gui/launcher`: sped up initialization time for faster load of the UI - `orders`: orders plugin functionality is now offered via an overlay widget when the manager orders screen is open -- `gui/quickcmd`: now has it's own global keybinding for your convenience: Ctrl-Shift-A +- `gui/quickcmd`: now has its own global keybinding for your convenience: Ctrl-Shift-A - Many DFHack windows can now be unfocused by clicking somewhere not over the tool window. This has the same effect as pinning previously did, but without the extra clicking. - `overlay`: overlay widgets can now specify a default enabled state if they are not already set in the player's overlay config file - `getplants`: ID values will now be accepted regardless of case From ab4c7668171005234206c6908e58b86db7faca05 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 30 Jan 2023 08:48:45 -0800 Subject: [PATCH 16/40] Revert "allow tile list icons to be rendered properly" This reverts commit 508777897be527c1e7c64afe47b62bfcfc14503f. the fix was incorrect. the icon should be set to the pen when it is not a string, the icon_pen is only for when it is a string --- library/lua/gui/widgets.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 653107179..0696459cd 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -1713,7 +1713,7 @@ function List:onRenderBody(dc) local function paint_icon(icon, obj) if type(icon) ~= 'string' then - dc:tile(nil,icon) + dc:char(nil,icon) else if current then dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen) From 2cbf9123f2725ed8806fc16d5cc4fe88a8d65b4a Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 30 Jan 2023 22:23:13 +0000 Subject: [PATCH 17/40] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index fd293eab7..9d42d260e 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit fd293eab744d4c01db66960fc9e7a858f0b6db1e +Subproject commit 9d42d260e7baa5b4962582bc93f87ad37da39827 From 0c92317cce75d8d339832174d8fc411dd79502bb Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 30 Jan 2023 13:30:15 -0800 Subject: [PATCH 18/40] give gui/control-panel a global hotkey --- data/init/dfhack.keybindings.init | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/init/dfhack.keybindings.init b/data/init/dfhack.keybindings.init index c1a4191ef..a793e3248 100644 --- a/data/init/dfhack.keybindings.init +++ b/data/init/dfhack.keybindings.init @@ -16,6 +16,9 @@ keybinding add Ctrl-Shift-P "gui/launcher --minimal" # show hotkey popup menu keybinding add Ctrl-Shift-C hotkeys +# control panel +keybinding add Shift-` gui/control-panel + # on-screen keyboard keybinding add Ctrl-Shift-K gui/cp437-table From 3805925c02e5a5f877365609d5d93cc446eb715c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 30 Jan 2023 13:30:34 -0800 Subject: [PATCH 19/40] update quickstart guide with control panel info --- docs/Quickstart.rst | 111 +++++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 49 deletions(-) diff --git a/docs/Quickstart.rst b/docs/Quickstart.rst index 81415e3a9..95b1fb0a1 100644 --- a/docs/Quickstart.rst +++ b/docs/Quickstart.rst @@ -46,53 +46,66 @@ Here are some common tasks people use DFHack tools to accomplish: your mining efforts Some tools are one-shot commands. For example, you can run `unforbid all ` -to claim all items on the map after a messy siege. +to claim all (reachable) items on the map after a messy siege. Other tools must be `enabled ` and then they will run in the background. For example, `enable seedwatch ` will start monitoring your stocks of seeds and prevent your chefs from cooking seeds that you need for planting. Tools that are enabled in the context of a fort will save their state with that -fort, and the will remember that they are enabled the next time you load your save. +fort, and they will remember that they are enabled the next time you load your save. A third class of tools add information to the screen or provide new integrated functionality via the DFHack `overlay` framework. For example, the `unsuspend` tool, in addition to its basic function of unsuspending all building construction jobs, can also overlay a marker on suspended buildings to indicate that they are suspended (and will use different markers to tell you whether this is a problem). -These overlays can be enabled and configured with the `gui/overlay` interface. How can I figure out which commands to run? ------------------------------------------- -There are several ways to scan DFHack tools and find the one you need right now. +There are several ways to scan DFHack tools and find the ones you need right now. The first place to check is the DFHack logo hover hotspot. It's in the upper left corner of the screen by default, though you can move it anywhere you want -with the `gui/overlay` tool. +with the `gui/overlay` configuration UI. When you hover the mouse over the logo (or hit the Ctrl-Shift-C keyboard shortcut) a list of DFHack tools relevant to the current context comes up. For example, when you have a unit selected, the hotspot will show a list of tools that inspect units, allow you to edit them, or maybe even teleport them. Next to each tool, -you'll see the global hotkey you can hit to invoke the command without even -opening the hover list. - -You can run any DFHack tool from `gui/launcher`, which is always listed first in -the hover list. You can also bring up the launcher by tapping the backtick key -(\`) or hitting Ctrl-Shift-D. In the launcher, you can quickly autocomplete any -command name by selecting it in the list on the right side of the window. -Commands are ordered by how often you run them, so your favorite commands will -always be on top. You can also pull full commandlines out of your history with -Alt-S (or by clicking on the "history search" hotkey hint). +you'll see the hotkey you can hit to invoke the command without even opening the +hover list. + +The second place to check is the DFHack control panel: `gui/control-panel`. It +will give you an overview of which tools are currently enabled, and will allow +you to toggle them on or off, see help text for them, or launch their dedicated +configuration UIs. You can launch the control panel from anywhere with the +tilde key (Shift-\`) or from the logo hover list. + +In the control panel, you can also select which tools you'd like to be +automatically enabled when you start a new fort. There are also system settings +you can change, like whether DFHack windows will pause the game when they come +up. + +Finally, you can explore the full extent of the DFHack catalog in `gui/launcher`, +which is always listed first in the DFHack logo hover list. You can also bring up +the launcher by tapping the backtick key (\`) or hitting Ctrl-Shift-D. In the +launcher, you can quickly autocomplete any command name by selecting it in the +list on the right side of the window. Commands are ordered by how often you run +them, so your favorite commands will always be on top. You can also pull full +commandlines out of your history with Alt-S or by clicking on the "history search" +hotkey hint. Once you have typed (or autocompleted, or searched for) a command, other commands -related to the one you have selected will appear in the autocomplete list. -Scanning through that list is a great way to learn about new tools that you might -find useful. You can also see how commands are grouped by running the `tags` command. +related to the one you have selected will appear in the right-hand panel. Scanning +through that list is a great way to learn about new tools that you might find +useful. You can also see how commands are grouped by running the `tags` command. The bottom panel will show the full help text for the command you are running, allowing you to refer to the usage documentation and examples when you are typing -your command. +your command. After you run a command, the bottom panel switches to command output +mode, but you can get back to the help text by hitting Ctrl-T or clicking on the +``Showing`` selector. How do DFHack in-game windows work? ----------------------------------- @@ -106,26 +119,27 @@ whether they capture keyboard and mouse input. The DFHack windowing system allows multiple overlapping windows to be active at once. The one with the highlighted title bar has focus and will receive anything you type at the keyboard. Hit Esc or right click to close the window or cancel -the current operation. You can click anywhere on the screen that is not a -DFHack window to unfocus the window and let it just sit in the background. It won't +the current action. You can click anywhere on the screen that is not a DFHack +window to unfocus the window and let it just sit in the background. It won't respond to key presses or mouse clicks until you click on it again to give it -focus. You can right click directly on an unfocused window to close it without -left clicking to activate it first. +focus. If no DFHack windows are focused, you can right click directly on a window +to close it without left clicking to focus it first. DFHack windows are draggable from the title bar or from anywhere on the window that doesn't have a mouse-clickable widget on it. Many are resizable as well (if the tool window has components that can reasonably be resized). You can generally use DFHack tools without interrupting the game. That is, if the -game is unpaused, it can continue to run while a DFHack window is open. Many tools -will initially pause the game to let you focus on the task at hand, but you can -unpause like normal if you want. You can also interact with the map, scrolling it -with the keyboard or mouse and selecting units, buildings, and items. Some tools -will capture all keyboard input, such as tools with editable text fields, and some -will force-pause the game if it makes sense to, like `gui/quickfort`, since you -cannot interact with the map normally while trying to apply a blueprint. Windows -for tools that force-pause the game will have a pause icon in their upper right -corner to indicate which tool is responsible for the pausing. +game is unpaused, it can continue to run while a DFHack window is open. If configured +to do so in `gui/control-panel`, tools will initially pause the game to let you +focus on the task at hand, but you can unpause like normal if you want. You can +also interact with the map, scrolling it with the keyboard or mouse and selecting +units, buildings, and items. Some tools will capture all keyboard input, such as +tools with editable text fields, and some will force-pause the game if it makes +sense to, like `gui/quickfort`, since you cannot interact with the map normally +while trying to apply a blueprint. Windows for tools that force-pause the game +will have a pause icon in their upper right corner to indicate which tool is +preventing you from unpausing. Where do I go next? ------------------- @@ -135,33 +149,32 @@ To recap: You can get to popular, relevant tools for the current context by hovering the mouse over the DFHack logo or by hitting Ctrl-Shift-C. +You can enable DFHack tools and configure settings with `gui/control-panel`, +which you can access directly with the tilde key (Shift-\`). + You can get to the launcher and its integrated autocomplete, history search, and help text by hitting backtick (\`) or Ctrl-Shift-D, or, of course, by running it from the logo hover list. -You can list and start tools that run in the background with the `enable` -command. - -You can configure screen overlays with the `gui/overlay` tool. - -With those four tools, you have the complete DFHack tool suite at your +With those three tools, you have the complete DFHack tool suite at your fingertips. So what to run first? Here are a few commands to get you started. You can run them all from the launcher. First, let's import some useful manager orders to keep your fort stocked with basic necessities. Run ``orders import library/basic``. If you go to your mangager orders screen, you can see all the orders that have been created for you. - -Next, try setting up `autochop` by running the GUI configuration `gui/autochop`. -You can enable it from the GUI, so you don't need to run `enable autochop ` -directly. You can set a target number of logs, and autochop will manage -your logging industry for you. You can control where your woodsdwarves go to -cut down trees by setting up burrows and configuring autochop to only cut in -those burrows. If you have the extra screen space, go ahead and set the -`gui/autochop` window to minimal mode (click on the hint near the upper right -corner of the window or hit Alt-M) and click on the map so the window loses -keyboard focus. As you play the game, you can glance at the status panel to -check on your stocks of wood. +Note that you could have imported the orders directly from this screen as well, +using the DFHack `overlay` widget at the bottom of the manager orders panel. + +Next, try setting up `autochop` to automatically designate trees for chopping when +you get low on usable logs. Run `gui/control-panel` and select ``autochop`` in the +list. Click on the button to the left of the name or hit Enter to enable it. You +can then click on the ``[configure]`` button to launch `gui/autochop` if you'd +like to customize its settings. If you have the extra screen space, you can go +ahead and set the `gui/autochop` window to minimal mode (click on the hint near +the upper right corner of the window or hit Alt-M) and click on the map so the +window loses keyboard focus. As you play the game, you can glance at the live +status panel to check on your stocks of wood. Finally, let's do some fort design copy-pasting. Go to some bedrooms that you have set up in your fort. Run `gui/blueprint`, set a name for your blueprint by From 69860ce419ce2141c5e9165f12108e4b977ff283 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 30 Jan 2023 13:32:51 -0800 Subject: [PATCH 20/40] update changelog --- docs/changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 74fc26086..a78b14fc7 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -54,8 +54,10 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `overlay`: overlay widgets can now specify a default enabled state if they are not already set in the player's overlay config file - `getplants`: ID values will now be accepted regardless of case -@ New borders for DFHack tool windows -- tell us what you think! +- `gui/control-panel`: new global hotkey: tilde (Shift-backtick on most keyboards) ## Documentation +-@ Quickstart guide has been updated with info on new window behavior and how to use the control panel ## API - ``Buildings::containsTile()``: no longer takes a ``room`` parameter since that's not how rooms work anymore. If the building has extents, the extents will be checked. otherwise, the result just depends on whether the tile is within the building's bounding box. From d7bd3e5d55180c488d938f6fc19cd6eb003327be Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 31 Jan 2023 07:14:26 +0000 Subject: [PATCH 21/40] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 9d42d260e..0410847a6 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 9d42d260e7baa5b4962582bc93f87ad37da39827 +Subproject commit 0410847a6dd65872d58287147dc4d1c81b60f86f From d6a4b1d37a751846a4719ec4ad5a9b0e88bb411b Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 1 Feb 2023 07:15:02 +0000 Subject: [PATCH 22/40] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 0410847a6..936051185 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 0410847a6dd65872d58287147dc4d1c81b60f86f +Subproject commit 9360511856f3f85664c39793a8c037b8d0ce5c53 From aa22917aeba47ffcf581f56afeb73c2f289d0194 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 31 Jan 2023 23:52:30 -0800 Subject: [PATCH 23/40] add new control panel textures --- data/art/control-panel.png | Bin 0 -> 639 bytes library/include/modules/Textures.h | 5 +++++ library/modules/Textures.cpp | 7 +++++++ 3 files changed, 12 insertions(+) create mode 100644 data/art/control-panel.png diff --git a/data/art/control-panel.png b/data/art/control-panel.png new file mode 100644 index 0000000000000000000000000000000000000000..2f5e9468af89d99e93137a1b95babf4479dae8bd GIT binary patch literal 639 zcmV-_0)YLAP)u4(3A3g<=htBMUaLt3`NV13#62n`I8G)`W z_&$#$%@e+90!i7xmv9k?If5q3NHXXBPDM0xAif2H7l&7)Ddyo6gfJj}Hqtf%k{fqy zge&m(Km_-j51)>n*bih_TR4gc@F8Lt;#+5}7$AG^wIxJ4)b)VIZigGlA`ssyX{nRs zVdFxEv;Ds?#W8&g1Yf?thx-`7fgNr7m7b_o~%u_MWvUY0|bj-U%3tmRHzOPX`%2R}hG z2U6Lxa|!~>WK6$dO|)o98o7K%7J=5okOl)8LAz+rH_V0y<>ml^oryEnG8qCMJ-q{A z$Vsb^)9@H=S`Lb)C9c{SnBa$P(-K!(8s4-d)x(3jX$b_sZ(Kq5FlxIJqJa3eG}B;i zTH{&xf`zp$jp=_(A7T|B?41W(z9qZ!01#jRdw6w(;>%@%0000EWmrjOO-%qQ00008 Z000000002eQ Date: Tue, 31 Jan 2023 23:54:27 -0800 Subject: [PATCH 24/40] allow tile in Label tokens to be either pen or id --- docs/changelog.txt | 1 + docs/dev/Lua API.rst | 3 ++- library/lua/gui/widgets.lua | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a78b14fc7..7fac2030c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -68,6 +68,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `helpdb`: changed from auto-refreshing every 60 seconds to only refreshing on explicit call to ``helpdb.refresh()``. docs very rarely change during a play session, and the automatic database refreshes were slowing down the startup of `gui/launcher` - ``widgets.Label``: ``label.scroll()`` now understands ``home`` and ``end`` keywords for scrolling to the top or bottom - ``dfhack.units.getCitizens()``: gets a list of citizens +- ``Label``: token ``tile`` properties can now be either pens or numeric texture ids ## Removed - `autohauler`: no plans to port to v50, as it just doesn't make sense with the new work detail system diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 658893ec1..78b324f63 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -4658,7 +4658,8 @@ containing newlines, or a table with the following possible fields: * ``token.tile = pen`` - Specifies a pen to paint as one tile before the main part of the token. + Specifies a pen or texture index to paint as one tile before the main part of + the token. * ``token.width = ...`` diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 0696459cd..4228bc9ad 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -1110,7 +1110,9 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) if token.tile then x = x + 1 if dc then - dc:tile(nil, token.tile) + local tile_pen = tonumber(token.tile) and + to_pen{tile=token.tile} or token.tile + dc:char(nil, token.tile) if token.width then dc:advance(token.width-1) end From 5127f0657193766a8e96d983f76f19acd393cd84 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 31 Jan 2023 23:55:30 -0800 Subject: [PATCH 25/40] expose new tiles to Lua --- library/LuaApi.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 699c8aef7..bb2392546 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1694,6 +1694,7 @@ static const LuaWrapper::FunctionReg dfhack_textures_module[] = { WRAPM(Textures, getRedPinTexposStart), WRAPM(Textures, getIconsTexposStart), WRAPM(Textures, getOnOffTexposStart), + WRAPM(Textures, getControlPanelTexposStart), WRAPM(Textures, getThinBordersTexposStart), WRAPM(Textures, getMediumBordersTexposStart), WRAPM(Textures, getPanelBordersTexposStart), From 0ff7ad0cc4a131502994388404f9f1004a8f83b6 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 31 Jan 2023 23:55:54 -0800 Subject: [PATCH 26/40] update tailor docs --- docs/plugins/tailor.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/plugins/tailor.rst b/docs/plugins/tailor.rst index af7d253b2..4dc4f53a4 100644 --- a/docs/plugins/tailor.rst +++ b/docs/plugins/tailor.rst @@ -35,6 +35,9 @@ are used, and in what order. Example ------- +``enable tailor`` + Start replacing tattered clothes with default settings. + ``tailor materials silk cloth yarn`` Restrict the materials used for automatically manufacturing clothing to silk, cloth, and yarn, preferred in that order. This saves leather for From be40d55e64e2a5fd1f211e845a2a81bdbc0c6184 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 9 Jan 2023 17:31:49 -0800 Subject: [PATCH 27/40] update seedwatch --- docs/plugins/seedwatch.rst | 41 ++- library/include/LuaTools.h | 8 + library/include/modules/Kitchen.h | 30 +- library/modules/Kitchen.cpp | 78 ---- plugins/CMakeLists.txt | 2 +- plugins/lua/seedwatch.lua | 67 ++++ plugins/seedwatch.cpp | 588 ++++++++++++++++++------------ 7 files changed, 452 insertions(+), 362 deletions(-) create mode 100644 plugins/lua/seedwatch.lua diff --git a/docs/plugins/seedwatch.rst b/docs/plugins/seedwatch.rst index b41f3a977..12a728c51 100644 --- a/docs/plugins/seedwatch.rst +++ b/docs/plugins/seedwatch.rst @@ -5,42 +5,47 @@ seedwatch :summary: Manages seed and plant cooking based on seed stock levels. :tags: fort auto plants -Each seed type can be assigned a target. If the number of seeds of that type -falls below that target, then the plants and seeds of that type will be excluded -from cookery. If the number rises above the target + 20, then cooking will be -allowed. +Unlike brewing and other kinds of processing, cooking plants does not produce +a usable seed. By default, all plants are allowed to be cooked. This often leads +to the situation where dwarves have no seeds left to plant crops with because +they cooked all the relevant plants. Seedwatch protects you from this problem. -The plugin needs a fortress to be loaded and will deactivate automatically -otherwise. You have to reactivate with ``enable seedwatch`` after you load a -fort. +Each seed type can be assigned a target stock amount. If the number of seeds of +that type falls below that target, then the plants and seeds of that type will +be protected from cookery. If the number rises above the target + 20, then +cooking will be allowed again. Usage ----- ``enable seedwatch`` - Start managing seed and plant cooking. By default, no types are watched. - You have to add them with further ``seedwatch`` commands. + Start managing seed and plant cooking. By default, all types are watched + with a target of ``30``, but you can adjust the list or even + ``seedwatch clear`` it and start your own if you like. +``seedwatch [status]`` + Display whether seedwatch is enabled and prints out the watch list, along + with the current seed counts. ``seedwatch `` Adds the specified type to the watchlist (if it's not already there) and sets the target number of seeds to the specified number. You can pass the keyword ``all`` instead of a specific type to set the target for all types. -``seedwatch `` - Removes the specified type from the watch list. + If you set the target to ``0``, it removes the specified type from the + watch list. ``seedwatch clear`` - Clears all types from the watch list. -``seedwatch info`` - Display whether seedwatch is enabled and prints out the watch list. + Clears all types from the watch list. Same as ``seedwatch all 0``. -To print out a list of all plant types, you can run this command:: +To see a list of all plant types that you might want to set targets for, you can +run this command:: devel/query --table df.global.world.raws.plants.all --search ^id --maxdepth 1 Examples -------- -``seedwatch all 30`` - Adds all seeds to the watch list and sets the targets to 30. +``enable seedwatch`` + Adds all seeds to the watch list, sets the targets to 30, and starts + monitoring your seed stock levels. ``seedwatch MUSHROOM_HELMET_PLUMP 50`` Add Plump Helmets to the watch list and sets the target to 50. -``seedwatch MUSHROOM_HELMET_PLUMP`` +``seedwatch MUSHROOM_HELMET_PLUMP 0`` removes Plump Helmets from the watch list. diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 9e1901f03..ca9aac788 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -30,6 +30,7 @@ distribution. #include #include #include +#include #include "df/interfacest.h" @@ -378,6 +379,13 @@ namespace DFHack {namespace Lua { TableInsert(L, entry.first, entry.second); } + template + void Push(lua_State *L, const std::unordered_map &pmap) { + lua_createtable(L, 0, pmap.size()); + for (auto &entry : pmap) + TableInsert(L, entry.first, entry.second); + } + DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true); DFHACK_EXPORT bool IsCoreContext(lua_State *state); diff --git a/library/include/modules/Kitchen.h b/library/include/modules/Kitchen.h index 3fde8edf8..f2708c844 100644 --- a/library/include/modules/Kitchen.h +++ b/library/include/modules/Kitchen.h @@ -27,11 +27,6 @@ distribution. * kitchen settings */ #include "Export.h" -#include "Module.h" -#include "Types.h" -#include "VersionInfo.h" -#include "Core.h" -#include "modules/Items.h" #include "df/kitchen_exc_type.h" /** @@ -43,38 +38,21 @@ namespace DFHack { namespace Kitchen { +// print the exclusion list, with the material index also translated into its token (for organics) - for debug really +DFHACK_EXPORT void debug_print(color_ostream &out); + /** * Kitchen exclusions manipulator. Currently geared towards plants and seeds. * \ingroup grp_modules * \ingroup grp_kitchen */ -// print the exclusion list, with the material index also translated into its token (for organics) - for debug really -DFHACK_EXPORT void debug_print(color_ostream &out); - // remove this plant from the exclusion list if it is in it DFHACK_EXPORT void allowPlantSeedCookery(int32_t plant_id); // add this plant to the exclusion list, if it is not already in it DFHACK_EXPORT void denyPlantSeedCookery(int32_t plant_id); -// fills a map with info from the limit info storage entries in the exclusion list -DFHACK_EXPORT void fillWatchMap(std::map& watchMap); - -// Finds the index of a limit info storage entry. Returns -1 if not found. -DFHACK_EXPORT int findLimit(int32_t plant_id); - -// removes a limit info storage entry from the exclusion list if it's present -DFHACK_EXPORT bool removeLimit(int32_t plant_id); - -// add a limit info storage item to the exclusion list, or alters an existing one -DFHACK_EXPORT bool setLimit(int32_t plant_id, int16_t limit); - -// clears all limit info storage items from the exclusion list -DFHACK_EXPORT void clearLimits(); - -DFHACK_EXPORT std::size_t size(); - // Finds the index of a kitchen exclusion in plotinfo.kitchen.exc_types. Returns -1 if not found. DFHACK_EXPORT int findExclusion(df::kitchen_exc_type type, df::item_type item_type, int16_t item_subtype, @@ -90,5 +68,7 @@ DFHACK_EXPORT bool removeExclusion(df::kitchen_exc_type type, df::item_type item_type, int16_t item_subtype, int16_t mat_type, int32_t mat_index); +DFHACK_EXPORT std::size_t size(); + } } diff --git a/library/modules/Kitchen.cpp b/library/modules/Kitchen.cpp index 65e47f528..5bbc2ee7c 100644 --- a/library/modules/Kitchen.cpp +++ b/library/modules/Kitchen.cpp @@ -78,84 +78,6 @@ void Kitchen::denyPlantSeedCookery(int32_t plant_id) type->material_defs.idx[plant_material_def::basic_mat]); } -void Kitchen::fillWatchMap(std::map& watchMap) -{ - watchMap.clear(); - for (std::size_t i = 0; i < size(); ++i) - { - if (plotinfo->kitchen.item_subtypes[i] == SEEDLIMIT_ITEMTYPE && - plotinfo->kitchen.item_subtypes[i] == SEEDLIMIT_ITEMSUBTYPE && - plotinfo->kitchen.exc_types[i] == SEEDLIMIT_EXCTYPE) - { - watchMap[plotinfo->kitchen.mat_indices[i]] = plotinfo->kitchen.mat_types[i]; - } - } -} - -int Kitchen::findLimit(int32_t plant_id) -{ - for (size_t i = 0; i < size(); ++i) - { - if (plotinfo->kitchen.item_types[i] == SEEDLIMIT_ITEMTYPE && - plotinfo->kitchen.item_subtypes[i] == SEEDLIMIT_ITEMSUBTYPE && - plotinfo->kitchen.mat_indices[i] == plant_id && - plotinfo->kitchen.exc_types[i] == SEEDLIMIT_EXCTYPE) - { - return int(i); - } - } - return -1; -} - -bool Kitchen::removeLimit(int32_t plant_id) -{ - int i = findLimit(plant_id); - if (i < 0) - return false; - - plotinfo->kitchen.item_types.erase(plotinfo->kitchen.item_types.begin() + i); - plotinfo->kitchen.item_subtypes.erase(plotinfo->kitchen.item_subtypes.begin() + i); - plotinfo->kitchen.mat_types.erase(plotinfo->kitchen.mat_types.begin() + i); - plotinfo->kitchen.mat_indices.erase(plotinfo->kitchen.mat_indices.begin() + i); - plotinfo->kitchen.exc_types.erase(plotinfo->kitchen.exc_types.begin() + i); - return true; -} - -bool Kitchen::setLimit(int32_t plant_id, int16_t limit) -{ - if (limit > SEEDLIMIT_MAX) - limit = SEEDLIMIT_MAX; - - int i = findLimit(plant_id); - if (i < 0) - { - plotinfo->kitchen.item_types.push_back(SEEDLIMIT_ITEMTYPE); - plotinfo->kitchen.item_subtypes.push_back(SEEDLIMIT_ITEMSUBTYPE); - plotinfo->kitchen.mat_types.push_back(limit); - plotinfo->kitchen.mat_indices.push_back(plant_id); - plotinfo->kitchen.exc_types.push_back(SEEDLIMIT_EXCTYPE); - } - else - { - plotinfo->kitchen.mat_types[i] = limit; - } - return true; -} - -void Kitchen::clearLimits() -{ - for (size_t i = 0; i < size(); ++i) - { - if (plotinfo->kitchen.item_types[i] == SEEDLIMIT_ITEMTYPE && - plotinfo->kitchen.item_subtypes[i] == SEEDLIMIT_ITEMSUBTYPE && - plotinfo->kitchen.exc_types[i] == SEEDLIMIT_EXCTYPE) - { - removeLimit(plotinfo->kitchen.mat_indices[i]); - --i; - } - } -} - size_t Kitchen::size() { return plotinfo->kitchen.item_types.size(); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 41b3ad542..d5f35fb21 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -149,7 +149,7 @@ add_subdirectory(remotefortressreader) #add_subdirectory(rendermax) dfhack_plugin(reveal reveal.cpp LINK_LIBRARIES lua) #dfhack_plugin(search search.cpp) -dfhack_plugin(seedwatch seedwatch.cpp) +dfhack_plugin(seedwatch seedwatch.cpp LINK_LIBRARIES lua) dfhack_plugin(showmood showmood.cpp) #dfhack_plugin(siege-engine siege-engine.cpp LINK_LIBRARIES lua) #dfhack_plugin(sort sort.cpp LINK_LIBRARIES lua) diff --git a/plugins/lua/seedwatch.lua b/plugins/lua/seedwatch.lua new file mode 100644 index 000000000..a72c65d47 --- /dev/null +++ b/plugins/lua/seedwatch.lua @@ -0,0 +1,67 @@ +local _ENV = mkmodule('plugins.seedwatch') + +local argparse = require('argparse') + +local function process_args(opts, args) + if args[1] == 'help' then + opts.help = true + return + end + + return argparse.processArgsGetopt(args, { + {'h', 'help', handler=function() opts.help = true end}, + }) +end + +local function print_status() + print(('seedwatch is %s'):format(isEnabled() and "enabled" or "disabled")) + print() + print('usable seed counts and current targets:') + local watch_map, seed_counts = seedwatch_getData() + local sum = 0 + local plants = df.global.world.raws.plants.all + for k,v in pairs(seed_counts) do + print((' %4d/%d %s'):format(v, watch_map[k] or 0, plants[k].id)) + sum = sum + v + end + print() + print(('total usable seeds: %d'):format(sum)) +end + +local function set_target(name, num) + if not name or #name == 0 then + qerror('must specify "all" or plant name') + end + + num = tonumber(num) + num = num and math.floor(num) or nil + if not num or num < 0 then + qerror('target must be a non-negative integer') + end + + seedwatch_setTarget(name, num) +end + +function parse_commandline(...) + local args, opts = {...}, {} + local positionals = process_args(opts, args) + + if opts.help then + return false + end + + local command = positionals[1] + if not command or command == 'status' then + print_status() + elseif command == 'clear' then + set_target('all', 0) + elseif positionals[2] and positionals[3] then + set_target(positionals[2], positionals[3]) + else + return false + end + + return true +end + +return _ENV diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 13ed8e66e..c6482b1ac 100644 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -4,218 +4,113 @@ // With thanks to peterix for DFHack and Quietust for information // http://www.bay12forums.com/smf/index.php?topic=91166.msg2605147#msg2605147 -#include -#include -#include -#include "Console.h" -#include "Core.h" -#include "Export.h" +#include "Debug.h" +#include "LuaTools.h" #include "PluginManager.h" -#include "modules/World.h" -#include "modules/Materials.h" +#include "TileTypes.h" + #include "modules/Kitchen.h" -#include "VersionInfo.h" -#include "df/world.h" -#include "df/plant_raw.h" +#include "modules/Maps.h" +#include "modules/Persistence.h" +#include "modules/Units.h" +#include "modules/World.h" + #include "df/item_flags.h" #include "df/items_other_id.h" +#include "df/plant_raw.h" +#include "df/world.h" + +#include -using namespace std; using namespace DFHack; using namespace df::enums; +using std::map; +using std::string; +using std::unordered_map; +using std::vector; + DFHACK_PLUGIN("seedwatch"); -DFHACK_PLUGIN_IS_ENABLED(running); // whether seedwatch is counting the seeds or not +DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); -const int buffer = 20; // seed number buffer - 20 is reasonable +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(seedwatch, config, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(seedwatch, cycle, DebugCategory::LINFO); +} // abbreviations for the standard plants -map abbreviations; - -bool ignoreSeeds(df::item_flags& f) // seeds with the following flags should not be counted -{ - return - f.bits.dump || - f.bits.forbid || - f.bits.garbage_collect || - f.bits.hidden || - f.bits.hostile || - f.bits.on_fire || - f.bits.rotten || - f.bits.trader || - f.bits.in_building || - f.bits.in_job; -}; +static unordered_map abbreviations; +static map world_plant_ids; +static const int DEFAULT_TARGET = 30; +static const int TARGET_BUFFER = 20; // seed number buffer; 20 is reasonable -// searches abbreviations, returns expansion if so, returns original if not -string searchAbbreviations(string in) -{ - if(abbreviations.count(in) > 0) - { - return abbreviations[in]; - } - else - { - return in; - } +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static const string SEED_CONFIG_KEY_PREFIX = string(plugin_name) + "/seed/"; +static PersistentDataItem config; +static unordered_map watched_seeds; + +enum ConfigValues { + CONFIG_IS_ENABLED = 0, }; -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) -{ - if(enable == true) - { - if(Core::getInstance().isWorldLoaded()) - { - running = true; - out.print("seedwatch supervision started.\n"); - } else { - out.printerr( - "This plugin needs a fortress to be loaded and will deactivate automatically otherwise.\n" - "Activate with 'seedwatch start' after you load the game.\n" - ); - } - } else { - running = false; - out.print("seedwatch supervision stopped.\n"); - } +enum SeedConfigValues { + SEED_CONFIG_ID = 0, + SEED_CONFIG_TARGET = 1, +}; - return CR_OK; +static int get_config_val(PersistentDataItem &c, int index) { + if (!c.isValid()) + return -1; + return c.ival(index); +} +static bool get_config_bool(PersistentDataItem &c, int index) { + return get_config_val(c, index) == 1; +} +static void set_config_val(PersistentDataItem &c, int index, int value) { + if (c.isValid()) + c.ival(index) = value; +} +static void set_config_bool(PersistentDataItem &c, int index, bool value) { + set_config_val(c, index, value ? 1 : 0); } -command_result df_seedwatch(color_ostream &out, vector& parameters) -{ - CoreSuspender suspend; - - map plantIDs; - for(size_t i = 0; i < world->raws.plants.all.size(); ++i) - { - auto & plant = world->raws.plants.all[i]; - if (plant->material_defs.type[plant_material_def::seed] != -1) - plantIDs[plant->id] = i; - } - - t_gamemodes gm; - World::ReadGameMode(gm);// FIXME: check return value - - // if game mode isn't fortress mode - if(gm.g_mode != game_mode::DWARF || !World::isFortressMode(gm.g_type)) - { - // just print the help - return CR_WRONG_USAGE; - } +static PersistentDataItem & ensure_seed_config(color_ostream &out, int id) { + if (watched_seeds.count(id)) + return watched_seeds[id]; + string keyname = SEED_CONFIG_KEY_PREFIX + int_to_string(id); + DEBUG(config,out).print("creating new persistent key for seed type %d\n", id); + watched_seeds.emplace(id, World::GetPersistentData(keyname, NULL)); + return watched_seeds[id]; +} +static void remove_seed_config(color_ostream &out, int id) { + if (!watched_seeds.count(id)) + return; + DEBUG(config,out).print("removing persistent key for seed type %d\n", id); + World::DeletePersistentData(watched_seeds[id]); + watched_seeds.erase(id); +} - string par; - int limit; - switch(parameters.size()) - { - case 0: - return CR_WRONG_USAGE; - - case 1: - par = parameters[0]; - if ((par == "help") || (par == "?")) - { - return CR_WRONG_USAGE; - } - else if(par == "start") - { - plugin_enable(out, true); +static const int32_t CYCLE_TICKS = 1200; +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle - } - else if(par == "stop") - { - plugin_enable(out, false); - } - else if(par == "clear") - { - Kitchen::clearLimits(); - out.print("seedwatch watchlist cleared\n"); - } - else if(par == "info") - { - out.print("seedwatch Info:\n"); - if(running) - { - out.print("seedwatch is supervising. Use 'disable seedwatch' to stop supervision.\n"); - } - else - { - out.print("seedwatch is not supervising. Use 'enable seedwatch' to start supervision.\n"); - } - map watchMap; - Kitchen::fillWatchMap(watchMap); - if(watchMap.empty()) - { - out.print("The watch list is empty.\n"); - } - else - { - out.print("The watch list is:\n"); - for(auto i = watchMap.begin(); i != watchMap.end(); ++i) - { - out.print("%s : %u\n", world->raws.plants.all[i->first]->id.c_str(), i->second); - } - } - } - else if(par == "debug") - { - map watchMap; - Kitchen::fillWatchMap(watchMap); - Kitchen::debug_print(out); - } - else - { - string token = searchAbbreviations(par); - if(plantIDs.count(token) > 0) - { - Kitchen::removeLimit(plantIDs[token]); - out.print("%s is not being watched\n", token.c_str()); - } - else - { - out.print("%s has not been found as a material.\n", token.c_str()); - } - } - break; - case 2: - limit = atoi(parameters[1].c_str()); - if(limit < 0) limit = 0; - if(parameters[0] == "all") - { - for(auto & entry : plantIDs) - Kitchen::setLimit(entry.second, limit); - } - else - { - string token = searchAbbreviations(parameters[0]); - if(plantIDs.count(token) > 0) - { - Kitchen::setLimit(plantIDs[token], limit); - out.print("%s is being watched.\n", token.c_str()); - } - else - { - out.print("%s has not been found as a material.\n", token.c_str()); - } - } - break; - default: - return CR_WRONG_USAGE; - break; - } +static command_result do_command(color_ostream &out, vector ¶meters); +static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds, int32_t *num_disabled_seeds); +static void seedwatch_setTarget(color_ostream &out, string name, int32_t num); - return CR_OK; -} +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + DEBUG(config,out).print("initializing %s\n", plugin_name); -DFhackCExport command_result plugin_init(color_ostream &out, vector& commands) -{ + // provide a configuration interface for the plugin commands.push_back(PluginCommand( - "seedwatch", - "Toggles seed cooking based on quantity available.", - df_seedwatch)); - // fill in the abbreviations map, with abbreviations for the standard plants + plugin_name, + "Automatically toggle seed cooking based on quantity available.", + do_command)); + + // fill in the abbreviations map abbreviations["bs"] = "SLIVER_BARB"; abbreviations["bt"] = "TUBER_BLOATED"; abbreviations["bw"] = "WEED_BLADE"; @@ -237,72 +132,285 @@ DFhackCExport command_result plugin_init(color_ostream &out, vector seedCount; // the number of seeds - - // count all seeds and plants by RAW material - for(size_t i = 0; i < world->items.other[items_other_id::SEEDS].size(); ++i) - { - df::item *item = world->items.other[items_other_id::SEEDS][i]; - MaterialInfo mat(item); - if (!mat.isPlant()) - continue; - if (!ignoreSeeds(item->flags)) - ++seedCount[mat.plant->index]; - } +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + DEBUG(config,out).print("shutting down %s\n", plugin_name); + + return CR_OK; +} + +DFhackCExport command_result plugin_load_data (color_ostream &out) { + world_plant_ids.clear(); + for (size_t i = 0; i < world->raws.plants.all.size(); ++i) { + auto & plant = world->raws.plants.all[i]; + if (plant->material_defs.type[plant_material_def::seed] != -1) + world_plant_ids[plant->id] = i; + } + + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(config,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + } + + is_enabled = get_config_bool(config, CONFIG_IS_ENABLED); + DEBUG(config,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); + watched_seeds.clear(); + vector seed_configs; + World::GetPersistentData(&seed_configs, SEED_CONFIG_KEY_PREFIX, true); + const size_t num_seed_configs = seed_configs.size(); + for (size_t idx = 0; idx < num_seed_configs; ++idx) { + auto &c = seed_configs[idx]; + watched_seeds.emplace(get_config_val(c, SEED_CONFIG_ID), c); + } - map watchMap; - Kitchen::fillWatchMap(watchMap); - for(auto i = watchMap.begin(); i != watchMap.end(); ++i) - { - if(seedCount[i->first] <= i->second) - { - Kitchen::denyPlantSeedCookery(i->first); - } - else if(i->second + buffer < seedCount[i->first]) - { - Kitchen::allowPlantSeedCookery(i->first); - } + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_WORLD_UNLOADED) { + if (is_enabled) { + DEBUG(config,out).print("world unloaded; disabling %s\n", + plugin_name); + is_enabled = false; } } return CR_OK; } -DFhackCExport command_result plugin_shutdown(Core* pCore) -{ +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) { + int32_t num_enabled_seeds, num_disabled_seeds; + do_cycle(out, &num_enabled_seeds, &num_disabled_seeds); + if (0 < num_enabled_seeds) + out.print("%s: enabled %d seed types for cooking\n", + plugin_name, num_enabled_seeds); + if (0 < num_disabled_seeds) + out.print("%s: protected %d seed types from cooking\n", + plugin_name, num_disabled_seeds); + } return CR_OK; } + +static bool call_seedwatch_lua(color_ostream *out, const char *fn_name, + int nargs = 0, int nres = 0, + Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA, + Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) { + CoreSuspender guard; + + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!out) + out = &Core::getInstance().getConsole(); + + DEBUG(config,*out).print("calling %s lua function: '%s'\n", plugin_name, fn_name); + + return Lua::CallLuaModuleFunction(*out, L, "plugins.seedwatch", fn_name, + nargs, nres, + std::forward(args_lambda), + std::forward(res_lambda)); +} + +static command_result do_command(color_ostream &out, vector ¶meters) { + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } + + bool show_help = false; + if (!call_seedwatch_lua(&out, "parse_commandline", parameters.size(), 1, + [&](lua_State *L) { + for (const string ¶m : parameters) + Lua::Push(L, param); + }, + [&](lua_State *L) { + show_help = !lua_toboolean(L, -1); + })) { + return CR_FAILURE; + } + + return show_help ? CR_WRONG_USAGE : CR_OK; +} + +///////////////////////////////////////////////////// +// cycle logic +// + +struct BadFlags { + uint32_t whole; + + // TODO: maybe don't filter out seeds that are in_building. that would + // allow us to count seeds that are in workshops. are there any negative + // consequences? + BadFlags() { + df::item_flags flags; + #define F(x) 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); + F(in_job); F(owned); F(in_chest); F(removed); + F(encased); F(spider_web); + #undef F + whole = flags.whole; + } +}; + +static bool is_accessible_item(const df::coord &pos, const vector &citizens) { + for (auto &unit : citizens) { + if (Maps::canWalkBetween(unit->pos, pos)) + return true; + } + return false; +} + +static void scan_seeds(color_ostream &out, unordered_map *accessible_counts, + unordered_map *inaccessible_counts = NULL) { + static const BadFlags bad_flags; + + vector citizens; + Units::getCitizens(citizens); + + for (auto &item : world->items.other[items_other_id::SEEDS]) { + MaterialInfo mat(item); + if (!mat.isPlant()) + continue; + if ((bad_flags.whole & item->flags.whole) || !is_accessible_item(item->pos, citizens)) { + if (inaccessible_counts) + ++(*inaccessible_counts)[mat.plant->index]; + } else { + if (accessible_counts) + ++(*accessible_counts)[mat.plant->index]; + } + } +} + +static void do_cycle(color_ostream &out, int32_t *num_enabled_seed_types, int32_t *num_disabled_seed_types) { + DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + if (num_enabled_seed_types) + *num_enabled_seed_types = 0; + if (num_disabled_seed_types) + *num_disabled_seed_types = 0; + + unordered_map accessible_counts; + scan_seeds(out, &accessible_counts); + + for (auto &entry : watched_seeds) { + int32_t id = entry.first; + int32_t target = get_config_val(entry.second, SEED_CONFIG_TARGET); + if (accessible_counts[id] <= target) { + DEBUG(cycle,out).print("disabling seed mat: %d\n", id); + if (num_disabled_seed_types) + ++*num_disabled_seed_types; + Kitchen::denyPlantSeedCookery(id); + } else if (target + TARGET_BUFFER < accessible_counts[id]) { + DEBUG(cycle,out).print("enabling seed mat: %d\n", id); + if (num_enabled_seed_types) + ++*num_enabled_seed_types; + Kitchen::allowPlantSeedCookery(id); + } + } +} + +///////////////////////////////////////////////////// +// Lua API +// core will already be suspended when coming in through here +// + +static void set_target(color_ostream &out, int32_t id, int32_t target) { + if (target == 0) + remove_seed_config(out, id); + else { + PersistentDataItem &c = ensure_seed_config(out, id); + set_config_val(c, SEED_CONFIG_TARGET, target); + } +} + +// searches abbreviations, returns expansion if so, returns original if not +static string searchAbbreviations(string in) { + if(abbreviations.count(in) > 0) + return abbreviations[in]; + return in; +}; + +static void seedwatch_setTarget(color_ostream &out, string name, int32_t num) { + DEBUG(config,out).print("entering seedwatch_setTarget\n"); + + if (num < 0) { + out.printerr("target must be at least 0\n"); + return; + } + + if (name == "all") { + for (auto &entry : world_plant_ids) { + set_target(out, entry.second, num); + } + return; + } + + string token = searchAbbreviations(name); + if (!world_plant_ids.count(token)) { + out.printerr("%s has not been found as a material.\n", token.c_str()); + return; + } + + set_target(out, world_plant_ids[token], num); +} + +static int seedwatch_getData(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + DEBUG(config,*out).print("entering seedwatch_getData\n"); + unordered_map watch_map, accessible_counts; + scan_seeds(*out, &accessible_counts); + for (auto &entry : watched_seeds) { + watch_map.emplace(entry.first, get_config_val(entry.second, SEED_CONFIG_TARGET)); + } + Lua::Push(L, watch_map); + Lua::Push(L, accessible_counts); + return 2; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(seedwatch_setTarget), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(seedwatch_getData), + DFHACK_LUA_END +}; From febb2bf03052a5d811bf2d766437de36bbd17817 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 04:24:42 -0800 Subject: [PATCH 28/40] use actual item and unit positions --- plugins/autochop.cpp | 8 +++++--- plugins/seedwatch.cpp | 11 +++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index e44b4f804..81c6196a1 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -7,6 +7,7 @@ #include "modules/Burrows.h" #include "modules/Designations.h" +#include "modules/Items.h" #include "modules/Maps.h" #include "modules/Persistence.h" #include "modules/Units.h" @@ -253,9 +254,10 @@ static command_result do_command(color_ostream &out, vector ¶meters) // cycle logic // -static bool is_accessible_item(const df::coord &pos, const vector &citizens) { +static bool is_accessible_item(df::item *item, const vector &citizens) { + const df::coord pos = Items::getPosition(item); for (auto &unit : citizens) { - if (Maps::canWalkBetween(unit->pos, pos)) + if (Maps::canWalkBetween(Units::getPosition(unit), pos)) return true; } return false; @@ -518,7 +520,7 @@ static void scan_logs(int32_t *usable_logs, const vector &citizens, if (!is_valid_item(item)) continue; - if (!is_accessible_item(item->pos, citizens)) { + if (!is_accessible_item(item, citizens)) { if (inaccessible_logs) ++*inaccessible_logs; } else if (usable_logs) { diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index c6482b1ac..03ea4f268 100644 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -9,6 +9,7 @@ #include "PluginManager.h" #include "TileTypes.h" +#include "modules/Items.h" #include "modules/Kitchen.h" #include "modules/Maps.h" #include "modules/Persistence.h" @@ -269,9 +270,6 @@ static command_result do_command(color_ostream &out, vector ¶meters) struct BadFlags { uint32_t whole; - // TODO: maybe don't filter out seeds that are in_building. that would - // allow us to count seeds that are in workshops. are there any negative - // consequences? BadFlags() { df::item_flags flags; #define F(x) flags.bits.x = true; @@ -285,9 +283,10 @@ struct BadFlags { } }; -static bool is_accessible_item(const df::coord &pos, const vector &citizens) { +static bool is_accessible_item(df::item *item, const vector &citizens) { + const df::coord pos = Items::getPosition(item); for (auto &unit : citizens) { - if (Maps::canWalkBetween(unit->pos, pos)) + if (Maps::canWalkBetween(Units::getPosition(unit), pos)) return true; } return false; @@ -304,7 +303,7 @@ static void scan_seeds(color_ostream &out, unordered_map *acce MaterialInfo mat(item); if (!mat.isPlant()) continue; - if ((bad_flags.whole & item->flags.whole) || !is_accessible_item(item->pos, citizens)) { + if ((bad_flags.whole & item->flags.whole) || !is_accessible_item(item, citizens)) { if (inaccessible_counts) ++(*inaccessible_counts)[mat.plant->index]; } else { From aae5d5f41157fe31cb7247c3b1a36c8949a983e5 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 04:28:07 -0800 Subject: [PATCH 29/40] reduce diff in Kitchen.h --- library/include/modules/Kitchen.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/include/modules/Kitchen.h b/library/include/modules/Kitchen.h index f2708c844..ed8eb95b1 100644 --- a/library/include/modules/Kitchen.h +++ b/library/include/modules/Kitchen.h @@ -38,21 +38,23 @@ namespace DFHack { namespace Kitchen { -// print the exclusion list, with the material index also translated into its token (for organics) - for debug really -DFHACK_EXPORT void debug_print(color_ostream &out); - /** * Kitchen exclusions manipulator. Currently geared towards plants and seeds. * \ingroup grp_modules * \ingroup grp_kitchen */ +// print the exclusion list, with the material index also translated into its token (for organics) - for debug really +DFHACK_EXPORT void debug_print(color_ostream &out); + // remove this plant from the exclusion list if it is in it DFHACK_EXPORT void allowPlantSeedCookery(int32_t plant_id); // add this plant to the exclusion list, if it is not already in it DFHACK_EXPORT void denyPlantSeedCookery(int32_t plant_id); +DFHACK_EXPORT std::size_t size(); + // Finds the index of a kitchen exclusion in plotinfo.kitchen.exc_types. Returns -1 if not found. DFHACK_EXPORT int findExclusion(df::kitchen_exc_type type, df::item_type item_type, int16_t item_subtype, @@ -68,7 +70,5 @@ DFHACK_EXPORT bool removeExclusion(df::kitchen_exc_type type, df::item_type item_type, int16_t item_subtype, int16_t mat_type, int32_t mat_index); -DFHACK_EXPORT std::size_t size(); - } } From b02405ea97556286571eb4846451ed83e0cddeee Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 04:30:45 -0800 Subject: [PATCH 30/40] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index a78b14fc7..bf64efb88 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -55,6 +55,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `getplants`: ID values will now be accepted regardless of case -@ New borders for DFHack tool windows -- tell us what you think! - `gui/control-panel`: new global hotkey: tilde (Shift-backtick on most keyboards) +- `seedwatch`: now persists enabled state in the savegame, automatically loads useful defaults, and respects reachability when counting available seeds ## Documentation -@ Quickstart guide has been updated with info on new window behavior and how to use the control panel From fd4b0d72330add10222fa8ef4a51ad2b9b3baf24 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 04:56:25 -0800 Subject: [PATCH 31/40] update and simplify nestboxes; persist state --- docs/changelog.txt | 1 + plugins/nestboxes.cpp | 201 ++++++++++++++++++++++++------------------ 2 files changed, 118 insertions(+), 84 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a78b14fc7..4b578f1da 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -47,6 +47,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - A new cross-compile build script was added for building DFHack for Windows from a Linux Docker builder (see the `compile` instructions in the docs) - You can now configure whether DFHack tool windows should pause the game by default - `hotkeys`: clicking on the DFHack logo no longer closes the popup menu +- `nestboxes`: now saves enabled state in your savegame - `gui/launcher`: sped up initialization time for faster load of the UI - `orders`: orders plugin functionality is now offered via an overlay widget when the manager orders screen is open - `gui/quickcmd`: now has its own global keybinding for your convenience: Ctrl-Shift-A diff --git a/plugins/nestboxes.cpp b/plugins/nestboxes.cpp index 1908684d4..8feee65f7 100644 --- a/plugins/nestboxes.cpp +++ b/plugins/nestboxes.cpp @@ -1,120 +1,153 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" +#include "Debug.h" #include "PluginManager.h" -#include "DataDefs.h" +#include "modules/Persistence.h" +#include "modules/World.h" + #include "df/world.h" -#include "df/plotinfost.h" #include "df/building_nest_boxst.h" -#include "df/building_type.h" -#include "df/buildings_other_id.h" -#include "df/global_objects.h" #include "df/item.h" #include "df/unit.h" -#include "df/building.h" -#include "df/items_other_id.h" -#include "df/creature_raw.h" -#include "modules/MapCache.h" -#include "modules/Items.h" - -using std::vector; using std::string; -using std::endl; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::plotinfo; +DFHACK_PLUGIN("nestboxes"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); -static command_result nestboxes(color_ostream &out, vector & parameters); +REQUIRE_GLOBAL(world); -DFHACK_PLUGIN("nestboxes"); +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(nestboxes, config, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(nestboxes, cycle, DebugCategory::LINFO); +} -DFHACK_PLUGIN_IS_ENABLED(enabled); - -static void eggscan(color_ostream &out) -{ - CoreSuspender suspend; - - for (df::building *build : world->buildings.other[df::buildings_other_id::NEST_BOX]) - { - auto type = build->getType(); - if (df::enums::building_type::NestBox == type) - { - bool fertile = false; - df::building_nest_boxst *nb = virtual_cast(build); - if (nb->claimed_by != -1) - { - df::unit* u = df::unit::find(nb->claimed_by); - if (u && u->pregnancy_timer > 0) - fertile = true; - } - for (size_t j = 1; j < nb->contained_items.size(); j++) - { - df::item* item = nb->contained_items[j]->item; - if (item->flags.bits.forbid != fertile) - { - item->flags.bits.forbid = fertile; - out << item->getStackSize() << " eggs " << (fertile ? "forbidden" : "unforbidden.") << endl; - } - } - } - } +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static PersistentDataItem config; + +enum ConfigValues { + CONFIG_IS_ENABLED = 0, +}; + +static int get_config_val(PersistentDataItem &c, int index) { + if (!c.isValid()) + return -1; + return c.ival(index); +} +static bool get_config_bool(PersistentDataItem &c, int index) { + return get_config_val(c, index) == 1; +} +static void set_config_val(PersistentDataItem &c, int index, int value) { + if (c.isValid()) + c.ival(index) = value; +} +static void set_config_bool(PersistentDataItem &c, int index, bool value) { + set_config_val(c, index, value ? 1 : 0); +} + +static const int32_t CYCLE_TICKS = 100; // need to react quickly if eggs are unforbidden +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle + +static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds, int32_t *num_disabled_seeds); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + DEBUG(config,out).print("initializing %s\n", plugin_name); + + return CR_OK; } +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot enable %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - if (world && plotinfo) { - commands.push_back( - PluginCommand( - "nestboxes", - "Protect fertile eggs incubating in a nestbox.", - nestboxes)); + if (enable != is_enabled) { + is_enabled = enable; + DEBUG(config,out).print("%s from the API; persisting\n", + is_enabled ? "enabled" : "disabled"); + set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + } else { + DEBUG(config,out).print("%s from the API, but already %s; no action\n", + is_enabled ? "enabled" : "disabled", + is_enabled ? "enabled" : "disabled"); } return CR_OK; } -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + DEBUG(config,out).print("shutting down %s\n", plugin_name); + return CR_OK; } -DFhackCExport command_result plugin_onupdate(color_ostream &out) -{ - if (!enabled) - return CR_OK; +DFhackCExport command_result plugin_load_data (color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); - static unsigned cnt = 0; - if ((++cnt % 5) != 0) - return CR_OK; + if (!config.isValid()) { + DEBUG(config,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + } - eggscan(out); + is_enabled = get_config_bool(config, CONFIG_IS_ENABLED); + DEBUG(config,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); return CR_OK; } -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) -{ - enabled = enable; +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_WORLD_UNLOADED) { + if (is_enabled) { + DEBUG(config,out).print("world unloaded; disabling %s\n", + plugin_name); + is_enabled = false; + } + } return CR_OK; } -static command_result nestboxes(color_ostream &out, vector & parameters) -{ - CoreSuspender suspend; - - if (parameters.size() == 1) { - if (parameters[0] == "enable") - enabled = true; - else if (parameters[0] == "disable") - enabled = false; - else - return CR_WRONG_USAGE; - } else { - out << "Plugin " << (enabled ? "enabled" : "disabled") << "." << endl; +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) { + int32_t num_enabled_seeds, num_disabled_seeds; + do_cycle(out, &num_enabled_seeds, &num_disabled_seeds); + if (0 < num_enabled_seeds) + out.print("%s: enabled %d seed types for cooking\n", + plugin_name, num_enabled_seeds); + if (0 < num_disabled_seeds) + out.print("%s: protected %d seed types from cooking\n", + plugin_name, num_disabled_seeds); } return CR_OK; } + +///////////////////////////////////////////////////// +// cycle logic +// + +static void do_cycle(color_ostream &out, int32_t *num_enabled_seed_types, int32_t *num_disabled_seed_types) { + DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + for (df::building_nest_boxst *nb : world->buildings.other.NEST_BOX) { + bool fertile = false; + if (nb->claimed_by != -1) { + df::unit *u = df::unit::find(nb->claimed_by); + if (u && u->pregnancy_timer > 0) + fertile = true; + } + for (auto &contained_item : nb->contained_items) { + df::item *item = contained_item->item; + if (item->flags.bits.forbid != fertile) { + item->flags.bits.forbid = fertile; + out.print("%d eggs %s.\n", item->getStackSize(), fertile ? "forbidden" : "unforbidden"); + } + } + } +} From 88860f21ec097e458f22d1548dfed7ab2e3351af Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 08:46:32 -0800 Subject: [PATCH 32/40] add defocusable attribute to ZScreen --- docs/changelog.txt | 1 + docs/dev/Lua API.rst | 6 ++++++ library/lua/gui.lua | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a78b14fc7..683db6359 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -68,6 +68,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `helpdb`: changed from auto-refreshing every 60 seconds to only refreshing on explicit call to ``helpdb.refresh()``. docs very rarely change during a play session, and the automatic database refreshes were slowing down the startup of `gui/launcher` - ``widgets.Label``: ``label.scroll()`` now understands ``home`` and ``end`` keywords for scrolling to the top or bottom - ``dfhack.units.getCitizens()``: gets a list of citizens +- ``gui.ZScreen``: new attribute: ``defocusable`` for controlling whether a window loses keyboard focus when the map is clicked ## Removed - `autohauler`: no plans to port to v50, as it just doesn't make sense with the new work detail system diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 658893ec1..dc33438d7 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -4187,6 +4187,12 @@ ZScreen provides the following functions: ZScreen subclasses can set the following attributes: +* ``defocusable`` (default: ``true``) + + Whether the ZScreen loses keyboard focus when the player clicks on an area + of the screen other than the tool window. If the player clicks on a different + ZScreen window, focus still transfers to that other ZScreen. + * ``initial_pause`` (default: ``DEFAULT_INITIAL_PAUSE``) Whether to pause the game when the ZScreen is shown. ``DEFAULT_INITIAL_PAUSE`` diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 061444f5d..b0aa35c81 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -699,6 +699,7 @@ local zscreen_inhibit_mouse_l = false ZScreen = defclass(ZScreen, Screen) ZScreen.ATTRS{ + defocusable=true, initial_pause=DEFAULT_NIL, force_pause=false, pass_pause=true, @@ -793,7 +794,7 @@ function ZScreen:onInput(keys) end if self.pass_mouse_clicks and keys._MOUSE_L_DOWN and not has_mouse then - self.defocused = true + self.defocused = self.defocusable self:sendInputToParent(keys) return elseif keys.LEAVESCREEN or keys._MOUSE_R_DOWN then From 6ae771ecb49b7a30c9ac4c4ea998dbc7ebf99b7d Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 14:01:29 -0800 Subject: [PATCH 33/40] display PAUSE FORCED instead of a pause icon if the window is forcing the game to pause. the icon looked too clickable --- docs/changelog.txt | 1 + library/lua/gui.lua | 17 +++-------------- library/lua/gui/widgets.lua | 20 -------------------- 3 files changed, 4 insertions(+), 34 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a78b14fc7..71bbd00a4 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -55,6 +55,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `getplants`: ID values will now be accepted regardless of case -@ New borders for DFHack tool windows -- tell us what you think! - `gui/control-panel`: new global hotkey: tilde (Shift-backtick on most keyboards) +- Windows now display "PAUSE FORCED" on the lower border if the tool is forcing the game to pause ## Documentation -@ Quickstart guide has been updated with info on new window behavior and how to use the control panel diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 061444f5d..9e69f961c 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -886,7 +886,7 @@ local BASE_FRAME = { title_pen = to_pen{ fg=COLOR_BLACK, bg=COLOR_GREY }, inactive_title_pen = to_pen{ fg=COLOR_GREY, bg=COLOR_BLACK }, signature_pen = to_pen{ fg=COLOR_GREY, bg=COLOR_BLACK }, - paused_pen = to_pen{tile=782, ch=216, fg=COLOR_GREY, bg=COLOR_BLACK}, + paused_pen = to_pen{fg=COLOR_RED, bg=COLOR_BLACK}, } local function make_frame(name, double_line) @@ -946,19 +946,8 @@ function paint_frame(dc,rect,style,title,inactive,pause_forced,resizable) end if pause_forced then - -- get the tiles for the activated pause symbol - local pause_texpos_ul = dfhack.screen.findGraphicsTile('INTERFACE_BITS', 18, 28) - local pause_texpos_ur = dfhack.screen.findGraphicsTile('INTERFACE_BITS', 19, 28) - local pause_texpos_ll = dfhack.screen.findGraphicsTile('INTERFACE_BITS', 18, 29) - local pause_texpos_lr = dfhack.screen.findGraphicsTile('INTERFACE_BITS', 19, 29) - if not pause_texpos_ul then - dscreen.paintTile(style.paused_pen, x2-1, y1) - else - dscreen.paintTile(style.paused_pen, x2-2, y1-1, nil, pause_texpos_ul) - dscreen.paintTile(style.paused_pen, x2-1, y1-1, nil, pause_texpos_ur) - dscreen.paintTile(style.paused_pen, x2-2, y1, nil, pause_texpos_ll) - dscreen.paintTile(style.paused_pen, x2-1, y1, nil, pause_texpos_lr) - end + dscreen.paintString(style.paused_pen or style.title_pen or pen, + x1+2, y2, ' PAUSE FORCED ') end end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 0696459cd..ce8e27ff5 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -271,26 +271,6 @@ local function Panel_on_double_click(self) Panel_update_frame(self, frame, true) end -local function panel_mouse_is_on_pause_icon(self) - local frame_rect = self.frame_rect - local x,y = dscreen.getMousePos() - return (x == frame_rect.x2-2 or x == frame_rect.x2-1) - and (y == frame_rect.y1-1 or y == frame_rect.y1) -end - -local function panel_has_pause_icon(self) - return self.parent_view and self.parent_view.force_pause -end - -function Panel:getMouseFramePos() - local x,y = Panel.super.getMouseFramePos(self) - if x then return x, y end - if panel_has_pause_icon(self) and panel_mouse_is_on_pause_icon(self) then - local frame_rect = self.frame_rect - return frame_rect.width - 3, 0 - end -end - function Panel:onInput(keys) if self.kbd_get_pos then if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R_DOWN then From 55d07a8cae86da7c05472ebb52ba5712e5f68b8a Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 14:51:46 -0800 Subject: [PATCH 34/40] actually use the tile --- library/lua/gui/widgets.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 4228bc9ad..21143f053 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -1112,7 +1112,7 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) if dc then local tile_pen = tonumber(token.tile) and to_pen{tile=token.tile} or token.tile - dc:char(nil, token.tile) + dc:char(nil, tile_pen) if token.width then dc:advance(token.width-1) end From 58be8cfd69b63b883889651578fa1a07df698003 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 17:39:32 -0800 Subject: [PATCH 35/40] support offset text in graphics mode for pens --- docs/changelog.txt | 1 + docs/dev/Lua API.rst | 6 ++++++ library/LuaApi.cpp | 2 ++ library/include/modules/Screen.h | 2 ++ library/modules/Screen.cpp | 20 ++++++++++++++++---- 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a78b14fc7..396bf89d2 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -62,6 +62,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - ``Buildings::containsTile()``: no longer takes a ``room`` parameter since that's not how rooms work anymore. If the building has extents, the extents will be checked. otherwise, the result just depends on whether the tile is within the building's bounding box. - ``Units::getCitizens()``: gets a list of citizens, which otherwise you'd have to iterate over all units the world to discover +- ``Screen::Pen``: now accepts ``top_of_text`` and ``bottom_of_text`` properties to support offset text in graphics mode ## Lua - `helpdb`: new function: ``helpdb.refresh()`` to force a refresh of the database. Call if you are a developer adding new scripts, loading new plugins, or changing help text during play diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 658893ec1..9e8604b15 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -2292,6 +2292,12 @@ a table with the following possible fields: ``write_to_lower`` If set to true, the specified ``tile`` will be written to the background instead of the foreground. + ``top_of_text`` + If set to true, the specified ``tile`` will have the top half of the specified + ``ch`` character superimposed over the lower half of the tile. + ``bottom_of_text`` + If set to true, the specified ``tile`` will have the bottom half of the specified + ``ch`` character superimposed over the top half of the tile. Alternatively, it may be a pre-parsed native object with the following API: diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 699c8aef7..1b674e0e8 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -222,6 +222,8 @@ static void decode_pen(lua_State *L, Pen &pen, int idx) get_bool_field(L, &pen.keep_lower, idx, "keep_lower", false); get_bool_field(L, &pen.write_to_lower, idx, "write_to_lower", false); + get_bool_field(L, &pen.top_of_text, idx, "top_of_text", false); + get_bool_field(L, &pen.bottom_of_text, idx, "bottom_of_text", false); } /************************************************** diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 48f34888f..0f0afd6e2 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -86,6 +86,8 @@ namespace DFHack bool write_to_lower = false; bool keep_lower = false; + bool top_of_text = false; + bool bottom_of_text = false; bool valid() const { return tile >= 0; } bool empty() const { return ch == 0 && tile == 0; } diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 5627c08da..13dbcb204 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -155,14 +155,18 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map) long *texpos_lower = &gps->screentexpos_lower[index]; uint32_t *flag = &gps->screentexpos_flag[index]; + // keep SCREENTEXPOS_FLAG_ANCHOR_SUBORDINATE so occluded anchored textures + // don't appear corrupted + uint32_t flag_mask = 0x4; + if (pen.write_to_lower) + flag_mask |= 0x18; + *screen = 0; *texpos = 0; if (!pen.keep_lower) *texpos_lower = 0; gps->screentexpos_anchored[index] = 0; - // keep SCREENTEXPOS_FLAG_ANCHOR_SUBORDINATE so occluded anchored textures - // don't appear corrupted - *flag &= 4; + *flag &= flag_mask; if (gps->top_in_use) { screen = &gps->screen_top[index * 8]; @@ -175,7 +179,7 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map) if (!pen.keep_lower) *texpos_lower = 0; gps->screentexpos_top_anchored[index] = 0; - *flag &= 4; // keep SCREENTEXPOS_FLAG_ANCHOR_SUBORDINATE + *flag &= flag_mask; } uint8_t fg = pen.fg | (pen.bold << 3); @@ -196,6 +200,14 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map) *texpos_lower = pen.tile; else *texpos = pen.tile; + + if (pen.top_of_text || pen.bottom_of_text) { + screen[0] = uint8_t(pen.ch); + if (pen.top_of_text) + *flag |= 0x8; + if (pen.bottom_of_text) + *flag |= 0x10; + } } else if (pen.ch) { screen[0] = uint8_t(pen.ch); *texpos_lower = 909; // basic black background From 648f2649062aa20d68170f6ffacd23507fd28a02 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 2 Feb 2023 01:55:29 +0000 Subject: [PATCH 36/40] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 936051185..e57e82a4c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 9360511856f3f85664c39793a8c037b8d0ce5c53 +Subproject commit e57e82a4c059a197e775f14e0fec9946fe50eddd From ca2078c62d5b0c3a3a3011e6f14cf532c3c34da9 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 2 Feb 2023 05:00:25 +0000 Subject: [PATCH 37/40] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index e57e82a4c..594bb2394 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e57e82a4c059a197e775f14e0fec9946fe50eddd +Subproject commit 594bb2394cf670b5ac0d7f414a32cf48ed86b26d From 5a1c3c7aa81d0ef25bd422fc53412f9d20da1637 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 22:30:56 -0800 Subject: [PATCH 38/40] remove unused vars copypastad from seedwatch --- plugins/nestboxes.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/plugins/nestboxes.cpp b/plugins/nestboxes.cpp index 8feee65f7..98becf170 100644 --- a/plugins/nestboxes.cpp +++ b/plugins/nestboxes.cpp @@ -51,7 +51,7 @@ static void set_config_bool(PersistentDataItem &c, int index, bool value) { static const int32_t CYCLE_TICKS = 100; // need to react quickly if eggs are unforbidden static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle -static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds, int32_t *num_disabled_seeds); +static void do_cycle(color_ostream &out); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { DEBUG(config,out).print("initializing %s\n", plugin_name); @@ -112,16 +112,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) { - int32_t num_enabled_seeds, num_disabled_seeds; - do_cycle(out, &num_enabled_seeds, &num_disabled_seeds); - if (0 < num_enabled_seeds) - out.print("%s: enabled %d seed types for cooking\n", - plugin_name, num_enabled_seeds); - if (0 < num_disabled_seeds) - out.print("%s: protected %d seed types from cooking\n", - plugin_name, num_disabled_seeds); - } + if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) + do_cycle(out); return CR_OK; } @@ -129,7 +121,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { // cycle logic // -static void do_cycle(color_ostream &out, int32_t *num_enabled_seed_types, int32_t *num_disabled_seed_types) { +static void do_cycle(color_ostream &out) { DEBUG(cycle,out).print("running %s cycle\n", plugin_name); // mark that we have recently run From 75bb67cfc4735084b848039a15996bd0198fc555 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 2 Feb 2023 07:14:31 +0000 Subject: [PATCH 39/40] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 594bb2394..a2ab2e52a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 594bb2394cf670b5ac0d7f414a32cf48ed86b26d +Subproject commit a2ab2e52aa1c96ba9d9f13e72c5749d451ae81f9 From 7d05a68c601ed3a2ff3549af39b41acdb4c18d1e Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 2 Feb 2023 12:53:20 -0800 Subject: [PATCH 40/40] clean up changelog, update refs --- docs/changelog.txt | 38 ++++++++++++++++++++++---------------- library/xml | 2 +- scripts | 2 +- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index d1155bea2..93a6586f9 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,46 +34,52 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins -- `autoslab`: Automatically create work orders to engrave slabs for ghostly dwarves. +- `autoslab`: automatically create work orders to engrave slabs for ghostly dwarves ## Fixes --@ DF screens can no longer get "stuck" on transitions when DFHack tool windows are visible. Instead, those DF screens are force-paused while DFHack windows are visible so the player can close them first and not corrupt the screen sequence. The "force pause" indicator will appear on these DFHack windows to indicate what is happening. --@ ``Screen``: allow `gui/launcher` and `gui/quickcmd` to launch themselves without hanging the game --@ Fix issues with clicks "passing through" some DFHack window elements, like scrollbars +-@ DF screens can no longer get "stuck" on transitions when DFHack tool windows are visible. Instead, those DF screens are force-paused while DFHack windows are visible so the player can close them first and not corrupt the screen sequence. The "PAUSE FORCED" indicator will appear on these DFHack windows to indicate what is happening. +-@ allow launcher tools to launch themselves without hanging the game +-@ fix issues with clicks "passing through" some DFHack window elements to the screen below - `getplants`: trees are now designated correctly --@ Sample orders: fix orders that create bags +-@ `orders`: fix orders in library/basic that create bags +- `orders`: library/military now sticks to vanilla rules and does not add orders for normally-mood-only platinum weapons. A new library orders file ``library/military_include_artifact_materials`` is now offered as an alternate ``library/military`` set of orders that still includes the platinum weapons. ## Misc Improvements -- A new cross-compile build script was added for building DFHack for Windows from a Linux Docker builder (see the `compile` instructions in the docs) -- You can now configure whether DFHack tool windows should pause the game by default -- `hotkeys`: clicking on the DFHack logo no longer closes the popup menu -- `nestboxes`: now saves enabled state in your savegame -- `gui/launcher`: sped up initialization time for faster load of the UI -- `orders`: orders plugin functionality is now offered via an overlay widget when the manager orders screen is open -- `gui/quickcmd`: now has its own global keybinding for your convenience: Ctrl-Shift-A -- Many DFHack windows can now be unfocused by clicking somewhere not over the tool window. This has the same effect as pinning previously did, but without the extra clicking. -- `overlay`: overlay widgets can now specify a default enabled state if they are not already set in the player's overlay config file +- DFHack windows can now be "defocused" by clicking somewhere not over the tool window. This has the same effect as pinning previously did, but without the extra clicking. - `getplants`: ID values will now be accepted regardless of case +- Windows now display "PAUSE FORCED" on the lower border if the tool is forcing the game to pause -@ New borders for DFHack tool windows -- tell us what you think! +- `automelt`: stockpile configuration can now be set from the commandline +- `channel-safely`: new monitoring for cave-in prevention +- `gui/control-panel`: you can now configure whether DFHack tool windows should pause the game by default - `gui/control-panel`: new global hotkey: tilde (Shift-backtick on most keyboards) -- Windows now display "PAUSE FORCED" on the lower border if the tool is forcing the game to pause +- `hotkeys`: clicking on the DFHack logo no longer closes the popup menu +- `nestboxes`: now saves enabled state in your savegame +- `gui/launcher`: sped up initialization time for faster window appearance +- `orders`: orders plugin functionality is now accessible via an `overlay` widget when the manager orders screen is open +- `gui/quickcmd`: now has its own global keybinding for your convenience: Ctrl-Shift-A - `seedwatch`: now persists enabled state in the savegame, automatically loads useful defaults, and respects reachability when counting available seeds +- `quickfort`: planned buildings are now properly attached to any pertinent overlapping zones ## Documentation +- `compile`: instructions added for cross-compiling DFHack for Windows from a Linux Docker builder -@ Quickstart guide has been updated with info on new window behavior and how to use the control panel ## API - ``Buildings::containsTile()``: no longer takes a ``room`` parameter since that's not how rooms work anymore. If the building has extents, the extents will be checked. otherwise, the result just depends on whether the tile is within the building's bounding box. - ``Units::getCitizens()``: gets a list of citizens, which otherwise you'd have to iterate over all units the world to discover - ``Screen::Pen``: now accepts ``top_of_text`` and ``bottom_of_text`` properties to support offset text in graphics mode +- `overlay`: overlay widgets can now specify a default enabled state if they are not already set in the player's overlay config file +- ``Lua::Push``: now supports ``std::unordered_map`` ## Lua - `helpdb`: new function: ``helpdb.refresh()`` to force a refresh of the database. Call if you are a developer adding new scripts, loading new plugins, or changing help text during play -- `helpdb`: changed from auto-refreshing every 60 seconds to only refreshing on explicit call to ``helpdb.refresh()``. docs very rarely change during a play session, and the automatic database refreshes were slowing down the startup of `gui/launcher` +- `helpdb`: changed from auto-refreshing every 60 seconds to only refreshing on explicit call to ``helpdb.refresh()``. docs very rarely change during a play session, and the automatic database refreshes were slowing down the startup of `gui/launcher` and anything else that displays help text. - ``widgets.Label``: ``label.scroll()`` now understands ``home`` and ``end`` keywords for scrolling to the top or bottom - ``dfhack.units.getCitizens()``: gets a list of citizens - ``gui.ZScreen``: new attribute: ``defocusable`` for controlling whether a window loses keyboard focus when the map is clicked - ``Label``: token ``tile`` properties can now be either pens or numeric texture ids +- `tiletypes`: now has a Lua API! ``tiletypes_setTile`` ## Removed - `autohauler`: no plans to port to v50, as it just doesn't make sense with the new work detail system diff --git a/library/xml b/library/xml index 518c1a431..e53321478 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 518c1a431dcfa0ca23110d94223973853d640a11 +Subproject commit e5332147871bbf3293931d3ea268ec30b1023297 diff --git a/scripts b/scripts index a2ab2e52a..8c7b3ed7a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a2ab2e52aa1c96ba9d9f13e72c5749d451ae81f9 +Subproject commit 8c7b3ed7a9ebcd1988fd6381bc943cd86bd2a3f8