diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index b6957b95e..1a2866920 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -58,6 +58,8 @@ Features -------- :require-vision: Toggle whether the dwarves need vision of a tile before channeling to it can be deemed unsafe. (default: enabled) +:risk-averse: Toggles whether to use cave-in prevention. Designations are activated in stages + and their priorities along edges are modified. (default: enabled) :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) diff --git a/plugins/channel-safely/channel-manager.cpp b/plugins/channel-safely/channel-manager.cpp index e6c7ea671..df3bcad71 100644 --- a/plugins/channel-safely/channel-manager.cpp +++ b/plugins/channel-safely/channel-manager.cpp @@ -57,8 +57,97 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool if (!set_marker_mode) { marker_mode = has_any_groups_above(groups, group); } - for (auto &designation: group) { - manage_one(designation, true, marker_mode); + // cavein prevention + bool cavein_possible = false; + uint8_t least_access = 100; + std::unordered_map cavein_candidates; + if (!marker_mode) { + /* To prevent cave-ins we're looking at accessibility of tiles with open space below them + * If it has space below, it has somewhere to fall + * Accessibility tells us how close to a cave-in a tile is, low values are at risk of cave-ins + * To count access, we find a random miner dwarf and count how many tile neighbours they can path to + * */ + // find a dwarf to path from + df::coord miner_pos = find_dwarf(*group.begin())->pos; + + // Analyze designations + for (const auto &pos: group) { + df::coord below(pos); + below.z--; + const auto below_ttype = *Maps::getTileType(below); + // we can skip designations already queued for insta-digging + if (CSP::dignow_queue.count(pos)) continue; + if (DFHack::isOpenTerrain(below_ttype) || DFHack::isFloorTerrain(below_ttype)) { + // the tile below is not solid earth + // we're counting accessibility then dealing with 0 access + DEBUG(manager).print("analysis: cave-in condition found\n"); + INFO(manager).print("(%d) checking accessibility of (" COORD ") from (" COORD ")\n", cavein_possible,COORDARGS(pos),COORDARGS(miner_pos)); + auto access = count_accessibility(miner_pos, pos); + if (access == 0) { + TRACE(groups).print(" has 0 access\n"); + if (config.insta_dig) { + manage_one(pos, true, false); + dig_now(DFHack::Core::getInstance().getConsole(), pos); + mark_done(pos); + } else { + // todo: engage dig management, swap channel designations for dig + } + } else { + WARN(manager).print(" has %d access\n", access); + cavein_possible = config.riskaverse; + cavein_candidates.emplace(pos, access); + least_access = min(access, least_access); + } + } else if (config.insta_dig && isEntombed(miner_pos, pos)) { + manage_one(pos, true, false); + dig_now(DFHack::Core::getInstance().getConsole(), pos); + mark_done(pos); + } + } + DEBUG(manager).print("cavein possible(%d)\n" + "%zu candidates\n" + "least access %d\n", cavein_possible, cavein_candidates.size(), least_access); + } + // managing designations + if (!group.empty()) { + DEBUG(manager).print("managing group #%d\n", groups.debugGIndex(*group.begin())); + } + for (auto &pos: group) { + // if no cave-in is possible [or we don't check for], we'll just execute normally and move on + if (!cavein_possible) { + TRACE(manager).print("cave-in evaluated false\n"); + assert(manage_one(pos, true, marker_mode)); + continue; + } + // cavein is only possible if marker_mode is false + const static uint8_t MAX = 84; //arbitrary number that indicates the value has changed + if (CSP::dignow_queue.count(pos) || (cavein_candidates.count(pos) && + least_access < MAX && cavein_candidates[pos] <= least_access+2)) { + + TRACE(manager).print("cave-in evaluated true and either of dignow or (%d <= %d)\n", cavein_candidates[pos], least_access+2); + // we want to dig the cavein candidates first + df::coord local(pos); + local.x %= 16; + local.y %= 16; + auto block = Maps::ensureTileBlock(pos); + 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 + auto b = max(0, cavein_candidates[pos] - least_access); + auto v = 1000 + (b * 1700); + DEBUG(manager).print("(" COORD ") 1000+1000(%d) -> %d {least-access: %d}\n",COORDARGS(pos), b, v, least_access); + evT->priority[Coord(local)] = v; + } + } + assert(manage_one(pos, true, false)); + continue; + } + if (cavein_candidates.count(pos)) { + DEBUG(manager).print("cave-in evaluated true and no dignow and (%d > %d)\n", cavein_candidates[pos], least_access + 2); + } else { + DEBUG(manager).print("cave-in evaluated true and no dignow and pos is not a candidate\n"); + } + assert(manage_one(pos, true, true)); } INFO(manager).print("manage_group() is done\n"); }