#include #include #include #include //hash function for df::coord #include #define NUMARGS(...) std::tuple_size::value #define d_assert(condition, ...) \ static_assert(NUMARGS(__VA_ARGS__) >= 1, "d_assert(condition, format, ...) requires at least up to format as arguments"); \ if (!condition) { \ DFHack::Core::getInstance().getConsole().printerr(__VA_ARGS__); \ assert(0); \ } 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() { 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 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) const { INFO(manager).print("manage_group()\n"); if (!set_marker_mode) { marker_mode = has_any_groups_above(groups, group); } // 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); } 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 = std::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); } } 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"); d_assert(manage_one(pos, true, marker_mode), "manage_one() is failing under !cavein"); continue; } // cavein is only possible if marker_mode is false // we want to dig the cavein candidates first, the least accessible ones specifically const static uint8_t MAX = 84; //arbitrary number that indicates the value has changed const static uint8_t OFFSET = 2; //value has been tweaked to avoid cave-ins whilst activating as many designations as possible if (CSP::dignow_queue.count(pos) || (cavein_candidates.count(pos) && least_access < MAX && cavein_candidates[pos] <= least_access+OFFSET)) { TRACE(manager).print("cave-in evaluated true and either of dignow or (%d <= %d)\n", cavein_candidates[pos], least_access+OFFSET); df::coord local(pos); local.x %= 16; local.y %= 16; auto block = Maps::ensureTileBlock(pos); // if we don't find the priority in block_events, it probably means bad things 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 = std::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; } } d_assert(manage_one(pos, true, false), "manage_one() is failing for cavein "); continue; } // cavein possible, but we failed to meet the criteria for activation if (cavein_candidates.count(pos)) { DEBUG(manager).print("cave-in evaluated true and the cavein candidate's accessibility check was made as (%d <= %d)\n", cavein_candidates[pos], least_access+OFFSET); } else { DEBUG(manager).print("cave-in evaluated true and the position was not a candidate, nor was it set for dignow\n"); } d_assert(manage_one(pos, true, true), "manage_one() is failing to set a cave-in causing designation to marker mode"); } INFO(manager).print("manage_group() is done\n"); } bool ChannelManager::manage_one(const df::coord &map_pos, bool set_marker_mode, bool marker_mode) const { if (Maps::isValidTilePos(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); 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 (!marker_mode) { // marker_mode is set to true if it is unsafe to dig marker_mode = (!set_marker_mode && (has_group_above(groups, map_pos) || !is_safe_to_dig_down(map_pos))) || (set_marker_mode && is_channel_designation(block->designation[Coord(local)]) && !is_safe_to_dig_down(map_pos)); } if (marker_mode) { if (jobs.count(map_pos)) { cancel_job(map_pos); } } else if (!block->flags.bits.designated) { block->flags.bits.designated = true; } tile_occupancy.bits.dig_marked = marker_mode; TRACE(manager).print("marker mode %s\n", marker_mode ? "ENABLED" : "DISABLED"); } else { // if we are though, it should be totally safe to dig tile_occupancy.bits.dig_marked = false; } TRACE(manager).print(" <- manage_one() exits normally\n"); return true; } return false; } 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); }