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>
261 lines
14 KiB
C++
261 lines
14 KiB
C++
#pragma once
|
|
#include "Common.h"
|
|
#include "Python.h"
|
|
#include "structmember.h"
|
|
#include "IndexTexture.h"
|
|
#include "Resources.h"
|
|
#include <list>
|
|
#include <unordered_set>
|
|
#include <string>
|
|
|
|
#include "PyCallable.h"
|
|
#include "PyTexture.h"
|
|
#include "PyDrawable.h"
|
|
#include "PyColor.h"
|
|
#include "PyVector.h"
|
|
#include "PyFont.h"
|
|
|
|
#include "UIGridPoint.h"
|
|
#include "UIBase.h"
|
|
#include "UISprite.h"
|
|
#include "EntityBehavior.h"
|
|
|
|
class UIGrid;
|
|
|
|
// UIEntity
|
|
/*
|
|
|
|
****************************************
|
|
* say it with me: *
|
|
* ✨ UIEntity is not a UIDrawable ✨ *
|
|
****************************************
|
|
|
|
**Why Not, John?**
|
|
Doesn't it say "UI" on the front of it?
|
|
It sure does. Probably should have called it "GridEntity", but it's a bit late now.
|
|
|
|
UIDrawables have positions in **screen pixel coordinates**. Their position is an offset from their parent's position, and they are deeply nestable (Scene -> Frame -> Frame -> ...)
|
|
|
|
However:
|
|
UIEntity has a position in **Grid tile coordinates**.
|
|
UIEntity is not nestable at all. Grid -> Entity.
|
|
UIEntity has a strict one/none relationship with a Grid: if you add it to another grid, it will have itself removed from the losing grid's collection.
|
|
UIEntity originally only allowed a single-tile sprite, but around mid-July 2025, I'm working to change that to allow any UIDrawable to go there, or multi-tile sprites.
|
|
UIEntity is, at its core, the container for *a perspective of map data*.
|
|
The Grid should contain the true, complete contents of the game's space, and the Entity should use pathfinding, field of view, and game logic to interact with the Grid's layer data.
|
|
|
|
In Conclusion, because UIEntity cannot be drawn on a Frame or Scene, and has the unique role of serving as a filter of the data contained in a Grid, UIEntity will not become a UIDrawable.
|
|
|
|
*/
|
|
|
|
//class UIEntity;
|
|
//typedef struct {
|
|
// PyObject_HEAD
|
|
// std::shared_ptr<UIEntity> data;
|
|
//} PyUIEntityObject;
|
|
|
|
// helper methods with no namespace requirement
|
|
PyObject* sfVector2f_to_PyObject(sf::Vector2f vector);
|
|
sf::Vector2f PyObject_to_sfVector2f(PyObject* obj);
|
|
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state);
|
|
PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec);
|
|
|
|
class UIEntity
|
|
{
|
|
public:
|
|
uint64_t serial_number = 0; // For Python object cache
|
|
PyObject* pyobject = nullptr; // Strong ref: preserves Python subclass identity while in grid
|
|
std::shared_ptr<UIGrid> grid;
|
|
std::vector<UIGridPointState> gridstate;
|
|
UISprite sprite;
|
|
sf::Vector2f position; //(x,y) in grid coordinates; float for animation
|
|
sf::Vector2i cell_position{0, 0}; // #295: integer logical position (decoupled from float position)
|
|
sf::Vector2f sprite_offset; // pixel offset for oversized sprites (applied pre-zoom)
|
|
std::unordered_set<std::string> labels; // #296: entity label system for collision/targeting
|
|
PyObject* step_callback = nullptr; // #299: callback for grid.step() turn management
|
|
int default_behavior = 0; // #299: BehaviorType::IDLE - behavior to revert to after DONE
|
|
EntityBehavior behavior; // #300: behavior state for grid.step()
|
|
int turn_order = 1; // #300: 0 = skip, higher = later in turn order
|
|
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();
|
|
~UIEntity();
|
|
|
|
// Release the strong reference that preserves Python subclass identity.
|
|
// Called when entity leaves a grid (die, set_grid, collection removal).
|
|
void releasePyIdentity() {
|
|
if (pyobject) {
|
|
PyObject* tmp = pyobject;
|
|
pyobject = nullptr;
|
|
Py_DECREF(tmp);
|
|
}
|
|
// #299: Clean up step callback
|
|
Py_XDECREF(step_callback);
|
|
step_callback = nullptr;
|
|
}
|
|
|
|
// Visibility methods
|
|
void ensureGridstate(); // Resize gridstate to match current grid dimensions
|
|
void updateVisibility(); // Update gridstate from current FOV
|
|
|
|
// Property system for animations
|
|
bool setProperty(const std::string& name, float value);
|
|
bool setProperty(const std::string& name, int value);
|
|
bool getProperty(const std::string& name, float& value) const;
|
|
bool hasProperty(const std::string& name) const;
|
|
|
|
// Animation shorthand helper - creates and starts an animation on this entity
|
|
// Returns a PyAnimation object. Used by the .animate() method.
|
|
static PyObject* animate(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
|
|
|
// Methods that delegate to sprite
|
|
sf::FloatRect get_bounds() const { return sprite.get_bounds(); }
|
|
void move(float dx, float dy) { sprite.move(dx, dy); position.x += dx; position.y += dy; }
|
|
void resize(float w, float h) { /* Entities don't support direct resizing */ }
|
|
|
|
static PyObject* at(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
|
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);
|
|
|
|
static PyObject* get_position(PyUIEntityObject* self, void* closure);
|
|
static int set_position(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_gridstate(PyUIEntityObject* self, void* closure);
|
|
static PyObject* get_spritenumber(PyUIEntityObject* self, void* closure);
|
|
static int set_spritenumber(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_float_member(PyUIEntityObject* self, void* closure);
|
|
static int set_float_member(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
|
|
// #176 - Pixel position (pos, x, y) computed from draw_pos * tile_size
|
|
static PyObject* get_pixel_pos(PyUIEntityObject* self, void* closure);
|
|
static int set_pixel_pos(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_pixel_member(PyUIEntityObject* self, void* closure);
|
|
static int set_pixel_member(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
// #176 - Integer grid position (grid_x, grid_y)
|
|
static PyObject* get_grid_int_member(PyUIEntityObject* self, void* closure);
|
|
static int set_grid_int_member(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_grid(PyUIEntityObject* self, void* closure);
|
|
static int set_grid(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_sprite_offset(PyUIEntityObject* self, void* closure);
|
|
static int set_sprite_offset(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_sprite_offset_member(PyUIEntityObject* self, void* closure);
|
|
static int set_sprite_offset_member(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
|
|
// #295 - cell_pos (integer logical position)
|
|
static PyObject* get_cell_pos(PyUIEntityObject* self, void* closure);
|
|
static int set_cell_pos(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_cell_member(PyUIEntityObject* self, void* closure);
|
|
static int set_cell_member(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
|
|
// #296 - Label system
|
|
static PyObject* get_labels(PyUIEntityObject* self, void* closure);
|
|
static int set_labels(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
|
|
// #299 - Step callback and default behavior
|
|
static PyObject* get_step(PyUIEntityObject* self, void* closure);
|
|
static int set_step(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_default_behavior(PyUIEntityObject* self, void* closure);
|
|
static int set_default_behavior(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
|
|
// #300 - Behavior system properties
|
|
static PyObject* get_behavior_type(PyUIEntityObject* self, void* closure);
|
|
static PyObject* get_turn_order(PyUIEntityObject* self, void* closure);
|
|
static int set_turn_order(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_move_speed(PyUIEntityObject* self, void* closure);
|
|
static int set_move_speed(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_target_label(PyUIEntityObject* self, void* closure);
|
|
static int set_target_label(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* get_sight_radius(PyUIEntityObject* self, void* closure);
|
|
static int set_sight_radius(PyUIEntityObject* self, PyObject* value, void* closure);
|
|
static PyObject* py_set_behavior(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
|
static PyObject* py_add_label(PyUIEntityObject* self, PyObject* arg);
|
|
static PyObject* py_remove_label(PyUIEntityObject* self, PyObject* arg);
|
|
static PyObject* py_has_label(PyUIEntityObject* self, PyObject* arg);
|
|
|
|
static PyMethodDef methods[];
|
|
static PyGetSetDef getsetters[];
|
|
static PyObject* repr(PyUIEntityObject* self);
|
|
};
|
|
|
|
// Forward declaration of methods array
|
|
extern PyMethodDef UIEntity_all_methods[];
|
|
|
|
namespace mcrfpydef {
|
|
inline PyTypeObject PyUIEntityType = {
|
|
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
|
.tp_name = "mcrfpy.Entity",
|
|
.tp_basicsize = sizeof(PyUIEntityObject),
|
|
.tp_itemsize = 0,
|
|
.tp_dealloc = [](PyObject* obj) {
|
|
auto* self = (PyUIEntityObject*)obj;
|
|
// Clear the identity ref without DECREF - we ARE this object
|
|
if (self->data) self->data->pyobject = nullptr;
|
|
if (self->weakreflist) PyObject_ClearWeakRefs(obj);
|
|
self->data.reset();
|
|
Py_TYPE(obj)->tp_free(obj);
|
|
},
|
|
.tp_repr = (reprfunc)UIEntity::repr,
|
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
|
.tp_doc = PyDoc_STR("Entity(grid_pos=None, texture=None, sprite_index=0, **kwargs)\n\n"
|
|
"A game entity that exists on a grid with sprite rendering.\n\n"
|
|
"Args:\n"
|
|
" grid_pos (tuple, optional): Grid position as (x, y) tuple. Default: (0, 0)\n"
|
|
" texture (Texture, optional): Texture object for sprite. Default: default texture\n"
|
|
" sprite_index (int, optional): Index into texture atlas. Default: 0\n\n"
|
|
"Keyword Args:\n"
|
|
" grid (Grid): Grid to attach entity to. Default: None\n"
|
|
" visible (bool): Visibility state. Default: True\n"
|
|
" opacity (float): Opacity (0.0-1.0). Default: 1.0\n"
|
|
" name (str): Element name for finding. Default: None\n"
|
|
" x (float): X grid position override (tile coords). Default: 0\n"
|
|
" y (float): Y grid position override (tile coords). Default: 0\n"
|
|
" sprite_offset (tuple): Pixel offset for oversized sprites. Default: (0, 0)\n\n"
|
|
"Attributes:\n"
|
|
" pos (Vector): Pixel position relative to grid (requires grid attachment)\n"
|
|
" x, y (float): Pixel position components (requires grid attachment)\n"
|
|
" grid_pos (Vector): Integer tile coordinates (logical game position)\n"
|
|
" grid_x, grid_y (int): Integer tile coordinate components\n"
|
|
" draw_pos (Vector): Fractional tile position for smooth animation\n"
|
|
" gridstate (GridPointState): Visibility state for grid points\n"
|
|
" sprite_index (int): Current sprite index\n"
|
|
" visible (bool): Visibility state\n"
|
|
" opacity (float): Opacity value\n"
|
|
" name (str): Element name\n"
|
|
" sprite_offset (Vector): Pixel offset for oversized sprites\n"
|
|
" sprite_offset_x (float): X component of sprite offset\n"
|
|
" sprite_offset_y (float): Y component of sprite offset"),
|
|
.tp_methods = UIEntity_all_methods,
|
|
.tp_getset = UIEntity::getsetters,
|
|
.tp_base = NULL,
|
|
.tp_init = (initproc)UIEntity::init,
|
|
.tp_new = PyType_GenericNew,
|
|
};
|
|
}
|