From 37b5be1f357cb27ada067a3847b592fd4cf70904 Mon Sep 17 00:00:00 2001 From: John Cosker Date: Fri, 27 Jan 2023 19:46:56 -0500 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 3f9f7855890add716034a5fc576126d3d8262db7 Mon Sep 17 00:00:00 2001 From: John Cosker Date: Sun, 29 Jan 2023 21:34:47 -0500 Subject: [PATCH 6/7] 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 7/7] 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 -----