Add collision label support for pathfinding (closes #302)
Add `collide` kwarg to Grid.find_path() and Grid.get_dijkstra_map() that treats entities bearing a given label as impassable obstacles via mark-and-restore on the TCOD walkability map. Dijkstra cache key now includes collide label for separate caching. Add Entity.find_path() convenience method that delegates to the grid. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6a0040d630
commit
c1a9523ac2
7 changed files with 546 additions and 32 deletions
|
|
@ -84,6 +84,7 @@ void GridData::syncTCODMap()
|
|||
}
|
||||
}
|
||||
fov_dirty = true;
|
||||
transparency_generation++;
|
||||
}
|
||||
|
||||
void GridData::syncTCODMapCell(int x, int y)
|
||||
|
|
@ -92,6 +93,7 @@ void GridData::syncTCODMapCell(int x, int y)
|
|||
const UIGridPoint& point = at(x, y);
|
||||
tcod_map->setProperties(x, y, point.transparent, point.walkable);
|
||||
fov_dirty = true;
|
||||
transparency_generation++;
|
||||
}
|
||||
|
||||
void GridData::computeFOV(int x, int y, int radius, bool light_walls, TCOD_fov_algorithm_t algo)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <optional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "PyCallable.h"
|
||||
|
|
@ -78,10 +79,15 @@ public:
|
|||
bool fov_last_light_walls = true;
|
||||
TCOD_fov_algorithm_t fov_last_algo = FOV_BASIC;
|
||||
|
||||
// #303 - Transparency generation counter for per-entity FOV caching
|
||||
// Bumped whenever any cell's transparent/walkable property changes
|
||||
uint32_t transparency_generation = 0;
|
||||
|
||||
// =========================================================================
|
||||
// Pathfinding caches
|
||||
// =========================================================================
|
||||
std::map<std::pair<int,int>, std::shared_ptr<DijkstraMap>> dijkstra_maps;
|
||||
// Cache key: (root_x, root_y, collide_label) where collide_label="" means no collision filtering
|
||||
std::map<std::tuple<int,int,std::string>, std::shared_ptr<DijkstraMap>> dijkstra_maps;
|
||||
|
||||
// =========================================================================
|
||||
// Layer system (#147, #150)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "UIEntity.h"
|
||||
#include "UIGrid.h"
|
||||
#include "UIGridPathfinding.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
|
@ -883,6 +884,77 @@ PyObject* UIEntity::path_to(PyUIEntityObject* self, PyObject* args, PyObject* kw
|
|||
return path_list;
|
||||
}
|
||||
|
||||
PyObject* UIEntity::find_path(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
||||
static const char* kwlist[] = {"target", "diagonal_cost", "collide", NULL};
|
||||
PyObject* target_obj = NULL;
|
||||
float diagonal_cost = 1.41f;
|
||||
const char* collide_label = NULL;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|fz", const_cast<char**>(kwlist),
|
||||
&target_obj, &diagonal_cost, &collide_label)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!self->data || !self->data->grid) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"Entity must be associated with a grid to compute paths");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
auto grid = self->data->grid;
|
||||
|
||||
// Extract target position
|
||||
int target_x, target_y;
|
||||
if (!UIGridPathfinding::ExtractPosition(target_obj, &target_x, &target_y,
|
||||
grid.get(), "target")) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int start_x = self->data->cell_position.x;
|
||||
int start_y = self->data->cell_position.y;
|
||||
|
||||
// Bounds check
|
||||
if (start_x < 0 || start_x >= grid->grid_w || start_y < 0 || start_y >= grid->grid_h ||
|
||||
target_x < 0 || target_x >= grid->grid_w || target_y < 0 || target_y >= grid->grid_h) {
|
||||
PyErr_SetString(PyExc_ValueError, "Position out of grid bounds");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Build args to delegate to Grid.find_path
|
||||
// Create a temporary PyUIGridObject wrapper for the grid
|
||||
auto grid_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid");
|
||||
if (!grid_type) return NULL;
|
||||
auto pyGrid = (PyUIGridObject*)grid_type->tp_alloc(grid_type, 0);
|
||||
Py_DECREF(grid_type);
|
||||
if (!pyGrid) return NULL;
|
||||
new (&pyGrid->data) std::shared_ptr<UIGrid>(grid);
|
||||
|
||||
// Build keyword args for Grid.find_path
|
||||
PyObject* start_tuple = Py_BuildValue("(ii)", start_x, start_y);
|
||||
PyObject* target_tuple = Py_BuildValue("(ii)", target_x, target_y);
|
||||
PyObject* fwd_args = PyTuple_Pack(2, start_tuple, target_tuple);
|
||||
Py_DECREF(start_tuple);
|
||||
Py_DECREF(target_tuple);
|
||||
|
||||
PyObject* fwd_kwds = PyDict_New();
|
||||
PyObject* py_diag = PyFloat_FromDouble(diagonal_cost);
|
||||
PyDict_SetItemString(fwd_kwds, "diagonal_cost", py_diag);
|
||||
Py_DECREF(py_diag);
|
||||
if (collide_label) {
|
||||
PyObject* py_collide = PyUnicode_FromString(collide_label);
|
||||
PyDict_SetItemString(fwd_kwds, "collide", py_collide);
|
||||
Py_DECREF(py_collide);
|
||||
}
|
||||
|
||||
PyObject* result = UIGridPathfinding::Grid_find_path(pyGrid, fwd_args, fwd_kwds);
|
||||
|
||||
Py_DECREF(fwd_args);
|
||||
Py_DECREF(fwd_kwds);
|
||||
Py_DECREF(pyGrid);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
PyObject* UIEntity::update_visibility(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored))
|
||||
{
|
||||
self->data->updateVisibility();
|
||||
|
|
@ -1002,6 +1074,18 @@ PyMethodDef UIEntity::methods[] = {
|
|||
" path = entity.path_to(10, 5)\n"
|
||||
" path = entity.path_to((10, 5))\n"
|
||||
" path = entity.path_to(pos=(10, 5))"},
|
||||
{"find_path", (PyCFunction)UIEntity::find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(target, diagonal_cost=1.41, collide=None) -> AStarPath | None\n\n"
|
||||
"Find a path from this entity to the target position.\n\n"
|
||||
"Args:\n"
|
||||
" target: Target as Vector, Entity, or (x, y) tuple.\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default 1.41).\n"
|
||||
" collide: Label string. Entities with this label block pathfinding.\n\n"
|
||||
"Returns:\n"
|
||||
" AStarPath object, or None if no path exists.\n\n"
|
||||
"Example:\n"
|
||||
" path = entity.find_path((10, 5))\n"
|
||||
" path = entity.find_path(player, collide='enemy')"},
|
||||
{"update_visibility", (PyCFunction)UIEntity::update_visibility, METH_NOARGS,
|
||||
"update_visibility() -> None\n\n"
|
||||
"Update entity's visibility state based on current FOV.\n\n"
|
||||
|
|
@ -1362,6 +1446,18 @@ PyMethodDef UIEntity_all_methods[] = {
|
|||
" path = entity.path_to(10, 5)\n"
|
||||
" path = entity.path_to((10, 5))\n"
|
||||
" path = entity.path_to(pos=(10, 5))"},
|
||||
{"find_path", (PyCFunction)UIEntity::find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(target, diagonal_cost=1.41, collide=None) -> AStarPath | None\n\n"
|
||||
"Find a path from this entity to the target position.\n\n"
|
||||
"Args:\n"
|
||||
" target: Target as Vector, Entity, or (x, y) tuple.\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default 1.41).\n"
|
||||
" collide: Label string. Entities with this label block pathfinding.\n\n"
|
||||
"Returns:\n"
|
||||
" AStarPath object, or None if no path exists.\n\n"
|
||||
"Example:\n"
|
||||
" path = entity.find_path((10, 5))\n"
|
||||
" path = entity.find_path(player, collide='enemy')"},
|
||||
{"update_visibility", (PyCFunction)UIEntity::update_visibility, METH_NOARGS,
|
||||
"update_visibility() -> None\n\n"
|
||||
"Update entity's visibility state based on current FOV.\n\n"
|
||||
|
|
|
|||
|
|
@ -79,6 +79,27 @@ public:
|
|||
float move_speed = 0.15f; // #300: animation duration for movement (0 = instant)
|
||||
std::string target_label; // #300: label to search for with TARGET trigger
|
||||
int sight_radius = 10; // #300: FOV radius for TARGET trigger
|
||||
|
||||
// #303 - Per-entity FOV result cache for TARGET trigger optimization
|
||||
// Caches the visibility bitmap from the last FOV computation so that
|
||||
// entities that haven't moved skip recomputation entirely.
|
||||
struct TargetFOVCache {
|
||||
sf::Vector2i origin{-1, -1};
|
||||
int radius = -1;
|
||||
uint32_t transparency_gen = 0;
|
||||
std::vector<bool> visibility; // (2*radius+1)^2 bitmap
|
||||
int vis_side = 0; // 2*radius+1
|
||||
|
||||
bool isValid(sf::Vector2i pos, int r, uint32_t gen) const {
|
||||
return vis_side > 0 && pos == origin && r == radius && gen == transparency_gen;
|
||||
}
|
||||
bool isVisible(int x, int y) const {
|
||||
int dx = x - origin.x + radius;
|
||||
int dy = y - origin.y + radius;
|
||||
if (dx < 0 || dx >= vis_side || dy < 0 || dy >= vis_side) return false;
|
||||
return visibility[dy * vis_side + dx];
|
||||
}
|
||||
} target_fov_cache;
|
||||
//void render(sf::Vector2f); //override final;
|
||||
|
||||
UIEntity();
|
||||
|
|
@ -120,6 +141,7 @@ public:
|
|||
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||
static PyObject* die(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||
static PyObject* path_to(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* find_path(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* update_visibility(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||
static PyObject* visible_entities(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||
static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||
|
|
|
|||
|
|
@ -2153,26 +2153,28 @@ PyMethodDef UIGrid::methods[] = {
|
|||
" True if the cell is visible, False otherwise\n\n"
|
||||
"Must call compute_fov() first to calculate visibility."},
|
||||
{"find_path", (PyCFunction)UIGridPathfinding::Grid_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(start, end, diagonal_cost: float = 1.41) -> AStarPath | None\n\n"
|
||||
"find_path(start, end, diagonal_cost=1.41, collide=None) -> AStarPath | None\n\n"
|
||||
"Compute A* path between two points.\n\n"
|
||||
"Args:\n"
|
||||
" start: Starting position as Vector, Entity, or (x, y) tuple\n"
|
||||
" end: Target position as Vector, Entity, or (x, y) tuple\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n"
|
||||
" collide: Label string. Entities with this label block pathfinding.\n\n"
|
||||
"Returns:\n"
|
||||
" AStarPath object if path exists, None otherwise.\n\n"
|
||||
"The returned AStarPath can be iterated or walked step-by-step."},
|
||||
{"get_dijkstra_map", (PyCFunction)UIGridPathfinding::Grid_get_dijkstra_map, METH_VARARGS | METH_KEYWORDS,
|
||||
"get_dijkstra_map(root, diagonal_cost: float = 1.41) -> DijkstraMap\n\n"
|
||||
"get_dijkstra_map(root, diagonal_cost=1.41, collide=None) -> DijkstraMap\n\n"
|
||||
"Get or create a Dijkstra distance map for a root position.\n\n"
|
||||
"Args:\n"
|
||||
" root: Root position as Vector, Entity, or (x, y) tuple\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n"
|
||||
" collide: Label string. Entities with this label block pathfinding.\n\n"
|
||||
"Returns:\n"
|
||||
" DijkstraMap object for querying distances and paths.\n\n"
|
||||
"Grid caches DijkstraMaps by root position. Multiple requests for the\n"
|
||||
"same root return the same cached map. Call clear_dijkstra_maps() after\n"
|
||||
"changing grid walkability to invalidate the cache."},
|
||||
"Grid caches DijkstraMaps by (root, collide) key. Multiple requests for\n"
|
||||
"the same root and collide label return the same cached map. Call\n"
|
||||
"clear_dijkstra_maps() after changing grid walkability to invalidate."},
|
||||
{"clear_dijkstra_maps", (PyCFunction)UIGridPathfinding::Grid_clear_dijkstra_maps, METH_NOARGS,
|
||||
"clear_dijkstra_maps() -> None\n\n"
|
||||
"Clear all cached Dijkstra maps.\n\n"
|
||||
|
|
@ -2329,27 +2331,64 @@ PyObject* UIGrid::py_step(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
|||
if (entity->behavior.type == BehaviorType::IDLE) continue;
|
||||
|
||||
// Check TARGET trigger (if target_label set)
|
||||
// #303: Tiered optimization:
|
||||
// Tier 1: O(1) label check (target_label.empty())
|
||||
// Tier 2: O(bucket) spatial hash pre-filter
|
||||
// Tier 3: O(radius^2) bounded FOV (TCOD respects radius)
|
||||
// Tier 4: Per-entity FOV cache — skip recomputation when
|
||||
// entity hasn't moved and map transparency unchanged
|
||||
if (!entity->target_label.empty()) {
|
||||
// Quick check: are there any entities with target_label nearby?
|
||||
// Tier 2: Spatial hash proximity pre-filter
|
||||
auto nearby = grid->spatial_hash.queryRadius(
|
||||
static_cast<float>(entity->cell_position.x),
|
||||
static_cast<float>(entity->cell_position.y),
|
||||
static_cast<float>(entity->sight_radius));
|
||||
|
||||
for (auto& target : nearby) {
|
||||
if (target.get() == entity.get()) continue;
|
||||
if (target->labels.count(entity->target_label)) {
|
||||
// Compute FOV to verify line of sight
|
||||
// Collect matching targets before touching FOV
|
||||
std::vector<std::shared_ptr<UIEntity>> matching_targets;
|
||||
for (auto& candidate : nearby) {
|
||||
if (candidate.get() != entity.get() &&
|
||||
candidate->labels.count(entity->target_label)) {
|
||||
matching_targets.push_back(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
if (!matching_targets.empty()) {
|
||||
auto& cache = entity->target_fov_cache;
|
||||
|
||||
// Tier 4: Check per-entity FOV cache
|
||||
if (!cache.isValid(entity->cell_position, entity->sight_radius,
|
||||
grid->transparency_generation)) {
|
||||
// Cache miss — compute FOV and snapshot the visibility bitmap
|
||||
grid->computeFOV(entity->cell_position.x, entity->cell_position.y,
|
||||
entity->sight_radius, true, grid->fov_algorithm);
|
||||
if (grid->isInFOV(target->cell_position.x, target->cell_position.y)) {
|
||||
// Fire TARGET trigger
|
||||
|
||||
int r = entity->sight_radius;
|
||||
int side = 2 * r + 1;
|
||||
cache.origin = entity->cell_position;
|
||||
cache.radius = r;
|
||||
cache.transparency_gen = grid->transparency_generation;
|
||||
cache.vis_side = side;
|
||||
cache.visibility.resize(side * side);
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
cache.visibility[(dy + r) * side + (dx + r)] =
|
||||
grid->isInFOV(entity->cell_position.x + dx,
|
||||
entity->cell_position.y + dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check targets against cached visibility
|
||||
for (auto& target : matching_targets) {
|
||||
if (cache.isVisible(target->cell_position.x,
|
||||
target->cell_position.y)) {
|
||||
PyObject* target_pyobj = Py_None;
|
||||
if (target->pyobject) {
|
||||
target_pyobj = target->pyobject;
|
||||
}
|
||||
fireStepCallback(entity, 2 /* TARGET */, target_pyobj);
|
||||
goto next_entity; // Skip behavior execution after TARGET
|
||||
goto next_entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2433,26 +2472,28 @@ PyMethodDef UIGrid_all_methods[] = {
|
|||
" True if the cell is visible, False otherwise\n\n"
|
||||
"Must call compute_fov() first to calculate visibility."},
|
||||
{"find_path", (PyCFunction)UIGridPathfinding::Grid_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(start, end, diagonal_cost: float = 1.41) -> AStarPath | None\n\n"
|
||||
"find_path(start, end, diagonal_cost=1.41, collide=None) -> AStarPath | None\n\n"
|
||||
"Compute A* path between two points.\n\n"
|
||||
"Args:\n"
|
||||
" start: Starting position as Vector, Entity, or (x, y) tuple\n"
|
||||
" end: Target position as Vector, Entity, or (x, y) tuple\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n"
|
||||
" collide: Label string. Entities with this label block pathfinding.\n\n"
|
||||
"Returns:\n"
|
||||
" AStarPath object if path exists, None otherwise.\n\n"
|
||||
"The returned AStarPath can be iterated or walked step-by-step."},
|
||||
{"get_dijkstra_map", (PyCFunction)UIGridPathfinding::Grid_get_dijkstra_map, METH_VARARGS | METH_KEYWORDS,
|
||||
"get_dijkstra_map(root, diagonal_cost: float = 1.41) -> DijkstraMap\n\n"
|
||||
"get_dijkstra_map(root, diagonal_cost=1.41, collide=None) -> DijkstraMap\n\n"
|
||||
"Get or create a Dijkstra distance map for a root position.\n\n"
|
||||
"Args:\n"
|
||||
" root: Root position as Vector, Entity, or (x, y) tuple\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n"
|
||||
" collide: Label string. Entities with this label block pathfinding.\n\n"
|
||||
"Returns:\n"
|
||||
" DijkstraMap object for querying distances and paths.\n\n"
|
||||
"Grid caches DijkstraMaps by root position. Multiple requests for the\n"
|
||||
"same root return the same cached map. Call clear_dijkstra_maps() after\n"
|
||||
"changing grid walkability to invalidate the cache."},
|
||||
"Grid caches DijkstraMaps by (root, collide) key. Multiple requests for\n"
|
||||
"the same root and collide label return the same cached map. Call\n"
|
||||
"clear_dijkstra_maps() after changing grid walkability to invalidate."},
|
||||
{"clear_dijkstra_maps", (PyCFunction)UIGridPathfinding::Grid_clear_dijkstra_maps, METH_NOARGS,
|
||||
"clear_dijkstra_maps() -> None\n\n"
|
||||
"Clear all cached Dijkstra maps.\n\n"
|
||||
|
|
|
|||
|
|
@ -458,14 +458,55 @@ PyObject* UIGridPathfinding::DijkstraMap_to_heightmap(PyDijkstraMapObject* self,
|
|||
// Grid Factory Methods
|
||||
//=============================================================================
|
||||
|
||||
// Helper: collect cells occupied by entities with a given label, mark non-walkable.
|
||||
// Returns the list of (x, y, was_walkable) for restoration.
|
||||
static std::vector<std::tuple<int,int,bool>> markCollisionLabel(
|
||||
GridData* grid, const std::string& collide_label)
|
||||
{
|
||||
std::vector<std::tuple<int,int,bool>> restore_list;
|
||||
if (!grid->entities || collide_label.empty()) return restore_list;
|
||||
|
||||
TCODMap* tcod_map = grid->getTCODMap();
|
||||
if (!tcod_map) return restore_list;
|
||||
|
||||
for (auto& entity : *grid->entities) {
|
||||
if (!entity) continue;
|
||||
if (entity->labels.count(collide_label)) {
|
||||
int ex = entity->cell_position.x;
|
||||
int ey = entity->cell_position.y;
|
||||
if (ex >= 0 && ex < grid->grid_w && ey >= 0 && ey < grid->grid_h) {
|
||||
bool was_walkable = tcod_map->isWalkable(ex, ey);
|
||||
if (was_walkable) {
|
||||
tcod_map->setProperties(ex, ey, tcod_map->isTransparent(ex, ey), false);
|
||||
restore_list.emplace_back(ex, ey, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return restore_list;
|
||||
}
|
||||
|
||||
// Helper: restore walkability after pathfinding.
|
||||
static void restoreCollisionLabel(GridData* grid,
|
||||
const std::vector<std::tuple<int,int,bool>>& restore_list)
|
||||
{
|
||||
TCODMap* tcod_map = grid->getTCODMap();
|
||||
if (!tcod_map) return;
|
||||
|
||||
for (auto& [x, y, was_walkable] : restore_list) {
|
||||
tcod_map->setProperties(x, y, tcod_map->isTransparent(x, y), was_walkable);
|
||||
}
|
||||
}
|
||||
|
||||
PyObject* UIGridPathfinding::Grid_find_path(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||
static const char* kwlist[] = {"start", "end", "diagonal_cost", NULL};
|
||||
static const char* kwlist[] = {"start", "end", "diagonal_cost", "collide", NULL};
|
||||
PyObject* start_obj = NULL;
|
||||
PyObject* end_obj = NULL;
|
||||
float diagonal_cost = 1.41f;
|
||||
const char* collide_label = NULL;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|f", const_cast<char**>(kwlist),
|
||||
&start_obj, &end_obj, &diagonal_cost)) {
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|fz", const_cast<char**>(kwlist),
|
||||
&start_obj, &end_obj, &diagonal_cost, &collide_label)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -489,9 +530,18 @@ PyObject* UIGridPathfinding::Grid_find_path(PyUIGridObject* self, PyObject* args
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// Mark-and-restore: temporarily block cells occupied by entities with collide label
|
||||
std::string label_str = collide_label ? collide_label : "";
|
||||
auto restore_list = markCollisionLabel(self->data.get(), label_str);
|
||||
|
||||
// Compute path using temporary TCODPath
|
||||
TCODPath tcod_path(self->data->getTCODMap(), diagonal_cost);
|
||||
if (!tcod_path.compute(x1, y1, x2, y2)) {
|
||||
bool found = tcod_path.compute(x1, y1, x2, y2);
|
||||
|
||||
// Restore walkability before returning
|
||||
restoreCollisionLabel(self->data.get(), restore_list);
|
||||
|
||||
if (!found) {
|
||||
Py_RETURN_NONE; // No path exists
|
||||
}
|
||||
|
||||
|
|
@ -518,12 +568,13 @@ PyObject* UIGridPathfinding::Grid_find_path(PyUIGridObject* self, PyObject* args
|
|||
}
|
||||
|
||||
PyObject* UIGridPathfinding::Grid_get_dijkstra_map(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||
static const char* kwlist[] = {"root", "diagonal_cost", NULL};
|
||||
static const char* kwlist[] = {"root", "diagonal_cost", "collide", NULL};
|
||||
PyObject* root_obj = NULL;
|
||||
float diagonal_cost = 1.41f;
|
||||
const char* collide_label = NULL;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|f", const_cast<char**>(kwlist),
|
||||
&root_obj, &diagonal_cost)) {
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|fz", const_cast<char**>(kwlist),
|
||||
&root_obj, &diagonal_cost, &collide_label)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -543,12 +594,13 @@ PyObject* UIGridPathfinding::Grid_get_dijkstra_map(PyUIGridObject* self, PyObjec
|
|||
return NULL;
|
||||
}
|
||||
|
||||
auto key = std::make_pair(root_x, root_y);
|
||||
std::string label_str = collide_label ? collide_label : "";
|
||||
auto key = std::make_tuple(root_x, root_y, label_str);
|
||||
|
||||
// Check cache
|
||||
auto it = self->data->dijkstra_maps.find(key);
|
||||
if (it != self->data->dijkstra_maps.end()) {
|
||||
// Check diagonal cost matches (or we could ignore this)
|
||||
// Check diagonal cost matches
|
||||
if (std::abs(it->second->getDiagonalCost() - diagonal_cost) < 0.001f) {
|
||||
// Return existing
|
||||
PyDijkstraMapObject* result = (PyDijkstraMapObject*)mcrfpydef::PyDijkstraMapType.tp_alloc(
|
||||
|
|
@ -561,10 +613,16 @@ PyObject* UIGridPathfinding::Grid_get_dijkstra_map(PyUIGridObject* self, PyObjec
|
|||
self->data->dijkstra_maps.erase(it);
|
||||
}
|
||||
|
||||
// Mark-and-restore: temporarily block cells with collide label
|
||||
auto restore_list = markCollisionLabel(self->data.get(), label_str);
|
||||
|
||||
// Create new DijkstraMap
|
||||
auto dijkstra = std::make_shared<DijkstraMap>(
|
||||
self->data->getTCODMap(), root_x, root_y, diagonal_cost);
|
||||
|
||||
// Restore walkability
|
||||
restoreCollisionLabel(self->data.get(), restore_list);
|
||||
|
||||
// Cache it
|
||||
self->data->dijkstra_maps[key] = dijkstra;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue