From 540faff88f97f9c9f2f7b8af2c992e9b66d38d8a Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 8 Dec 2022 11:21:17 -0800 Subject: [PATCH] Implements plugin: channel-safely v1.1.2b - Updates rst documentation - Adds troubleshooting section - Renames the monitor feature to monitoring - Adds cave-in helper functions - ChannelJobs::has_cavein_conditions() - ChannelJobs::possible_cavein() - find_dwarf() ~ finds the nearest dwarf or the first one that has a path to a position - Moves dignow/resurrect to inlines.h - Improves management of regular dig designations - Adds df::job* tracking back into ChannelJobs to simplify cancellations - Updates/improves debug logging - Switches unordered structures with ordered in some locations to have ordered debugging information - Simplifies ChannelManager::manage_group() - Fixes up ChannelManager::manage_one() - the return value is now useful even if unused --- docs/plugins/channel-safely.rst | 19 ++- plugins/channel-safely/channel-groups.cpp | 77 ++++++++++-- plugins/channel-safely/channel-manager.cpp | 67 +++++++---- .../channel-safely/channel-safely-plugin.cpp | 113 ++++++++---------- .../channel-safely/include/channel-groups.h | 8 +- plugins/channel-safely/include/channel-jobs.h | 12 ++ .../channel-safely/include/channel-manager.h | 6 +- plugins/channel-safely/include/inlines.h | 101 +++++++++++++++- plugins/channel-safely/include/plugin.h | 3 +- 9 files changed, 298 insertions(+), 108 deletions(-) diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index e87a36455..b6957b95e 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -52,18 +52,17 @@ 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). +:rebuild: Rebuild the designation group data. You should also read Troubleshooting. 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) +:monitoring: 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 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) + Runs on the refresh cycles. Use with caution. (default: disabled) Settings -------- @@ -74,3 +73,15 @@ Settings :ignore-threshold: Sets the priority threshold below which designations are processed. You can set to 1 or 0 to effectively disable the scanning. (default: 5) :fall-threshold: Sets the fall threshold beyond which is considered unsafe. (default: 1) + +Troubleshooting +--------------- + +If designations aren't switching correctly, try putting the designations into marker mode. +Then press . (next) or resume. If you're debugging code you'll want these: + +[todo: make this a block of code] + +:filter1: ``debugfilter set Info channel manager`` +:filter2: ``debugfilter set Debug channel plugin`` +:filter3: ``debugfilter set Trace channel group`` diff --git a/plugins/channel-safely/channel-groups.cpp b/plugins/channel-safely/channel-groups.cpp index 8f7f4b117..6c310b5c4 100644 --- a/plugins/channel-safely/channel-groups.cpp +++ b/plugins/channel-safely/channel-groups.cpp @@ -13,12 +13,63 @@ void ChannelJobs::load_channel_jobs() { while (node) { df::job* job = node->item; node = node->next; - if (is_dig_job(job)) { + if (is_channel_job(job)) { locations.emplace(job->pos); + jobs.emplace(job->pos, job); } } } +bool ChannelJobs::has_cavein_conditions(const df::coord &map_pos) { + auto p = map_pos; + auto ttype = *Maps::getTileType(p); + if (!DFHack::isOpenTerrain(ttype)) { + // check shared neighbour for cave-in conditions + df::coord neighbours[4]; + get_connected_neighbours(map_pos, neighbours); + int connectedness = 4; + for (auto n: neighbours) { + if (active.count(n) || DFHack::isOpenTerrain(*Maps::getTileType(n))) { + connectedness--; + } + } + if (!connectedness) { + // do what? + p.z--; + ttype = *Maps::getTileType(p); + if (DFHack::isOpenTerrain(ttype) || DFHack::isFloorTerrain(ttype)) { + return true; + } + } + } + return false; +} + +bool ChannelJobs::possible_cavein(const df::coord &job_pos) { + for (auto iter = active.begin(); iter != active.end(); ++iter) { + if (*iter == job_pos) continue; + if (calc_distance(job_pos, *iter) <= 2) { + // find neighbours + df::coord n1[8]; + df::coord n2[8]; + get_neighbours(job_pos, n1); + get_neighbours(*iter, n2); + // find shared neighbours + for (int i = 0; i < 7; ++i) { + for (int j = i + 1; j < 8; ++j) { + if (n1[i] == n2[j]) { + if (has_cavein_conditions(n1[i])) { + WARN(jobs).print("Channel-Safely::jobs: Cave-in conditions detected at (" COORD ")\n", COORDARGS(n1[i])); + return true; + } + } + } + } + } + } + return false; +} + // 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 @@ -137,6 +188,7 @@ void ChannelGroups::scan(bool full_scan) { std::set gone_jobs; set_difference(last_jobs, jobs, gone_jobs); set_difference(jobs, last_jobs, new_jobs); + INFO(groups).print("gone jobs: %" PRIu64 "\nnew jobs: %" PRIu64 "\n",gone_jobs.size(), new_jobs.size()); for (auto &pos : new_jobs) { add(pos); } @@ -168,7 +220,15 @@ void ChannelGroups::scan(bool full_scan) { jobs.erase(map_pos); } block->designation[lx][ly].bits.dig = df::tile_dig_designation::No; - } else if (is_dig_designation(block->designation[lx][ly]) || block->occupancy[lx][ly].bits.dig_marked) { + } else if (is_dig_designation(block->designation[lx][ly]) || block->occupancy[lx][ly].bits.dig_marked ) { + if (!is_channel_designation(block->designation[lx][ly])) { + if (df::map_block* block_above = Maps::getBlock(bx, by, z+1)) { + if (!is_channel_designation(block_above->designation[lx][ly])) { + // dig designation without a channel above it, we can skip this. + continue; + } + } + } 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 @@ -203,6 +263,7 @@ void ChannelGroups::scan(bool full_scan) { void ChannelGroups::clear() { debug_map(); WARN(groups).print(" <- clearing groups\n"); + jobs.clear(); group_blocks.clear(); free_spots.clear(); groups_map.clear(); @@ -269,13 +330,13 @@ size_t ChannelGroups::count(const df::coord &map_pos) const { // prints debug info about the groups stored, and their members void ChannelGroups::debug_groups() { - if (DFHack::debug_groups.isEnabled(DebugCategory::LTRACE)) { + if (DFHack::debug_groups.isEnabled(DebugCategory::LDEBUG)) { int idx = 0; - TRACE(groups).print(" debugging group data\n"); + DEBUG(groups).print(" debugging group data\n"); for (auto &group: groups) { - TRACE(groups).print(" group %d (size: %zu)\n", idx, group.size()); + DEBUG(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); + DEBUG(groups).print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z); } idx++; } @@ -284,10 +345,10 @@ void ChannelGroups::debug_groups() { // prints debug info group mappings void ChannelGroups::debug_map() { - if (DFHack::debug_groups.isEnabled(DebugCategory::LDEBUG)) { + if (DFHack::debug_groups.isEnabled(DebugCategory::LTRACE)) { 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); + TRACE(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 e905f2cfb..e6c7ea671 100644 --- a/plugins/channel-safely/channel-manager.cpp +++ b/plugins/channel-safely/channel-manager.cpp @@ -5,6 +5,28 @@ #include //hash function for df::coord #include +#include +#include + +df::unit* find_dwarf(const df::coord &map_pos) { + df::unit* nearest = nullptr; + uint32_t distance; + for (auto unit : df::global::world->units.active) { + if (!nearest) { + nearest = unit; + distance = calc_distance(unit->pos, map_pos); + } else if (unit->status.labors[df::unit_labor::MINE]) { + uint32_t d = calc_distance(unit->pos, map_pos); + if (d < distance) { + nearest = unit; + distance = d; + } else if (Maps::canWalkBetween(unit->pos, map_pos)) { + return unit; + } + } + } + return nearest; +} // sets mark flags as necessary, for all designations void ChannelManager::manage_groups() { @@ -33,21 +55,17 @@ void ChannelManager::manage_group(const df::coord &map_pos, bool set_marker_mode 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; - } + marker_mode = has_any_groups_above(groups, group); } for (auto &designation: group) { - manage_one(group, designation, true, marker_mode); + manage_one(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) { +bool ChannelManager::manage_one(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)); + TRACE(manager).print("manage_one((" COORD "), %d, %d)\n", COORDARGS(map_pos), set_marker_mode, marker_mode); df::map_block* block = Maps::getTileBlock(map_pos); // we calculate the position inside the block* df::coord local(map_pos); @@ -58,41 +76,46 @@ bool ChannelManager::manage_one(const Group &group, const df::coord &map_pos, bo 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"); + TRACE(manager).print(" -> setting marker mode\n"); // if enabling marker mode, just do it if (marker_mode) { - tile_occupancy.bits.dig_marked = marker_mode; - return true; + goto markerMode; } - // if activating designation, check if it is safe to dig or not a channel designation + // otherwise we check for some safety if (!is_channel_designation(block->designation[Coord(local)]) || is_safe_to_dig_down(map_pos)) { + // not a channel designation, or it is safe to dig down if it is if (!block->flags.bits.designated) { block->flags.bits.designated = true; } - tile_occupancy.bits.dig_marked = false; + // we need to cache the tile now that we're activating the designation TileCache::Get().cache(map_pos, block->tiletype[Coord(local)]); + TRACE(manager).print("marker mode DISABLED\n"); + tile_occupancy.bits.dig_marked = false; + } else { + // we want to activate the designation, that's not what we get + goto markerMode; } - return false; - } else { - // next search for the designation priority - DEBUG(manager).print(" if(has_groups_above())\n"); + // not set_marker_mode + TRACE(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"); + + markerMode: + + TRACE(manager).print("marker mode ENABLED\n"); tile_occupancy.bits.dig_marked = true; if (jobs.count(map_pos)) { - jobs.erase(map_pos); + cancel_job(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"); + TRACE(manager).print(" <- manage_one() exits normally\n"); + return true; } return false; } diff --git a/plugins/channel-safely/channel-safely-plugin.cpp b/plugins/channel-safely/channel-safely-plugin.cpp index 8af90c335..882685385 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. 28 2022 +Updated: Dec. 5 2022 Enable plugin: -> build groups @@ -56,8 +56,6 @@ Updated: Nov. 28 2022 #include #include -#include -#include #include #include #include @@ -98,7 +96,8 @@ enum FeatureConfigData { VISION, MONITOR, RESURRECT, - INSTADIG + INSTADIG, + RISKAVERSE }; enum SettingConfigData { @@ -136,38 +135,13 @@ df::coord simulate_area_fall(const df::coord &pos) { return lowest; } -// executes dig designations for the specified tile coordinates -inline bool dig_now(color_ostream &out, const df::coord &map_pos) { - 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 -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 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; @@ -184,10 +158,11 @@ namespace CSP { void SaveSettings() { if (pfeature.isValid() && psetting.isValid()) { try { - pfeature.ival(MONITOR) = config.monitor_active; + pfeature.ival(MONITOR) = config.monitoring; pfeature.ival(VISION) = config.require_vision; pfeature.ival(INSTADIG) = config.insta_dig; pfeature.ival(RESURRECT) = config.resurrect; + pfeature.ival(RISKAVERSE) = config.riskaverse; psetting.ival(REFRESH_RATE) = config.refresh_freq; psetting.ival(MONITOR_RATE) = config.monitor_freq; @@ -209,10 +184,11 @@ namespace CSP { SaveSettings(); } else { try { - config.monitor_active = pfeature.ival(MONITOR); + config.monitoring = pfeature.ival(MONITOR); config.require_vision = pfeature.ival(VISION); config.insta_dig = pfeature.ival(INSTADIG); config.resurrect = pfeature.ival(RESURRECT); + config.riskaverse = pfeature.ival(RISKAVERSE); config.ignore_threshold = psetting.ival(IGNORE_THRESH); config.fall_threshold = psetting.ival(FALL_THRESH); @@ -227,28 +203,36 @@ namespace CSP { void UnpauseEvent(bool full_scan = false){ CoreSuspender suspend; // we need exclusive access to df memory and this call stack doesn't already have a lock - INFO(monitor).print("UnpauseEvent()\n"); + DEBUG(plugin).print("UnpauseEvent()\n"); ChannelManager::Get().build_groups(full_scan); ChannelManager::Get().manage_groups(); - ChannelManager::Get().debug(); - INFO(monitor).print("UnpauseEvent() exits\n"); + DEBUG(plugin).print("UnpauseEvent() exits\n"); } void JobStartedEvent(color_ostream &out, void* j) { if (enabled && World::isFortressMode() && Maps::IsValid()) { - INFO(jobs).print("JobStartedEvent()\n"); + TRACE(jobs).print("JobStartedEvent()\n"); auto job = (df::job*) j; // validate job type if (ChannelManager::Get().exists(job->pos)) { - WARN(jobs).print(" valid channel job:\n"); + 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)) { + ChannelManager::Get().jobs.mark_active(job->pos); + if (config.riskaverse) { + if (ChannelManager::Get().jobs.possible_cavein(job->pos)) { + cancel_job(job); + ChannelManager::Get().manage_one(job->pos, true, true); + } else { + ChannelManager::Get().manage_group(job->pos, true, false); + } + } 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) { + TRACE(jobs).print(" -> Starting job at (" COORD ")\n", COORDARGS(pos)); + if (config.monitoring || config.resurrect) { job_id_map.emplace(job, job->id); active_jobs.emplace(job->id, job); active_workers[job->id] = worker; @@ -263,16 +247,17 @@ namespace CSP { Maps::getTileDesignation(job->pos)->bits.traffic = df::tile_traffic::Restricted; } } - INFO(jobs).print(" <- JobStartedEvent() exits normally\n"); + TRACE(jobs).print(" <- JobStartedEvent() exits normally\n"); } } void JobCompletedEvent(color_ostream &out, void* j) { if (enabled && World::isFortressMode() && Maps::IsValid()) { - INFO(jobs).print("JobCompletedEvent()\n"); + TRACE(jobs).print("JobCompletedEvent()\n"); auto job = (df::job*) j; // we only care if the job is a channeling one if (ChannelManager::Get().exists(job->pos)) { + ChannelManager::Get().manage_group(job->pos, true, false); // check job outcome auto block = Maps::getTileBlock(job->pos); df::coord local(job->pos); @@ -283,15 +268,14 @@ namespace CSP { // the job can be considered done df::coord below(job->pos); below.z--; - WARN(jobs).print(" -> (" COORD ") is marked done, managing group below.\n", COORDARGS(job->pos)); - // mark done and manage below + DEBUG(jobs).print(" -> (" COORD ") is marked done, managing group below.\n", COORDARGS(job->pos)); + // mark done and manage below (and the rest of the group, if there were cavein candidates) block->designation[Coord(local)].bits.traffic = df::tile_traffic::Normal; ChannelManager::Get().mark_done(job->pos); ChannelManager::Get().manage_group(below); - ChannelManager::Get().debug(); if (config.resurrect) { // this is the only place we can be 100% sure of "safety" - // (excluding deadly enemies that will have arrived) + // (excluding deadly enemies that may have arrived, and future digging) if (active_workers.count(job->id)) { df::unit* worker = active_workers[job->id]; last_safe[worker->id] = worker->pos; @@ -304,7 +288,7 @@ namespace CSP { active_workers.erase(job->id); active_jobs.erase(job->id); } - INFO(jobs).print("JobCompletedEvent() exits\n"); + TRACE(jobs).print("JobCompletedEvent() exits\n"); } } @@ -365,9 +349,7 @@ 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); - }; + CoreSuspender suspend; 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; @@ -382,17 +364,18 @@ namespace CSP { if (config.insta_dig) { TRACE(monitor).print(" -> evaluate dignow queue\n"); for (auto iter = dignow_queue.begin(); iter != dignow_queue.end();) { - dig_now(out, *iter); // teleports units to the bottom of a simulated fall + auto map_pos = *iter; + dig_now(out, map_pos); // teleports units to the bottom of a simulated fall + ChannelManager::Get().mark_done(map_pos); iter = dignow_queue.erase(iter); - DEBUG(plugin).print(">INSTA-DIGGING<\n"); } } - UnpauseEvent(); + UnpauseEvent(false); TRACE(monitor).print("OnUpdate() refresh done\n"); } // Clean up stale df::job* - if ((config.monitor_active || config.resurrect) && tick - last_tick >= 1) { + if ((config.monitoring || config.resurrect) && tick - last_tick >= 1) { last_tick = tick; // make note of valid jobs std::unordered_map valid_jobs; @@ -415,7 +398,7 @@ namespace CSP { } // Monitoring Active and Resurrecting Dead - if (config.monitor_active && tick - last_monitor_tick >= config.monitor_freq) { + if (config.monitoring && tick - last_monitor_tick >= config.monitor_freq) { last_monitor_tick = tick; TRACE(monitor).print("OnUpdate() monitoring now\n"); @@ -427,7 +410,7 @@ namespace CSP { if (!Maps::isValidTilePos(job->pos)) continue; TRACE(monitor).print(" -> check for job in tracking\n"); if (Units::isAlive(unit)) { - if (!config.monitor_active) continue; + if (!config.monitoring) continue; TRACE(monitor).print(" -> compare positions of worker and job\n"); // save position @@ -475,7 +458,6 @@ namespace CSP { df::coord lowest = simulate_fall(last_safe[unit->id]); Units::teleport(unit, lowest); } - print_res_msg(unit); } } TRACE(monitor).print("OnUpdate() monitoring done\n"); @@ -504,7 +486,6 @@ namespace CSP { df::coord lowest = simulate_fall(last_safe[unit->id]); Units::teleport(unit, lowest); } - print_res_msg(unit); } } } @@ -547,7 +528,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { 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(); + CSP::UnpauseEvent(true); } else if (!enable) { // don't need the groups if the plugin isn't going to be enabled EM::unregisterAll(plugin_self); @@ -562,11 +543,10 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan case SC_UNPAUSED: if (enabled && World::isFortressMode() && Maps::IsValid()) { // manage all designations on unpause - CSP::UnpauseEvent(); + CSP::UnpauseEvent(true); } break; case SC_MAP_LOADED: - case SC_WORLD_LOADED: // cache the map size Maps::getSize(mapx, mapy, mapz); CSP::ClearData(); @@ -609,15 +589,17 @@ command_result channel_safely(color_ostream &out, std::vector ¶ return DFHack::CR_WRONG_USAGE; } try { - if(parameters[1] == "monitor"){ - if (state != config.monitor_active) { - config.monitor_active = state; + if(parameters[1] == "monitoring"){ + if (state != config.monitoring) { + config.monitoring = state; // if this is a fresh start if (state && !config.resurrect) { // we need a fresh start CSP::active_workers.clear(); } } + } else if (parameters[1] == "risk-averse") { + config.riskaverse = state; } else if (parameters[1] == "require-vision") { config.require_vision = state; } else if (parameters[1] == "insta-dig") { @@ -626,7 +608,7 @@ command_result channel_safely(color_ostream &out, std::vector ¶ if (state != config.resurrect) { config.resurrect = state; // if this is a fresh start - if (state && !config.monitor_active) { + if (state && !config.monitoring) { // we need a fresh start CSP::active_workers.clear(); } @@ -656,7 +638,8 @@ command_result channel_safely(color_ostream &out, std::vector ¶ } else { out.print("Channel-Safely is %s\n", enabled ? "ENABLED." : "DISABLED."); out.print(" FEATURES:\n"); - out.print(" %-20s\t%s\n", "monitor-active: ", config.monitor_active ? "on." : "off."); + out.print(" %-20s\t%s\n", "risk-averse: ", config.riskaverse ? "on." : "off."); + out.print(" %-20s\t%s\n", "monitoring: ", config.monitoring ? "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."); diff --git a/plugins/channel-safely/include/channel-groups.h b/plugins/channel-safely/include/channel-groups.h index 8e3224c98..5ec6b15c9 100644 --- a/plugins/channel-safely/include/channel-groups.h +++ b/plugins/channel-safely/include/channel-groups.h @@ -12,7 +12,7 @@ using namespace DFHack; -using Group = std::unordered_set; +using Group = std::set; using Groups = std::vector; /* Used to build groups of adjacent channel designations/jobs @@ -37,6 +37,12 @@ private: protected: void add(const df::coord &map_pos); public: + int debugGIndex(const df::coord &map_pos) { + if (groups_map.count(map_pos)) { + return groups_map[map_pos]; + } + return -1; + } explicit ChannelGroups(ChannelJobs &jobs) : jobs(jobs) { groups.reserve(200); } void scan_one(const df::coord &map_pos); void scan(bool full_scan = false); diff --git a/plugins/channel-safely/include/channel-jobs.h b/plugins/channel-safely/include/channel-jobs.h index 3be704aeb..13f0d0e77 100644 --- a/plugins/channel-safely/include/channel-jobs.h +++ b/plugins/channel-safely/include/channel-jobs.h @@ -6,6 +6,7 @@ #include #include +#include using namespace DFHack; @@ -23,20 +24,31 @@ private: friend class ChannelGroup; using Jobs = std::unordered_set; // job* will exist until it is complete, and likely beyond + std::unordered_map jobs; Jobs locations; + Jobs active; +protected: + bool has_cavein_conditions(const df::coord &map_pos); public: void load_channel_jobs(); + bool possible_cavein(const df::coord &job_pos); + void mark_active(const df::coord &map_pos) { active.emplace(map_pos); } void clear() { + active.clear(); locations.clear(); + jobs.clear(); } int count(const df::coord &map_pos) const { return locations.count(map_pos); } Jobs::iterator erase(const df::coord &map_pos) { + active.erase(map_pos); + jobs.erase(map_pos); auto iter = locations.find(map_pos); if (iter != locations.end()) { return locations.erase(iter); } return iter; } + df::job* find_job(const df::coord &map_pos) const { return jobs.count(map_pos) ? jobs.find(map_pos)->second : nullptr; } 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 323ce8468..4df02ed38 100644 --- a/plugins/channel-safely/include/channel-manager.h +++ b/plugins/channel-safely/include/channel-manager.h @@ -12,11 +12,11 @@ using namespace DFHack; // Uses GroupData to detect an unsafe work environment class ChannelManager { private: - ChannelJobs jobs; ChannelManager()= default; protected: public: ChannelGroups groups = ChannelGroups(jobs); + ChannelJobs jobs; static ChannelManager& Get(){ static ChannelManager instance; @@ -28,12 +28,12 @@ public: 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); + bool manage_one(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"); - groups.debug_groups(); groups.debug_map(); + groups.debug_groups(); } }; diff --git a/plugins/channel-safely/include/inlines.h b/plugins/channel-safely/include/inlines.h index a8686d235..38e73814c 100644 --- a/plugins/channel-safely/include/inlines.h +++ b/plugins/channel-safely/include/inlines.h @@ -2,21 +2,41 @@ #include "plugin.h" #include "channel-manager.h" +#include +#include +#include #include #include -#include #include #include #define Coord(id) id.x][id.y -#define COORD "%" PRIi16 " %" PRIi16 " %" PRIi16 +#define COORD "%" PRIi16 ",%" PRIi16 ",%" PRIi16 #define COORDARGS(id) id.x, id.y, id.z namespace CSP { extern std::unordered_set dignow_queue; } +inline uint32_t calc_distance(df::coord p1, df::coord p2) { + // calculate chebyshev (chessboard) distance + uint32_t distance = abs(p2.z - p1.z); + distance += max(abs(p2.x - p1.x), abs(p2.y - p1.y)); + return distance; +} + +inline void get_connected_neighbours(const df::coord &map_pos, df::coord(&neighbours)[4]) { + neighbours[0] = map_pos; + neighbours[1] = map_pos; + neighbours[2] = map_pos; + neighbours[3] = map_pos; + neighbours[0].y--; + neighbours[1].x--; + neighbours[2].x++; + neighbours[3].y++; +} + inline void get_neighbours(const df::coord &map_pos, df::coord(&neighbours)[8]) { neighbours[0] = map_pos; neighbours[1] = map_pos; @@ -36,6 +56,39 @@ inline void get_neighbours(const df::coord &map_pos, df::coord(&neighbours)[8]) neighbours[7].x++; neighbours[7].y++; } +inline uint8_t count_accessibility(const df::coord &unit_pos, const df::coord &map_pos) { + df::coord neighbours[8]; + df::coord connections[4]; + get_neighbours(map_pos, neighbours); + get_connected_neighbours(map_pos, connections); + uint8_t accessibility = Maps::canWalkBetween(unit_pos, map_pos) ? 1 : 0; + for (auto n: neighbours) { + if (Maps::canWalkBetween(unit_pos, n)) { + accessibility++; + } + } + for (auto n : connections) { + if (Maps::canWalkBetween(unit_pos, n)) { + accessibility++; + } + } + return accessibility; +} + +inline bool isEntombed(const df::coord &unit_pos, const df::coord &map_pos) { + if (Maps::canWalkBetween(unit_pos, map_pos)) { + return false; + } + df::coord neighbours[8]; + get_neighbours(map_pos, neighbours); + for (auto n: neighbours) { + if (Maps::canWalkBetween(unit_pos, n)) { + return false; + } + } + return true; +} + inline bool is_dig_job(const df::job* job) { return job->job_type == df::job_type::Dig || job->job_type == df::job_type::DigChannel; } @@ -135,15 +188,20 @@ inline bool has_any_groups_above(const ChannelGroups &groups, const Group &group } inline void cancel_job(df::job* job) { - if (job != nullptr) { - df::coord &pos = job->pos; + if (job) { + const 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]; auto type = job->job_type; + ChannelManager::Get().jobs.erase(pos); + Job::removeWorker(job); + Job::removePostings(job, true); Job::removeJob(job); + job_block->flags.bits.designated = true; + job_block->occupancy[x][y].bits.dig_marked = true; switch (type) { case job_type::Dig: designation.bits.dig = df::tile_dig_designation::Default; @@ -170,6 +228,41 @@ inline void cancel_job(df::job* job) { } } +inline void cancel_job(const df::coord &map_pos) { + cancel_job(ChannelManager::Get().jobs.find_job(map_pos)); + ChannelManager::Get().jobs.erase(map_pos); +} + +// executes dig designations for the specified tile coordinates +inline bool dig_now(color_ostream &out, const df::coord &map_pos) { + out.color(color_value::COLOR_YELLOW); + out.print("channel-safely: insta-dig: digging (" COORD ")<\n", COORDARGS(map_pos)); + 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 +inline void resurrect(color_ostream &out, const int32_t &unit) { + out.color(DFHack::COLOR_RED); + out.print("channel-safely: resurrecting [id: %d]\n", unit); + std::vector params{"-r", "--unit", std::to_string(unit)}; + Core::getInstance().runCommand(out,"full-heal", params); +} + template void set_difference(const Ctr1 &c1, const Ctr2 &c2, Ctr3 &c3) { for (const auto &a : c1) { diff --git a/plugins/channel-safely/include/plugin.h b/plugins/channel-safely/include/plugin.h index 23b2f8441..336d944ab 100644 --- a/plugins/channel-safely/include/plugin.h +++ b/plugins/channel-safely/include/plugin.h @@ -9,7 +9,8 @@ namespace DFHack { } struct Configuration { - bool monitor_active = false; + bool riskaverse = true; + bool monitoring = false; bool require_vision = true; bool insta_dig = false; bool resurrect = false;