diff --git a/docs/changelog.txt b/docs/changelog.txt index a78dbe471d..a60584799f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -96,6 +96,7 @@ Template for new versions: - `timestream`: improve FPS by a further 10% - `fix/occupancy`: additionally handle the case where tile building occupancy needs to be set instead of cleared - `orders`: ``orders sort`` now moves orders that are tied to a specific workshop to the top of the list in the global manager orders screen +- `gui/pathable`: make wagon path to depot representation more robust ## Documentation - Dreamfort: add link to Dreamfort tutorial youtube series: https://www.youtube.com/playlist?list=PLzXx9JcB9oXxmrtkO1y8ZXzBCFEZrKxve diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index 832a817313..2e0e8d7e9a 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" @@ -168,11 +173,14 @@ static bool get_pathability_groups(color_ostream &out, unordered_set * static bool get_entry_tiles(unordered_set * entry_tiles, const unordered_set & depot_pathability_groups) { auto & edge = plotinfo->map_edge; size_t num_edge_tiles = edge.surface_x.size(); + uint32_t max_x, max_y, max_z; + Maps::getTileSize(max_x, max_y, max_z); bool found = false; for (size_t idx = 0; idx < num_edge_tiles; ++idx) { df::coord pos(edge.surface_x[idx], edge.surface_y[idx], edge.surface_z[idx]); auto wgroup = Maps::getWalkableGroup(pos); - if (depot_pathability_groups.contains(wgroup)) { + if (depot_pathability_groups.contains(wgroup) && + (pos.x == 0 || pos.y == 0 || pos.x == (int16_t)max_x || pos.y == (int16_t)max_y)) { found = true; if (!entry_tiles) break; @@ -203,20 +211,99 @@ 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; +} + +// NOTE: When i.e. tracks, stairs have a bridge over them, the tile will have +// an occupancy of floored. +static bool is_wagon_tile_traversible(df::tiletype tt) { + auto shape = tileShape(tt); + auto special = tileSpecial(tt); + auto material = tileMaterial(tt); + + // Allow ramps (murky pool and river tiles are blocked except for ramps) + if (shape == df::tiletype_shape::RAMP_TOP) + return true; + // NOTE: smoothing a boulder turns it into a smoothed floor + else 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; + else if (special == df::tiletype_special::TRACK) + return false; + // Fires seem to have their own path group, and group for lava is 0 + // According to wiki, the wagon won't path thru pool and river tiles, but ramps are ok + else if (material == df::tiletype_material::POOL || material == df::tiletype_material::RIVER) + 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) + auto occp = Maps::getTileOccupancy(pos); + if (!occp) return false; + auto & occ = *occp; + switch (occ.bits.building) { + case tile_building_occ::Obstacle: // Statues, windmills (middle tile) + //FALLTHROUGH + case tile_building_occ::Well: + //FALLTHROUGH + case tile_building_occ::Impassable: // Raised bridges + 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: + // Any tile with no building or a passable building including + // beds, supports, rollers, armor/weapon stands, cages (not traps), + // open wall grate/vertical bars, retracted bridges, open floodgates, + // workshops (tiles with open space are handled by the tile check) + if (is_wagon_tile_traversible(*tt) == false) + return false; + break; + case tile_building_occ::Floored: + // depot, lowered bridges or retractable bridges, forbidden hatches + break; + } if (ctx.wgroup == Maps::getWalkableGroup(pos)) return true; @@ -248,12 +335,13 @@ 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) && + if (is_wagon_traversible(ctx, pos, 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) && is_wagon_traversible(ctx, pos+df::coord(-1, 0, 0), pos) && @@ -286,6 +374,7 @@ static bool wagon_flood(color_ostream &out, unordered_set * wagon_pat ctx.wagon_path.emplace(depot_pos); ctx.seen.emplace(depot_pos); ctx.search_edge.emplace(depot_pos); + while (!ctx.search_edge.empty()) { df::coord pos = ctx.search_edge.top(); ctx.search_edge.pop();