Skip to content

Commit

Permalink
Merge pull request #4741 from myk002/myk_pathable
Browse files Browse the repository at this point in the history
[pathable] add trade depot accessibility detection and highlight
  • Loading branch information
myk002 authored Jun 22, 2024
2 parents 9a505b2 + 9102cc3 commit 7e05282
Show file tree
Hide file tree
Showing 2 changed files with 239 additions and 20 deletions.
2 changes: 1 addition & 1 deletion docs/plugins/pathable.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pathable
========

.. dfhack-tool::
:summary: Marks tiles that are reachable from the cursor.
:summary: Highlights pathable tiles.
:tags: dev
:no-command:

Expand Down
257 changes: 238 additions & 19 deletions plugins/pathable.cpp
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
#include "Debug.h"
#include "PluginManager.h"
#include "TileTypes.h"

#include "modules/Gui.h"
#include "modules/Maps.h"
#include "modules/Screen.h"
#include "modules/Textures.h"

#include "df/building_tradedepotst.h"
#include "df/init.h"
#include "df/plotinfost.h"
#include "df/world.h"

using namespace DFHack;
using std::stack;
using std::unordered_set;

DFHACK_PLUGIN("pathable");

REQUIRE_GLOBAL(init);
REQUIRE_GLOBAL(plotinfo);
REQUIRE_GLOBAL(window_x);
REQUIRE_GLOBAL(window_y);
REQUIRE_GLOBAL(window_z);
REQUIRE_GLOBAL(world);

namespace DFHack {
DBG_DECLARE(pathable, log, DebugCategory::LINFO);
Expand All @@ -32,22 +40,29 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) {
return CR_OK;
}

