470 lines
17 KiB
C++
470 lines
17 KiB
C++
#include "UIEntity.h"
|
|
#include "UIGrid.h"
|
|
#include "McRFPy_API.h"
|
|
#include <algorithm>
|
|
#include "PyObjectUtils.h"
|
|
#include "PyVector.h"
|
|
#include "PyArgHelpers.h"
|
|
// UIDrawable methods now in UIBase.h
|
|
#include "UIEntityPyMethods.h"
|
|
|
|
|
|
UIEntity::UIEntity()
|
|
: self(nullptr), grid(nullptr), position(0.0f, 0.0f)
|
|
{
|
|
// Initialize sprite with safe defaults (sprite has its own safe constructor now)
|
|
// gridstate vector starts empty since we don't know grid dimensions
|
|
}
|
|
|
|
UIEntity::UIEntity(UIGrid& grid)
|
|
: gridstate(grid.grid_x * grid.grid_y)
|
|
{
|
|
}
|
|
|
|
PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) {
|
|
int x, y;
|
|
if (!PyArg_ParseTuple(o, "ii", &x, &y)) {
|
|
PyErr_SetString(PyExc_TypeError, "UIEntity.at requires two integer arguments: (x, y)");
|
|
return NULL;
|
|
}
|
|
|
|
if (self->data->grid == NULL) {
|
|
PyErr_SetString(PyExc_ValueError, "Entity cannot access surroundings because it is not associated with a grid");
|
|
return NULL;
|
|
}
|
|
/*
|
|
PyUIGridPointStateObject* obj = (PyUIGridPointStateObject*)((&mcrfpydef::PyUIGridPointStateType)->tp_alloc(&mcrfpydef::PyUIGridPointStateType, 0));
|
|
*/
|
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState");
|
|
auto obj = (PyUIGridPointStateObject*)type->tp_alloc(type, 0);
|
|
//auto target = std::static_pointer_cast<UIEntity>(target);
|
|
obj->data = &(self->data->gridstate[y + self->data->grid->grid_x * x]);
|
|
obj->grid = self->data->grid;
|
|
obj->entity = self->data;
|
|
return (PyObject*)obj;
|
|
|
|
}
|
|
|
|
PyObject* UIEntity::index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored)) {
|
|
// Check if entity has an associated grid
|
|
if (!self->data || !self->data->grid) {
|
|
PyErr_SetString(PyExc_RuntimeError, "Entity is not associated with a grid");
|
|
return NULL;
|
|
}
|
|
|
|
// Get the grid's entity collection
|
|
auto entities = self->data->grid->entities;
|
|
if (!entities) {
|
|
PyErr_SetString(PyExc_RuntimeError, "Grid has no entity collection");
|
|
return NULL;
|
|
}
|
|
|
|
// Find this entity in the collection
|
|
int index = 0;
|
|
for (auto it = entities->begin(); it != entities->end(); ++it, ++index) {
|
|
if (it->get() == self->data.get()) {
|
|
return PyLong_FromLong(index);
|
|
}
|
|
}
|
|
|
|
// Entity not found in its grid's collection
|
|
PyErr_SetString(PyExc_ValueError, "Entity not found in its grid's entity collection");
|
|
return NULL;
|
|
}
|
|
|
|
int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
|
// Try parsing with PyArgHelpers for grid position
|
|
int arg_idx = 0;
|
|
auto grid_pos_result = PyArgHelpers::parseGridPosition(args, kwds, &arg_idx);
|
|
|
|
// Default values
|
|
float grid_x = 0.0f, grid_y = 0.0f;
|
|
int sprite_index = 0;
|
|
PyObject* texture = nullptr;
|
|
PyObject* grid_obj = nullptr;
|
|
|
|
// Case 1: Got grid position from helpers (tuple format)
|
|
if (grid_pos_result.valid) {
|
|
grid_x = grid_pos_result.grid_x;
|
|
grid_y = grid_pos_result.grid_y;
|
|
|
|
// Parse remaining arguments
|
|
static const char* remaining_keywords[] = {
|
|
"texture", "sprite_index", "grid", nullptr
|
|
};
|
|
|
|
// Create new tuple with remaining args
|
|
Py_ssize_t total_args = PyTuple_Size(args);
|
|
PyObject* remaining_args = PyTuple_GetSlice(args, arg_idx, total_args);
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(remaining_args, kwds, "|OiO",
|
|
const_cast<char**>(remaining_keywords),
|
|
&texture, &sprite_index, &grid_obj)) {
|
|
Py_DECREF(remaining_args);
|
|
if (grid_pos_result.error) PyErr_SetString(PyExc_TypeError, grid_pos_result.error);
|
|
return -1;
|
|
}
|
|
Py_DECREF(remaining_args);
|
|
}
|
|
// Case 2: Traditional format
|
|
else {
|
|
PyErr_Clear(); // Clear any errors from helpers
|
|
|
|
static const char* keywords[] = {
|
|
"grid_x", "grid_y", "texture", "sprite_index", "grid", "grid_pos", nullptr
|
|
};
|
|
PyObject* grid_pos_obj = nullptr;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOiOO",
|
|
const_cast<char**>(keywords),
|
|
&grid_x, &grid_y, &texture, &sprite_index,
|
|
&grid_obj, &grid_pos_obj)) {
|
|
return -1;
|
|
}
|
|
|
|
// Handle grid_pos keyword override
|
|
if (grid_pos_obj && grid_pos_obj != Py_None) {
|
|
if (PyTuple_Check(grid_pos_obj) && PyTuple_Size(grid_pos_obj) == 2) {
|
|
PyObject* x_val = PyTuple_GetItem(grid_pos_obj, 0);
|
|
PyObject* y_val = PyTuple_GetItem(grid_pos_obj, 1);
|
|
if ((PyFloat_Check(x_val) || PyLong_Check(x_val)) &&
|
|
(PyFloat_Check(y_val) || PyLong_Check(y_val))) {
|
|
grid_x = PyFloat_Check(x_val) ? PyFloat_AsDouble(x_val) : PyLong_AsLong(x_val);
|
|
grid_y = PyFloat_Check(y_val) ? PyFloat_AsDouble(y_val) : PyLong_AsLong(y_val);
|
|
}
|
|
} else {
|
|
PyErr_SetString(PyExc_TypeError, "grid_pos must be a tuple (x, y)");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check types for texture
|
|
//
|
|
// Set Texture - allow None or use default
|
|
//
|
|
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
|
if (texture != NULL && texture != Py_None && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
|
|
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
|
|
return -1;
|
|
} else if (texture != NULL && texture != Py_None) {
|
|
auto pytexture = (PyTextureObject*)texture;
|
|
texture_ptr = pytexture->data;
|
|
} else {
|
|
// Use default texture when None or not provided
|
|
texture_ptr = McRFPy_API::default_texture;
|
|
}
|
|
|
|
// Allow creation without texture for testing purposes
|
|
// if (!texture_ptr) {
|
|
// PyErr_SetString(PyExc_RuntimeError, "No texture provided and no default texture available");
|
|
// return -1;
|
|
// }
|
|
|
|
if (grid_obj != NULL && !PyObject_IsInstance(grid_obj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
|
|
PyErr_SetString(PyExc_TypeError, "grid must be a mcrfpy.Grid instance");
|
|
return -1;
|
|
}
|
|
|
|
if (grid_obj == NULL)
|
|
self->data = std::make_shared<UIEntity>();
|
|
else
|
|
self->data = std::make_shared<UIEntity>(*((PyUIGridObject*)grid_obj)->data);
|
|
|
|
// Store reference to Python object
|
|
self->data->self = (PyObject*)self;
|
|
Py_INCREF(self);
|
|
|
|
// TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers
|
|
if (texture_ptr) {
|
|
self->data->sprite = UISprite(texture_ptr, sprite_index, sf::Vector2f(0,0), 1.0);
|
|
} else {
|
|
// Create an empty sprite for testing
|
|
self->data->sprite = UISprite();
|
|
}
|
|
|
|
// Set position using grid coordinates
|
|
self->data->position = sf::Vector2f(grid_x, grid_y);
|
|
|
|
if (grid_obj != NULL) {
|
|
PyUIGridObject* pygrid = (PyUIGridObject*)grid_obj;
|
|
self->data->grid = pygrid->data;
|
|
// todone - on creation of Entity with Grid assignment, also append it to the entity list
|
|
pygrid->data->entities->push_back(self->data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
PyObject* UIEntity::get_spritenumber(PyUIEntityObject* self, void* closure) {
|
|
return PyLong_FromDouble(self->data->sprite.getSpriteIndex());
|
|
}
|
|
|
|
PyObject* sfVector2f_to_PyObject(sf::Vector2f vec) {
|
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
|
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
|
|
if (obj) {
|
|
obj->data = vec;
|
|
}
|
|
return (PyObject*)obj;
|
|
}
|
|
|
|
PyObject* sfVector2i_to_PyObject(sf::Vector2i vec) {
|
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
|
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
|
|
if (obj) {
|
|
obj->data = sf::Vector2f(static_cast<float>(vec.x), static_cast<float>(vec.y));
|
|
}
|
|
return (PyObject*)obj;
|
|
}
|
|
|
|
sf::Vector2f PyObject_to_sfVector2f(PyObject* obj) {
|
|
PyVectorObject* vec = PyVector::from_arg(obj);
|
|
if (!vec) {
|
|
// PyVector::from_arg already set the error
|
|
return sf::Vector2f(0, 0);
|
|
}
|
|
return vec->data;
|
|
}
|
|
|
|
sf::Vector2i PyObject_to_sfVector2i(PyObject* obj) {
|
|
PyVectorObject* vec = PyVector::from_arg(obj);
|
|
if (!vec) {
|
|
// PyVector::from_arg already set the error
|
|
return sf::Vector2i(0, 0);
|
|
}
|
|
return sf::Vector2i(static_cast<int>(vec->data.x), static_cast<int>(vec->data.y));
|
|
}
|
|
|
|
// TODO - deprecate / remove this helper
|
|
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) {
|
|
// This function is incomplete - it creates an empty object without setting state data
|
|
// Should use PyObjectUtils::createGridPointState() instead
|
|
return PyObjectUtils::createPyObjectGeneric("GridPointState");
|
|
}
|
|
|
|
PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec) {
|
|
PyObject* list = PyList_New(vec.size());
|
|
if (!list) return PyErr_NoMemory();
|
|
|
|
for (size_t i = 0; i < vec.size(); ++i) {
|
|
PyObject* obj = UIGridPointState_to_PyObject(vec[i]);
|
|
if (!obj) { // Cleanup on failure
|
|
Py_DECREF(list);
|
|
return NULL;
|
|
}
|
|
PyList_SET_ITEM(list, i, obj); // This steals a reference to obj
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) {
|
|
if (reinterpret_cast<long>(closure) == 0) {
|
|
return sfVector2f_to_PyObject(self->data->position);
|
|
} else {
|
|
// Return integer-cast position for grid coordinates
|
|
sf::Vector2i int_pos(static_cast<int>(self->data->position.x),
|
|
static_cast<int>(self->data->position.y));
|
|
return sfVector2i_to_PyObject(int_pos);
|
|
}
|
|
}
|
|
|
|
int UIEntity::set_position(PyUIEntityObject* self, PyObject* value, void* closure) {
|
|
if (reinterpret_cast<long>(closure) == 0) {
|
|
sf::Vector2f vec = PyObject_to_sfVector2f(value);
|
|
if (PyErr_Occurred()) {
|
|
return -1; // Error already set by PyObject_to_sfVector2f
|
|
}
|
|
self->data->position = vec;
|
|
} else {
|
|
// For integer position, convert to float and set position
|
|
sf::Vector2i vec = PyObject_to_sfVector2i(value);
|
|
if (PyErr_Occurred()) {
|
|
return -1; // Error already set by PyObject_to_sfVector2i
|
|
}
|
|
self->data->position = sf::Vector2f(static_cast<float>(vec.x),
|
|
static_cast<float>(vec.y));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
PyObject* UIEntity::get_gridstate(PyUIEntityObject* self, void* closure) {
|
|
// Assuming a function to convert std::vector<UIGridPointState> to PyObject* list
|
|
return UIGridPointStateVector_to_PyList(self->data->gridstate);
|
|
}
|
|
|
|
int UIEntity::set_spritenumber(PyUIEntityObject* self, PyObject* value, void* closure) {
|
|
int val;
|
|
if (PyLong_Check(value))
|
|
val = PyLong_AsLong(value);
|
|
else
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "sprite_index must be an integer");
|
|
return -1;
|
|
}
|
|
//self->data->sprite.sprite_index = val;
|
|
self->data->sprite.setSpriteIndex(val); // todone - I don't like ".sprite.sprite" in this stack of UIEntity.UISprite.sf::Sprite
|
|
return 0;
|
|
}
|
|
|
|
PyObject* UIEntity::get_float_member(PyUIEntityObject* self, void* closure)
|
|
{
|
|
auto member_ptr = reinterpret_cast<long>(closure);
|
|
if (member_ptr == 0) // x
|
|
return PyFloat_FromDouble(self->data->position.x);
|
|
else if (member_ptr == 1) // y
|
|
return PyFloat_FromDouble(self->data->position.y);
|
|
else
|
|
{
|
|
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
int UIEntity::set_float_member(PyUIEntityObject* self, PyObject* value, void* closure)
|
|
{
|
|
float val;
|
|
auto member_ptr = reinterpret_cast<long>(closure);
|
|
if (PyFloat_Check(value))
|
|
{
|
|
val = PyFloat_AsDouble(value);
|
|
}
|
|
else if (PyLong_Check(value))
|
|
{
|
|
val = PyLong_AsLong(value);
|
|
}
|
|
else
|
|
{
|
|
PyErr_SetString(PyExc_TypeError, "Position must be a number (int or float)");
|
|
return -1;
|
|
}
|
|
if (member_ptr == 0) // x
|
|
{
|
|
self->data->position.x = val;
|
|
}
|
|
else if (member_ptr == 1) // y
|
|
{
|
|
self->data->position.y = val;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
PyObject* UIEntity::die(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored))
|
|
{
|
|
// Check if entity has a grid
|
|
if (!self->data || !self->data->grid) {
|
|
Py_RETURN_NONE; // Entity not on a grid, nothing to do
|
|
}
|
|
|
|
// Remove entity from grid's entity list
|
|
auto grid = self->data->grid;
|
|
auto& entities = grid->entities;
|
|
|
|
// Find and remove this entity from the list
|
|
auto it = std::find_if(entities->begin(), entities->end(),
|
|
[self](const std::shared_ptr<UIEntity>& e) {
|
|
return e.get() == self->data.get();
|
|
});
|
|
|
|
if (it != entities->end()) {
|
|
entities->erase(it);
|
|
// Clear the grid reference
|
|
self->data->grid.reset();
|
|
}
|
|
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
PyMethodDef UIEntity::methods[] = {
|
|
{"at", (PyCFunction)UIEntity::at, METH_O},
|
|
{"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"},
|
|
{"die", (PyCFunction)UIEntity::die, METH_NOARGS, "Remove this entity from its grid"},
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
// Define the PyObjectType alias for the macros
|
|
typedef PyUIEntityObject PyObjectType;
|
|
|
|
// Combine base methods with entity-specific methods
|
|
PyMethodDef UIEntity_all_methods[] = {
|
|
UIDRAWABLE_METHODS,
|
|
{"at", (PyCFunction)UIEntity::at, METH_O},
|
|
{"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"},
|
|
{"die", (PyCFunction)UIEntity::die, METH_NOARGS, "Remove this entity from its grid"},
|
|
{NULL} // Sentinel
|
|
};
|
|
|
|
PyGetSetDef UIEntity::getsetters[] = {
|
|
{"draw_pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (graphically)", (void*)0},
|
|
{"pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (integer grid coordinates)", (void*)1},
|
|
{"gridstate", (getter)UIEntity::get_gridstate, NULL, "Grid point states for the entity", NULL},
|
|
{"sprite_index", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite index on the texture on the display", NULL},
|
|
{"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite index (DEPRECATED: use sprite_index instead)", NULL},
|
|
{"x", (getter)UIEntity::get_float_member, (setter)UIEntity::set_float_member, "Entity x position", (void*)0},
|
|
{"y", (getter)UIEntity::get_float_member, (setter)UIEntity::set_float_member, "Entity y position", (void*)1},
|
|
{"visible", (getter)UIEntity_get_visible, (setter)UIEntity_set_visible, "Visibility flag", NULL},
|
|
{"opacity", (getter)UIEntity_get_opacity, (setter)UIEntity_set_opacity, "Opacity (0.0 = transparent, 1.0 = opaque)", NULL},
|
|
{"name", (getter)UIEntity_get_name, (setter)UIEntity_set_name, "Name for finding elements", NULL},
|
|
{NULL} /* Sentinel */
|
|
};
|
|
|
|
PyObject* UIEntity::repr(PyUIEntityObject* self) {
|
|
std::ostringstream ss;
|
|
if (!self->data) ss << "<Entity (invalid internal object)>";
|
|
else {
|
|
auto ent = self->data;
|
|
ss << "<Entity (x=" << self->data->position.x << ", y=" << self->data->position.y << ", sprite_index=" << self->data->sprite.getSpriteIndex() <<
|
|
")>";
|
|
}
|
|
std::string repr_str = ss.str();
|
|
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
|
}
|
|
|
|
// Property system implementation for animations
|
|
bool UIEntity::setProperty(const std::string& name, float value) {
|
|
if (name == "x") {
|
|
position.x = value;
|
|
// Update sprite position based on grid position
|
|
// Note: This is a simplified version - actual grid-to-pixel conversion depends on grid properties
|
|
sprite.setPosition(sf::Vector2f(position.x, position.y));
|
|
return true;
|
|
}
|
|
else if (name == "y") {
|
|
position.y = value;
|
|
// Update sprite position based on grid position
|
|
sprite.setPosition(sf::Vector2f(position.x, position.y));
|
|
return true;
|
|
}
|
|
else if (name == "sprite_scale") {
|
|
sprite.setScale(sf::Vector2f(value, value));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UIEntity::setProperty(const std::string& name, int value) {
|
|
if (name == "sprite_index" || name == "sprite_number") {
|
|
sprite.setSpriteIndex(value);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UIEntity::getProperty(const std::string& name, float& value) const {
|
|
if (name == "x") {
|
|
value = position.x;
|
|
return true;
|
|
}
|
|
else if (name == "y") {
|
|
value = position.y;
|
|
return true;
|
|
}
|
|
else if (name == "sprite_scale") {
|
|
value = sprite.getScale().x; // Assuming uniform scale
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|