diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index c207360ac4..9206ae5a8f 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -63,6 +63,8 @@ distribution. #include "df/world_underground_region.h" #include "df/z_level_flags.h" +#include +#include #include #include #include @@ -757,9 +759,45 @@ bool Maps::ReadGeology(vector > *layer_mats, vector return true; } -uint16_t Maps::getWalkableGroup(df::coord pos) { +uint16_t getNeighborWalkableGroup(df::map_block * block, df::coord pos) { + auto neighborGroup = [block, pos](int dx, int dy) { + return index_tile(block->walkable, pos + df::coord(dx, dy, 0)); + }; + + constexpr std::array, 8> directions = {{ + {-1, -1}, {0, -1}, {1, -1}, + {-1, 0}, {1, 0}, + {-1, 1}, {0, 1}, {1, 1} + }}; + + for (const auto& [dx, dy] : directions) { + uint16_t g = neighborGroup(dx, dy); + if (g) return g; + } + return 0; +} + +uint16_t Maps::getWalkableGroup(df::coord pos) +{ auto block = getTileBlock(pos); - return block ? index_tile(block->walkable, pos) : 0; + if (!block) return 0; + + /* + * RAMP_TOP tiles do not have a walkability group assigned to them directly. + * To determine walkability, we assign the group of an adjacent tile, + * with the constraint that the assigned group matches the walkability group + * of the same tile one z-level below. + */ + auto tt = getTileType(pos); + if (tt && tileShape(*tt) == df::tiletype_shape::RAMP_TOP) { + auto below_group = getWalkableGroup(pos + df::coord(0, 0, -1)); + if (below_group != 0) { + auto neigh_group = getNeighborWalkableGroup(block, pos); + return below_group == neigh_group ? neigh_group : 0; + } + } + + return index_tile(block->walkable, pos); } bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index 832a817313..cf7fde2b5d 100644 --- a/plugins/pathable.cpp +++ b/plugins/pathable.cpp @@ -3,6 +3,11 @@ #include "PluginManager.h" #include "TileTypes.h" +#include "df/building_type.h" +#include "df/building_hatchst.h" +#include "df/building_bars_floorst.h" +#include "df/building_grate_floorst.h" +#include "df/tile_building_occ.h" #include "modules/Buildings.h" #include "modules/Gui.h" #include "modules/Maps.h" @@ -203,24 +208,88 @@ struct FloodCtx { : wgroup(wgroup), wagon_path(wagon_path), entry_tiles(entry_tiles) {} }; -static bool is_wagon_traversible(FloodCtx & ctx, const df::coord & pos, const df::coord & prev_pos) { - if (auto bld = Buildings::findAtTile(pos)) { - auto btype = bld->getType(); - if (btype == df::building_type::Trap || btype == df::building_type::Door) +static bool is_wagon_dynamic_traversible(df::tiletype_shape shape, const df::coord & pos) { + auto bld = Buildings::findAtTile(pos); + if (!bld) return false; + + auto btype = bld->getType(); + // open hatch should be inaccessible regardless of the tile it sits on + if (btype == df::building_type::Hatch) { + if (shape == df::tiletype_shape::RAMP_TOP) return false; + + auto& hatch = *static_cast(bld); + if (hatch.door_flags.bits.closed) + return true; + // open floor grate/bar should be inaccessible regardless of the tile it sits on + } else if (btype == df::building_type::GrateFloor) { + auto& b = *static_cast(bld); + if (b.gate_flags.bits.closed) + return true; + } else if (btype == df::building_type::BarsFloor) { + auto& b = *static_cast(bld); + if (b.gate_flags.bits.closed) + return true; } + // Doors, traps..etc + return false; +} +static bool is_wagon_tile_traversible(df::tiletype_shape shape) { + // TODO: smoothed boulders are traversible + if (shape == df::tiletype_shape::STAIR_UP || shape == df::tiletype_shape::STAIR_DOWN || + shape == df::tiletype_shape::STAIR_UPDOWN || shape == df::tiletype_shape::BOULDER || + shape == df::tiletype_shape::EMPTY || shape == df::tiletype_shape::NONE) + return false; + return true; +} + +static bool is_wagon_traversible(FloodCtx & ctx, const df::coord & pos, const df::coord & prev_pos) { auto tt = Maps::getTileType(pos); if (!tt) return false; auto shape = tileShape(*tt); - if (shape == df::tiletype_shape::STAIR_UP || shape == df::tiletype_shape::STAIR_UPDOWN) - return false; + + auto& occ = *Maps::getTileOccupancy(pos); + switch (occ.bits.building) { + case tile_building_occ::Obstacle: // Statue + //FALLTHROUGH + case tile_building_occ::Well: + //FALLTHROUGH + case tile_building_occ::Impassable: // Raised bridge + return false; + + case tile_building_occ::Dynamic: + // doors(block), levers (block), traps (block), hatches (OK, but block on down ramp) + // closed floor grates (OK), closed floor bars (OK) + if (is_wagon_dynamic_traversible(shape, pos) == false) + return false; + break; + + case tile_building_occ::None: // Not occupied by a building + //FALLTHROUGH + case tile_building_occ::Planned: + //FALLTHROUGH + case tile_building_occ::Passable: + // bed, support, rollers, armor/weapon stand, cage (not trap) + // open wall grate/vertical bars, retracted bridge, open floodgate + if (is_wagon_tile_traversible(shape) == false) + return false; + break; + case tile_building_occ::Floored: + // depot, lowered bridge, retracable bridge, forbidden hatch + break; + default: + //NOTREACHED + break; + } if (ctx.wgroup == Maps::getWalkableGroup(pos)) return true; + // RAMP_TOP is assigned a walkability group if that commit is accepted + // so this test, I think, would be useless. if (shape == df::tiletype_shape::RAMP_TOP ) { df::coord pos_below = pos + df::coord(0, 0, -1); if (Maps::getWalkableGroup(pos_below)) { @@ -241,6 +310,31 @@ static bool is_wagon_traversible(FloodCtx & ctx, const df::coord & pos, const df return false; } +static bool is_wagon_traversible_by_ramp(FloodCtx & ctx, const df::coord & pos) { + if ((is_wagon_traversible(ctx, pos+df::coord(-1, -1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 0, -1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, -1, 0), pos)) || // Top + + (is_wagon_traversible(ctx, pos+df::coord(-1, 0, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 0, 0, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, 0, 0), pos)) || // Middle + + (is_wagon_traversible(ctx, pos+df::coord(-1, 1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 0, 1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, 1, 0), pos)) || // Bottom + + (is_wagon_traversible(ctx, pos+df::coord(-1, -1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord(-1, 0, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord(-1, 1, 0), pos)) || // Left + + (is_wagon_traversible(ctx, pos+df::coord( 1, -1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, 0, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, 1, 0), pos))) { // Right + return true; + } + return false; +} + static void check_wagon_tile(FloodCtx & ctx, const df::coord & pos) { if (ctx.seen.contains(pos)) return; @@ -248,23 +342,45 @@ static void check_wagon_tile(FloodCtx & ctx, const df::coord & pos) { ctx.seen.emplace(pos); if (ctx.entry_tiles.contains(pos)) { - ctx.wagon_path.emplace(pos); + ctx.wagon_path.emplace(pos); // Is this needed? ctx.search_edge.emplace(pos); return; } - if (is_wagon_traversible(ctx, pos+df::coord(-1, -1, 0), pos) && - is_wagon_traversible(ctx, pos+df::coord( 0, -1, 0), pos) && - is_wagon_traversible(ctx, pos+df::coord( 1, -1, 0), pos) && - is_wagon_traversible(ctx, pos+df::coord(-1, 0, 0), pos) && - is_wagon_traversible(ctx, pos+df::coord( 1, 0, 0), pos) && - is_wagon_traversible(ctx, pos+df::coord(-1, 1, 0), pos) && - is_wagon_traversible(ctx, pos+df::coord( 0, 1, 0), pos) && - is_wagon_traversible(ctx, pos+df::coord( 1, 1, 0), pos)) - { + auto tt = Maps::getTileType(pos); + if (!tt) + return; + + auto shape = tileShape(*tt); + if ((shape != df::tiletype_shape::RAMP_TOP && + (is_wagon_traversible(ctx, pos+df::coord(-1, -1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 0, -1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, -1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord(-1, 0, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, 0, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord(-1, 1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 0, 1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, 1, 0), pos))) || + (shape == df::tiletype_shape::RAMP_TOP && is_wagon_traversible_by_ramp(ctx, pos))) { + ctx.wagon_path.emplace(pos); + ctx.search_edge.emplace(pos); + } + +#if 0 + // Use this if we don't mind red Xs on down ramps + if ((is_wagon_traversible(ctx, pos+df::coord(-1, -1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 0, -1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, -1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord(-1, 0, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, 0, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord(-1, 1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 0, 1, 0), pos) && + is_wagon_traversible(ctx, pos+df::coord( 1, 1, 0), pos))) { ctx.wagon_path.emplace(pos); ctx.search_edge.emplace(pos); } +#endif + } // returns true if a continuous 3-wide path can be found to an entry tile @@ -293,7 +409,10 @@ static bool wagon_flood(color_ostream &out, unordered_set * wagon_pat TRACE(log,out).print("checking tile: (%d, %d, %d); pathability group: %d\n", pos.x, pos.y, pos.z, Maps::getWalkableGroup(pos)); - if (entry_tiles.contains(pos)) { + // Ensure our wagon flood end points lands on an edge tile. + // When there is no path to the depot entry_tiles will not + // contain any edge tiles. + if ((pos.x == 0 || pos.y == 0) && entry_tiles.contains(pos)) { found = true; if (!wagon_path) break;