static void paintScreenPathable(df::coord target, bool show_hidden = false) {
DEBUG(log).print("entering paintScreen\n");

bool use_graphics = Screen::inGraphicsMode();

int selected_tile_texpos = 0;
Screen::findGraphicsTile("CURSORS", 4, 3, &selected_tile_texpos);
struct PaintCtx {
bool use_graphics;
int selected_tile_texpos;
long pathable_tile_texpos;
long unpathable_tile_texpos;

long pathable_tile_texpos = init->load_bar_texpos[1];
long unpathable_tile_texpos = init->load_bar_texpos[4];
long on_off_texpos = Textures::getTexposByHandle(textures[0]);
if (on_off_texpos > 0) {
pathable_tile_texpos = on_off_texpos;
unpathable_tile_texpos = Textures::getTexposByHandle(textures[1]);
PaintCtx() {
use_graphics = Screen::inGraphicsMode();
selected_tile_texpos = 0;
Screen::findGraphicsTile("CURSORS", 4, 3, &selected_tile_texpos);
pathable_tile_texpos = init->load_bar_texpos[1];
unpathable_tile_texpos = init->load_bar_texpos[4];
long on_off_texpos = Textures::getTexposByHandle(textures[0]);
if (on_off_texpos > 0) {
pathable_tile_texpos = on_off_texpos;
unpathable_tile_texpos = Textures::getTexposByHandle(textures[1]);
}
}
};

static void paint_screen(const PaintCtx & ctx, const unordered_set<df::coord> & targets,
bool show_hidden, std::function<bool(const df::coord & pos)> get_can_walk)
{
auto dims = Gui::getDwarfmodeViewDims().map();
for (int y = dims.first.y; y <= dims.second.y; ++y) {
for (int x = dims.first.x; x <= dims.second.x; ++x) {
Expand All @@ -57,7 +72,7 @@ static void paintScreenPathable(df::coord target, bool show_hidden = false) {
continue;

// don't overwrite the target tile
if (!use_graphics && map_pos == target) {
if (!ctx.use_graphics && targets.contains(map_pos)) {
TRACE(log).print("skipping target tile\n");
continue;
}
Expand All @@ -79,16 +94,16 @@ static void paintScreenPathable(df::coord target, bool show_hidden = false) {
continue;
}

bool can_walk = Maps::canWalkBetween(target, map_pos);
bool can_walk = get_can_walk(map_pos);
DEBUG(log).print("tile is %swalkable at offset %d, %d\n",
can_walk ? "" : "not ", x, y);

if (use_graphics) {
if (map_pos == target) {
cur_tile.tile = selected_tile_texpos;
if (ctx.use_graphics) {
if (targets.contains(map_pos)) {
cur_tile.tile = ctx.selected_tile_texpos;
} else{
cur_tile.tile = can_walk ?
pathable_tile_texpos : unpathable_tile_texpos;
ctx.pathable_tile_texpos : ctx.unpathable_tile_texpos;
}
} else {
int color = can_walk ? COLOR_GREEN : COLOR_RED;
Expand All @@ -111,7 +126,211 @@ static void paintScreenPathable(df::coord target, bool show_hidden = false) {
}
}

static void paintScreenPathable(df::coord target, bool show_hidden = false) {
DEBUG(log).print("entering paintScreen\n");

PaintCtx ctx;
unordered_set<df::coord> targets;
targets.emplace(target);
paint_screen(ctx, targets, show_hidden, [&](const df::coord & pos){
return Maps::canWalkBetween(target, pos);
});
}

static bool get_depot_coords(unordered_set<df::coord> * depot_coords) {
CHECK_NULL_POINTER(depot_coords);

depot_coords->clear();
for (auto bld : world->buildings.other.TRADE_DEPOT)
depot_coords->emplace(bld->centerx, bld->centery, bld->z);

return !depot_coords->empty();
}

static bool get_pathability_groups(unordered_set<uint16_t> * depot_pathability_groups,
const unordered_set<df::coord> & depot_coords)
{
CHECK_NULL_POINTER(depot_pathability_groups);

for (auto pos : depot_coords) {
auto wgroup = Maps::getWalkableGroup(pos);
if (wgroup)
depot_pathability_groups->emplace(wgroup);
}
return !depot_pathability_groups->empty();
}

// if entry_tiles is given, it is filled with the surface edge tiles that match one of the given
// depot pathability groups. If entry_tiles is NULL, just returns true if such a tile is found.
// returns false if no tiles are found
static bool get_entry_tiles(unordered_set<df::coord> * entry_tiles, const unordered_set<uint16_t> & depot_pathability_groups) {
auto & edge = plotinfo->map_edge;
size_t num_edge_tiles = edge.surface_x.size();
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)) {
found = true;
if (!entry_tiles)
break;
entry_tiles->emplace(pos);
}
}
return found;
}

static bool getDepotAccessibleByAnimals() {
unordered_set<df::coord> depot_coords;
if (!get_depot_coords(&depot_coords))
return false;
unordered_set<uint16_t> depot_pathability_groups;
if (!get_pathability_groups(&depot_pathability_groups, depot_coords))
return false;
return get_entry_tiles(NULL, depot_pathability_groups);
}

struct FloodCtx {
uint16_t wgroup;
unordered_set<df::coord> & wagon_path;
unordered_set<df::coord> seen; // contains tiles that should not be added to search_edge
stack<df::coord> search_edge; // contains tiles that can be successfully moved into
const unordered_set<df::coord> & entry_tiles;

FloodCtx(uint16_t wgroup, unordered_set<df::coord> & wagon_path, const unordered_set<df::coord> & entry_tiles)
: 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 (ctx.wgroup == Maps::getWalkableGroup(pos))
return true;

auto tt = Maps::getTileType(pos);
if (!tt)
return false;

auto shape = tileShape(*tt);
if (shape == df::tiletype_shape::RAMP_TOP ) {
df::coord pos_below = pos + df::coord(0, 0, -1);
if (Maps::getWalkableGroup(pos_below)) {
ctx.search_edge.emplace(pos_below);
return true;
}
} else if (shape == df::tiletype_shape::WALL) {
auto prev_tt = Maps::getTileType(prev_pos);
if (prev_tt && tileShape(*prev_tt) == df::tiletype_shape::RAMP) {
df::coord pos_above = pos + df::coord(0, 0, 1);
if (Maps::getWalkableGroup(pos_above)) {
ctx.search_edge.emplace(pos_above);
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.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))
{
ctx.wagon_path.emplace(pos);
ctx.search_edge.emplace(pos);
}
}

// returns true if a continuous 3-wide path can be found to an entry tile
// assumes:
// - wagons can only move in orthogonal directions
// - if three adjacent tiles are in the same pathability group, then they are traversible by a wagon
// - a wagon needs a single ramp to move elevations as long as the adjacent tiles are walkable
static bool wagon_flood(unordered_set<df::coord> * wagon_path, const df::coord & depot_pos,
const unordered_set<df::coord> & entry_tiles)
{
bool found = false;
unordered_set<df::coord> temp_wagon_path;
FloodCtx ctx(Maps::getWalkableGroup(depot_pos), wagon_path ? *wagon_path : temp_wagon_path, entry_tiles);

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();

if (entry_tiles.contains(pos)) {
found = true;
if (!wagon_path)
break;
continue;
}

check_wagon_tile(ctx, pos+df::coord( 0, -1, 0));
check_wagon_tile(ctx, pos+df::coord( 0, 1, 0));
check_wagon_tile(ctx, pos+df::coord(-1, 0, 0));
check_wagon_tile(ctx, pos+df::coord( 1, 0, 0));
}

return found;
}

static unordered_set<df::coord> wagon_path;

static bool getDepotAccessibleByWagons(bool cache_scan_for_painting) {
unordered_set<df::coord> depot_coords;
if (!get_depot_coords(&depot_coords))
return false;
unordered_set<uint16_t> depot_pathability_groups;
if (!get_pathability_groups(&depot_pathability_groups, depot_coords))
return false;
unordered_set<df::coord> entry_tiles;
if (!get_entry_tiles(&entry_tiles, depot_pathability_groups))
return false;
if (cache_scan_for_painting)
wagon_path.clear();
bool found_edge = false;
for (auto depot_pos : depot_coords) {
if (wagon_flood(cache_scan_for_painting ? &wagon_path : NULL, depot_pos, entry_tiles)) {
found_edge = true;
if (!cache_scan_for_painting)
break;
}
}
return found_edge;
}

static void paintScreenDepotAccess() {
unordered_set<df::coord> depot_coords;
if (!get_depot_coords(&depot_coords))
return;

PaintCtx ctx;
paint_screen(ctx, depot_coords, false, [&](const df::coord & pos){
return wagon_path.contains(pos);
});
}

DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(paintScreenPathable),
DFHACK_LUA_FUNCTION(getDepotAccessibleByAnimals),
DFHACK_LUA_FUNCTION(getDepotAccessibleByWagons),
DFHACK_LUA_FUNCTION(paintScreenDepotAccess),
DFHACK_LUA_END
};

0 comments on commit 7e05282

Please sign in to comment.