Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#5031: Fix accessibility logic for wagons #5053

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
42 changes: 40 additions & 2 deletions library/modules/Maps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ distribution.
#include "df/world_underground_region.h"
#include "df/z_level_flags.h"

#include <array>
#include <utility>
#include <string>
#include <vector>
#include <map>
Expand Down Expand Up @@ -757,9 +759,45 @@ bool Maps::ReadGeology(vector<vector<int16_t> > *layer_mats, vector<df::coord2d>
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<std::pair<int, int>, 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)
Expand Down
153 changes: 136 additions & 17 deletions plugins/pathable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<df::building_hatchst*>(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<df::building_grate_floorst*>(bld);
if (b.gate_flags.bits.closed)
return true;
} else if (btype == df::building_type::BarsFloor) {
auto& b = *static_cast<df::building_bars_floorst*>(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)) {
Expand All @@ -241,30 +310,77 @@ 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;

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
Expand Down Expand Up @@ -293,7 +409,10 @@ static bool wagon_flood(color_ostream &out, unordered_set<df::coord> * 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;
Expand Down