From 22414f26fa0035b85e5946010eb3eb01a4e5cf16 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 6 Nov 2022 00:12:35 -0700 Subject: [PATCH 01/22] Implements plugin: channel-safely v0.1 --- docs/plugins/channel-safely.rst | 62 +++ plugins/CMakeLists.txt | 1 + plugins/channel-safely/CMakeLists.txt | 10 + plugins/channel-safely/channel-groups.cpp | 253 +++++++++ plugins/channel-safely/channel-jobs.cpp | 46 ++ plugins/channel-safely/channel-manager.cpp | 99 ++++ .../channel-safely/channel-safely-plugin.cpp | 509 ++++++++++++++++++ .../channel-safely/include/channel-groups.h | 49 ++ plugins/channel-safely/include/channel-jobs.h | 29 + .../channel-safely/include/channel-manager.h | 39 ++ plugins/channel-safely/include/inlines.h | 149 +++++ plugins/channel-safely/include/plugin.h | 23 + 12 files changed, 1269 insertions(+) create mode 100644 docs/plugins/channel-safely.rst create mode 100644 plugins/channel-safely/CMakeLists.txt create mode 100644 plugins/channel-safely/channel-groups.cpp create mode 100644 plugins/channel-safely/channel-jobs.cpp create mode 100644 plugins/channel-safely/channel-manager.cpp create mode 100644 plugins/channel-safely/channel-safely-plugin.cpp create mode 100644 plugins/channel-safely/include/channel-groups.h create mode 100644 plugins/channel-safely/include/channel-jobs.h create mode 100644 plugins/channel-safely/include/channel-manager.h create mode 100644 plugins/channel-safely/include/inlines.h create mode 100644 plugins/channel-safely/include/plugin.h diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst new file mode 100644 index 000000000..6010f4925 --- /dev/null +++ b/docs/plugins/channel-safely.rst @@ -0,0 +1,62 @@ +channel-safely +============== + +.. dfhack-tool:: + :summary: Auto-manage channel designations to keep dwarves safe + :tags: fort auto + +Multi-level channel projects can be dangerous, and managing the safety of your +dwarves throughout the completion of such projects can be difficult and time +consuming. This plugin keeps your dwarves safe (while channeling) so you don't +have to. Now you can focus on designing your dwarven cities with the deep chasms +they were meant to have. + +Usage +----- + +:: + enable channel-safely + channel-safely set + channel-safely enable|disable + channel-safely run once + +When enabled the map will be scanned for channel designations which will be grouped +together based on adjacency and z-level. These groups will then be analyzed for safety +and designations deemed unsafe will be put into :wiki:`Marker Mode `. +Each time a channel designation is completed its group status is checked, and if the group +is complete pending groups below are made active again. + +Examples +-------- + +``channel-safely`` + The plugin reports its configured status. + +``channel-safely run once`` + Runs the safety procedures once. You can use this if you prefer initiating scans manually. + +``channel-safely disable require-vision`` + Allows the plugin to read all tiles, including the ones your dwarves know nothing about. + +``channel-safely enable monitor-active`` + Enables monitoring active channel digging jobs. Meaning that if another unit it present + or the tile below becomes open space the job will be paused or canceled (respectively). + +``channel-safely set ignore-threshold 3`` + Configures the plugin to ignore designations equal to or above priority 3 designations. + +Features +-------- +:monitor-active: Toggle whether to monitor the conditions of active digs. (default: disabled) +:require-vision: Toggle whether the dwarves need vision of a tile before channeling to it can be deemed unsafe. (default: enabled) +:insta-dig: Toggle whether to use insta-digging on unreachable designations. (default: disabled) + +Settings +-------- +:refresh-freq: The rate at which full refreshes are performed. + This can be expensive if you're undertaking many mega projects. (default:600, twice a day) +:monitor-freq: The rate at which active jobs are monitored. + todo: this can have a massive impact? (default:10) +:ignore-threshold: Sets the priority threshold below which designations are processed. You can set to 1 or 0 to + effectively disable the scanning. (default: 7) +:fall-threshold: Sets the fall threshold beyond which is considered unsafe. (default: 1) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9038d5d79..c3cd88479 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -100,6 +100,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(changeitem changeitem.cpp) dfhack_plugin(changelayer changelayer.cpp) dfhack_plugin(changevein changevein.cpp) + add_subdirectory(channel-safely) dfhack_plugin(cleanconst cleanconst.cpp) dfhack_plugin(cleaners cleaners.cpp) dfhack_plugin(cleanowned cleanowned.cpp) diff --git a/plugins/channel-safely/CMakeLists.txt b/plugins/channel-safely/CMakeLists.txt new file mode 100644 index 000000000..d660d2262 --- /dev/null +++ b/plugins/channel-safely/CMakeLists.txt @@ -0,0 +1,10 @@ +project(channel-safely) + +include_directories(include) +SET(SOURCES + channel-jobs.cpp + channel-groups.cpp + channel-manager.cpp + channel-safely-plugin.cpp) + +dfhack_plugin(${PROJECT_NAME} ${SOURCES} LINK_LIBRARIES lua) diff --git a/plugins/channel-safely/channel-groups.cpp b/plugins/channel-safely/channel-groups.cpp new file mode 100644 index 000000000..7ee779df8 --- /dev/null +++ b/plugins/channel-safely/channel-groups.cpp @@ -0,0 +1,253 @@ +#include +#include +#include +#include + +#include + +// scans the map for channel designations +void ChannelGroups::scan_map() { + static std::default_random_engine RNG(0); + static std::bernoulli_distribution optimizing(0.3333); + DEBUG(groups).print(" scan_map()\n"); + // foreach block + for (int32_t z = mapz - 1; z >= 0; --z) { + for (int32_t by = 0; by < mapy; ++by) { + for (int32_t bx = 0; bx < mapx; ++bx) { + // the block + if (df::map_block* block = Maps::getBlock(bx, by, z)) { + // skip this block? + if (!block->flags.bits.designated && optimizing(RNG)) { + // todo: add remainder of block width onto bx + TRACE(groups).print(" skipping this block, it has no designations\n"); + continue; + } + // foreach tile + for (int16_t lx = 0; lx < 16; ++lx) { + for (int16_t ly = 0; ly < 16; ++ly) { + // the tile, check if it has a channel designation + if (is_dig_designation(block->designation[lx][ly])) { + for (df::block_square_event* event: block->block_events) { + if (auto evT = virtual_cast(event)) { + // we want to let the user keep some designations free of being managed + TRACE(groups).print(" tile designation priority: %d\n", evT->priority[lx][ly]); + if (evT->priority[lx][ly] < 1000 * config.ignore_threshold) { + df::coord map_pos((bx * 16) + lx, (by * 16) + ly, z); + TRACE(groups).print(" adding (" COORD ")\n", COORDARGS(map_pos)); + add(map_pos); + } + } + } + } + } + } + } + } + } + } + INFO(groups).print("scan_map() exits\n"); +} + +// scans a single tile for channel designations +void ChannelGroups::scan_one(const df::coord &map_pos) { + df::map_block* block = Maps::getTileBlock(map_pos); + int16_t lx = map_pos.x % 16; + int16_t ly = map_pos.y % 16; + if (is_dig_designation(block->designation[lx][ly])) { + for (df::block_square_event* event: block->block_events) { + if (auto evT = virtual_cast(event)) { + // we want to let the user keep some designations free of being managed + if (evT->priority[lx][ly] < 1000 * config.ignore_threshold) { + TRACE(groups).print(" adding (" COORD ")\n", COORDARGS(map_pos)); + add(map_pos); + } + } + } + } +} + +// adds map_pos to a group if an adjacent one exists, or creates one if none exist... if multiple exist they're merged into the first found +void ChannelGroups::add(const df::coord &map_pos) { + // if we've already added this, we don't need to do it again + if (groups_map.count(map_pos)) { + return; + } + /* We need to add map_pos to an existing group if possible... + * So what we do is we look at neighbours to see if they belong to one or more existing groups + * If there is more than one group, we'll be merging them + */ + df::coord neighbors[8]; + get_neighbours(map_pos, neighbors); + Group* group = nullptr; + int group_index = -1; + + DEBUG(groups).print(" add(" COORD ")\n", COORDARGS(map_pos)); + // and so we begin iterating the neighbours + for (auto &neighbour: neighbors) { + // go to the next neighbour if this one doesn't have a group + if (!groups_map.count(neighbour)) { + TRACE(groups).print(" -> neighbour is not designated\n"); + continue; + } + // get the group, since at least one exists... then merge any additional into that one + if (!group){ + TRACE(groups).print(" -> group* has no valid state yet\n"); + group_index = groups_map.find(neighbour)->second; + group = &groups.at(group_index); + } else { + TRACE(groups).print(" -> group* has an existing state\n"); + + // we don't do anything if the found group is the same as the existing group + auto index2 = groups_map.find(neighbour)->second; + if (group_index != index2) { + // we already have group "prime" if you will, so we're going to merge the new find into prime + Group &group2 = groups.at(index2); + // merge + TRACE(groups).print(" -> merging two groups. group 1 size: %zu. group 2 size: %zu\n", group->size(), + group2.size()); + for (auto pos2: group2) { + group->emplace(pos2); + groups_map[pos2] = group_index; + } + group2.clear(); + free_spots.emplace(index2); + TRACE(groups).print(" merged size: %zu\n", group->size()); + } + } + } + // if we haven't found at least one group by now we need to create/get one + if (!group) { + TRACE(groups).print(" -> no merging took place\n"); + // first we check if we can re-use a group that's been freed + if (!free_spots.empty()) { + TRACE(groups).print(" -> use recycled old group\n"); + // first element in a set is always the lowest value, so we re-use from the front of the vector + group_index = *free_spots.begin(); + group = &groups[group_index]; + free_spots.erase(free_spots.begin()); + } else { + TRACE(groups).print(" -> brand new group\n"); + // we create a brand-new group to use + group_index = groups.size(); + groups.push_back(Group()); + group = &groups[group_index]; + } + } + // puts the "add" in "ChannelGroups::add" + group->emplace(map_pos); + DEBUG(groups).print(" = group[%d] of (" COORD ") is size: %zu\n", group_index, COORDARGS(map_pos), group->size()); +// ERR(groups).print("\n\n\nDEBUG MAPPINGS:\n"); +// debug_map(); +// DEBUG(groups).flush(); + + // we may have performed a merge, so we update all the `coord -> group index` mappings + for (auto &wpos: *group) { + groups_map[wpos] = group_index; + } + DEBUG(groups).print(" <- add() exits, there are %zu mappings\n", groups_map.size()); +// ERR(groups).print("\n\n\nDEBUG MAPPINGS:\n"); +// debug_map(); +// DEBUG(groups).flush(); +} + +// builds groupings of adjacent channel designations +void ChannelGroups::build() { + clear(); + // iterate over each job, finding channel jobs + jobs.load_channel_jobs(); + // transpose channel jobs to + for (auto &pos : jobs) { + add(pos); + } + scan_map(); +} + +// clears out the containers for unloading maps or disabling the plugin +void ChannelGroups::clear() { + debug_map(); + WARN(groups).print(" <- clearing groups\n"); + free_spots.clear(); + groups_map.clear(); + for(size_t i = 0; i < groups.size(); ++i) { + groups[i].clear(); + free_spots.emplace(i); + } +} + +// erases map_pos from its group, and deletes mappings IFF the group is empty +void ChannelGroups::remove(const df::coord &map_pos) { + // we don't need to do anything if the position isn't in a group (granted, that should never be the case) + INFO(groups).print(" remove()\n"); + if (groups_map.count(map_pos)) { + INFO(groups).print(" -> found group\n"); + // get the group, and map_pos' block* + int group_index = groups_map.find(map_pos)->second; + Group &group = groups[group_index]; + // erase map_pos from the group + INFO(groups).print(" -> erase(" COORD ")\n", COORDARGS(map_pos)); + group.erase(map_pos); + groups_map.erase(map_pos); + // clean up if the group is empty + if (group.empty()) { + WARN(groups).print(" -> group is empty\n"); + // erase `coord -> group group_index` mappings + for (auto iter = groups_map.begin(); iter != groups_map.end();) { + if (group_index == iter->second) { + iter = groups_map.erase(iter); + continue; + } + ++iter; + } + // flag the `groups` group_index as available + free_spots.insert(group_index); + } + } + INFO(groups).print(" remove() exits\n"); +} + +// finds a group corresponding to a map position if one exists +Groups::const_iterator ChannelGroups::find(const df::coord &map_pos) const { + const auto iter = groups_map.find(map_pos); + if (iter != groups_map.end()) { + return groups.begin() + iter->second; + } + return groups.end(); +} + +// returns an iterator to the first element stored +Groups::const_iterator ChannelGroups::begin() const { + return groups.begin(); +} + +// returns an iterator to after the last element stored +Groups::const_iterator ChannelGroups::end() const { + return groups.end(); +} + +// returns a count of 0 or 1 depending on whether map_pos is mapped to a group +size_t ChannelGroups::count(const df::coord &map_pos) const { + return groups_map.count(map_pos); +} + +// prints debug info about the groups stored, and their members +void ChannelGroups::debug_groups() { +// int idx = 0; +// TRACE(groups).print(" debugging group data\n"); +// for (auto &group : groups) { +// TRACE(groups).print(" group %d (size: %zu)\n", idx, group.size()); +// for (auto &pos : group) { +// TRACE(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z); +// } +// idx++; +// } +} + +// prints debug info group mappings +void ChannelGroups::debug_map() { +// INFO(groups).print("Group Mappings: %zu\n", groups_map.size()); +// for (auto &pair : groups_map) { +// DEBUG(groups).print(" map[" COORD "] = %d\n",COORDARGS(pair.first), pair.second); +// } +} + + diff --git a/plugins/channel-safely/channel-jobs.cpp b/plugins/channel-safely/channel-jobs.cpp new file mode 100644 index 000000000..7a1c2f4be --- /dev/null +++ b/plugins/channel-safely/channel-jobs.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +// iterates the DF job list and adds channel jobs to the `jobs` container +void ChannelJobs::load_channel_jobs() { + jobs.clear(); + df::job_list_link* node = df::global::world->jobs.list.next; + while (node) { + df::job* job = node->item; + node = node->next; + if (is_dig_job(job)) { + jobs.emplace(job->pos); + } + } +} + +// clears the container +void ChannelJobs::clear() { + jobs.clear(); +} + +// finds and erases a job corresponding to a map position, then returns the iterator following the element removed +std::set::iterator ChannelJobs::erase(const df::coord &map_pos) { + auto iter = jobs.find(map_pos); + if (iter != jobs.end()) { + return jobs.erase(iter); + } + return iter; +} + +// finds a job corresponding to a map position if one exists +std::set::const_iterator ChannelJobs::find(const df::coord &map_pos) const { + return jobs.find(map_pos); +} + +// returns an iterator to the first element stored +std::set::const_iterator ChannelJobs::begin() const { + return jobs.begin(); +} + +// returns an iterator to after the last element stored +std::set::const_iterator ChannelJobs::end() const { + return jobs.end(); +} diff --git a/plugins/channel-safely/channel-manager.cpp b/plugins/channel-safely/channel-manager.cpp new file mode 100644 index 000000000..79bc6a7aa --- /dev/null +++ b/plugins/channel-safely/channel-manager.cpp @@ -0,0 +1,99 @@ +#include +#include + +#include + +/** +blocks[48][96][135]: +blocks[48][96][135].default_liquid.hidden: false +blocks[48][96][135].designation[10][0].hidden: false + * */ + +// sets mark flags as necessary, for all designations +void ChannelManager::manage_all() { + INFO(manager).print("manage_all()\n"); + // make sure we've got a fort map to analyze + if (World::isFortressMode() && Maps::IsValid()) { + // iterate the groups we built/updated + for (const auto &group: groups) { + manage_group(group, true, has_any_groups_above(groups, group)); + } + } +} + +void ChannelManager::manage_group(const df::coord &map_pos, bool set_marker_mode, bool marker_mode) { + INFO(manager).print("manage_group(" COORD ")\n ", COORDARGS(map_pos)); + if (!groups.count(map_pos)) { + groups.scan_one(map_pos); + } + auto iter = groups.find(map_pos); + if (iter != groups.end()) { + manage_group(*iter, set_marker_mode, marker_mode); + } + INFO(manager).print("manage_group() is done\n"); +} + +void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool marker_mode) { + INFO(manager).print("manage_group()\n"); + if (!set_marker_mode) { + if (has_any_groups_above(groups, group)) { + marker_mode = true; + } else { + marker_mode = false; + } + } + for (auto &designation: group) { + manage_one(group, designation, true, marker_mode); + } + INFO(manager).print("manage_group() is done\n"); +} + +bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bool set_marker_mode, bool marker_mode) { + if (Maps::isValidTilePos(map_pos)) { + INFO(manager).print("manage_one(" COORD ")\n", COORDARGS(map_pos)); + df::map_block* block = Maps::getTileBlock(map_pos); + // we calculate the position inside the block* + df::coord local(map_pos); + local.x = local.x % 16; + local.y = local.y % 16; + df::tile_occupancy &tile_occupancy = block->occupancy[Coord(local)]; + // ensure that we aren't on the top-most layers + if (map_pos.z < mapz - 3) { + // do we already know whether to set marker mode? + if (set_marker_mode) { + DEBUG(manager).print(" -> marker_mode\n"); + tile_occupancy.bits.dig_marked = marker_mode; + jobs.erase(map_pos); + return true; + } else { + // next search for the designation priority + for (df::block_square_event* event: block->block_events) { + if (auto evT = virtual_cast(event)) { + // we want to let the user keep some designations free of being managed + if (evT->priority[Coord(local)] < 1000 * config.ignore_threshold) { + DEBUG(manager).print(" if(has_groups_above())\n"); + // check that the group has no incomplete groups directly above it + if (has_group_above(groups, map_pos)) { + DEBUG(manager).print(" has_groups_above: setting marker mode\n"); + tile_occupancy.bits.dig_marked = true; + jobs.erase(map_pos); + WARN(manager).print(" <- manage_one() exits normally\n"); + return true; + } + } + } + } + } + } else { + // if we are though, it should be totally safe to dig + tile_occupancy.bits.dig_marked = false; + } + WARN(manager).print(" <- manage_one() exits normally\n"); + } + return false; +} + +void ChannelManager::mark_done(const df::coord &map_pos) { + groups.remove(map_pos); + jobs.erase(map_pos); +} diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp new file mode 100644 index 000000000..e6cdf231b --- /dev/null +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -0,0 +1,509 @@ +/* Prevent channeling down into known open space. +Author: Josh Cooper +Created: Aug. 4 2020 +Updated: Nov. 1 2022 + + Enable plugin: + -> build groups + -> manage designations + + Unpause event: + -> build groups + -> manage designations + + Manage Designation(s): + -> for each group in groups: + -> for each designation in this group: + -> + + + Job started event: + -> validate job type (channel) + -> check pathing: + -> Can: add job/worker to tracking + -> Can: set tile to restricted + -> Cannot: remove worker + -> Cannot: insta-dig & delete job + -> Cannot: set designation to Marker Mode (no insta-digging) + + OnUpdate: + -> check worker location: + -> CanFall: check if a fall would be safe: + -> Safe: do nothing + -> Unsafe: remove worker + -> Unsafe: insta-dig & delete job (presumes the job is only accessible from directly on the tile) + -> Unsafe: set designation to Marker Mode (no insta-digging) + -> check tile occupancy: + -> HasUnit: check if a fall would be safe: + -> Safe: do nothing, let them fall + -> Unsafe: remove worker for 1 tick (test if this "pauses" or cancels the job) + -> Unsafe: Add feature to teleport unit? + + Job completed event: + -> validate job type (channel) + -> verify completion: + -> IsOpenSpace: mark done + -> IsOpenSpace: manage tile below + -> NotOpenSpace: check for designation + -> HasDesignation: do nothing + -> NoDesignation: mark done (erases from group) + -> NoDesignation: manage tile below +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +// Debugging +namespace DFHack { + DBG_DECLARE(channelsafely, monitor, DebugCategory::LINFO); + DBG_DECLARE(channelsafely, manager, DebugCategory::LINFO); + DBG_DECLARE(channelsafely, groups, DebugCategory::LINFO); + DBG_DECLARE(channelsafely, jobs, DebugCategory::LINFO); +} + +DFHACK_PLUGIN("channel-safely"); +DFHACK_PLUGIN_IS_ENABLED(enabled); +REQUIRE_GLOBAL(world); + +namespace EM = EventManager; +using namespace DFHack; +using namespace EM::EventType; + +int32_t mapx, mapy, mapz; +Configuration config; +PersistentDataItem pconfig; +const std::string CONFIG_KEY = std::string(plugin_name) + "/config"; +//std::unordered_set active_jobs; + +#include + +enum ConfigurationData { + MONITOR, + VISION, + INSTADIG, + IGNORE_THRESH, + FALL_THRESH, + REFRESH_RATE, + MONITOR_RATE +}; + +inline void saveConfig() { + if (pconfig.isValid()) { + pconfig.ival(MONITOR) = config.monitor_active; + pconfig.ival(VISION) = config.require_vision; + pconfig.ival(INSTADIG) = config.insta_dig; + pconfig.ival(REFRESH_RATE) = config.refresh_freq; + pconfig.ival(MONITOR_RATE) = config.monitor_freq; + pconfig.ival(IGNORE_THRESH) = config.ignore_threshold; + pconfig.ival(FALL_THRESH) = config.fall_threshold; + } +} + +// executes dig designations for the specified tile coordinates +inline bool dig_now(color_ostream &out, const df::coord &map_pos) { + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 2) || + !Lua::PushModulePublic(out, L, "plugins.dig-now", "dig_now_tile")) + return false; + + Lua::Push(L, map_pos); + + if (!Lua::SafeCall(out, L, 1, 1)) + return false; + + return lua_toboolean(L, -1); + +} + +namespace CSP { + std::unordered_map active_workers; + std::unordered_map last_safe; + std::unordered_set dignow_queue; + + void UnpauseEvent(){ + INFO(monitor).print("UnpauseEvent()\n"); + ChannelManager::Get().build_groups(); + INFO(monitor).print("after building groups\n"); + ChannelManager::Get().debug(); + ChannelManager::Get().manage_all(); + INFO(monitor).print("UnpauseEvent() exits\n"); + ChannelManager::Get().debug(); + } + + void JobStartedEvent(color_ostream &out, void* p) { + if (config.monitor_active) { + if (enabled && World::isFortressMode() && Maps::IsValid()) { + INFO(monitor).print("JobStartedEvent()\n"); + auto job = (df::job*) p; + // validate job type + if (is_dig_job(job)) { + DEBUG(monitor).print(" valid channel job:\n"); + df::unit* worker = Job::getWorker(job); + // there is a valid worker (living citizen) on the job? right.. + if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) { + DEBUG(monitor).print(" valid worker:\n"); + df::coord local(job->pos); + local.x = local.x % 16; + local.y = local.y % 16; + // check pathing exists to job + if (Maps::canWalkBetween(worker->pos, job->pos)) { + DEBUG(monitor).print(" can path from (" COORD ") to (" COORD ")\n", + COORDARGS(worker->pos), COORDARGS(job->pos)); + // track workers on jobs + active_workers.emplace(job->id, Units::findIndexById(Job::getWorker(job)->id)); + // set tile to restricted + TRACE(monitor).print(" setting job tile to restricted\n"); + Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Restricted; + } else { + DEBUG(monitor).print(" no path exists to job:\n"); + // if we can't get there, then we should remove the worker and cancel the job (restore tile designation) + Job::removeWorker(job); + cancel_job(job); + if (!config.insta_dig) { + TRACE(monitor).print(" setting marker mode for (" COORD ")\n", COORDARGS(job->pos)); + // set to marker mode + auto occupancy = Maps::getTileOccupancy(job->pos); + if (!occupancy) { + WARN(monitor).print(" Could not acquire tile occupancy*\n"); + return; + } + occupancy->bits.dig_marked = true; + // prevent algorithm from re-enabling designation + df::map_block* block = Maps::getTileBlock(job->pos); + if (!block) { + WARN(monitor).print(" Could not acquire block*\n"); + return; + } + for (auto &be: block->block_events) { ; + if (auto bsedp = virtual_cast(be)) { + TRACE(monitor).print(" re-setting priority\n"); + bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1; + } + } + } else { + TRACE(monitor).print(" deleting job, and queuing insta-dig)\n"); + // queue digging the job instantly + dignow_queue.emplace(job->pos); + } + } + } + } + INFO(monitor).print(" <- JobStartedEvent() exits normally\n"); + } + } + } + + void JobCompletedEvent(color_ostream &out, void* job_ptr) { + if (config.monitor_active) { + INFO(monitor).print("JobCompletedEvent()\n"); + if (enabled && World::isFortressMode() && Maps::IsValid()) { + auto job = (df::job*) job_ptr; + // we only care if the job is a channeling one + if (is_dig_job(job)) { + // untrack job/worker + active_workers.erase(job->id); + // check job outcome + df::coord local(job->pos); + auto block = Maps::getTileBlock(local); + local.x = local.x % 16; + local.y = local.y % 16; + // verify completion + if (isOpenTerrain(block->tiletype[local.x][local.y]) + || block->designation[local.x][local.y].bits.dig != df::enums::tile_dig_designation::Channel) { + // the job can be considered done + df::coord below(job->pos); + below.z--; + WARN(monitor).print(" -> Marking tile done and managing the group below.\n"); + // mark done and manage below + Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Normal; + ChannelManager::Get().mark_done(job->pos); + ChannelManager::Get().manage_group(below); + ChannelManager::Get().debug(); + Job::removeJob(job); + } + } + } + INFO(monitor).print("JobCompletedEvent() exits\n"); + } + } + + void OnUpdate(color_ostream &out) { + if (enabled && World::isFortressMode() && Maps::IsValid() && !World::ReadPauseState()) { + static int32_t last_monitor_tick = df::global::world->frame_counter; + static int32_t last_refresh_tick = df::global::world->frame_counter; + int32_t tick = df::global::world->frame_counter; + if (tick - last_refresh_tick >= config.refresh_freq) { + last_refresh_tick = tick; + TRACE(monitor).print("OnUpdate()\n"); + UnpauseEvent(); + } + if (config.monitor_active && tick - last_monitor_tick >= config.monitor_freq) { + last_monitor_tick = tick; + TRACE(monitor).print("OnUpdate()\n"); + for (df::job_list_link* link = &df::global::world->jobs.list; link != nullptr; link = link->next) { + df::job* job = link->item; + if (job) { + auto iter = active_workers.find(job->id); + TRACE(monitor).print(" -> check for job in tracking\n"); + if (iter != active_workers.end()) { + df::unit* unit = df::global::world->units.active[iter->second]; + TRACE(monitor).print(" -> compare positions of worker and job\n"); + // check if fall is possible + if (unit->pos == job->pos) { + // can fall, is safe? + TRACE(monitor).print(" equal -> check if safe fall\n"); + if (!is_safe_fall(job->pos)) { + // unsafe + Job::removeWorker(job); + if (config.insta_dig) { + TRACE(monitor).print(" -> insta-dig\n"); + // delete the job + Job::removeJob(job); + // queue digging the job instantly + dignow_queue.emplace(job->pos); + // worker is currently in the air + Units::teleport(unit, last_safe[unit->id]); + last_safe.erase(unit->id); + } else { + TRACE(monitor).print(" -> set marker mode\n"); + // set to marker mode + Maps::getTileOccupancy(job->pos)->bits.dig_marked = true; + // prevent algorithm from re-enabling designation + for (auto &be: Maps::getBlock(job->pos)->block_events) { ; + if (auto bsedp = virtual_cast( + be)) { + df::coord local(job->pos); + local.x = local.x % 16; + local.y = local.y % 16; + bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1; + break; + } + } + } + } + } else { + TRACE(monitor).print(" -> save safe position\n"); + // worker is perfectly safe right now + last_safe[unit->id] = unit->pos; + } + } + } + } + TRACE(monitor).print(" -> evaluate dignow queue\n"); + for (const df::coord &pos: dignow_queue) { + if (!has_unit(Maps::getTileOccupancy(pos))) { + dig_now(out, pos); + } else { + // todo: teleport? + //Units::teleport() + } + } + TRACE(monitor).print("OnUpdate() exits\n"); + } + } + } +} + +command_result channel_safely(color_ostream &out, std::vector ¶meters); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand("channel-safely", + "Automatically manage channel designations.", + channel_safely, + false)); + DBG_NAME(monitor).allowed(DFHack::DebugCategory::LERROR); + DBG_NAME(manager).allowed(DFHack::DebugCategory::LERROR); + DBG_NAME(groups).allowed(DFHack::DebugCategory::LERROR); + DBG_NAME(jobs).allowed(DFHack::DebugCategory::LERROR); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) { + EM::unregisterAll(plugin_self); + return CR_OK; +} + +DFhackCExport command_result plugin_load_data (color_ostream &out) { + pconfig = World::GetPersistentData(CONFIG_KEY); + + if (!pconfig.isValid()) { + pconfig = World::AddPersistentData(CONFIG_KEY); + saveConfig(); + } else { + config.monitor_active = pconfig.ival(MONITOR); + config.require_vision = pconfig.ival(VISION); + config.insta_dig = pconfig.ival(INSTADIG); + config.refresh_freq = pconfig.ival(REFRESH_RATE); + config.monitor_freq = pconfig.ival(MONITOR_RATE); + config.ignore_threshold = pconfig.ival(IGNORE_THRESH); + config.fall_threshold = pconfig.ival(FALL_THRESH); + } + return DFHack::CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (enable && !enabled) { + // register events to check jobs / update tracking + EM::EventHandler jobStartHandler(CSP::JobStartedEvent, 0); + EM::EventHandler jobCompletionHandler(CSP::JobCompletedEvent, 0); + EM::registerListener(EventType::JOB_STARTED, jobStartHandler, plugin_self); + EM::registerListener(EventType::JOB_COMPLETED, jobCompletionHandler, plugin_self); + // manage designations to start off (first time building groups [very important]) + out.print("channel-safely: enabled!\n"); + CSP::UnpauseEvent(); + } else if (!enable) { + // don't need the groups if the plugin isn't going to be enabled + EM::unregisterAll(plugin_self); + out.print("channel-safely: disabled!\n"); + } + enabled = enable; + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (enabled && World::isFortressMode() && Maps::IsValid()) { + switch (event) { + case SC_MAP_LOADED: + // cache the map size + Maps::getSize(mapx, mapy, mapz); + case SC_UNPAUSED: + // manage all designations on unpause + CSP::UnpauseEvent(); + default: + break; + } + } + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) { + CSP::OnUpdate(out); + return DFHack::CR_OK; +} + +command_result channel_safely(color_ostream &out, std::vector ¶meters) { + if (!parameters.empty()) { + if (parameters.size() >= 2 && parameters.size() <= 3) { + if (parameters[0] == "run" && parameters[1] == "once") { + CSP::UnpauseEvent(); + return DFHack::CR_OK; + } + bool state = false; + bool set = false; + if (parameters[0] == "enable") { + state = true; + } else if (parameters[0] == "disable") { + state = false; + } else if (parameters[0] == "set") { + set = true; + } else { + return DFHack::CR_WRONG_USAGE; + } + try { + if (parameters[1] == "debug") { + auto level = std::abs(std::stol(parameters[2])); + config.debug = true; + switch (level) { + case 1: + DBG_NAME(manager).allowed(DFHack::DebugCategory::LDEBUG); + DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO); + DBG_NAME(groups).allowed(DFHack::DebugCategory::LINFO); + DBG_NAME(jobs).allowed(DFHack::DebugCategory::LINFO); + break; + case 2: + DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE); + DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO); + DBG_NAME(groups).allowed(DFHack::DebugCategory::LDEBUG); + DBG_NAME(jobs).allowed(DFHack::DebugCategory::LDEBUG); + break; + case 3: + DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE); + DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO); + DBG_NAME(groups).allowed(DFHack::DebugCategory::LDEBUG); + DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE); + break; + case 4: + DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE); + DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO); + DBG_NAME(groups).allowed(DFHack::DebugCategory::LTRACE); + DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE); + break; + case 5: + DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE); + DBG_NAME(monitor).allowed(DFHack::DebugCategory::LDEBUG); + DBG_NAME(groups).allowed(DFHack::DebugCategory::LTRACE); + DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE); + break; + case 6: + DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE); + DBG_NAME(monitor).allowed(DFHack::DebugCategory::LTRACE); + DBG_NAME(groups).allowed(DFHack::DebugCategory::LTRACE); + DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE); + break; + case 0: + default: + DBG_NAME(monitor).allowed(DFHack::DebugCategory::LERROR); + DBG_NAME(manager).allowed(DFHack::DebugCategory::LERROR); + DBG_NAME(groups).allowed(DFHack::DebugCategory::LERROR); + DBG_NAME(jobs).allowed(DFHack::DebugCategory::LERROR); + } + } else if(parameters[1] == "monitor-active"){ + config.monitor_active = state; + } else if (parameters[1] == "require-vision") { + config.require_vision = state; + } else if (parameters[1] == "insta-dig") { + config.insta_dig = state; + } else if (parameters[1] == "refresh-freq" && set && parameters.size() == 3) { + config.refresh_freq = std::abs(std::stol(parameters[2])); + } else if (parameters[1] == "monitor-freq" && set && parameters.size() == 3) { + config.monitor_freq = std::abs(std::stol(parameters[2])); + } else if (parameters[1] == "ignore-threshold" && set && parameters.size() == 3) { + config.ignore_threshold = std::abs(std::stol(parameters[2])); + } else if (parameters[1] == "fall-threshold" && set && parameters.size() == 3) { + uint8_t t = std::abs(std::stol(parameters[2])); + if (t > 0) { + config.fall_threshold = t; + } else { + out.printerr("fall-threshold must have a value greater than 0 or the plugin does a lot of nothing.\n"); + return DFHack::CR_FAILURE; + } + } else { + return DFHack::CR_WRONG_USAGE; + } + } catch (const std::exception &e) { + out.printerr("%s\n", e.what()); + return DFHack::CR_FAILURE; + } + } + } else { + out.print("Channel-Safely is %s\n", enabled ? "ENABLED." : "DISABLED."); + out.print("monitor-active: %s\n", config.monitor_active ? "on." : "off."); + out.print("require-vision: %s\n", config.require_vision ? "on." : "off."); + out.print("insta-dig: %s\n", config.insta_dig ? "on." : "off."); + out.print("refresh-freq: %" PRIi32 "\n", config.refresh_freq); + out.print("monitor-freq: %" PRIi32 "\n", config.monitor_freq); + out.print("ignore-threshold: %" PRIu8 "\n", config.ignore_threshold); + out.print("fall-threshold: %" PRIu8 "\n", config.fall_threshold); + } + saveConfig(); + return DFHack::CR_OK; +} + + diff --git a/plugins/channel-safely/include/channel-groups.h b/plugins/channel-safely/include/channel-groups.h new file mode 100644 index 000000000..d39780df3 --- /dev/null +++ b/plugins/channel-safely/include/channel-groups.h @@ -0,0 +1,49 @@ +#pragma once +#include "plugin.h" +#include "channel-jobs.h" + +#include +#include + +#include +#include +#include + +using namespace DFHack; + +using Group = std::set; +using Groups = std::vector; + +/* Used to build groups of adjacent channel designations/jobs + * groups_map: maps coordinates to a group index in `groups` + * groups: list of Groups + * Group: used to track designations which are connected through adjacency to one another (a group cannot span Z) + * Note: a designation plan may become unsafe if the jobs aren't completed in a specific order; + * the easiest way to programmatically ensure safety is to.. + * lock overlapping groups directly adjacent across Z until the above groups are complete, or no longer overlap + * groups may no longer overlap if the adjacent designations are completed, but requires a rebuild of groups + * jobs: list of coordinates with channel jobs associated to them + */ +class ChannelGroups { +private: + using GroupsMap = std::map; + GroupsMap groups_map; + Groups groups; + ChannelJobs &jobs; + std::set free_spots; +protected: + void scan_map(); + void add(const df::coord &map_pos); +public: + explicit ChannelGroups(ChannelJobs &jobs) : jobs(jobs) { groups.reserve(200); } + void scan_one(const df::coord &map_pos); + void build(); + void clear(); + void remove(const df::coord &map_pos); + Groups::const_iterator find(const df::coord &map_pos) const; + Groups::const_iterator begin() const; + Groups::const_iterator end() const; + size_t count(const df::coord &map_pos) const; + void debug_groups(); + void debug_map(); +}; diff --git a/plugins/channel-safely/include/channel-jobs.h b/plugins/channel-safely/include/channel-jobs.h new file mode 100644 index 000000000..d0aaead7c --- /dev/null +++ b/plugins/channel-safely/include/channel-jobs.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include + +using namespace DFHack; + +/* Used to read/store/iterate channel digging jobs + * jobs: list of coordinates with channel jobs associated to them + * load_channel_jobs: iterates world->jobs.list to find channel jobs and adds them into the `jobs` map + * clear: empties the container + * erase: finds a job corresponding to a coord, removes the mapping in jobs, and calls Job::removeJob, then returns an iterator following the element removed + * find: returns an iterator to a job if one exists for a map coordinate + * begin: returns jobs.begin() + * end: returns jobs.end() + */ +class ChannelJobs { +private: + friend class ChannelGroup; + using Jobs = std::set; // job* will exist until it is complete, and likely beyond + Jobs jobs; +public: + void load_channel_jobs(); + void clear(); + Jobs::iterator erase(const df::coord &map_pos); + Jobs::const_iterator find(const df::coord &map_pos) const; + Jobs::const_iterator begin() const; + Jobs::const_iterator end() const; +}; diff --git a/plugins/channel-safely/include/channel-manager.h b/plugins/channel-safely/include/channel-manager.h new file mode 100644 index 000000000..2e33c5c46 --- /dev/null +++ b/plugins/channel-safely/include/channel-manager.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include +#include +#include +#include +#include "channel-groups.h" +#include "plugin.h" + +using namespace DFHack; + +// Uses GroupData to detect an unsafe work environment +class ChannelManager { +private: + ChannelJobs jobs; + ChannelManager()= default; +protected: +public: + ChannelGroups groups = ChannelGroups(jobs); + + static ChannelManager& Get(){ + static ChannelManager instance; + return instance; + } + + void build_groups() { groups.build(); debug(); } + void manage_all(); + void manage_group(const df::coord &map_pos, bool set_marker_mode = false, bool marker_mode = false); + void manage_group(const Group &group, bool set_marker_mode = false, bool marker_mode = false); + bool manage_one(const Group &group, const df::coord &map_pos, bool set_marker_mode = false, bool marker_mode = false); + void mark_done(const df::coord &map_pos); + void debug() { + if (config.debug) { + groups.debug_groups(); + groups.debug_map(); + //std::terminate(); + } + } +}; diff --git a/plugins/channel-safely/include/inlines.h b/plugins/channel-safely/include/inlines.h new file mode 100644 index 000000000..72042327a --- /dev/null +++ b/plugins/channel-safely/include/inlines.h @@ -0,0 +1,149 @@ +#pragma once +#include "plugin.h" +#include "channel-manager.h" + +#include +#include +#include + +#include +#include + +#define Coord(id) id.x][id.y +#define COORD "%" PRIi16 " %" PRIi16 " %" PRIi16 +#define COORDARGS(id) id.x, id.y, id.z + +namespace CSP { + extern std::unordered_set dignow_queue; +} + +inline bool is_dig_job(const df::job* job) { + return job->job_type == df::job_type::Dig || job->job_type == df::job_type::DigChannel; +} + +inline bool is_dig_designation(const df::tile_designation &designation) { + return designation.bits.dig != df::tile_dig_designation::No; +} + +inline bool has_unit(const df::tile_occupancy* occupancy) { + return occupancy->bits.unit || occupancy->bits.unit_grounded; +} + +inline bool is_safe_fall(const df::coord &map_pos) { + df::coord below(map_pos); + for (uint8_t zi = 0; zi < config.fall_threshold; ++zi) { + below.z--; + if (config.require_vision && Maps::getTileDesignation(below)->bits.hidden) { + return true; //we require vision, and we can't see below.. so we gotta assume it's safe + } + df::tiletype type = *Maps::getTileType(below); + if (!isOpenTerrain(type)) { + return true; + } + } + return false; +} + +inline bool is_safe_to_dig_down(const df::coord &map_pos) { + df::coord pos(map_pos); + + for (uint8_t zi = 0; zi <= config.fall_threshold; ++zi) { + // assume safe if we can't see and need vision + if (config.require_vision && Maps::getTileDesignation(pos)->bits.hidden) { + return true; + } + df::tiletype type = *Maps::getTileType(pos); + if (zi == 0 && isOpenTerrain(type)) { + // the starting tile is open space, that's obviously not safe + return false; + } else if (!isOpenTerrain(type)) { + // a tile after the first one is not open space + return true; + } + pos.z--; + } + return false; +} + +inline bool is_group_occupied(const ChannelGroups &groups, const Group &group) { + // return true if any tile in the group is occupied by a unit + return std::any_of(group.begin(), group.end(), [](const Group::key_type &pos){ + return has_unit(Maps::getTileOccupancy(pos)); + }); +} + +inline bool has_group_above(const ChannelGroups &groups, const df::coord &map_pos) { + df::coord above(map_pos); + above.z++; + if (groups.count(above)) { + return true; + } + return false; +} + +inline bool has_any_groups_above(const ChannelGroups &groups, const Group &group) { + // for each designation in the group + for (auto &pos : group) { + df::coord above(pos); + above.z++; + if (groups.count(above)) { + return true; + } + } + // if there are no incomplete groups above this group, then this group is ready + return false; +} + +inline void cancel_job(df::job* job) { + if (job != nullptr) { + df::coord &pos = job->pos; + df::map_block* job_block = Maps::getTileBlock(pos); + uint16_t x, y; + x = pos.x % 16; + y = pos.y % 16; + df::tile_designation &designation = job_block->designation[x][y]; + switch (job->job_type) { + case job_type::Dig: + designation.bits.dig = df::tile_dig_designation::Default; + break; + case job_type::CarveUpwardStaircase: + designation.bits.dig = df::tile_dig_designation::UpStair; + break; + case job_type::CarveDownwardStaircase: + designation.bits.dig = df::tile_dig_designation::DownStair; + break; + case job_type::CarveUpDownStaircase: + designation.bits.dig = df::tile_dig_designation::UpDownStair; + break; + case job_type::CarveRamp: + designation.bits.dig = df::tile_dig_designation::Ramp; + break; + case job_type::DigChannel: + designation.bits.dig = df::tile_dig_designation::Channel; + break; + default: + designation.bits.dig = df::tile_dig_designation::No; + break; + } + Job::removeJob(job); + } +} + +inline void get_neighbours(const df::coord &map_pos, df::coord(&neighbours)[8]) { + neighbours[0] = map_pos; + neighbours[1] = map_pos; + neighbours[2] = map_pos; + neighbours[3] = map_pos; + neighbours[4] = map_pos; + neighbours[5] = map_pos; + neighbours[6] = map_pos; + neighbours[7] = map_pos; + neighbours[0].x--; neighbours[0].y--; + neighbours[1].y--; + neighbours[2].x++; neighbours[2].y--; + neighbours[3].x--; + neighbours[4].x++; + neighbours[5].x--; neighbours[5].y++; + neighbours[6].y++; + neighbours[7].x++; neighbours[7].y++; +} diff --git a/plugins/channel-safely/include/plugin.h b/plugins/channel-safely/include/plugin.h new file mode 100644 index 000000000..71e4665c7 --- /dev/null +++ b/plugins/channel-safely/include/plugin.h @@ -0,0 +1,23 @@ +#pragma once +#include + +namespace DFHack { + DBG_EXTERN(channelsafely, monitor); + DBG_EXTERN(channelsafely, manager); + DBG_EXTERN(channelsafely, groups); + DBG_EXTERN(channelsafely, jobs); +} + +struct Configuration { + bool debug = false; + bool monitor_active = false; + bool require_vision = true; + bool insta_dig = false; + int32_t refresh_freq = 600; + int32_t monitor_freq = 10; + uint8_t ignore_threshold = 7; + uint8_t fall_threshold = 1; +}; + +extern Configuration config; +extern int32_t mapx, mapy, mapz; From bd6c748d00d99202ccf9844c3f8459561a265973 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 6 Nov 2022 11:53:46 -0800 Subject: [PATCH 02/22] Implements plugin: channel-safely v0.2 --- plugins/channel-safely/channel-groups.cpp | 156 +++++++++--------- plugins/channel-safely/channel-manager.cpp | 6 +- .../channel-safely/channel-safely-plugin.cpp | 20 ++- .../channel-safely/include/channel-groups.h | 5 +- .../channel-safely/include/channel-manager.h | 7 +- 5 files changed, 104 insertions(+), 90 deletions(-) diff --git a/plugins/channel-safely/channel-groups.cpp b/plugins/channel-safely/channel-groups.cpp index 7ee779df8..41928c556 100644 --- a/plugins/channel-safely/channel-groups.cpp +++ b/plugins/channel-safely/channel-groups.cpp @@ -5,67 +5,6 @@ #include -// scans the map for channel designations -void ChannelGroups::scan_map() { - static std::default_random_engine RNG(0); - static std::bernoulli_distribution optimizing(0.3333); - DEBUG(groups).print(" scan_map()\n"); - // foreach block - for (int32_t z = mapz - 1; z >= 0; --z) { - for (int32_t by = 0; by < mapy; ++by) { - for (int32_t bx = 0; bx < mapx; ++bx) { - // the block - if (df::map_block* block = Maps::getBlock(bx, by, z)) { - // skip this block? - if (!block->flags.bits.designated && optimizing(RNG)) { - // todo: add remainder of block width onto bx - TRACE(groups).print(" skipping this block, it has no designations\n"); - continue; - } - // foreach tile - for (int16_t lx = 0; lx < 16; ++lx) { - for (int16_t ly = 0; ly < 16; ++ly) { - // the tile, check if it has a channel designation - if (is_dig_designation(block->designation[lx][ly])) { - for (df::block_square_event* event: block->block_events) { - if (auto evT = virtual_cast(event)) { - // we want to let the user keep some designations free of being managed - TRACE(groups).print(" tile designation priority: %d\n", evT->priority[lx][ly]); - if (evT->priority[lx][ly] < 1000 * config.ignore_threshold) { - df::coord map_pos((bx * 16) + lx, (by * 16) + ly, z); - TRACE(groups).print(" adding (" COORD ")\n", COORDARGS(map_pos)); - add(map_pos); - } - } - } - } - } - } - } - } - } - } - INFO(groups).print("scan_map() exits\n"); -} - -// scans a single tile for channel designations -void ChannelGroups::scan_one(const df::coord &map_pos) { - df::map_block* block = Maps::getTileBlock(map_pos); - int16_t lx = map_pos.x % 16; - int16_t ly = map_pos.y % 16; - if (is_dig_designation(block->designation[lx][ly])) { - for (df::block_square_event* event: block->block_events) { - if (auto evT = virtual_cast(event)) { - // we want to let the user keep some designations free of being managed - if (evT->priority[lx][ly] < 1000 * config.ignore_threshold) { - TRACE(groups).print(" adding (" COORD ")\n", COORDARGS(map_pos)); - add(map_pos); - } - } - } - } -} - // adds map_pos to a group if an adjacent one exists, or creates one if none exist... if multiple exist they're merged into the first found void ChannelGroups::add(const df::coord &map_pos) { // if we've already added this, we don't need to do it again @@ -150,16 +89,79 @@ void ChannelGroups::add(const df::coord &map_pos) { // DEBUG(groups).flush(); } +// scans a single tile for channel designations +void ChannelGroups::scan_one(const df::coord &map_pos) { + df::map_block* block = Maps::getTileBlock(map_pos); + int16_t lx = map_pos.x % 16; + int16_t ly = map_pos.y % 16; + if (is_dig_designation(block->designation[lx][ly])) { + for (df::block_square_event* event: block->block_events) { + if (auto evT = virtual_cast(event)) { + // we want to let the user keep some designations free of being managed + if (evT->priority[lx][ly] < 1000 * config.ignore_threshold) { + TRACE(groups).print(" adding (" COORD ")\n", COORDARGS(map_pos)); + add(map_pos); + } + } + } + } else if (isOpenTerrain(block->tiletype[lx][ly])) { + remove(map_pos); + } +} + // builds groupings of adjacent channel designations -void ChannelGroups::build() { - clear(); +void ChannelGroups::scan() { // iterate over each job, finding channel jobs jobs.load_channel_jobs(); // transpose channel jobs to for (auto &pos : jobs) { add(pos); } - scan_map(); + DEBUG(groups).print(" scan()\n"); + // foreach block + for (int32_t z = mapz - 1; z >= 0; --z) { + for (int32_t by = 0; by < mapy; ++by) { + for (int32_t bx = 0; bx < mapx; ++bx) { + // the block + if (df::map_block* block = Maps::getBlock(bx, by, z)) { + // skip this block? + if (!block->flags.bits.designated && !group_blocks.count(block)) { + continue; + } + // foreach tile + bool empty_group = true; + for (int16_t lx = 0; lx < 16; ++lx) { + for (int16_t ly = 0; ly < 16; ++ly) { + // the tile, check if it has a channel designation + df::coord map_pos((bx * 16) + lx, (by * 16) + ly, z); + if (is_dig_designation(block->designation[lx][ly])) { + for (df::block_square_event* event: block->block_events) { + if (auto evT = virtual_cast(event)) { + // we want to let the user keep some designations free of being managed + TRACE(groups).print(" tile designation priority: %d\n", evT->priority[lx][ly]); + if (evT->priority[lx][ly] < 1000 * config.ignore_threshold) { + if (empty_group) { + group_blocks.emplace(block); + } + TRACE(groups).print(" adding (" COORD ")\n", COORDARGS(map_pos)); + add(map_pos); + empty_group = false; + } + } + } + } else if (isOpenTerrain(block->tiletype[lx][ly])) { + remove(map_pos); + } + } + } + if (empty_group) { + group_blocks.erase(block); + } + } + } + } + } + INFO(groups).print("scan() exits\n"); } // clears out the containers for unloading maps or disabling the plugin @@ -231,23 +233,23 @@ size_t ChannelGroups::count(const df::coord &map_pos) const { // prints debug info about the groups stored, and their members void ChannelGroups::debug_groups() { -// int idx = 0; -// TRACE(groups).print(" debugging group data\n"); -// for (auto &group : groups) { -// TRACE(groups).print(" group %d (size: %zu)\n", idx, group.size()); -// for (auto &pos : group) { -// TRACE(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z); -// } -// idx++; -// } + int idx = 0; + TRACE(groups).print(" debugging group data\n"); + for (auto &group : groups) { + TRACE(groups).print(" group %d (size: %zu)\n", idx, group.size()); + for (auto &pos : group) { + TRACE(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z); + } + idx++; + } } // prints debug info group mappings void ChannelGroups::debug_map() { -// INFO(groups).print("Group Mappings: %zu\n", groups_map.size()); -// for (auto &pair : groups_map) { -// DEBUG(groups).print(" map[" COORD "] = %d\n",COORDARGS(pair.first), pair.second); -// } + INFO(groups).print("Group Mappings: %zu\n", groups_map.size()); + for (auto &pair : groups_map) { + DEBUG(groups).print(" map[" COORD "] = %d\n",COORDARGS(pair.first), pair.second); + } } diff --git a/plugins/channel-safely/channel-manager.cpp b/plugins/channel-safely/channel-manager.cpp index 79bc6a7aa..28d757772 100644 --- a/plugins/channel-safely/channel-manager.cpp +++ b/plugins/channel-safely/channel-manager.cpp @@ -10,8 +10,8 @@ blocks[48][96][135].designation[10][0].hidden: false * */ // sets mark flags as necessary, for all designations -void ChannelManager::manage_all() { - INFO(manager).print("manage_all()\n"); +void ChannelManager::manage_groups() { + INFO(manager).print("manage_groups()\n"); // make sure we've got a fort map to analyze if (World::isFortressMode() && Maps::IsValid()) { // iterate the groups we built/updated @@ -95,5 +95,5 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo void ChannelManager::mark_done(const df::coord &map_pos) { groups.remove(map_pos); - jobs.erase(map_pos); + jobs.erase(map_pos); //redundant (repopulated on each build) } diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index e6cdf231b..b473598ae 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -140,11 +140,9 @@ namespace CSP { void UnpauseEvent(){ INFO(monitor).print("UnpauseEvent()\n"); ChannelManager::Get().build_groups(); - INFO(monitor).print("after building groups\n"); + ChannelManager::Get().manage_groups(); ChannelManager::Get().debug(); - ChannelManager::Get().manage_all(); INFO(monitor).print("UnpauseEvent() exits\n"); - ChannelManager::Get().debug(); } void JobStartedEvent(color_ostream &out, void* p) { @@ -387,10 +385,22 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan // manage all designations on unpause CSP::UnpauseEvent(); default: - break; + return DFHack::CR_OK; } } - return CR_OK; + switch (event) { + case SC_WORLD_LOADED: + case SC_WORLD_UNLOADED: + case SC_MAP_UNLOADED: + // destroy any old group data + out.print("channel-safely: unloading data!\n"); + ChannelManager::Get().destroy_groups(); + case SC_MAP_LOADED: + // cache the map size + Maps::getSize(mapx, mapy, mapz); + default: + return DFHack::CR_OK; + } } DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) { diff --git a/plugins/channel-safely/include/channel-groups.h b/plugins/channel-safely/include/channel-groups.h index d39780df3..abdbc56fc 100644 --- a/plugins/channel-safely/include/channel-groups.h +++ b/plugins/channel-safely/include/channel-groups.h @@ -26,18 +26,19 @@ using Groups = std::vector; */ class ChannelGroups { private: + using GroupBlocks = std::set; using GroupsMap = std::map; + GroupBlocks group_blocks; GroupsMap groups_map; Groups groups; ChannelJobs &jobs; std::set free_spots; protected: - void scan_map(); void add(const df::coord &map_pos); public: explicit ChannelGroups(ChannelJobs &jobs) : jobs(jobs) { groups.reserve(200); } void scan_one(const df::coord &map_pos); - void build(); + void scan(); void clear(); void remove(const df::coord &map_pos); Groups::const_iterator find(const df::coord &map_pos) const; diff --git a/plugins/channel-safely/include/channel-manager.h b/plugins/channel-safely/include/channel-manager.h index 2e33c5c46..79c1e3770 100644 --- a/plugins/channel-safely/include/channel-manager.h +++ b/plugins/channel-safely/include/channel-manager.h @@ -23,17 +23,18 @@ public: return instance; } - void build_groups() { groups.build(); debug(); } - void manage_all(); + void build_groups() { groups.scan(); debug(); } + void destroy_groups() { groups.clear(); debug(); } + void manage_groups(); void manage_group(const df::coord &map_pos, bool set_marker_mode = false, bool marker_mode = false); void manage_group(const Group &group, bool set_marker_mode = false, bool marker_mode = false); bool manage_one(const Group &group, const df::coord &map_pos, bool set_marker_mode = false, bool marker_mode = false); void mark_done(const df::coord &map_pos); void debug() { + DEBUG(groups).print(" DEBUGGING GROUPS:\n"); if (config.debug) { groups.debug_groups(); groups.debug_map(); - //std::terminate(); } } }; From 3a6205d19e2f218b33dae5f4f0fea16154575678 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 6 Nov 2022 11:55:45 -0800 Subject: [PATCH 03/22] Removes extra EOF lines --- plugins/channel-safely/channel-groups.cpp | 2 -- plugins/channel-safely/channel-safely-plugin.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/plugins/channel-safely/channel-groups.cpp b/plugins/channel-safely/channel-groups.cpp index 41928c556..638ba395b 100644 --- a/plugins/channel-safely/channel-groups.cpp +++ b/plugins/channel-safely/channel-groups.cpp @@ -251,5 +251,3 @@ void ChannelGroups::debug_map() { DEBUG(groups).print(" map[" COORD "] = %d\n",COORDARGS(pair.first), pair.second); } } - - diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index b473598ae..a5f5be583 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -515,5 +515,3 @@ command_result channel_safely(color_ostream &out, std::vector ¶ saveConfig(); return DFHack::CR_OK; } - - From b7ee01108e1a5ae5e984f4f6d4d51103f520d7db Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 6 Nov 2022 12:04:01 -0800 Subject: [PATCH 04/22] Implements plugin: channel-safely v0.3 --- .../channel-safely/channel-safely-plugin.cpp | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index a5f5be583..58b92f4a6 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -209,33 +209,30 @@ namespace CSP { } void JobCompletedEvent(color_ostream &out, void* job_ptr) { - if (config.monitor_active) { + if (enabled && World::isFortressMode() && Maps::IsValid()) { INFO(monitor).print("JobCompletedEvent()\n"); - if (enabled && World::isFortressMode() && Maps::IsValid()) { - auto job = (df::job*) job_ptr; - // we only care if the job is a channeling one - if (is_dig_job(job)) { - // untrack job/worker - active_workers.erase(job->id); - // check job outcome - df::coord local(job->pos); - auto block = Maps::getTileBlock(local); - local.x = local.x % 16; - local.y = local.y % 16; - // verify completion - if (isOpenTerrain(block->tiletype[local.x][local.y]) - || block->designation[local.x][local.y].bits.dig != df::enums::tile_dig_designation::Channel) { - // the job can be considered done - df::coord below(job->pos); - below.z--; - WARN(monitor).print(" -> Marking tile done and managing the group below.\n"); - // mark done and manage below - Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Normal; - ChannelManager::Get().mark_done(job->pos); - ChannelManager::Get().manage_group(below); - ChannelManager::Get().debug(); - Job::removeJob(job); - } + auto job = (df::job*) job_ptr; + // we only care if the job is a channeling one + if (is_dig_job(job)) { + // untrack job/worker + active_workers.erase(job->id); + // check job outcome + df::coord local(job->pos); + auto block = Maps::getTileBlock(local); + local.x = local.x % 16; + local.y = local.y % 16; + // verify completion + if (isOpenTerrain(block->tiletype[local.x][local.y]) + || block->designation[local.x][local.y].bits.dig != df::enums::tile_dig_designation::Channel) { + // the job can be considered done + df::coord below(job->pos); + below.z--; + WARN(monitor).print(" -> Marking tile done and managing the group below.\n"); + // mark done and manage below + Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Normal; + ChannelManager::Get().mark_done(job->pos); + ChannelManager::Get().manage_group(below); + ChannelManager::Get().debug(); } } INFO(monitor).print("JobCompletedEvent() exits\n"); From a8dcfeead9dddfb8a90f2533c0ac2c7ce406bc63 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 6 Nov 2022 12:09:38 -0800 Subject: [PATCH 05/22] Implements plugin: channel-safely v0.3.1 --- .../channel-safely/channel-safely-plugin.cpp | 104 +++++++++--------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index 58b92f4a6..333d4452a 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -146,65 +146,63 @@ namespace CSP { } void JobStartedEvent(color_ostream &out, void* p) { - if (config.monitor_active) { - if (enabled && World::isFortressMode() && Maps::IsValid()) { - INFO(monitor).print("JobStartedEvent()\n"); - auto job = (df::job*) p; - // validate job type - if (is_dig_job(job)) { - DEBUG(monitor).print(" valid channel job:\n"); - df::unit* worker = Job::getWorker(job); - // there is a valid worker (living citizen) on the job? right.. - if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) { - DEBUG(monitor).print(" valid worker:\n"); - df::coord local(job->pos); - local.x = local.x % 16; - local.y = local.y % 16; - // check pathing exists to job - if (Maps::canWalkBetween(worker->pos, job->pos)) { - DEBUG(monitor).print(" can path from (" COORD ") to (" COORD ")\n", - COORDARGS(worker->pos), COORDARGS(job->pos)); - // track workers on jobs - active_workers.emplace(job->id, Units::findIndexById(Job::getWorker(job)->id)); - // set tile to restricted - TRACE(monitor).print(" setting job tile to restricted\n"); - Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Restricted; - } else { - DEBUG(monitor).print(" no path exists to job:\n"); - // if we can't get there, then we should remove the worker and cancel the job (restore tile designation) - Job::removeWorker(job); - cancel_job(job); - if (!config.insta_dig) { - TRACE(monitor).print(" setting marker mode for (" COORD ")\n", COORDARGS(job->pos)); - // set to marker mode - auto occupancy = Maps::getTileOccupancy(job->pos); - if (!occupancy) { - WARN(monitor).print(" Could not acquire tile occupancy*\n"); - return; - } - occupancy->bits.dig_marked = true; - // prevent algorithm from re-enabling designation - df::map_block* block = Maps::getTileBlock(job->pos); - if (!block) { - WARN(monitor).print(" Could not acquire block*\n"); - return; - } - for (auto &be: block->block_events) { ; - if (auto bsedp = virtual_cast(be)) { - TRACE(monitor).print(" re-setting priority\n"); - bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1; - } + if (enabled && World::isFortressMode() && Maps::IsValid()) { + INFO(monitor).print("JobStartedEvent()\n"); + auto job = (df::job*) p; + // validate job type + if (is_dig_job(job)) { + DEBUG(monitor).print(" valid channel job:\n"); + df::unit* worker = Job::getWorker(job); + // there is a valid worker (living citizen) on the job? right.. + if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) { + DEBUG(monitor).print(" valid worker:\n"); + df::coord local(job->pos); + local.x = local.x % 16; + local.y = local.y % 16; + // check pathing exists to job + if (Maps::canWalkBetween(worker->pos, job->pos)) { + DEBUG(monitor).print(" can path from (" COORD ") to (" COORD ")\n", + COORDARGS(worker->pos), COORDARGS(job->pos)); + // track workers on jobs + active_workers.emplace(job->id, Units::findIndexById(Job::getWorker(job)->id)); + // set tile to restricted + TRACE(monitor).print(" setting job tile to restricted\n"); + Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Restricted; + } else { + DEBUG(monitor).print(" no path exists to job:\n"); + // if we can't get there, then we should remove the worker and cancel the job (restore tile designation) + Job::removeWorker(job); + cancel_job(job); + if (!config.insta_dig) { + TRACE(monitor).print(" setting marker mode for (" COORD ")\n", COORDARGS(job->pos)); + // set to marker mode + auto occupancy = Maps::getTileOccupancy(job->pos); + if (!occupancy) { + WARN(monitor).print(" Could not acquire tile occupancy*\n"); + return; + } + occupancy->bits.dig_marked = true; + // prevent algorithm from re-enabling designation + df::map_block* block = Maps::getTileBlock(job->pos); + if (!block) { + WARN(monitor).print(" Could not acquire block*\n"); + return; + } + for (auto &be: block->block_events) { ; + if (auto bsedp = virtual_cast(be)) { + TRACE(monitor).print(" re-setting priority\n"); + bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1; } - } else { - TRACE(monitor).print(" deleting job, and queuing insta-dig)\n"); - // queue digging the job instantly - dignow_queue.emplace(job->pos); } + } else { + TRACE(monitor).print(" deleting job, and queuing insta-dig)\n"); + // queue digging the job instantly + dignow_queue.emplace(job->pos); } } } - INFO(monitor).print(" <- JobStartedEvent() exits normally\n"); } + INFO(monitor).print(" <- JobStartedEvent() exits normally\n"); } } From c2d346fc841dfa8b6affdb4c683ed15241b410cc Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 6 Nov 2022 13:16:27 -0800 Subject: [PATCH 06/22] Implements plugin: channel-safely v0.4 --- docs/plugins/channel-safely.rst | 5 +- plugins/channel-safely/channel-groups.cpp | 32 ++++++++- plugins/channel-safely/channel-manager.cpp | 18 +++-- .../channel-safely/channel-safely-plugin.cpp | 33 ++++----- plugins/channel-safely/include/inlines.h | 67 ++++++++++++------- plugins/channel-safely/include/plugin.h | 4 +- 6 files changed, 105 insertions(+), 54 deletions(-) diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index 6010f4925..842d57353 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -55,8 +55,7 @@ Settings -------- :refresh-freq: The rate at which full refreshes are performed. This can be expensive if you're undertaking many mega projects. (default:600, twice a day) -:monitor-freq: The rate at which active jobs are monitored. - todo: this can have a massive impact? (default:10) +:monitor-freq: The rate at which active jobs are monitored. (default:1) :ignore-threshold: Sets the priority threshold below which designations are processed. You can set to 1 or 0 to - effectively disable the scanning. (default: 7) + effectively disable the scanning. (default: 5) :fall-threshold: Sets the fall threshold beyond which is considered unsafe. (default: 1) diff --git a/plugins/channel-safely/channel-groups.cpp b/plugins/channel-safely/channel-groups.cpp index 638ba395b..7e5cccf20 100644 --- a/plugins/channel-safely/channel-groups.cpp +++ b/plugins/channel-safely/channel-groups.cpp @@ -5,6 +5,22 @@ #include +template +void set_difference(const Ctr1 &c1, const Ctr2 &c2, Ctr3 &c3) { + for (const auto &a : c1) { + bool matched = false; + for (const auto &b : c2) { + if (a == b) { + matched = true; + break; + } + } + if (!matched) { + c3.emplace(a); + } + } +} + // adds map_pos to a group if an adjacent one exists, or creates one if none exist... if multiple exist they're merged into the first found void ChannelGroups::add(const df::coord &map_pos) { // if we've already added this, we don't need to do it again @@ -111,12 +127,24 @@ void ChannelGroups::scan_one(const df::coord &map_pos) { // builds groupings of adjacent channel designations void ChannelGroups::scan() { - // iterate over each job, finding channel jobs + // save current jobs, then clear and load the current jobs + std::set last_jobs; + for (auto &pos : jobs) { + last_jobs.emplace(pos); + } jobs.load_channel_jobs(); // transpose channel jobs to - for (auto &pos : jobs) { + std::set new_jobs; + std::set gone_jobs; + set_difference(last_jobs, jobs, gone_jobs); + set_difference(jobs, last_jobs, new_jobs); + for (auto &pos : new_jobs) { add(pos); } + for (auto &pos : gone_jobs){ + remove(pos); + } + DEBUG(groups).print(" scan()\n"); // foreach block for (int32_t z = mapz - 1; z >= 0; --z) { diff --git a/plugins/channel-safely/channel-manager.cpp b/plugins/channel-safely/channel-manager.cpp index 28d757772..86ce5a726 100644 --- a/plugins/channel-safely/channel-manager.cpp +++ b/plugins/channel-safely/channel-manager.cpp @@ -62,9 +62,18 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo // do we already know whether to set marker mode? if (set_marker_mode) { DEBUG(manager).print(" -> marker_mode\n"); - tile_occupancy.bits.dig_marked = marker_mode; - jobs.erase(map_pos); - return true; + // if enabling marker mode, just do it + if (marker_mode) { + tile_occupancy.bits.dig_marked = marker_mode; + return true; + } + // if activating designation, check if it is safe to dig or not a channel designation + if (!is_channel_designation(block->designation[Coord(local)]) || is_safe_to_dig_down(map_pos)) { + tile_occupancy.bits.dig_marked = marker_mode; + return marker_mode; + } + return false; + } else { // next search for the designation priority for (df::block_square_event* event: block->block_events) { @@ -73,7 +82,7 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo if (evT->priority[Coord(local)] < 1000 * config.ignore_threshold) { DEBUG(manager).print(" if(has_groups_above())\n"); // check that the group has no incomplete groups directly above it - if (has_group_above(groups, map_pos)) { + if (has_group_above(groups, map_pos) || !is_safe_to_dig_down(map_pos)) { DEBUG(manager).print(" has_groups_above: setting marker mode\n"); tile_occupancy.bits.dig_marked = true; jobs.erase(map_pos); @@ -95,5 +104,4 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo void ChannelManager::mark_done(const df::coord &map_pos) { groups.remove(map_pos); - jobs.erase(map_pos); //redundant (repopulated on each build) } diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index 333d4452a..a63228e27 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -1,7 +1,7 @@ /* Prevent channeling down into known open space. Author: Josh Cooper Created: Aug. 4 2020 -Updated: Nov. 1 2022 +Updated: Nov. 6 2022 Enable plugin: -> build groups @@ -13,9 +13,9 @@ Updated: Nov. 1 2022 Manage Designation(s): -> for each group in groups: - -> for each designation in this group: - -> - + -> does any tile in group have a group above + -> Yes: set entire group to marker mode + -> No: activate entire group (still checks is_safe_to_dig_down before activating each designation) Job started event: -> validate job type (channel) @@ -150,7 +150,7 @@ namespace CSP { INFO(monitor).print("JobStartedEvent()\n"); auto job = (df::job*) p; // validate job type - if (is_dig_job(job)) { + if (is_channel_job(job)) { DEBUG(monitor).print(" valid channel job:\n"); df::unit* worker = Job::getWorker(job); // there is a valid worker (living citizen) on the job? right.. @@ -160,7 +160,7 @@ namespace CSP { local.x = local.x % 16; local.y = local.y % 16; // check pathing exists to job - if (Maps::canWalkBetween(worker->pos, job->pos)) { + if (can_reach_designation(worker->pos, job->pos)) { DEBUG(monitor).print(" can path from (" COORD ") to (" COORD ")\n", COORDARGS(worker->pos), COORDARGS(job->pos)); // track workers on jobs @@ -246,6 +246,17 @@ namespace CSP { last_refresh_tick = tick; TRACE(monitor).print("OnUpdate()\n"); UnpauseEvent(); + + TRACE(monitor).print(" -> evaluate dignow queue\n"); + for (const df::coord &pos: dignow_queue) { + if (!has_unit(Maps::getTileOccupancy(pos))) { + dig_now(out, pos); + } else { + // todo: teleport? + //Units::teleport() + } + } + TRACE(monitor).print("OnUpdate() exits\n"); } if (config.monitor_active && tick - last_monitor_tick >= config.monitor_freq) { last_monitor_tick = tick; @@ -299,16 +310,6 @@ namespace CSP { } } } - TRACE(monitor).print(" -> evaluate dignow queue\n"); - for (const df::coord &pos: dignow_queue) { - if (!has_unit(Maps::getTileOccupancy(pos))) { - dig_now(out, pos); - } else { - // todo: teleport? - //Units::teleport() - } - } - TRACE(monitor).print("OnUpdate() exits\n"); } } } diff --git a/plugins/channel-safely/include/inlines.h b/plugins/channel-safely/include/inlines.h index 72042327a..e8689d245 100644 --- a/plugins/channel-safely/include/inlines.h +++ b/plugins/channel-safely/include/inlines.h @@ -17,16 +17,39 @@ namespace CSP { extern std::unordered_set dignow_queue; } +inline void get_neighbours(const df::coord &map_pos, df::coord(&neighbours)[8]) { + neighbours[0] = map_pos; + neighbours[1] = map_pos; + neighbours[2] = map_pos; + neighbours[3] = map_pos; + neighbours[4] = map_pos; + neighbours[5] = map_pos; + neighbours[6] = map_pos; + neighbours[7] = map_pos; + neighbours[0].x--; neighbours[0].y--; + neighbours[1].y--; + neighbours[2].x++; neighbours[2].y--; + neighbours[3].x--; + neighbours[4].x++; + neighbours[5].x--; neighbours[5].y++; + neighbours[6].y++; + neighbours[7].x++; neighbours[7].y++; +} + inline bool is_dig_job(const df::job* job) { return job->job_type == df::job_type::Dig || job->job_type == df::job_type::DigChannel; } +inline bool is_channel_job(const df::job* job) { + return job->job_type == df::job_type::DigChannel; +} + inline bool is_dig_designation(const df::tile_designation &designation) { return designation.bits.dig != df::tile_dig_designation::No; } -inline bool has_unit(const df::tile_occupancy* occupancy) { - return occupancy->bits.unit || occupancy->bits.unit_grounded; +inline bool is_channel_designation(const df::tile_designation &designation) { + return designation.bits.dig != df::tile_dig_designation::Channel; } inline bool is_safe_fall(const df::coord &map_pos) { @@ -65,11 +88,22 @@ inline bool is_safe_to_dig_down(const df::coord &map_pos) { return false; } -inline bool is_group_occupied(const ChannelGroups &groups, const Group &group) { - // return true if any tile in the group is occupied by a unit - return std::any_of(group.begin(), group.end(), [](const Group::key_type &pos){ - return has_unit(Maps::getTileOccupancy(pos)); - }); +inline bool can_reach_designation(const df::coord &start, const df::coord &end) { + if (!Maps::canWalkBetween(start,end)) { + df::coord neighbours[8]; + get_neighbours(end, neighbours); + for (auto &pos : neighbours) { + if (Maps::canWalkBetween(start, pos)) { + return true; + } + } + return false; + } + return true; +} + +inline bool has_unit(const df::tile_occupancy* occupancy) { + return occupancy->bits.unit || occupancy->bits.unit_grounded; } inline bool has_group_above(const ChannelGroups &groups, const df::coord &map_pos) { @@ -128,22 +162,3 @@ inline void cancel_job(df::job* job) { Job::removeJob(job); } } - -inline void get_neighbours(const df::coord &map_pos, df::coord(&neighbours)[8]) { - neighbours[0] = map_pos; - neighbours[1] = map_pos; - neighbours[2] = map_pos; - neighbours[3] = map_pos; - neighbours[4] = map_pos; - neighbours[5] = map_pos; - neighbours[6] = map_pos; - neighbours[7] = map_pos; - neighbours[0].x--; neighbours[0].y--; - neighbours[1].y--; - neighbours[2].x++; neighbours[2].y--; - neighbours[3].x--; - neighbours[4].x++; - neighbours[5].x--; neighbours[5].y++; - neighbours[6].y++; - neighbours[7].x++; neighbours[7].y++; -} diff --git a/plugins/channel-safely/include/plugin.h b/plugins/channel-safely/include/plugin.h index 71e4665c7..d165d3f18 100644 --- a/plugins/channel-safely/include/plugin.h +++ b/plugins/channel-safely/include/plugin.h @@ -14,8 +14,8 @@ struct Configuration { bool require_vision = true; bool insta_dig = false; int32_t refresh_freq = 600; - int32_t monitor_freq = 10; - uint8_t ignore_threshold = 7; + int32_t monitor_freq = 1; + uint8_t ignore_threshold = 5; uint8_t fall_threshold = 1; }; From e5dbaac85d83bad166948fb0446cf59a6e6a7a20 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 6 Nov 2022 15:59:30 -0800 Subject: [PATCH 07/22] Implements plugin: channel-safely v0.5 --- plugins/channel-safely/channel-groups.cpp | 29 ++++++++------- plugins/channel-safely/channel-manager.cpp | 14 ++++++-- .../channel-safely/channel-safely-plugin.cpp | 30 +++++++++------- plugins/channel-safely/include/channel-jobs.h | 1 + plugins/channel-safely/include/inlines.h | 4 +++ plugins/channel-safely/include/tile-cache.h | 35 +++++++++++++++++++ 6 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 plugins/channel-safely/include/tile-cache.h diff --git a/plugins/channel-safely/channel-groups.cpp b/plugins/channel-safely/channel-groups.cpp index 7e5cccf20..48f4a9ee5 100644 --- a/plugins/channel-safely/channel-groups.cpp +++ b/plugins/channel-safely/channel-groups.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -91,18 +92,12 @@ void ChannelGroups::add(const df::coord &map_pos) { // puts the "add" in "ChannelGroups::add" group->emplace(map_pos); DEBUG(groups).print(" = group[%d] of (" COORD ") is size: %zu\n", group_index, COORDARGS(map_pos), group->size()); -// ERR(groups).print("\n\n\nDEBUG MAPPINGS:\n"); -// debug_map(); -// DEBUG(groups).flush(); // we may have performed a merge, so we update all the `coord -> group index` mappings for (auto &wpos: *group) { groups_map[wpos] = group_index; } DEBUG(groups).print(" <- add() exits, there are %zu mappings\n", groups_map.size()); -// ERR(groups).print("\n\n\nDEBUG MAPPINGS:\n"); -// debug_map(); -// DEBUG(groups).flush(); } // scans a single tile for channel designations @@ -120,7 +115,8 @@ void ChannelGroups::scan_one(const df::coord &map_pos) { } } } - } else if (isOpenTerrain(block->tiletype[lx][ly])) { + } else if (TileCache::Get().hasChanged(map_pos, block->tiletype[lx][ly])) { + TileCache::Get().uncache(map_pos); remove(map_pos); } } @@ -145,6 +141,9 @@ void ChannelGroups::scan() { remove(pos); } + static std::default_random_engine RNG(0); + static std::bernoulli_distribution optimizing(0.75); // fixing OpenSpace as designated + DEBUG(groups).print(" scan()\n"); // foreach block for (int32_t z = mapz - 1; z >= 0; --z) { @@ -153,7 +152,7 @@ void ChannelGroups::scan() { // the block if (df::map_block* block = Maps::getBlock(bx, by, z)) { // skip this block? - if (!block->flags.bits.designated && !group_blocks.count(block)) { + if (!block->flags.bits.designated && !group_blocks.count(block) && optimizing(RNG)) { continue; } // foreach tile @@ -162,7 +161,13 @@ void ChannelGroups::scan() { for (int16_t ly = 0; ly < 16; ++ly) { // the tile, check if it has a channel designation df::coord map_pos((bx * 16) + lx, (by * 16) + ly, z); - if (is_dig_designation(block->designation[lx][ly])) { + if (TileCache::Get().hasChanged(map_pos, block->tiletype[lx][ly])) { + remove(map_pos); + if (jobs.count(map_pos)) { + jobs.erase(map_pos); + } + block->designation[lx][ly].bits.dig = df::tile_dig_designation::No; + } else if (is_dig_designation(block->designation[lx][ly])) { for (df::block_square_event* event: block->block_events) { if (auto evT = virtual_cast(event)) { // we want to let the user keep some designations free of being managed @@ -170,18 +175,17 @@ void ChannelGroups::scan() { if (evT->priority[lx][ly] < 1000 * config.ignore_threshold) { if (empty_group) { group_blocks.emplace(block); + empty_group = false; } TRACE(groups).print(" adding (" COORD ")\n", COORDARGS(map_pos)); add(map_pos); - empty_group = false; } } } - } else if (isOpenTerrain(block->tiletype[lx][ly])) { - remove(map_pos); } } } + // erase the block if we didn't find anything iterating through it if (empty_group) { group_blocks.erase(block); } @@ -196,6 +200,7 @@ void ChannelGroups::scan() { void ChannelGroups::clear() { debug_map(); WARN(groups).print(" <- clearing groups\n"); + group_blocks.clear(); free_spots.clear(); groups_map.clear(); for(size_t i = 0; i < groups.size(); ++i) { diff --git a/plugins/channel-safely/channel-manager.cpp b/plugins/channel-safely/channel-manager.cpp index 86ce5a726..7d923e9b2 100644 --- a/plugins/channel-safely/channel-manager.cpp +++ b/plugins/channel-safely/channel-manager.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -69,8 +70,11 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo } // if activating designation, check if it is safe to dig or not a channel designation if (!is_channel_designation(block->designation[Coord(local)]) || is_safe_to_dig_down(map_pos)) { - tile_occupancy.bits.dig_marked = marker_mode; - return marker_mode; + if (!block->flags.bits.designated) { + block->flags.bits.designated = true; + } + tile_occupancy.bits.dig_marked = false; + TileCache::Get().cache(map_pos, block->tiletype[Coord(local)]); } return false; @@ -85,7 +89,9 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo if (has_group_above(groups, map_pos) || !is_safe_to_dig_down(map_pos)) { DEBUG(manager).print(" has_groups_above: setting marker mode\n"); tile_occupancy.bits.dig_marked = true; - jobs.erase(map_pos); + if (jobs.count(map_pos)) { + jobs.erase(map_pos); + } WARN(manager).print(" <- manage_one() exits normally\n"); return true; } @@ -104,4 +110,6 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo void ChannelManager::mark_done(const df::coord &map_pos) { groups.remove(map_pos); + jobs.erase(map_pos); + TileCache::Get().uncache(map_pos); } diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index a63228e27..8c22ede44 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -53,6 +53,7 @@ Updated: Nov. 6 2022 #include #include #include +#include #include #include @@ -211,23 +212,23 @@ namespace CSP { INFO(monitor).print("JobCompletedEvent()\n"); auto job = (df::job*) job_ptr; // we only care if the job is a channeling one - if (is_dig_job(job)) { + if (ChannelManager::Get().groups.count(job->pos)) { // untrack job/worker active_workers.erase(job->id); // check job outcome + auto block = Maps::getTileBlock(job->pos); df::coord local(job->pos); - auto block = Maps::getTileBlock(local); local.x = local.x % 16; local.y = local.y % 16; + const auto &type = block->tiletype[Coord(local)]; // verify completion - if (isOpenTerrain(block->tiletype[local.x][local.y]) - || block->designation[local.x][local.y].bits.dig != df::enums::tile_dig_designation::Channel) { + if (TileCache::Get().hasChanged(job->pos, type)) { // the job can be considered done df::coord below(job->pos); below.z--; WARN(monitor).print(" -> Marking tile done and managing the group below.\n"); // mark done and manage below - Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Normal; + block->designation[Coord(local)].bits.traffic = df::tile_traffic::Normal; ChannelManager::Get().mark_done(job->pos); ChannelManager::Get().manage_group(below); ChannelManager::Get().debug(); @@ -247,16 +248,18 @@ namespace CSP { TRACE(monitor).print("OnUpdate()\n"); UnpauseEvent(); - TRACE(monitor).print(" -> evaluate dignow queue\n"); - for (const df::coord &pos: dignow_queue) { - if (!has_unit(Maps::getTileOccupancy(pos))) { - dig_now(out, pos); - } else { - // todo: teleport? - //Units::teleport() + if (config.insta_dig) { + TRACE(monitor).print(" -> evaluate dignow queue\n"); + for (const df::coord &pos: dignow_queue) { + if (!has_unit(Maps::getTileOccupancy(pos))) { + out.print("channel-safely: insta-dig: Digging now!\n"); + dig_now(out, pos); + } else { + // todo: teleport? + //Units::teleport() + } } } - TRACE(monitor).print("OnUpdate() exits\n"); } if (config.monitor_active && tick - last_monitor_tick >= config.monitor_freq) { last_monitor_tick = tick; @@ -311,6 +314,7 @@ namespace CSP { } } } + TRACE(monitor).print("OnUpdate() exits\n"); } } } diff --git a/plugins/channel-safely/include/channel-jobs.h b/plugins/channel-safely/include/channel-jobs.h index d0aaead7c..0290baa19 100644 --- a/plugins/channel-safely/include/channel-jobs.h +++ b/plugins/channel-safely/include/channel-jobs.h @@ -22,6 +22,7 @@ private: public: void load_channel_jobs(); void clear(); + int count(const df::coord &map_pos) const { return jobs.count(map_pos); } Jobs::iterator erase(const df::coord &map_pos); Jobs::const_iterator find(const df::coord &map_pos) const; Jobs::const_iterator begin() const; diff --git a/plugins/channel-safely/include/inlines.h b/plugins/channel-safely/include/inlines.h index e8689d245..dca17460e 100644 --- a/plugins/channel-safely/include/inlines.h +++ b/plugins/channel-safely/include/inlines.h @@ -44,6 +44,10 @@ inline bool is_channel_job(const df::job* job) { return job->job_type == df::job_type::DigChannel; } +inline bool is_group_job(const ChannelGroups &groups, const df::job* job) { + return groups.count(job->pos); +} + inline bool is_dig_designation(const df::tile_designation &designation) { return designation.bits.dig != df::tile_dig_designation::No; } diff --git a/plugins/channel-safely/include/tile-cache.h b/plugins/channel-safely/include/tile-cache.h new file mode 100644 index 000000000..ddd37c34a --- /dev/null +++ b/plugins/channel-safely/include/tile-cache.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +#include + +class TileCache { +private: + TileCache() = default; + std::map locations; +public: + static TileCache& Get() { + static TileCache instance; + return instance; + } + + void cache(const df::coord &pos, df::tiletype type) { + locations.emplace(pos, type); + } + + void uncache(const df::coord &pos) { + locations.erase(pos); + } + + bool hasChanged(const df::coord &pos, const df::tiletype &type) { + if (locations.count(pos)) { + if (type != locations.find(pos)->second){ + return true; + } + } + return false; + } +}; From 9db0d809fadea8617b87f4ca860a35029fb957dd Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 6 Nov 2022 16:18:39 -0800 Subject: [PATCH 08/22] Implements plugin: channel-safely v0.5.1 --- .../channel-safely/channel-safely-plugin.cpp | 106 +++++++++--------- plugins/channel-safely/include/inlines.h | 2 +- 2 files changed, 55 insertions(+), 53 deletions(-) diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index 8c22ede44..3796bddc1 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -148,68 +148,70 @@ namespace CSP { void JobStartedEvent(color_ostream &out, void* p) { if (enabled && World::isFortressMode() && Maps::IsValid()) { - INFO(monitor).print("JobStartedEvent()\n"); - auto job = (df::job*) p; - // validate job type - if (is_channel_job(job)) { - DEBUG(monitor).print(" valid channel job:\n"); - df::unit* worker = Job::getWorker(job); - // there is a valid worker (living citizen) on the job? right.. - if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) { - DEBUG(monitor).print(" valid worker:\n"); - df::coord local(job->pos); - local.x = local.x % 16; - local.y = local.y % 16; - // check pathing exists to job - if (can_reach_designation(worker->pos, job->pos)) { - DEBUG(monitor).print(" can path from (" COORD ") to (" COORD ")\n", - COORDARGS(worker->pos), COORDARGS(job->pos)); - // track workers on jobs - active_workers.emplace(job->id, Units::findIndexById(Job::getWorker(job)->id)); - // set tile to restricted - TRACE(monitor).print(" setting job tile to restricted\n"); - Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Restricted; - } else { - DEBUG(monitor).print(" no path exists to job:\n"); - // if we can't get there, then we should remove the worker and cancel the job (restore tile designation) - Job::removeWorker(job); - cancel_job(job); - if (!config.insta_dig) { - TRACE(monitor).print(" setting marker mode for (" COORD ")\n", COORDARGS(job->pos)); - // set to marker mode - auto occupancy = Maps::getTileOccupancy(job->pos); - if (!occupancy) { - WARN(monitor).print(" Could not acquire tile occupancy*\n"); - return; - } - occupancy->bits.dig_marked = true; - // prevent algorithm from re-enabling designation - df::map_block* block = Maps::getTileBlock(job->pos); - if (!block) { - WARN(monitor).print(" Could not acquire block*\n"); - return; - } - for (auto &be: block->block_events) { ; - if (auto bsedp = virtual_cast(be)) { - TRACE(monitor).print(" re-setting priority\n"); - bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1; + if (config.monitor_active) { + INFO(monitor).print("JobStartedEvent()\n"); + auto job = (df::job*) p; + // validate job type + if (is_channel_job(job)) { + DEBUG(monitor).print(" valid channel job:\n"); + df::unit* worker = Job::getWorker(job); + // there is a valid worker (living citizen) on the job? right.. + if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) { + DEBUG(monitor).print(" valid worker:\n"); + df::coord local(job->pos); + local.x = local.x % 16; + local.y = local.y % 16; + // check pathing exists to job + if (can_reach_designation(worker->pos, job->pos)) { + DEBUG(monitor).print(" can path from (" COORD ") to (" COORD ")\n", + COORDARGS(worker->pos), COORDARGS(job->pos)); + // track workers on jobs + active_workers.emplace(job->id, Units::findIndexById(Job::getWorker(job)->id)); + // set tile to restricted + TRACE(monitor).print(" setting job tile to restricted\n"); + Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Restricted; + } else { + DEBUG(monitor).print(" no path exists to job:\n"); + // if we can't get there, then we should remove the worker and cancel the job (restore tile designation) + Job::removeWorker(job); + cancel_job(job); + if (!config.insta_dig) { + TRACE(monitor).print(" setting marker mode for (" COORD ")\n", COORDARGS(job->pos)); + // set to marker mode + auto occupancy = Maps::getTileOccupancy(job->pos); + if (!occupancy) { + WARN(monitor).print(" Could not acquire tile occupancy*\n"); + return; + } + occupancy->bits.dig_marked = true; + // prevent algorithm from re-enabling designation + df::map_block* block = Maps::getTileBlock(job->pos); + if (!block) { + WARN(monitor).print(" Could not acquire block*\n"); + return; } + for (auto &be: block->block_events) { ; + if (auto bsedp = virtual_cast(be)) { + TRACE(monitor).print(" re-setting priority\n"); + bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1; + } + } + } else { + TRACE(monitor).print(" deleting job, and queuing insta-dig)\n"); + // queue digging the job instantly + dignow_queue.emplace(job->pos); } - } else { - TRACE(monitor).print(" deleting job, and queuing insta-dig)\n"); - // queue digging the job instantly - dignow_queue.emplace(job->pos); } } } + INFO(monitor).print(" <- JobStartedEvent() exits normally\n"); } - INFO(monitor).print(" <- JobStartedEvent() exits normally\n"); } } void JobCompletedEvent(color_ostream &out, void* job_ptr) { if (enabled && World::isFortressMode() && Maps::IsValid()) { - INFO(monitor).print("JobCompletedEvent()\n"); + INFO(jobs).print("JobCompletedEvent()\n"); auto job = (df::job*) job_ptr; // we only care if the job is a channeling one if (ChannelManager::Get().groups.count(job->pos)) { @@ -226,7 +228,7 @@ namespace CSP { // the job can be considered done df::coord below(job->pos); below.z--; - WARN(monitor).print(" -> Marking tile done and managing the group below.\n"); + WARN(jobs).print(" -> Marking tile done and managing the group below.\n"); // mark done and manage below block->designation[Coord(local)].bits.traffic = df::tile_traffic::Normal; ChannelManager::Get().mark_done(job->pos); diff --git a/plugins/channel-safely/include/inlines.h b/plugins/channel-safely/include/inlines.h index dca17460e..928cc10a8 100644 --- a/plugins/channel-safely/include/inlines.h +++ b/plugins/channel-safely/include/inlines.h @@ -97,7 +97,7 @@ inline bool can_reach_designation(const df::coord &start, const df::coord &end) df::coord neighbours[8]; get_neighbours(end, neighbours); for (auto &pos : neighbours) { - if (Maps::canWalkBetween(start, pos)) { + if (Maps::isValidTilePos(pos) && Maps::canWalkBetween(start, pos)) { return true; } } From 84ffeef0925fb1dbbd1d614b166ebb5aef93177b Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sun, 6 Nov 2022 17:02:11 -0800 Subject: [PATCH 09/22] Implements plugin: channel-safely v0.5.2 --- .../channel-safely/channel-safely-plugin.cpp | 108 ++++++------------ plugins/channel-safely/include/inlines.h | 16 +-- 2 files changed, 45 insertions(+), 79 deletions(-) diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index 3796bddc1..37bee4282 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -71,10 +71,10 @@ Updated: Nov. 6 2022 // Debugging namespace DFHack { - DBG_DECLARE(channelsafely, monitor, DebugCategory::LINFO); - DBG_DECLARE(channelsafely, manager, DebugCategory::LINFO); - DBG_DECLARE(channelsafely, groups, DebugCategory::LINFO); - DBG_DECLARE(channelsafely, jobs, DebugCategory::LINFO); + DBG_DECLARE(channelsafely, monitor, DebugCategory::LERROR); + DBG_DECLARE(channelsafely, manager, DebugCategory::LERROR); + DBG_DECLARE(channelsafely, groups, DebugCategory::LERROR); + DBG_DECLARE(channelsafely, jobs, DebugCategory::LERROR); } DFHACK_PLUGIN("channel-safely"); @@ -146,73 +146,34 @@ namespace CSP { INFO(monitor).print("UnpauseEvent() exits\n"); } - void JobStartedEvent(color_ostream &out, void* p) { + void JobStartedEvent(color_ostream &out, void* j) { if (enabled && World::isFortressMode() && Maps::IsValid()) { - if (config.monitor_active) { - INFO(monitor).print("JobStartedEvent()\n"); - auto job = (df::job*) p; - // validate job type - if (is_channel_job(job)) { - DEBUG(monitor).print(" valid channel job:\n"); - df::unit* worker = Job::getWorker(job); - // there is a valid worker (living citizen) on the job? right.. - if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) { - DEBUG(monitor).print(" valid worker:\n"); - df::coord local(job->pos); - local.x = local.x % 16; - local.y = local.y % 16; - // check pathing exists to job - if (can_reach_designation(worker->pos, job->pos)) { - DEBUG(monitor).print(" can path from (" COORD ") to (" COORD ")\n", - COORDARGS(worker->pos), COORDARGS(job->pos)); - // track workers on jobs - active_workers.emplace(job->id, Units::findIndexById(Job::getWorker(job)->id)); - // set tile to restricted - TRACE(monitor).print(" setting job tile to restricted\n"); - Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Restricted; - } else { - DEBUG(monitor).print(" no path exists to job:\n"); - // if we can't get there, then we should remove the worker and cancel the job (restore tile designation) - Job::removeWorker(job); - cancel_job(job); - if (!config.insta_dig) { - TRACE(monitor).print(" setting marker mode for (" COORD ")\n", COORDARGS(job->pos)); - // set to marker mode - auto occupancy = Maps::getTileOccupancy(job->pos); - if (!occupancy) { - WARN(monitor).print(" Could not acquire tile occupancy*\n"); - return; - } - occupancy->bits.dig_marked = true; - // prevent algorithm from re-enabling designation - df::map_block* block = Maps::getTileBlock(job->pos); - if (!block) { - WARN(monitor).print(" Could not acquire block*\n"); - return; - } - for (auto &be: block->block_events) { ; - if (auto bsedp = virtual_cast(be)) { - TRACE(monitor).print(" re-setting priority\n"); - bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1; - } - } - } else { - TRACE(monitor).print(" deleting job, and queuing insta-dig)\n"); - // queue digging the job instantly - dignow_queue.emplace(job->pos); - } - } + INFO(jobs).print("JobStartedEvent()\n"); + auto job = (df::job*) j; + // validate job type + if (is_channel_job(job)) { + DEBUG(jobs).print(" valid channel job:\n"); + df::unit* worker = Job::getWorker(job); + // there is a valid worker (living citizen) on the job? right.. + if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) { + DEBUG(jobs).print(" valid worker:\n"); + // track workers on jobs + if (config.monitor_active) { + active_workers.emplace(job->id, Units::findIndexById(worker->id)); } + // set tile to restricted + TRACE(jobs).print(" setting job tile to restricted\n"); + Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Restricted; } - INFO(monitor).print(" <- JobStartedEvent() exits normally\n"); } + INFO(jobs).print(" <- JobStartedEvent() exits normally\n"); } } - void JobCompletedEvent(color_ostream &out, void* job_ptr) { + void JobCompletedEvent(color_ostream &out, void* j) { if (enabled && World::isFortressMode() && Maps::IsValid()) { INFO(jobs).print("JobCompletedEvent()\n"); - auto job = (df::job*) job_ptr; + auto job = (df::job*) j; // we only care if the job is a channeling one if (ChannelManager::Get().groups.count(job->pos)) { // untrack job/worker @@ -222,9 +183,8 @@ namespace CSP { df::coord local(job->pos); local.x = local.x % 16; local.y = local.y % 16; - const auto &type = block->tiletype[Coord(local)]; // verify completion - if (TileCache::Get().hasChanged(job->pos, type)) { + if (TileCache::Get().hasChanged(job->pos, block->tiletype[Coord(local)])) { // the job can be considered done df::coord below(job->pos); below.z--; @@ -236,10 +196,15 @@ namespace CSP { ChannelManager::Get().debug(); } } - INFO(monitor).print("JobCompletedEvent() exits\n"); + INFO(jobs).print("JobCompletedEvent() exits\n"); } } + void NewReportEvent(color_ostream &out, void* r) { + int32_t report_id = (int32_t)(intptr_t(r)); + out.print("%d\n", report_id); + } + void OnUpdate(color_ostream &out) { if (enabled && World::isFortressMode() && Maps::IsValid() && !World::ReadPauseState()) { static int32_t last_monitor_tick = df::global::world->frame_counter; @@ -247,7 +212,7 @@ namespace CSP { int32_t tick = df::global::world->frame_counter; if (tick - last_refresh_tick >= config.refresh_freq) { last_refresh_tick = tick; - TRACE(monitor).print("OnUpdate()\n"); + TRACE(monitor).print("OnUpdate() refreshing now\n"); UnpauseEvent(); if (config.insta_dig) { @@ -261,11 +226,12 @@ namespace CSP { //Units::teleport() } } + TRACE(monitor).print("OnUpdate() refresh done\n"); } } if (config.monitor_active && tick - last_monitor_tick >= config.monitor_freq) { last_monitor_tick = tick; - TRACE(monitor).print("OnUpdate()\n"); + TRACE(monitor).print("OnUpdate() monitoring now\n"); for (df::job_list_link* link = &df::global::world->jobs.list; link != nullptr; link = link->next) { df::job* job = link->item; if (job) { @@ -315,8 +281,8 @@ namespace CSP { } } } + TRACE(monitor).print("OnUpdate() monitoring done\n"); } - TRACE(monitor).print("OnUpdate() exits\n"); } } } @@ -328,10 +294,6 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Sun, 6 Nov 2022 18:07:22 -0800 Subject: [PATCH 10/22] Implements plugin: channel-safely v0.5.3 --- plugins/channel-safely/channel-manager.cpp | 25 +++++++------------ .../channel-safely/channel-safely-plugin.cpp | 11 ++++++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/plugins/channel-safely/channel-manager.cpp b/plugins/channel-safely/channel-manager.cpp index 7d923e9b2..9f5c5d4b4 100644 --- a/plugins/channel-safely/channel-manager.cpp +++ b/plugins/channel-safely/channel-manager.cpp @@ -80,23 +80,16 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo } else { // next search for the designation priority - for (df::block_square_event* event: block->block_events) { - if (auto evT = virtual_cast(event)) { - // we want to let the user keep some designations free of being managed - if (evT->priority[Coord(local)] < 1000 * config.ignore_threshold) { - DEBUG(manager).print(" if(has_groups_above())\n"); - // check that the group has no incomplete groups directly above it - if (has_group_above(groups, map_pos) || !is_safe_to_dig_down(map_pos)) { - DEBUG(manager).print(" has_groups_above: setting marker mode\n"); - tile_occupancy.bits.dig_marked = true; - if (jobs.count(map_pos)) { - jobs.erase(map_pos); - } - WARN(manager).print(" <- manage_one() exits normally\n"); - return true; - } - } + DEBUG(manager).print(" if(has_groups_above())\n"); + // check that the group has no incomplete groups directly above it + if (has_group_above(groups, map_pos) || !is_safe_to_dig_down(map_pos)) { + DEBUG(manager).print(" has_groups_above: setting marker mode\n"); + tile_occupancy.bits.dig_marked = true; + if (jobs.count(map_pos)) { + jobs.erase(map_pos); } + WARN(manager).print(" <- manage_one() exits normally\n"); + return true; } } } else { diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index 37bee4282..fa6864b8c 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -202,7 +202,14 @@ namespace CSP { void NewReportEvent(color_ostream &out, void* r) { int32_t report_id = (int32_t)(intptr_t(r)); - out.print("%d\n", report_id); + if (df::global::world) { + auto &reports = df::global::world->status.reports; + size_t idx = df::report::binsearch_index(reports, report_id); + if (idx >= 0 && idx < reports.size()){ + auto report = reports[report_id]; + out.print("%d\n%s\n", report_id, report->text.c_str()); + } + } } void OnUpdate(color_ostream &out) { @@ -326,9 +333,9 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { EM::EventHandler jobStartHandler(CSP::JobStartedEvent, 0); EM::EventHandler jobCompletionHandler(CSP::JobCompletedEvent, 0); EM::EventHandler reportHandler(CSP::NewReportEvent, 0); + EM::registerListener(EventType::REPORT, reportHandler, plugin_self); EM::registerListener(EventType::JOB_STARTED, jobStartHandler, plugin_self); EM::registerListener(EventType::JOB_COMPLETED, jobCompletionHandler, plugin_self); - EM::registerListener(EventType::REPORT, reportHandler, plugin_self); // manage designations to start off (first time building groups [very important]) out.print("channel-safely: enabled!\n"); CSP::UnpauseEvent(); From 92537bc4596392188863b15b38e1119bc575467b Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 7 Nov 2022 14:54:39 -0800 Subject: [PATCH 11/22] Implements plugin: channel-safely v0.6 --- docs/plugins/channel-safely.rst | 6 +- plugins/channel-safely/channel-groups.cpp | 16 +- .../channel-safely/channel-safely-plugin.cpp | 341 ++++++++++++------ plugins/channel-safely/include/inlines.h | 32 ++ plugins/channel-safely/include/plugin.h | 1 + 5 files changed, 275 insertions(+), 121 deletions(-) diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index 842d57353..3dd051edb 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -47,9 +47,11 @@ Examples Features -------- -:monitor-active: Toggle whether to monitor the conditions of active digs. (default: disabled) :require-vision: Toggle whether the dwarves need vision of a tile before channeling to it can be deemed unsafe. (default: enabled) -:insta-dig: Toggle whether to use insta-digging on unreachable designations. (default: disabled) +:monitor: Toggle whether to monitor the conditions of active digs. (default: disabled) +:resurrect: Toggle whether to resurrect dwarves killed on the job. (default: disabled) +:insta-dig: Toggle whether to use insta-digging on unreachable designations. + Runs on the refresh cycles. (default: disabled) Settings -------- diff --git a/plugins/channel-safely/channel-groups.cpp b/plugins/channel-safely/channel-groups.cpp index 48f4a9ee5..1a7f81a13 100644 --- a/plugins/channel-safely/channel-groups.cpp +++ b/plugins/channel-safely/channel-groups.cpp @@ -6,21 +6,7 @@ #include -template -void set_difference(const Ctr1 &c1, const Ctr2 &c2, Ctr3 &c3) { - for (const auto &a : c1) { - bool matched = false; - for (const auto &b : c2) { - if (a == b) { - matched = true; - break; - } - } - if (!matched) { - c3.emplace(a); - } - } -} + // adds map_pos to a group if an adjacent one exists, or creates one if none exist... if multiple exist they're merged into the first found void ChannelGroups::add(const df::coord &map_pos) { diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index fa6864b8c..c74eecc34 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -60,17 +60,19 @@ Updated: Nov. 6 2022 #include #include #include +#include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include // Debugging namespace DFHack { + DBG_DECLARE(channelsafely, plugin, DebugCategory::LINFO); DBG_DECLARE(channelsafely, monitor, DebugCategory::LERROR); DBG_DECLARE(channelsafely, manager, DebugCategory::LERROR); DBG_DECLARE(channelsafely, groups, DebugCategory::LERROR); @@ -87,33 +89,25 @@ using namespace EM::EventType; int32_t mapx, mapy, mapz; Configuration config; -PersistentDataItem pconfig; -const std::string CONFIG_KEY = std::string(plugin_name) + "/config"; +PersistentDataItem psetting; +PersistentDataItem pfeature; +const std::string FCONFIG_KEY = std::string(plugin_name) + "/feature"; +const std::string SCONFIG_KEY = std::string(plugin_name) + "/setting"; //std::unordered_set active_jobs; -#include - -enum ConfigurationData { - MONITOR, +enum FeatureConfigData { VISION, - INSTADIG, - IGNORE_THRESH, - FALL_THRESH, - REFRESH_RATE, - MONITOR_RATE + MONITOR, + RESURRECT, + INSTADIG }; -inline void saveConfig() { - if (pconfig.isValid()) { - pconfig.ival(MONITOR) = config.monitor_active; - pconfig.ival(VISION) = config.require_vision; - pconfig.ival(INSTADIG) = config.insta_dig; - pconfig.ival(REFRESH_RATE) = config.refresh_freq; - pconfig.ival(MONITOR_RATE) = config.monitor_freq; - pconfig.ival(IGNORE_THRESH) = config.ignore_threshold; - pconfig.ival(FALL_THRESH) = config.fall_threshold; - } -} +enum SettingConfigData { + REFRESH_RATE, + MONITOR_RATE, + IGNORE_THRESH, + FALL_THRESH +}; // executes dig designations for the specified tile coordinates inline bool dig_now(color_ostream &out, const df::coord &map_pos) { @@ -133,11 +127,65 @@ inline bool dig_now(color_ostream &out, const df::coord &map_pos) { } +// fully heals the unit specified, resurrecting if need be +inline void resurrect(color_ostream &out, const int32_t &unit) { + std::vector params{"-r", "--unit", std::to_string(unit)}; + Core::getInstance().runCommand(out,"full-heal", params); +} + namespace CSP { - std::unordered_map active_workers; + std::unordered_set endangered_workers; + std::unordered_map job_ids; + std::unordered_map active_jobs; + std::unordered_map active_workers; + std::unordered_map last_safe; std::unordered_set dignow_queue; + void SaveSettings() { + if (pfeature.isValid() && psetting.isValid()) { + try { + pfeature.ival(MONITOR) = config.monitor_active; + pfeature.ival(VISION) = config.require_vision; + pfeature.ival(INSTADIG) = config.insta_dig; + pfeature.ival(RESURRECT) = config.resurrect; + + psetting.ival(REFRESH_RATE) = config.refresh_freq; + psetting.ival(MONITOR_RATE) = config.monitor_freq; + psetting.ival(IGNORE_THRESH) = config.ignore_threshold; + psetting.ival(FALL_THRESH) = config.fall_threshold; + } catch (std::exception &e) { + ERR(plugin).print("%s\n", e.what()); + } + } + } + + void LoadSettings() { + pfeature = World::GetPersistentData(FCONFIG_KEY); + psetting = World::GetPersistentData(SCONFIG_KEY); + + if (!pfeature.isValid() || !psetting.isValid()) { + pfeature = World::AddPersistentData(FCONFIG_KEY); + psetting = World::AddPersistentData(SCONFIG_KEY); + SaveSettings(); + } else { + try { + config.monitor_active = pfeature.ival(MONITOR); + config.require_vision = pfeature.ival(VISION); + config.insta_dig = pfeature.ival(INSTADIG); + config.resurrect = pfeature.ival(RESURRECT); + + config.ignore_threshold = psetting.ival(IGNORE_THRESH); + config.fall_threshold = psetting.ival(FALL_THRESH); + config.refresh_freq = psetting.ival(REFRESH_RATE); + config.monitor_freq = psetting.ival(MONITOR_RATE); + } catch (std::exception &e) { + ERR(plugin).print("%s\n", e.what()); + } + } + active_workers.clear(); + } + void UnpauseEvent(){ INFO(monitor).print("UnpauseEvent()\n"); ChannelManager::Get().build_groups(); @@ -158,8 +206,10 @@ namespace CSP { if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) { DEBUG(jobs).print(" valid worker:\n"); // track workers on jobs - if (config.monitor_active) { - active_workers.emplace(job->id, Units::findIndexById(worker->id)); + if (config.monitor_active || config.resurrect) { + job_ids.emplace(job, job->id); + active_jobs.emplace(job->id, job); + active_workers[job->id] = worker; } // set tile to restricted TRACE(jobs).print(" setting job tile to restricted\n"); @@ -176,8 +226,6 @@ namespace CSP { auto job = (df::job*) j; // we only care if the job is a channeling one if (ChannelManager::Get().groups.count(job->pos)) { - // untrack job/worker - active_workers.erase(job->id); // check job outcome auto block = Maps::getTileBlock(job->pos); df::coord local(job->pos); @@ -188,12 +236,22 @@ namespace CSP { // the job can be considered done df::coord below(job->pos); below.z--; - WARN(jobs).print(" -> Marking tile done and managing the group below.\n"); + WARN(jobs).print(" -> (" COORD ") is marked done, managing group below.\n", COORDARGS(job->pos)); // mark done and manage below block->designation[Coord(local)].bits.traffic = df::tile_traffic::Normal; ChannelManager::Get().mark_done(job->pos); ChannelManager::Get().manage_group(below); ChannelManager::Get().debug(); + } else { + ERR(jobs).print(" -> (" COORD ") is not done but the job \"completed\".\n", COORDARGS(job->pos)); + endangered_workers.emplace(active_workers[job->id]); + } + // clean up + if (!config.resurrect) { + auto jp = active_jobs[job->id]; + job_ids.erase(jp); + active_workers.erase(job->id); + active_jobs.erase(job->id); } } INFO(jobs).print("JobCompletedEvent() exits\n"); @@ -201,22 +259,34 @@ namespace CSP { } void NewReportEvent(color_ostream &out, void* r) { - int32_t report_id = (int32_t)(intptr_t(r)); + auto report_id = (int32_t)(intptr_t(r)); if (df::global::world) { - auto &reports = df::global::world->status.reports; - size_t idx = df::report::binsearch_index(reports, report_id); - if (idx >= 0 && idx < reports.size()){ - auto report = reports[report_id]; - out.print("%d\n%s\n", report_id, report->text.c_str()); + std::vector &reports = df::global::world->status.reports; + size_t idx = -1; + idx = df::report::binsearch_index(reports, report_id); + df::report* report = reports.at(idx); + switch (report->type) { + case announcement_type::CAVE_COLLAPSE: + for (auto p : active_workers) { + endangered_workers.emplace(p.second); + } + case announcement_type::CANCEL_JOB: + out.print("%d, pos: " COORD "\n%s\n", report_id, COORDARGS(report->pos), report->text.c_str()); + default: + break; } } } void OnUpdate(color_ostream &out) { if (enabled && World::isFortressMode() && Maps::IsValid() && !World::ReadPauseState()) { + static int32_t last_tick = df::global::world->frame_counter; static int32_t last_monitor_tick = df::global::world->frame_counter; static int32_t last_refresh_tick = df::global::world->frame_counter; + static int32_t last_resurrect_tick = df::global::world->frame_counter; int32_t tick = df::global::world->frame_counter; + + // Refreshing the group data with full scanning if (tick - last_refresh_tick >= config.refresh_freq) { last_refresh_tick = tick; TRACE(monitor).print("OnUpdate() refreshing now\n"); @@ -236,60 +306,112 @@ namespace CSP { TRACE(monitor).print("OnUpdate() refresh done\n"); } } + + // Clean up stale df::job* + if ((config.monitor_active || config.resurrect) && tick - last_tick >= 1) { + last_tick = tick; + // make note of valid jobs + std::unordered_map valid_jobs; + for (df::job_list_link* link = &df::global::world->jobs.list; link != nullptr; link = link->next) { + df::job* job = link->item; + if (job && active_jobs.count(job->id)) { + valid_jobs.emplace(job->id, job); + } + } + + // erase the active jobs that aren't valid + std::unordered_set erase; + map_value_difference(active_jobs, valid_jobs, erase); + for (auto j : erase) { + auto id = job_ids[j]; + job_ids.erase(j); + active_jobs.erase(id); + active_workers.erase(id); + } + } + + // Monitoring Active and Resurrecting Dead if (config.monitor_active && tick - last_monitor_tick >= config.monitor_freq) { last_monitor_tick = tick; TRACE(monitor).print("OnUpdate() monitoring now\n"); - for (df::job_list_link* link = &df::global::world->jobs.list; link != nullptr; link = link->next) { - df::job* job = link->item; - if (job) { - auto iter = active_workers.find(job->id); - TRACE(monitor).print(" -> check for job in tracking\n"); - if (iter != active_workers.end()) { - df::unit* unit = df::global::world->units.active[iter->second]; - TRACE(monitor).print(" -> compare positions of worker and job\n"); - // check if fall is possible - if (unit->pos == job->pos) { - // can fall, is safe? - TRACE(monitor).print(" equal -> check if safe fall\n"); - if (!is_safe_fall(job->pos)) { - // unsafe - Job::removeWorker(job); - if (config.insta_dig) { - TRACE(monitor).print(" -> insta-dig\n"); - // delete the job - Job::removeJob(job); - // queue digging the job instantly - dignow_queue.emplace(job->pos); - // worker is currently in the air - Units::teleport(unit, last_safe[unit->id]); - last_safe.erase(unit->id); - } else { - TRACE(monitor).print(" -> set marker mode\n"); - // set to marker mode - Maps::getTileOccupancy(job->pos)->bits.dig_marked = true; - // prevent algorithm from re-enabling designation - for (auto &be: Maps::getBlock(job->pos)->block_events) { ; - if (auto bsedp = virtual_cast( - be)) { - df::coord local(job->pos); - local.x = local.x % 16; - local.y = local.y % 16; - bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1; - break; - } - } + + // iterate active jobs + for (auto pair: active_jobs) { + df::job* job = pair.second; + df::unit* unit = active_workers[job->id]; + if (!unit) continue; + TRACE(monitor).print(" -> check for job in tracking\n"); + if (Units::isAlive(unit)) { + if (!config.monitor_active) continue; + TRACE(monitor).print(" -> compare positions of worker and job\n"); + + // save position + if (unit->pos != job->pos && isFloorTerrain(*Maps::getTileType(unit->pos))) { + // worker is perfectly safe right now + last_safe[unit->id] = unit->pos; + TRACE(monitor).print(" -> save safe position\n"); + continue; + } + + // check for fall safety + if (unit->pos == job->pos && !is_safe_fall(job->pos)) { + // unsafe + WARN(monitor).print(" -> unsafe job\n"); + Job::removeWorker(job); + + // decide to insta-dig or marker mode + if (config.insta_dig) { + // delete the job + Job::removeJob(job); + // queue digging the job instantly + dignow_queue.emplace(job->pos); + DEBUG(monitor).print(" -> insta-dig\n"); + } else if (Maps::isValidTilePos(job->pos)) { + // set marker mode + Maps::getTileOccupancy(job->pos)->bits.dig_marked = true; + + // prevent algorithm from re-enabling designation + for (auto &be: Maps::getBlock(job->pos)->block_events) { ; + if (auto bsedp = virtual_cast( + be)) { + df::coord local(job->pos); + local.x = local.x % 16; + local.y = local.y % 16; + bsedp->priority[Coord(local)] = config.ignore_threshold * 1000 + 1; + break; } } - } else { - TRACE(monitor).print(" -> save safe position\n"); - // worker is perfectly safe right now - last_safe[unit->id] = unit->pos; + DEBUG(monitor).print(" -> set marker mode\n"); } } } } TRACE(monitor).print("OnUpdate() monitoring done\n"); } + + if (config.resurrect && tick - last_resurrect_tick >= 1) { + last_resurrect_tick = tick; + static std::unordered_map age; + + // clean up any "endangered" workers that have been tracked 100 ticks or more + for (auto iter = age.begin(); iter != age.end();) { + if (tick - iter->second >= 1200) { //keep watch 1 day + iter = age.erase(iter); + continue; + } + ++iter; + } + + // resurrect any dead units + for (auto unit : endangered_workers) { + age.emplace(unit, tick); + if (!Units::isAlive(unit)) { + resurrect(out, unit->id); + Units::teleport(unit, last_safe[unit->id]); + WARN(plugin).print(">RESURRECTING<\n"); + } + } + } } } } @@ -310,19 +432,10 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) { } DFhackCExport command_result plugin_load_data (color_ostream &out) { - pconfig = World::GetPersistentData(CONFIG_KEY); - - if (!pconfig.isValid()) { - pconfig = World::AddPersistentData(CONFIG_KEY); - saveConfig(); - } else { - config.monitor_active = pconfig.ival(MONITOR); - config.require_vision = pconfig.ival(VISION); - config.insta_dig = pconfig.ival(INSTADIG); - config.refresh_freq = pconfig.ival(REFRESH_RATE); - config.monitor_freq = pconfig.ival(MONITOR_RATE); - config.ignore_threshold = pconfig.ival(IGNORE_THRESH); - config.fall_threshold = pconfig.ival(FALL_THRESH); + CSP::LoadSettings(); + if (enabled) { + std::vector params; + channel_safely(out, params); } return DFHack::CR_OK; } @@ -352,6 +465,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan if (enabled && World::isFortressMode() && Maps::IsValid()) { switch (event) { case SC_MAP_LOADED: + CSP::active_workers.clear(); // cache the map size Maps::getSize(mapx, mapy, mapz); case SC_UNPAUSED: @@ -447,12 +561,28 @@ command_result channel_safely(color_ostream &out, std::vector ¶ DBG_NAME(groups).allowed(DFHack::DebugCategory::LERROR); DBG_NAME(jobs).allowed(DFHack::DebugCategory::LERROR); } - } else if(parameters[1] == "monitor-active"){ - config.monitor_active = state; + } else if(parameters[1] == "monitor"){ + if (state != config.monitor_active) { + config.monitor_active = state; + // if this is a fresh start + if (state && !config.resurrect) { + // we need a fresh start + CSP::active_workers.clear(); + } + } } else if (parameters[1] == "require-vision") { config.require_vision = state; } else if (parameters[1] == "insta-dig") { config.insta_dig = state; + } else if (parameters[1] == "resurrect") { + if (state != config.resurrect) { + config.resurrect = state; + // if this is a fresh start + if (state && !config.monitor_active) { + // we need a fresh start + CSP::active_workers.clear(); + } + } } else if (parameters[1] == "refresh-freq" && set && parameters.size() == 3) { config.refresh_freq = std::abs(std::stol(parameters[2])); } else if (parameters[1] == "monitor-freq" && set && parameters.size() == 3) { @@ -477,14 +607,17 @@ command_result channel_safely(color_ostream &out, std::vector ¶ } } else { out.print("Channel-Safely is %s\n", enabled ? "ENABLED." : "DISABLED."); - out.print("monitor-active: %s\n", config.monitor_active ? "on." : "off."); - out.print("require-vision: %s\n", config.require_vision ? "on." : "off."); - out.print("insta-dig: %s\n", config.insta_dig ? "on." : "off."); - out.print("refresh-freq: %" PRIi32 "\n", config.refresh_freq); - out.print("monitor-freq: %" PRIi32 "\n", config.monitor_freq); - out.print("ignore-threshold: %" PRIu8 "\n", config.ignore_threshold); - out.print("fall-threshold: %" PRIu8 "\n", config.fall_threshold); + out.print(" FEATURES:\n"); + out.print(" %-20s\t%s\n", "monitor-active: ", config.monitor_active ? "on." : "off."); + out.print(" %-20s\t%s\n", "require-vision: ", config.require_vision ? "on." : "off."); + out.print(" %-20s\t%s\n", "insta-dig: ", config.insta_dig ? "on." : "off."); + out.print(" %-20s\t%s\n", "resurrect: ", config.resurrect ? "on." : "off."); + out.print(" SETTINGS:\n"); + out.print(" %-20s\t%" PRIi32 "\n", "refresh-freq: ", config.refresh_freq); + out.print(" %-20s\t%" PRIi32 "\n", "monitor-freq: ", config.monitor_freq); + out.print(" %-20s\t%" PRIu8 "\n", "ignore-threshold: ", config.ignore_threshold); + out.print(" %-20s\t%" PRIu8 "\n", "fall-threshold: ", config.fall_threshold); } - saveConfig(); + CSP::SaveSettings(); return DFHack::CR_OK; } diff --git a/plugins/channel-safely/include/inlines.h b/plugins/channel-safely/include/inlines.h index 242bcb652..e22210f7a 100644 --- a/plugins/channel-safely/include/inlines.h +++ b/plugins/channel-safely/include/inlines.h @@ -168,3 +168,35 @@ inline void cancel_job(df::job* job) { Job::removeJob(job); } } + +template +void set_difference(const Ctr1 &c1, const Ctr2 &c2, Ctr3 &c3) { + for (const auto &a : c1) { + bool matched = false; + for (const auto &b : c2) { + if (a == b) { + matched = true; + break; + } + } + if (!matched) { + c3.emplace(a); + } + } +} + +template +void map_value_difference(const Ctr1 &c1, const Ctr2 &c2, Ctr3 &c3) { + for (const auto &a : c1) { + bool matched = false; + for (const auto &b : c2) { + if (a.second == b.second) { + matched = true; + break; + } + } + if (!matched) { + c3.emplace(a.second); + } + } +} diff --git a/plugins/channel-safely/include/plugin.h b/plugins/channel-safely/include/plugin.h index d165d3f18..2451033c5 100644 --- a/plugins/channel-safely/include/plugin.h +++ b/plugins/channel-safely/include/plugin.h @@ -13,6 +13,7 @@ struct Configuration { bool monitor_active = false; bool require_vision = true; bool insta_dig = false; + bool resurrect = false; int32_t refresh_freq = 600; int32_t monitor_freq = 1; uint8_t ignore_threshold = 5; From a9f00219272653d8bebd289cf1df0781d46493b7 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 8 Nov 2022 11:38:30 -0800 Subject: [PATCH 12/22] Implements plugin: channel-safely v0.6.1 --- .../channel-safely/channel-safely-plugin.cpp | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index c74eecc34..8c1700b69 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -243,8 +243,13 @@ namespace CSP { ChannelManager::Get().manage_group(below); ChannelManager::Get().debug(); } else { - ERR(jobs).print(" -> (" COORD ") is not done but the job \"completed\".\n", COORDARGS(job->pos)); + // the tile is unchanged + ERR(jobs).print(" -> (" COORD ") stopped working but (" COORD ") doesn't appear done.\n",COORDARGS(worker->pos), COORDARGS(job->pos)); + df::unit* worker = active_workers[job->id]; endangered_workers.emplace(active_workers[job->id]); + if (config.insta_dig) { + dignow_queue.emplace(job->pos); + } } // clean up if (!config.resurrect) { @@ -266,12 +271,18 @@ namespace CSP { idx = df::report::binsearch_index(reports, report_id); df::report* report = reports.at(idx); switch (report->type) { + case announcement_type::CANCEL_JOB: + out.print("%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos), COORDARGS(report->pos2), report->text.c_str()); + if (report->text.find("Dangerous") != std::string::npos) { + dignow_queue.emplace(report->pos); + break; + } else if (!report->flags.bits.unconscious) { + break; + } case announcement_type::CAVE_COLLAPSE: for (auto p : active_workers) { endangered_workers.emplace(p.second); } - case announcement_type::CANCEL_JOB: - out.print("%d, pos: " COORD "\n%s\n", report_id, COORDARGS(report->pos), report->text.c_str()); default: break; } @@ -294,14 +305,17 @@ namespace CSP { if (config.insta_dig) { TRACE(monitor).print(" -> evaluate dignow queue\n"); - for (const df::coord &pos: dignow_queue) { - if (!has_unit(Maps::getTileOccupancy(pos))) { - out.print("channel-safely: insta-dig: Digging now!\n"); - dig_now(out, pos); + for (auto iter = dignow_queue.begin(); iter != dignow_queue.end();) { + if (!has_unit(Maps::getTileOccupancy(*iter))) { + dig_now(out, *iter); + iter = dignow_queue.erase(iter); + WARN(plugin).print(">INSTA-DIGGING<\n"); + continue; } else { // todo: teleport? //Units::teleport() } + ++iter; } TRACE(monitor).print("OnUpdate() refresh done\n"); } @@ -389,6 +403,7 @@ namespace CSP { TRACE(monitor).print("OnUpdate() monitoring done\n"); } + // Resurrect Dead Workers if (config.resurrect && tick - last_resurrect_tick >= 1) { last_resurrect_tick = tick; static std::unordered_map age; From 8847ed23b4c0ff4f1b7cc9e3c6a3d96f4a37f13d Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 8 Nov 2022 11:46:01 -0800 Subject: [PATCH 13/22] Implements plugin: channel-safely v0.6.1.1 --- plugins/channel-safely/channel-safely-plugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index 8c1700b69..bfb99b463 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -244,9 +244,9 @@ namespace CSP { ChannelManager::Get().debug(); } else { // the tile is unchanged - ERR(jobs).print(" -> (" COORD ") stopped working but (" COORD ") doesn't appear done.\n",COORDARGS(worker->pos), COORDARGS(job->pos)); df::unit* worker = active_workers[job->id]; endangered_workers.emplace(active_workers[job->id]); + ERR(jobs).print(" -> (" COORD ") stopped working but (" COORD ") doesn't appear done.\n",COORDARGS(worker->pos), COORDARGS(job->pos)); if (config.insta_dig) { dignow_queue.emplace(job->pos); } From 3cb186a62fba3505a174bc438833dd17ec337f8c Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 8 Nov 2022 20:01:30 -0800 Subject: [PATCH 14/22] Implements plugin: channel-safely v0.6.1.2 --- plugins/channel-safely/channel-safely-plugin.cpp | 10 ++++++---- plugins/channel-safely/include/channel-manager.h | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index bfb99b463..1224413f1 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -199,13 +199,15 @@ namespace CSP { INFO(jobs).print("JobStartedEvent()\n"); auto job = (df::job*) j; // validate job type - if (is_channel_job(job)) { - DEBUG(jobs).print(" valid channel job:\n"); + if (ChannelManager::Get().exists(job->pos)) { + WARN(jobs).print(" valid channel job:\n"); df::unit* worker = Job::getWorker(job); // there is a valid worker (living citizen) on the job? right.. if (worker && Units::isAlive(worker) && Units::isCitizen(worker)) { DEBUG(jobs).print(" valid worker:\n"); // track workers on jobs + df::coord &pos = job->pos; + WARN(jobs).print(" -> Starting job at (" COORD ")\n", COORDARGS(pos)); if (config.monitor_active || config.resurrect) { job_ids.emplace(job, job->id); active_jobs.emplace(job->id, job); @@ -225,7 +227,7 @@ namespace CSP { INFO(jobs).print("JobCompletedEvent()\n"); auto job = (df::job*) j; // we only care if the job is a channeling one - if (ChannelManager::Get().groups.count(job->pos)) { + if (ChannelManager::Get().exists(job->pos)) { // check job outcome auto block = Maps::getTileBlock(job->pos); df::coord local(job->pos); @@ -246,7 +248,7 @@ namespace CSP { // the tile is unchanged df::unit* worker = active_workers[job->id]; endangered_workers.emplace(active_workers[job->id]); - ERR(jobs).print(" -> (" COORD ") stopped working but (" COORD ") doesn't appear done.\n",COORDARGS(worker->pos), COORDARGS(job->pos)); + ERR(jobs).print("() -> job at (" COORD ") is done, but (" COORD ") doesn't appear done.\n",COORDARGS(worker->pos), COORDARGS(job->pos)); if (config.insta_dig) { dignow_queue.emplace(job->pos); } diff --git a/plugins/channel-safely/include/channel-manager.h b/plugins/channel-safely/include/channel-manager.h index 79c1e3770..d36e98ca3 100644 --- a/plugins/channel-safely/include/channel-manager.h +++ b/plugins/channel-safely/include/channel-manager.h @@ -30,6 +30,7 @@ public: void manage_group(const Group &group, bool set_marker_mode = false, bool marker_mode = false); bool manage_one(const Group &group, const df::coord &map_pos, bool set_marker_mode = false, bool marker_mode = false); void mark_done(const df::coord &map_pos); + bool exists(const df::coord &map_pos) const { return groups.count(map_pos); } void debug() { DEBUG(groups).print(" DEBUGGING GROUPS:\n"); if (config.debug) { From c4e55f1cc9f1138c6125cd8e4a71b6d434fb336c Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 10 Nov 2022 11:06:05 -0800 Subject: [PATCH 15/22] Implements plugin: channel-safely v0.6.1.3 --- plugins/channel-safely/channel-safely-plugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index 1224413f1..acb0ba15a 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -413,6 +413,7 @@ namespace CSP { // clean up any "endangered" workers that have been tracked 100 ticks or more for (auto iter = age.begin(); iter != age.end();) { if (tick - iter->second >= 1200) { //keep watch 1 day + endangered_workers.erase(iter->first); iter = age.erase(iter); continue; } From 6c68e72295c4af7706c9c86a0d17a19677e8b118 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 10 Nov 2022 15:59:48 -0800 Subject: [PATCH 16/22] Implements plugin: channel-safely v1.0a --- docs/plugins/channel-safely.rst | 3 +- plugins/channel-safely/channel-manager.cpp | 2 + .../channel-safely/channel-safely-plugin.cpp | 206 ++++++++++++------ 3 files changed, 141 insertions(+), 70 deletions(-) diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index 3dd051edb..703600f43 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -49,7 +49,8 @@ Features -------- :require-vision: Toggle whether the dwarves need vision of a tile before channeling to it can be deemed unsafe. (default: enabled) :monitor: Toggle whether to monitor the conditions of active digs. (default: disabled) -:resurrect: Toggle whether to resurrect dwarves killed on the job. (default: disabled) +:resurrect: Toggle whether to resurrect units involved in cave-ins, and if monitor is enabled + units who die while digging. (default: disabled) :insta-dig: Toggle whether to use insta-digging on unreachable designations. Runs on the refresh cycles. (default: disabled) diff --git a/plugins/channel-safely/channel-manager.cpp b/plugins/channel-safely/channel-manager.cpp index 9f5c5d4b4..27d4c5153 100644 --- a/plugins/channel-safely/channel-manager.cpp +++ b/plugins/channel-safely/channel-manager.cpp @@ -2,6 +2,7 @@ #include #include +#include //hash function for df::coord #include /** @@ -104,5 +105,6 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo void ChannelManager::mark_done(const df::coord &map_pos) { groups.remove(map_pos); jobs.erase(map_pos); + CSP::dignow_queue.erase(map_pos); TileCache::Get().uncache(map_pos); } diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index acb0ba15a..b6abbf2d2 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -109,6 +109,33 @@ enum SettingConfigData { FALL_THRESH }; +// dig-now.cpp +df::coord simulate_fall(const df::coord &pos) { + df::coord resting_pos(pos); + + while (Maps::ensureTileBlock(resting_pos)) { + df::tiletype tt = *Maps::getTileType(resting_pos); + df::tiletype_shape_basic basic_shape = tileShapeBasic(tileShape(tt)); + if (isWalkable(tt) && basic_shape != df::tiletype_shape_basic::Open) + break; + --resting_pos.z; + } + + return resting_pos; +} + +df::coord simulate_area_fall(const df::coord &pos) { + df::coord neighbours[8]{}; + get_neighbours(pos, neighbours); + df::coord lowest = simulate_fall(pos); + for (auto p : neighbours) { + if (p.z < lowest.z) { + lowest = p; + } + } + return lowest; +} + // executes dig designations for the specified tile coordinates inline bool dig_now(color_ostream &out, const df::coord &map_pos) { auto L = Lua::Core::State; @@ -134,14 +161,24 @@ inline void resurrect(color_ostream &out, const int32_t &unit) { } namespace CSP { - std::unordered_set endangered_workers; - std::unordered_map job_ids; + std::unordered_map endangered_units; + std::unordered_map job_id_map; std::unordered_map active_jobs; std::unordered_map active_workers; std::unordered_map last_safe; std::unordered_set dignow_queue; + void ClearData() { + ChannelManager::Get().destroy_groups(); + dignow_queue.clear(); + last_safe.clear(); + endangered_units.clear(); + active_workers.clear(); + active_jobs.clear(); + job_id_map.clear(); + } + void SaveSettings() { if (pfeature.isValid() && psetting.isValid()) { try { @@ -209,9 +246,14 @@ namespace CSP { df::coord &pos = job->pos; WARN(jobs).print(" -> Starting job at (" COORD ")\n", COORDARGS(pos)); if (config.monitor_active || config.resurrect) { - job_ids.emplace(job, job->id); + job_id_map.emplace(job, job->id); active_jobs.emplace(job->id, job); active_workers[job->id] = worker; + if (config.resurrect) { + // this is the only place we can be 100% sure of "safety" + // (excluding deadly enemies that will have arrived) + last_safe[worker->id] = worker->pos; + } } // set tile to restricted TRACE(jobs).print(" setting job tile to restricted\n"); @@ -244,28 +286,27 @@ namespace CSP { ChannelManager::Get().mark_done(job->pos); ChannelManager::Get().manage_group(below); ChannelManager::Get().debug(); - } else { - // the tile is unchanged - df::unit* worker = active_workers[job->id]; - endangered_workers.emplace(active_workers[job->id]); - ERR(jobs).print("() -> job at (" COORD ") is done, but (" COORD ") doesn't appear done.\n",COORDARGS(worker->pos), COORDARGS(job->pos)); - if (config.insta_dig) { - dignow_queue.emplace(job->pos); + if (config.resurrect) { + // this is the only place we can be 100% sure of "safety" + // (excluding deadly enemies that will have arrived) + if (active_workers.count(job->id)) { + df::unit* worker = active_workers[job->id]; + last_safe[worker->id] = worker->pos; + } } } // clean up - if (!config.resurrect) { - auto jp = active_jobs[job->id]; - job_ids.erase(jp); - active_workers.erase(job->id); - active_jobs.erase(job->id); - } + auto jp = active_jobs[job->id]; + job_id_map.erase(jp); + active_workers.erase(job->id); + active_jobs.erase(job->id); } INFO(jobs).print("JobCompletedEvent() exits\n"); } } void NewReportEvent(color_ostream &out, void* r) { + int32_t tick = df::global::world->frame_counter; auto report_id = (int32_t)(intptr_t(r)); if (df::global::world) { std::vector &reports = df::global::world->status.reports; @@ -274,17 +315,46 @@ namespace CSP { df::report* report = reports.at(idx); switch (report->type) { case announcement_type::CANCEL_JOB: - out.print("%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos), COORDARGS(report->pos2), report->text.c_str()); - if (report->text.find("Dangerous") != std::string::npos) { - dignow_queue.emplace(report->pos); - break; - } else if (!report->flags.bits.unconscious) { - break; + if (config.insta_dig) { + if (report->text.find("cancels Dig") != std::string::npos) { + dignow_queue.emplace(report->pos); + } else if (report->text.find("path") != std::string::npos) { + dignow_queue.emplace(report->pos); + } + DEBUG(plugin).print("%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos), + COORDARGS(report->pos2), report->text.c_str()); } + break; case announcement_type::CAVE_COLLAPSE: - for (auto p : active_workers) { - endangered_workers.emplace(p.second); + if (config.resurrect) { + DEBUG(plugin).print("CAVE IN\n%d, pos: " COORD ", pos2: " COORD "\n%s\n", report_id, COORDARGS(report->pos), + COORDARGS(report->pos2), report->text.c_str()); + + df::coord below = report->pos; + below.z -= 1; + below = simulate_area_fall(below); + df::coord areaMin{report->pos}; + df::coord areaMax{areaMin}; + areaMin.x -= 15; + areaMin.y -= 15; + areaMax.x += 15; + areaMax.y += 15; + areaMin.z = below.z; + areaMax.z += 1; + std::vector units; + Units::getUnitsInBox(units, COORDARGS(areaMin), COORDARGS(areaMax)); + for (auto unit: units) { + endangered_units[unit] = tick; + DEBUG(plugin).print(" [id %d] was near a cave in.\n", unit->id); + } + for (auto unit : world->units.all) { + if (last_safe.count(unit->id)) { + endangered_units[unit] = tick; + DEBUG(plugin).print(" [id %d] is/was a worker, we'll track them too.\n", unit->id); + } + } } + break; default: break; } @@ -292,6 +362,9 @@ namespace CSP { } void OnUpdate(color_ostream &out) { + static auto print_res_msg = [](df::unit* unit) { + WARN(plugin).print("Channel-Safely: Resurrecting..\n [id: %d]\n", unit->id); + }; if (enabled && World::isFortressMode() && Maps::IsValid() && !World::ReadPauseState()) { static int32_t last_tick = df::global::world->frame_counter; static int32_t last_monitor_tick = df::global::world->frame_counter; @@ -303,24 +376,16 @@ namespace CSP { if (tick - last_refresh_tick >= config.refresh_freq) { last_refresh_tick = tick; TRACE(monitor).print("OnUpdate() refreshing now\n"); - UnpauseEvent(); - if (config.insta_dig) { TRACE(monitor).print(" -> evaluate dignow queue\n"); for (auto iter = dignow_queue.begin(); iter != dignow_queue.end();) { - if (!has_unit(Maps::getTileOccupancy(*iter))) { - dig_now(out, *iter); - iter = dignow_queue.erase(iter); - WARN(plugin).print(">INSTA-DIGGING<\n"); - continue; - } else { - // todo: teleport? - //Units::teleport() - } - ++iter; + dig_now(out, *iter); // teleports units to the bottom of a simulated fall + iter = dignow_queue.erase(iter); + DEBUG(plugin).print(">INSTA-DIGGING<\n"); } - TRACE(monitor).print("OnUpdate() refresh done\n"); } + UnpauseEvent(); + TRACE(monitor).print("OnUpdate() refresh done\n"); } // Clean up stale df::job* @@ -339,8 +404,8 @@ namespace CSP { std::unordered_set erase; map_value_difference(active_jobs, valid_jobs, erase); for (auto j : erase) { - auto id = job_ids[j]; - job_ids.erase(j); + auto id = job_id_map[j]; + job_id_map.erase(j); active_jobs.erase(id); active_workers.erase(id); } @@ -356,6 +421,7 @@ namespace CSP { df::job* job = pair.second; df::unit* unit = active_workers[job->id]; if (!unit) continue; + if (!Maps::isValidTilePos(job->pos)) continue; TRACE(monitor).print(" -> check for job in tracking\n"); if (Units::isAlive(unit)) { if (!config.monitor_active) continue; @@ -363,9 +429,7 @@ namespace CSP { // save position if (unit->pos != job->pos && isFloorTerrain(*Maps::getTileType(unit->pos))) { - // worker is perfectly safe right now - last_safe[unit->id] = unit->pos; - TRACE(monitor).print(" -> save safe position\n"); + // worker is probably safe right now continue; } @@ -382,7 +446,9 @@ namespace CSP { // queue digging the job instantly dignow_queue.emplace(job->pos); DEBUG(monitor).print(" -> insta-dig\n"); - } else if (Maps::isValidTilePos(job->pos)) { + } else if (config.resurrect) { + endangered_units.emplace(unit, tick); + } else { // set marker mode Maps::getTileOccupancy(job->pos)->bits.dig_marked = true; @@ -400,6 +466,13 @@ namespace CSP { DEBUG(monitor).print(" -> set marker mode\n"); } } + } else if (config.resurrect) { + resurrect(out, unit->id); + if (last_safe.count(unit->id)) { + df::coord lowest = simulate_fall(last_safe[unit->id]); + Units::teleport(unit, lowest); + } + print_res_msg(unit); } } TRACE(monitor).print("OnUpdate() monitoring done\n"); @@ -408,25 +481,27 @@ namespace CSP { // Resurrect Dead Workers if (config.resurrect && tick - last_resurrect_tick >= 1) { last_resurrect_tick = tick; - static std::unordered_map age; // clean up any "endangered" workers that have been tracked 100 ticks or more - for (auto iter = age.begin(); iter != age.end();) { + for (auto iter = endangered_units.begin(); iter != endangered_units.end();) { if (tick - iter->second >= 1200) { //keep watch 1 day - endangered_workers.erase(iter->first); - iter = age.erase(iter); + DEBUG(plugin).print("It has been one day since [id %d]'s last incident.\n", iter->first->id); + iter = endangered_units.erase(iter); continue; } ++iter; } // resurrect any dead units - for (auto unit : endangered_workers) { - age.emplace(unit, tick); + for (auto pair : endangered_units) { + auto unit = pair.first; if (!Units::isAlive(unit)) { resurrect(out, unit->id); - Units::teleport(unit, last_safe[unit->id]); - WARN(plugin).print(">RESURRECTING<\n"); + if (last_safe.count(unit->id)) { + df::coord lowest = simulate_fall(last_safe[unit->id]); + Units::teleport(unit, lowest); + } + print_res_msg(unit); } } } @@ -480,32 +555,25 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { - if (enabled && World::isFortressMode() && Maps::IsValid()) { - switch (event) { - case SC_MAP_LOADED: - CSP::active_workers.clear(); - // cache the map size - Maps::getSize(mapx, mapy, mapz); - case SC_UNPAUSED: + switch (event) { + case SC_UNPAUSED: + if (enabled && World::isFortressMode() && Maps::IsValid()) { // manage all designations on unpause CSP::UnpauseEvent(); - default: - return DFHack::CR_OK; - } - } - switch (event) { - case SC_WORLD_LOADED: - case SC_WORLD_UNLOADED: - case SC_MAP_UNLOADED: - // destroy any old group data - out.print("channel-safely: unloading data!\n"); - ChannelManager::Get().destroy_groups(); + } + break; case SC_MAP_LOADED: // cache the map size Maps::getSize(mapx, mapy, mapz); + case SC_WORLD_LOADED: + case SC_WORLD_UNLOADED: + case SC_MAP_UNLOADED: + CSP::ClearData(); + break; default: return DFHack::CR_OK; } + return DFHack::CR_OK; } DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) { From 9959ef1b36a5d839d3a71c062c71de39957b1a10 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 18 Nov 2022 10:47:39 -0800 Subject: [PATCH 17/22] Implements plugin: channel-safely v1.0.1a --- docs/plugins/channel-safely.rst | 6 ++++-- plugins/channel-safely/channel-safely-plugin.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index 703600f43..1fce8043d 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -18,7 +18,7 @@ Usage enable channel-safely channel-safely set channel-safely enable|disable - channel-safely run once + channel-safely runonce When enabled the map will be scanned for channel designations which will be grouped together based on adjacency and z-level. These groups will then be analyzed for safety @@ -32,7 +32,7 @@ Examples ``channel-safely`` The plugin reports its configured status. -``channel-safely run once`` +``channel-safely runonce`` Runs the safety procedures once. You can use this if you prefer initiating scans manually. ``channel-safely disable require-vision`` @@ -47,6 +47,7 @@ Examples Features -------- + :require-vision: Toggle whether the dwarves need vision of a tile before channeling to it can be deemed unsafe. (default: enabled) :monitor: Toggle whether to monitor the conditions of active digs. (default: disabled) :resurrect: Toggle whether to resurrect units involved in cave-ins, and if monitor is enabled @@ -56,6 +57,7 @@ Features Settings -------- + :refresh-freq: The rate at which full refreshes are performed. This can be expensive if you're undertaking many mega projects. (default:600, twice a day) :monitor-freq: The rate at which active jobs are monitored. (default:1) diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index b6abbf2d2..d4107bc56 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -584,7 +584,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_ev command_result channel_safely(color_ostream &out, std::vector ¶meters) { if (!parameters.empty()) { if (parameters.size() >= 2 && parameters.size() <= 3) { - if (parameters[0] == "run" && parameters[1] == "once") { + if (parameters[0] == "runonce") { CSP::UnpauseEvent(); return DFHack::CR_OK; } From 8a0999ffdc564b3d45e2628a8df9e37b9e7824ca Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 21 Nov 2022 12:39:26 -0800 Subject: [PATCH 18/22] Implements plugin: channel-safely v1.1a --- docs/plugins/channel-safely.rst | 15 ++- plugins/channel-safely/CMakeLists.txt | 1 - plugins/channel-safely/channel-groups.cpp | 40 +++++--- plugins/channel-safely/channel-jobs.cpp | 46 ---------- plugins/channel-safely/channel-manager.cpp | 5 - .../channel-safely/channel-safely-plugin.cpp | 91 +++++-------------- .../channel-safely/include/channel-groups.h | 11 ++- plugins/channel-safely/include/channel-jobs.h | 31 +++++-- .../channel-safely/include/channel-manager.h | 6 +- plugins/channel-safely/include/inlines.h | 20 ++-- plugins/channel-safely/include/plugin.h | 1 - plugins/channel-safely/include/tile-cache.h | 12 +-- 12 files changed, 105 insertions(+), 174 deletions(-) delete mode 100644 plugins/channel-safely/channel-jobs.cpp diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index 1fce8043d..4106aee04 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -2,12 +2,12 @@ channel-safely ============== .. dfhack-tool:: - :summary: Auto-manage channel designations to keep dwarves safe + :summary: Auto-manage channel designations to keep dwarves safe. :tags: fort auto Multi-level channel projects can be dangerous, and managing the safety of your dwarves throughout the completion of such projects can be difficult and time -consuming. This plugin keeps your dwarves safe (while channeling) so you don't +consuming. This plugin keeps your dwarves safe (at least while channeling) so you don't have to. Now you can focus on designing your dwarven cities with the deep chasms they were meant to have. @@ -18,7 +18,7 @@ Usage enable channel-safely channel-safely set channel-safely enable|disable - channel-safely runonce + channel-safely When enabled the map will be scanned for channel designations which will be grouped together based on adjacency and z-level. These groups will then be analyzed for safety @@ -38,13 +38,20 @@ Examples ``channel-safely disable require-vision`` Allows the plugin to read all tiles, including the ones your dwarves know nothing about. -``channel-safely enable monitor-active`` +``channel-safely enable monitor`` Enables monitoring active channel digging jobs. Meaning that if another unit it present or the tile below becomes open space the job will be paused or canceled (respectively). ``channel-safely set ignore-threshold 3`` Configures the plugin to ignore designations equal to or above priority 3 designations. +Commands +-------- + +:runonce: Run the safety procedures once to set the marker mode of designations. +:rebuild: Rebuild the designation group data. Intended for to be used in the event + the marker mode isn't being set correctly (mostly for debugging). + Features -------- diff --git a/plugins/channel-safely/CMakeLists.txt b/plugins/channel-safely/CMakeLists.txt index d660d2262..36c7307e4 100644 --- a/plugins/channel-safely/CMakeLists.txt +++ b/plugins/channel-safely/CMakeLists.txt @@ -2,7 +2,6 @@ project(channel-safely) include_directories(include) SET(SOURCES - channel-jobs.cpp channel-groups.cpp channel-manager.cpp channel-safely-plugin.cpp) diff --git a/plugins/channel-safely/channel-groups.cpp b/plugins/channel-safely/channel-groups.cpp index 1a7f81a13..52f7e6c40 100644 --- a/plugins/channel-safely/channel-groups.cpp +++ b/plugins/channel-safely/channel-groups.cpp @@ -6,7 +6,18 @@ #include - +// iterates the DF job list and adds channel jobs to the `jobs` container +void ChannelJobs::load_channel_jobs() { + locations.clear(); + df::job_list_link* node = df::global::world->jobs.list.next; + while (node) { + df::job* job = node->item; + node = node->next; + if (is_dig_job(job)) { + locations.emplace(job->pos); + } + } +} // adds map_pos to a group if an adjacent one exists, or creates one if none exist... if multiple exist they're merged into the first found void ChannelGroups::add(const df::coord &map_pos) { @@ -148,6 +159,7 @@ void ChannelGroups::scan() { // the tile, check if it has a channel designation df::coord map_pos((bx * 16) + lx, (by * 16) + ly, z); if (TileCache::Get().hasChanged(map_pos, block->tiletype[lx][ly])) { + TileCache::Get().uncache(map_pos); remove(map_pos); if (jobs.count(map_pos)) { jobs.erase(map_pos); @@ -165,6 +177,8 @@ void ChannelGroups::scan() { } TRACE(groups).print(" adding (" COORD ")\n", COORDARGS(map_pos)); add(map_pos); + } else if (groups_map.count(map_pos)) { + remove(map_pos); } } } @@ -252,21 +266,25 @@ size_t ChannelGroups::count(const df::coord &map_pos) const { // prints debug info about the groups stored, and their members void ChannelGroups::debug_groups() { - int idx = 0; - TRACE(groups).print(" debugging group data\n"); - for (auto &group : groups) { - TRACE(groups).print(" group %d (size: %zu)\n", idx, group.size()); - for (auto &pos : group) { - TRACE(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z); + if (DFHack::debug_groups.isEnabled(DebugCategory::LTRACE)) { + int idx = 0; + TRACE(groups).print(" debugging group data\n"); + for (auto &group: groups) { + TRACE(groups).print(" group %d (size: %zu)\n", idx, group.size()); + for (auto &pos: group) { + TRACE(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z); + } + idx++; } - idx++; } } // prints debug info group mappings void ChannelGroups::debug_map() { - INFO(groups).print("Group Mappings: %zu\n", groups_map.size()); - for (auto &pair : groups_map) { - DEBUG(groups).print(" map[" COORD "] = %d\n",COORDARGS(pair.first), pair.second); + if (DFHack::debug_groups.isEnabled(DebugCategory::LDEBUG)) { + INFO(groups).print("Group Mappings: %zu\n", groups_map.size()); + for (auto &pair: groups_map) { + DEBUG(groups).print(" map[" COORD "] = %d\n", COORDARGS(pair.first), pair.second); + } } } diff --git a/plugins/channel-safely/channel-jobs.cpp b/plugins/channel-safely/channel-jobs.cpp deleted file mode 100644 index 7a1c2f4be..000000000 --- a/plugins/channel-safely/channel-jobs.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include -#include - -// iterates the DF job list and adds channel jobs to the `jobs` container -void ChannelJobs::load_channel_jobs() { - jobs.clear(); - df::job_list_link* node = df::global::world->jobs.list.next; - while (node) { - df::job* job = node->item; - node = node->next; - if (is_dig_job(job)) { - jobs.emplace(job->pos); - } - } -} - -// clears the container -void ChannelJobs::clear() { - jobs.clear(); -} - -// finds and erases a job corresponding to a map position, then returns the iterator following the element removed -std::set::iterator ChannelJobs::erase(const df::coord &map_pos) { - auto iter = jobs.find(map_pos); - if (iter != jobs.end()) { - return jobs.erase(iter); - } - return iter; -} - -// finds a job corresponding to a map position if one exists -std::set::const_iterator ChannelJobs::find(const df::coord &map_pos) const { - return jobs.find(map_pos); -} - -// returns an iterator to the first element stored -std::set::const_iterator ChannelJobs::begin() const { - return jobs.begin(); -} - -// returns an iterator to after the last element stored -std::set::const_iterator ChannelJobs::end() const { - return jobs.end(); -} diff --git a/plugins/channel-safely/channel-manager.cpp b/plugins/channel-safely/channel-manager.cpp index 27d4c5153..e905f2cfb 100644 --- a/plugins/channel-safely/channel-manager.cpp +++ b/plugins/channel-safely/channel-manager.cpp @@ -5,11 +5,6 @@ #include //hash function for df::coord #include -/** -blocks[48][96][135]: -blocks[48][96][135].default_liquid.hidden: false -blocks[48][96][135].designation[10][0].hidden: false - * */ // sets mark flags as necessary, for all designations void ChannelManager::manage_groups() { diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index d4107bc56..e5c9e2760 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -93,7 +93,6 @@ PersistentDataItem psetting; PersistentDataItem pfeature; const std::string FCONFIG_KEY = std::string(plugin_name) + "/feature"; const std::string SCONFIG_KEY = std::string(plugin_name) + "/setting"; -//std::unordered_set active_jobs; enum FeatureConfigData { VISION, @@ -138,20 +137,22 @@ df::coord simulate_area_fall(const df::coord &pos) { // executes dig designations for the specified tile coordinates inline bool dig_now(color_ostream &out, const df::coord &map_pos) { - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, 2) || - !Lua::PushModulePublic(out, L, "plugins.dig-now", "dig_now_tile")) - return false; - - Lua::Push(L, map_pos); - - if (!Lua::SafeCall(out, L, 1, 1)) - return false; - - return lua_toboolean(L, -1); - + bool ret = false; + + lua_State* state = Lua::Core::State; + static const char* module_name = "plugins.dig-now"; + static const char* fn_name = "dig_now_tile"; + // the stack layout isn't likely to change, ever + static auto args_lambda = [&map_pos](lua_State* L) { + Lua::Push(L, map_pos); + }; + static auto res_lambda = [&ret](lua_State* L) { + ret = lua_toboolean(L, -1); + }; + + Lua::StackUnwinder top(state); + Lua::CallLuaModuleFunction(out, state, module_name, fn_name, 1, 1, args_lambda, res_lambda); + return ret; } // fully heals the unit specified, resurrecting if need be @@ -583,11 +584,14 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_ev command_result channel_safely(color_ostream &out, std::vector ¶meters) { if (!parameters.empty()) { + if (parameters[0] == "runonce") { + CSP::UnpauseEvent(); + return DFHack::CR_OK; + } else if (parameters[0] == "rebuild") { + ChannelManager::Get().destroy_groups(); + ChannelManager::Get().build_groups(); + } if (parameters.size() >= 2 && parameters.size() <= 3) { - if (parameters[0] == "runonce") { - CSP::UnpauseEvent(); - return DFHack::CR_OK; - } bool state = false; bool set = false; if (parameters[0] == "enable") { @@ -600,54 +604,7 @@ command_result channel_safely(color_ostream &out, std::vector ¶ return DFHack::CR_WRONG_USAGE; } try { - if (parameters[1] == "debug") { - auto level = std::abs(std::stol(parameters[2])); - config.debug = true; - switch (level) { - case 1: - DBG_NAME(manager).allowed(DFHack::DebugCategory::LDEBUG); - DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO); - DBG_NAME(groups).allowed(DFHack::DebugCategory::LINFO); - DBG_NAME(jobs).allowed(DFHack::DebugCategory::LINFO); - break; - case 2: - DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE); - DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO); - DBG_NAME(groups).allowed(DFHack::DebugCategory::LDEBUG); - DBG_NAME(jobs).allowed(DFHack::DebugCategory::LDEBUG); - break; - case 3: - DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE); - DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO); - DBG_NAME(groups).allowed(DFHack::DebugCategory::LDEBUG); - DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE); - break; - case 4: - DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE); - DBG_NAME(monitor).allowed(DFHack::DebugCategory::LINFO); - DBG_NAME(groups).allowed(DFHack::DebugCategory::LTRACE); - DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE); - break; - case 5: - DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE); - DBG_NAME(monitor).allowed(DFHack::DebugCategory::LDEBUG); - DBG_NAME(groups).allowed(DFHack::DebugCategory::LTRACE); - DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE); - break; - case 6: - DBG_NAME(manager).allowed(DFHack::DebugCategory::LTRACE); - DBG_NAME(monitor).allowed(DFHack::DebugCategory::LTRACE); - DBG_NAME(groups).allowed(DFHack::DebugCategory::LTRACE); - DBG_NAME(jobs).allowed(DFHack::DebugCategory::LTRACE); - break; - case 0: - default: - DBG_NAME(monitor).allowed(DFHack::DebugCategory::LERROR); - DBG_NAME(manager).allowed(DFHack::DebugCategory::LERROR); - DBG_NAME(groups).allowed(DFHack::DebugCategory::LERROR); - DBG_NAME(jobs).allowed(DFHack::DebugCategory::LERROR); - } - } else if(parameters[1] == "monitor"){ + if(parameters[1] == "monitor"){ if (state != config.monitor_active) { config.monitor_active = state; // if this is a fresh start diff --git a/plugins/channel-safely/include/channel-groups.h b/plugins/channel-safely/include/channel-groups.h index abdbc56fc..7547e2564 100644 --- a/plugins/channel-safely/include/channel-groups.h +++ b/plugins/channel-safely/include/channel-groups.h @@ -4,14 +4,15 @@ #include #include +#include //hash functions (they should probably get moved at this point, the ones that aren't specifically for EM anyway) #include -#include -#include +#include +#include using namespace DFHack; -using Group = std::set; +using Group = std::unordered_set; using Groups = std::vector; /* Used to build groups of adjacent channel designations/jobs @@ -26,8 +27,8 @@ using Groups = std::vector; */ class ChannelGroups { private: - using GroupBlocks = std::set; - using GroupsMap = std::map; + using GroupBlocks = std::unordered_set; + using GroupsMap = std::unordered_map; GroupBlocks group_blocks; GroupsMap groups_map; Groups groups; diff --git a/plugins/channel-safely/include/channel-jobs.h b/plugins/channel-safely/include/channel-jobs.h index 0290baa19..3be704aeb 100644 --- a/plugins/channel-safely/include/channel-jobs.h +++ b/plugins/channel-safely/include/channel-jobs.h @@ -1,7 +1,11 @@ #pragma once #include #include -#include +#include //hash functions (they should probably get moved at this point, the ones that aren't specifically for EM anyway) +#include +#include + +#include using namespace DFHack; @@ -17,14 +21,23 @@ using namespace DFHack; class ChannelJobs { private: friend class ChannelGroup; - using Jobs = std::set; // job* will exist until it is complete, and likely beyond - Jobs jobs; + + using Jobs = std::unordered_set; // job* will exist until it is complete, and likely beyond + Jobs locations; public: void load_channel_jobs(); - void clear(); - int count(const df::coord &map_pos) const { return jobs.count(map_pos); } - Jobs::iterator erase(const df::coord &map_pos); - Jobs::const_iterator find(const df::coord &map_pos) const; - Jobs::const_iterator begin() const; - Jobs::const_iterator end() const; + void clear() { + locations.clear(); + } + int count(const df::coord &map_pos) const { return locations.count(map_pos); } + Jobs::iterator erase(const df::coord &map_pos) { + auto iter = locations.find(map_pos); + if (iter != locations.end()) { + return locations.erase(iter); + } + return iter; + } + Jobs::const_iterator find(const df::coord &map_pos) const { return locations.find(map_pos); } + Jobs::const_iterator begin() const { return locations.begin(); } + Jobs::const_iterator end() const { return locations.end(); } }; diff --git a/plugins/channel-safely/include/channel-manager.h b/plugins/channel-safely/include/channel-manager.h index d36e98ca3..0cd3abfac 100644 --- a/plugins/channel-safely/include/channel-manager.h +++ b/plugins/channel-safely/include/channel-manager.h @@ -33,9 +33,7 @@ public: bool exists(const df::coord &map_pos) const { return groups.count(map_pos); } void debug() { DEBUG(groups).print(" DEBUGGING GROUPS:\n"); - if (config.debug) { - groups.debug_groups(); - groups.debug_map(); - } + groups.debug_groups(); + groups.debug_map(); } }; diff --git a/plugins/channel-safely/include/inlines.h b/plugins/channel-safely/include/inlines.h index e22210f7a..8bd1de44d 100644 --- a/plugins/channel-safely/include/inlines.h +++ b/plugins/channel-safely/include/inlines.h @@ -64,7 +64,7 @@ inline bool is_safe_fall(const df::coord &map_pos) { return true; //we require vision, and we can't see below.. so we gotta assume it's safe } df::tiletype type = *Maps::getTileType(below); - if (!isOpenTerrain(type)) { + if (!DFHack::isOpenTerrain(type)) { return true; } } @@ -80,10 +80,10 @@ inline bool is_safe_to_dig_down(const df::coord &map_pos) { return true; } df::tiletype type = *Maps::getTileType(pos); - if (zi == 0 && isOpenTerrain(type)) { + if (zi == 0 && DFHack::isOpenTerrain(type)) { // the starting tile is open space, that's obviously not safe return false; - } else if (!isOpenTerrain(type)) { + } else if (!DFHack::isOpenTerrain(type)) { // a tile after the first one is not open space return true; } @@ -142,7 +142,9 @@ inline void cancel_job(df::job* job) { x = pos.x % 16; y = pos.y % 16; df::tile_designation &designation = job_block->designation[x][y]; - switch (job->job_type) { + auto type = job->job_type; + Job::removeJob(job); + switch (type) { case job_type::Dig: designation.bits.dig = df::tile_dig_designation::Default; break; @@ -165,21 +167,13 @@ inline void cancel_job(df::job* job) { designation.bits.dig = df::tile_dig_designation::No; break; } - Job::removeJob(job); } } template void set_difference(const Ctr1 &c1, const Ctr2 &c2, Ctr3 &c3) { for (const auto &a : c1) { - bool matched = false; - for (const auto &b : c2) { - if (a == b) { - matched = true; - break; - } - } - if (!matched) { + if (!c2.count(a)) { c3.emplace(a); } } diff --git a/plugins/channel-safely/include/plugin.h b/plugins/channel-safely/include/plugin.h index 2451033c5..23b2f8441 100644 --- a/plugins/channel-safely/include/plugin.h +++ b/plugins/channel-safely/include/plugin.h @@ -9,7 +9,6 @@ namespace DFHack { } struct Configuration { - bool debug = false; bool monitor_active = false; bool require_vision = true; bool insta_dig = false; diff --git a/plugins/channel-safely/include/tile-cache.h b/plugins/channel-safely/include/tile-cache.h index ddd37c34a..10e91cd46 100644 --- a/plugins/channel-safely/include/tile-cache.h +++ b/plugins/channel-safely/include/tile-cache.h @@ -3,13 +3,14 @@ #include #include #include +#include //hash functions (they should probably get moved at this point, the ones that aren't specifically for EM anyway) -#include +#include class TileCache { private: TileCache() = default; - std::map locations; + std::unordered_map locations; public: static TileCache& Get() { static TileCache instance; @@ -25,11 +26,6 @@ public: } bool hasChanged(const df::coord &pos, const df::tiletype &type) { - if (locations.count(pos)) { - if (type != locations.find(pos)->second){ - return true; - } - } - return false; + return locations.count(pos) && type != locations[pos]; } }; From c6b15b1ccb77e3e821aa31c0577d7a462d9cada6 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 22 Nov 2022 11:03:28 -0800 Subject: [PATCH 19/22] Implements plugin: channel-safely v1.2a --- plugins/channel-safely/channel-safely-plugin.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index e5c9e2760..d291c0efc 100644 --- a/plugins/channel-safely/channel-safely-plugin.cpp +++ b/plugins/channel-safely/channel-safely-plugin.cpp @@ -128,8 +128,9 @@ df::coord simulate_area_fall(const df::coord &pos) { get_neighbours(pos, neighbours); df::coord lowest = simulate_fall(pos); for (auto p : neighbours) { - if (p.z < lowest.z) { - lowest = p; + auto nlow = simulate_fall(p); + if (nlow.z < lowest.z) { + lowest = nlow; } } return lowest; @@ -225,6 +226,7 @@ namespace CSP { } void UnpauseEvent(){ + CoreSuspender suspend; // we need exclusive access to df memory and this call stack doesn't already have a lock INFO(monitor).print("UnpauseEvent()\n"); ChannelManager::Get().build_groups(); ChannelManager::Get().manage_groups(); From 86ec1c17adceca353c0968d1827d4f87e112a50e Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 22 Nov 2022 13:28:27 -0800 Subject: [PATCH 20/22] Fixes doc formatting --- docs/plugins/channel-safely.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index 4106aee04..4a9902216 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -15,6 +15,7 @@ Usage ----- :: + enable channel-safely channel-safely set channel-safely enable|disable From 6cdb192181694072422e5564f1479f7032512e4f Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 23 Nov 2022 10:37:33 -0800 Subject: [PATCH 21/22] Documents persistence of settings --- docs/plugins/channel-safely.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index 4a9902216..812936d33 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -27,6 +27,8 @@ and designations deemed unsafe will be put into :wiki:`Marker Mode Date: Wed, 23 Nov 2022 11:39:04 -0800 Subject: [PATCH 22/22] Update docs/plugins/channel-safely.rst --- docs/plugins/channel-safely.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index 812936d33..1b72c93df 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -27,7 +27,7 @@ and designations deemed unsafe will be put into :wiki:`Marker Mode