diff --git a/src/McRFPy_Libtcod.cpp b/src/McRFPy_Libtcod.cpp index 912528a..f94d0a9 100644 --- a/src/McRFPy_Libtcod.cpp +++ b/src/McRFPy_Libtcod.cpp @@ -42,8 +42,8 @@ static PyObject* McRFPy_Libtcod::compute_fov(PyObject* self, PyObject* args) { // Return list of visible cells PyObject* visible_list = PyList_New(0); - for (int gy = 0; gy < grid->grid_y; gy++) { - for (int gx = 0; gx < grid->grid_x; gx++) { + for (int gy = 0; gy < grid->grid_h; gy++) { + for (int gx = 0; gx < grid->grid_w; gx++) { if (grid->isInFOV(gx, gy)) { PyObject* pos = Py_BuildValue("(ii)", gx, gy); PyList_Append(visible_list, pos); diff --git a/src/UICollection.cpp b/src/UICollection.cpp index 73b3dde..ce154c6 100644 --- a/src/UICollection.cpp +++ b/src/UICollection.cpp @@ -1128,25 +1128,23 @@ PyObject* UICollection::find(PyUICollectionObject* self, PyObject* args, PyObjec if (recursive && drawable->derived_type() == PyObjectsEnum::UIFRAME) { auto frame = std::static_pointer_cast(drawable); // Create temporary collection object for recursive call - PyTypeObject* collType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollection"); - if (collType) { - PyUICollectionObject* child_coll = (PyUICollectionObject*)collType->tp_alloc(collType, 0); - if (child_coll) { - child_coll->data = frame->children; - PyObject* child_results = find(child_coll, args, kwds); - if (child_results && PyList_Check(child_results)) { - // Extend results with child results - for (Py_ssize_t i = 0; i < PyList_Size(child_results); i++) { - PyObject* item = PyList_GetItem(child_results, i); - Py_INCREF(item); - PyList_Append(results, item); - Py_DECREF(item); - } - Py_DECREF(child_results); + // Use the type directly from namespace (#189 - type not exported to module) + PyTypeObject* collType = &PyUICollectionType; + PyUICollectionObject* child_coll = (PyUICollectionObject*)collType->tp_alloc(collType, 0); + if (child_coll) { + child_coll->data = frame->children; + PyObject* child_results = find(child_coll, args, kwds); + if (child_results && PyList_Check(child_results)) { + // Extend results with child results + for (Py_ssize_t i = 0; i < PyList_Size(child_results); i++) { + PyObject* item = PyList_GetItem(child_results, i); + Py_INCREF(item); + PyList_Append(results, item); + Py_DECREF(item); } - Py_DECREF(child_coll); + Py_DECREF(child_results); } - Py_DECREF(collType); + Py_DECREF(child_coll); } } } @@ -1162,21 +1160,17 @@ PyObject* UICollection::find(PyUICollectionObject* self, PyObject* args, PyObjec // Recursive search into Frame children if (recursive && drawable->derived_type() == PyObjectsEnum::UIFRAME) { auto frame = std::static_pointer_cast(drawable); - PyTypeObject* collType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollection"); - if (collType) { - PyUICollectionObject* child_coll = (PyUICollectionObject*)collType->tp_alloc(collType, 0); - if (child_coll) { - child_coll->data = frame->children; - PyObject* result = find(child_coll, args, kwds); - Py_DECREF(child_coll); - Py_DECREF(collType); - if (result && result != Py_None) { - return result; - } - Py_XDECREF(result); - } else { - Py_DECREF(collType); + // Use the type directly from namespace (#189 - type not exported to module) + PyTypeObject* collType = &PyUICollectionType; + PyUICollectionObject* child_coll = (PyUICollectionObject*)collType->tp_alloc(collType, 0); + if (child_coll) { + child_coll->data = frame->children; + PyObject* result = find(child_coll, args, kwds); + Py_DECREF(child_coll); + if (result && result != Py_None) { + return result; } + Py_XDECREF(result); } } } diff --git a/src/UIDrawable.cpp b/src/UIDrawable.cpp index 6a32e61..f9a6dbe 100644 --- a/src/UIDrawable.cpp +++ b/src/UIDrawable.cpp @@ -774,7 +774,7 @@ void UIDrawable::removeFromParent() { auto p = parent.lock(); if (!p) return; - // Check if parent is a UIFrame (has children vector) + // Check if parent is a UIFrame or UIGrid (both have children vector) if (p->derived_type() == PyObjectsEnum::UIFRAME) { auto frame = std::static_pointer_cast(p); auto& children = *frame->children; @@ -790,7 +790,18 @@ void UIDrawable::removeFromParent() { } frame->children_need_sort = true; } - // TODO: Handle UIGrid children when needed + else if (p->derived_type() == PyObjectsEnum::UIGRID) { + auto grid = std::static_pointer_cast(p); + auto& children = *grid->children; + + for (auto it = children.begin(); it != children.end(); ++it) { + if (it->get() == this) { + children.erase(it); + break; + } + } + grid->children_need_sort = true; + } parent.reset(); } @@ -1031,9 +1042,20 @@ int UIDrawable::set_parent(PyObject* self, PyObject* value, void* closure) { } if (children_ptr && *children_ptr) { - // Add to new parent's children - (*children_ptr)->push_back(drawable); - drawable->setParent(new_parent); + // Check if already in this parent's collection (prevent duplicates) + bool already_present = false; + for (const auto& child : **children_ptr) { + if (child.get() == drawable.get()) { + already_present = true; + break; + } + } + + if (!already_present) { + // Add to new parent's children + (*children_ptr)->push_back(drawable); + drawable->setParent(new_parent); + } } return 0; diff --git a/src/UIEntity.cpp b/src/UIEntity.cpp index e689670..25c15a8 100644 --- a/src/UIEntity.cpp +++ b/src/UIEntity.cpp @@ -37,7 +37,7 @@ void UIEntity::updateVisibility() // Lazy initialize gridstate if needed if (gridstate.size() == 0) { - gridstate.resize(grid->grid_x * grid->grid_y); + gridstate.resize(grid->grid_w * grid->grid_h); // Initialize all cells as not visible/discovered for (auto& state : gridstate) { state.visible = false; @@ -58,9 +58,9 @@ void UIEntity::updateVisibility() grid->computeFOV(x, y, grid->fov_radius, true, grid->fov_algorithm); // Update visible cells based on FOV computation - for (int gy = 0; gy < grid->grid_y; gy++) { - for (int gx = 0; gx < grid->grid_x; gx++) { - int idx = gy * grid->grid_x + gx; + for (int gy = 0; gy < grid->grid_h; gy++) { + for (int gx = 0; gx < grid->grid_w; gx++) { + int idx = gy * grid->grid_w + gx; if (grid->isInFOV(gx, gy)) { gridstate[idx].visible = true; gridstate[idx].discovered = true; // Once seen, always discovered @@ -108,7 +108,7 @@ PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { // Lazy initialize gridstate if needed if (self->data->gridstate.size() == 0) { - self->data->gridstate.resize(self->data->grid->grid_x * self->data->grid->grid_y); + self->data->gridstate.resize(self->data->grid->grid_w * self->data->grid->grid_h); // Initialize all cells as not visible/discovered for (auto& state : self->data->gridstate) { state.visible = false; @@ -117,7 +117,7 @@ PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { } // Bounds check - if (x < 0 || x >= self->data->grid->grid_x || y < 0 || y >= self->data->grid->grid_y) { + if (x < 0 || x >= self->data->grid->grid_w || y < 0 || y >= self->data->grid->grid_h) { PyErr_Format(PyExc_IndexError, "Grid coordinates (%d, %d) out of bounds", x, y); return NULL; } @@ -125,7 +125,7 @@ PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { // Use type directly since GridPointState is internal-only (not exported to module) auto type = &mcrfpydef::PyUIGridPointStateType; auto obj = (PyUIGridPointStateObject*)type->tp_alloc(type, 0); - obj->data = &(self->data->gridstate[y * self->data->grid->grid_x + x]); + obj->data = &(self->data->gridstate[y * self->data->grid->grid_w + x]); obj->grid = self->data->grid; obj->entity = self->data; obj->x = x; // #16 - Store position for .point property @@ -469,6 +469,157 @@ int UIEntity::set_float_member(PyUIEntityObject* self, PyObject* value, void* cl return 0; } +// #176 - Helper to get cell dimensions from grid +static void get_cell_dimensions(UIEntity* entity, float& cell_width, float& cell_height) { + // Default cell dimensions when no texture + constexpr float DEFAULT_CELL_WIDTH = 16.0f; + constexpr float DEFAULT_CELL_HEIGHT = 16.0f; + + if (entity->grid) { + auto ptex = entity->grid->getTexture(); + cell_width = ptex ? static_cast(ptex->sprite_width) : DEFAULT_CELL_WIDTH; + cell_height = ptex ? static_cast(ptex->sprite_height) : DEFAULT_CELL_HEIGHT; + } else { + cell_width = DEFAULT_CELL_WIDTH; + cell_height = DEFAULT_CELL_HEIGHT; + } +} + +// #176 - Pixel position: pos = draw_pos * tile_size +PyObject* UIEntity::get_pixel_pos(PyUIEntityObject* self, void* closure) { + if (!self->data->grid) { + PyErr_SetString(PyExc_RuntimeError, "entity is not attached to a Grid"); + return NULL; + } + + float cell_width, cell_height; + get_cell_dimensions(self->data.get(), cell_width, cell_height); + + sf::Vector2f pixel_pos( + self->data->position.x * cell_width, + self->data->position.y * cell_height + ); + return sfVector2f_to_PyObject(pixel_pos); +} + +int UIEntity::set_pixel_pos(PyUIEntityObject* self, PyObject* value, void* closure) { + if (!self->data->grid) { + PyErr_SetString(PyExc_RuntimeError, "entity is not attached to a Grid"); + return -1; + } + + sf::Vector2f pixel_vec = PyObject_to_sfVector2f(value); + if (PyErr_Occurred()) { + return -1; + } + + float cell_width, cell_height; + get_cell_dimensions(self->data.get(), cell_width, cell_height); + + // Save old position for spatial hash update + float old_x = self->data->position.x; + float old_y = self->data->position.y; + + // Convert pixels to tile coordinates + self->data->position.x = pixel_vec.x / cell_width; + self->data->position.y = pixel_vec.y / cell_height; + + // Update spatial hash + self->data->grid->spatial_hash.update(self->data, old_x, old_y); + + return 0; +} + +// #176 - Individual pixel coordinates (x, y) +PyObject* UIEntity::get_pixel_member(PyUIEntityObject* self, void* closure) { + if (!self->data->grid) { + PyErr_SetString(PyExc_RuntimeError, "entity is not attached to a Grid"); + return NULL; + } + + float cell_width, cell_height; + get_cell_dimensions(self->data.get(), cell_width, cell_height); + + auto member_ptr = reinterpret_cast(closure); + if (member_ptr == 0) // x + return PyFloat_FromDouble(self->data->position.x * cell_width); + else // y + return PyFloat_FromDouble(self->data->position.y * cell_height); +} + +int UIEntity::set_pixel_member(PyUIEntityObject* self, PyObject* value, void* closure) { + if (!self->data->grid) { + PyErr_SetString(PyExc_RuntimeError, "entity is not attached to a Grid"); + return -1; + } + + float val; + 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; + } + + float cell_width, cell_height; + get_cell_dimensions(self->data.get(), cell_width, cell_height); + + // Save old position for spatial hash update + float old_x = self->data->position.x; + float old_y = self->data->position.y; + + auto member_ptr = reinterpret_cast(closure); + if (member_ptr == 0) // x + self->data->position.x = val / cell_width; + else // y + self->data->position.y = val / cell_height; + + // Update spatial hash + self->data->grid->spatial_hash.update(self->data, old_x, old_y); + + return 0; +} + +// #176 - Integer grid position (grid_x, grid_y) +PyObject* UIEntity::get_grid_int_member(PyUIEntityObject* self, void* closure) { + auto member_ptr = reinterpret_cast(closure); + if (member_ptr == 0) // grid_x + return PyLong_FromLong(static_cast(self->data->position.x)); + else // grid_y + return PyLong_FromLong(static_cast(self->data->position.y)); +} + +int UIEntity::set_grid_int_member(PyUIEntityObject* self, PyObject* value, void* closure) { + int val; + if (PyLong_Check(value)) { + val = PyLong_AsLong(value); + } else if (PyFloat_Check(value)) { + val = static_cast(PyFloat_AsDouble(value)); + } else { + PyErr_SetString(PyExc_TypeError, "Grid position must be an integer"); + return -1; + } + + // Save old position for spatial hash update + float old_x = self->data->position.x; + float old_y = self->data->position.y; + + auto member_ptr = reinterpret_cast(closure); + if (member_ptr == 0) // grid_x + self->data->position.x = static_cast(val); + else // grid_y + self->data->position.y = static_cast(val); + + // Update spatial hash if grid exists + if (self->data->grid) { + self->data->grid->spatial_hash.update(self->data, old_x, old_y); + } + + return 0; +} + PyObject* UIEntity::get_grid(PyUIEntityObject* self, void* closure) { if (!self->data || !self->data->grid) { @@ -544,7 +695,7 @@ int UIEntity::set_grid(PyUIEntityObject* self, PyObject* value, void* closure) // Initialize gridstate if needed if (self->data->gridstate.size() == 0) { - self->data->gridstate.resize(new_grid->grid_x * new_grid->grid_y); + self->data->gridstate.resize(new_grid->grid_w * new_grid->grid_h); for (auto& state : self->data->gridstate) { state.visible = false; state.discovered = false; @@ -605,9 +756,9 @@ PyObject* UIEntity::path_to(PyUIEntityObject* self, PyObject* args, PyObject* kw // Validate target position auto grid = self->data->grid; - if (target_x < 0 || target_x >= grid->grid_x || target_y < 0 || target_y >= grid->grid_y) { + if (target_x < 0 || target_x >= grid->grid_w || target_y < 0 || target_y >= grid->grid_h) { PyErr_Format(PyExc_ValueError, "Target position (%d, %d) is out of grid bounds (0-%d, 0-%d)", - target_x, target_y, grid->grid_x - 1, grid->grid_y - 1); + target_x, target_y, grid->grid_w - 1, grid->grid_h - 1); return NULL; } @@ -789,7 +940,7 @@ PyMethodDef UIEntity_all_methods[] = { MCRF_SIG("(property: str, target: Any, duration: float, easing=None, delta=False, callback=None, conflict_mode='replace')", "Animation"), MCRF_DESC("Create and start an animation on this entity's property."), MCRF_ARGS_START - MCRF_ARG("property", "Name of the property to animate (e.g., 'x', 'y', 'sprite_index')") + MCRF_ARG("property", "Name of the property to animate: 'draw_x', 'draw_y' (tile coords), 'sprite_scale', 'sprite_index'") MCRF_ARG("target", "Target value - float or int depending on property") MCRF_ARG("duration", "Animation duration in seconds") MCRF_ARG("easing", "Easing function: Easing enum value, string name, or None for linear") @@ -797,8 +948,8 @@ PyMethodDef UIEntity_all_methods[] = { MCRF_ARG("callback", "Optional callable invoked when animation completes") MCRF_ARG("conflict_mode", "'replace' (default), 'queue', or 'error' if property already animating") MCRF_RETURNS("Animation object for monitoring progress") - MCRF_RAISES("ValueError", "If property name is not valid for Entity (x, y, sprite_scale, sprite_index)") - MCRF_NOTE("Entity animations use grid coordinates for x/y, not pixel coordinates.") + MCRF_RAISES("ValueError", "If property name is not valid for Entity (draw_x, draw_y, sprite_scale, sprite_index)") + MCRF_NOTE("Use 'draw_x'/'draw_y' to animate tile coordinates for smooth movement between grid cells.") )}, {"at", (PyCFunction)UIEntity::at, METH_VARARGS | METH_KEYWORDS, "at(x, y) or at(pos) -> GridPointState\n\n" @@ -846,8 +997,27 @@ PyMethodDef UIEntity_all_methods[] = { }; 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}, + // #176 - Pixel coordinates (relative to grid, like UIDrawable.pos) + {"pos", (getter)UIEntity::get_pixel_pos, (setter)UIEntity::set_pixel_pos, + "Pixel position relative to grid (Vector). Computed as draw_pos * tile_size. " + "Requires entity to be attached to a grid.", NULL}, + {"x", (getter)UIEntity::get_pixel_member, (setter)UIEntity::set_pixel_member, + "Pixel X position relative to grid. Requires entity to be attached to a grid.", (void*)0}, + {"y", (getter)UIEntity::get_pixel_member, (setter)UIEntity::set_pixel_member, + "Pixel Y position relative to grid. Requires entity to be attached to a grid.", (void*)1}, + + // #176 - Integer tile coordinates (logical game position) + {"grid_pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, + "Grid position as integer tile coordinates (Vector). The logical cell this entity occupies.", (void*)1}, + {"grid_x", (getter)UIEntity::get_grid_int_member, (setter)UIEntity::set_grid_int_member, + "Grid X position as integer tile coordinate.", (void*)0}, + {"grid_y", (getter)UIEntity::get_grid_int_member, (setter)UIEntity::set_grid_int_member, + "Grid Y position as integer tile coordinate.", (void*)1}, + + // Float tile coordinates (for smooth animation between tiles) + {"draw_pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, + "Fractional tile position for rendering (Vector). Use for smooth animation between grid cells.", (void*)0}, + {"gridstate", (getter)UIEntity::get_gridstate, NULL, "Grid point states for the entity", NULL}, {"grid", (getter)UIEntity::get_grid, (setter)UIEntity::set_grid, "Grid this entity belongs to. " @@ -855,8 +1025,6 @@ PyGetSetDef UIEntity::getsetters[] = { "Set: Assign a Grid to move entity, or None to remove from grid.", 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}, @@ -867,23 +1035,26 @@ PyObject* UIEntity::repr(PyUIEntityObject* self) { std::ostringstream ss; if (!self->data) ss << ""; else { - auto ent = self->data; - ss << ""; + // #176 - Use grid_x/grid_y naming to reflect tile coordinates + ss << ""; } std::string repr_str = ss.str(); return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); } // Property system implementation for animations +// #176 - Animation properties use tile coordinates (draw_x, draw_y) +// "x" and "y" are kept as aliases for backwards compatibility bool UIEntity::setProperty(const std::string& name, float value) { - if (name == "x") { + if (name == "draw_x" || name == "x") { // #176 - draw_x is preferred, x is alias position.x = value; // Don't update sprite position here - UIGrid::render() handles the pixel positioning if (grid) grid->markDirty(); // #144 - Propagate to parent grid for texture caching return true; } - else if (name == "y") { + else if (name == "draw_y" || name == "y") { // #176 - draw_y is preferred, y is alias position.y = value; // Don't update sprite position here - UIGrid::render() handles the pixel positioning if (grid) grid->markDirty(); // #144 - Propagate to parent grid for texture caching @@ -907,11 +1078,11 @@ bool UIEntity::setProperty(const std::string& name, int value) { } bool UIEntity::getProperty(const std::string& name, float& value) const { - if (name == "x") { + if (name == "draw_x" || name == "x") { // #176 value = position.x; return true; } - else if (name == "y") { + else if (name == "draw_y" || name == "y") { // #176 value = position.y; return true; } @@ -923,8 +1094,8 @@ bool UIEntity::getProperty(const std::string& name, float& value) const { } bool UIEntity::hasProperty(const std::string& name) const { - // Float properties - if (name == "x" || name == "y" || name == "sprite_scale") { + // #176 - Float properties (draw_x/draw_y preferred, x/y are aliases) + if (name == "draw_x" || name == "draw_y" || name == "x" || name == "y" || name == "sprite_scale") { return true; } // Int properties @@ -956,7 +1127,7 @@ PyObject* UIEntity::animate(PyUIEntityObject* self, PyObject* args, PyObject* kw if (!self->data->hasProperty(property_name)) { PyErr_Format(PyExc_ValueError, "Property '%s' is not valid for animation on Entity. " - "Valid properties: x, y, sprite_scale, sprite_index, sprite_number", + "Valid properties: draw_x, draw_y (tile coords), sprite_scale, sprite_index", property_name); return NULL; } diff --git a/src/UIEntity.h b/src/UIEntity.h index 757659c..37ca504 100644 --- a/src/UIEntity.h +++ b/src/UIEntity.h @@ -104,6 +104,15 @@ public: 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 PyMethodDef methods[]; @@ -133,12 +142,14 @@ namespace mcrfpydef { " 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. Default: 0\n" - " y (float): Y grid position override. Default: 0\n\n" + " x (float): X grid position override (tile coords). Default: 0\n" + " y (float): Y grid position override (tile coords). Default: 0\n\n" "Attributes:\n" - " pos (tuple): Grid position as (x, y) tuple\n" - " x, y (float): Grid position coordinates\n" - " draw_pos (tuple): Pixel position for rendering\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" diff --git a/src/UIFrame.cpp b/src/UIFrame.cpp index 16dfada..251e9af 100644 --- a/src/UIFrame.cpp +++ b/src/UIFrame.cpp @@ -174,9 +174,9 @@ void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target) PyObject* UIFrame::get_children(PyUIFrameObject* self, void* closure) { // create PyUICollection instance pointing to self->data->children - auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollection"); + // Use the type directly from namespace (#189 - type not exported to module) + PyTypeObject* type = &mcrfpydef::PyUICollectionType; auto o = (PyUICollectionObject*)type->tp_alloc(type, 0); - Py_DECREF(type); if (o) { o->data = self->data->children; o->owner = self->data; // #122: Set owner for parent tracking diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index 2910005..f6f21a5 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -14,7 +14,7 @@ // UIDrawable methods now in UIBase.h UIGrid::UIGrid() -: grid_x(0), grid_y(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr), +: grid_w(0), grid_h(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr), fill_color(8, 8, 8, 255), tcod_map(nullptr), tcod_dijkstra(nullptr), tcod_path(nullptr), perspective_enabled(false), fov_algorithm(FOV_BASIC), fov_radius(10), use_chunks(false) // Default to omniscient view @@ -39,12 +39,12 @@ UIGrid::UIGrid() output.setPosition(0, 0); output.setTexture(renderTexture.getTexture()); - // Points vector starts empty (grid_x * grid_y = 0) + // Points vector starts empty (grid_w * grid_h = 0) // TCOD map will be created when grid is resized } UIGrid::UIGrid(int gx, int gy, std::shared_ptr _ptex, sf::Vector2f _xy, sf::Vector2f _wh) -: grid_x(gx), grid_y(gy), +: grid_w(gx), grid_h(gy), zoom(1.0f), ptex(_ptex), fill_color(8, 8, 8, 255), tcod_map(nullptr), tcod_dijkstra(nullptr), tcod_path(nullptr), @@ -166,10 +166,10 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) int top_spritepixels = center_y - (box.getSize().y / 2.0 / zoom); int x_limit = left_edge + width_sq + 2; - if (x_limit > grid_x) x_limit = grid_x; + if (x_limit > grid_w) x_limit = grid_w; int y_limit = top_edge + height_sq + 2; - if (y_limit > grid_y) y_limit = grid_y; + if (y_limit > grid_h) y_limit = grid_h; // #150 - Layers are now the sole source of grid rendering (base layer removed) // Render layers with z_index < 0 (below entities) @@ -274,14 +274,14 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) y+=1) { // Skip out-of-bounds cells - if (x < 0 || x >= grid_x || y < 0 || y >= grid_y) continue; + if (x < 0 || x >= grid_w || y < 0 || y >= grid_h) continue; auto pixel_pos = sf::Vector2f( (x*cell_width - left_spritepixels) * zoom, (y*cell_height - top_spritepixels) * zoom ); // Get visibility state from entity's perspective - int idx = y * grid_x + x; + int idx = y * grid_w + x; if (idx >= 0 && idx < static_cast(entity->gridstate.size())) { const auto& state = entity->gridstate[idx]; @@ -313,7 +313,7 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) y+=1) { // Skip out-of-bounds cells - if (x < 0 || x >= grid_x || y < 0 || y >= grid_y) continue; + if (x < 0 || x >= grid_w || y < 0 || y >= grid_h) continue; auto pixel_pos = sf::Vector2f( (x*cell_width - left_spritepixels) * zoom, @@ -361,7 +361,7 @@ UIGridPoint& UIGrid::at(int x, int y) if (use_chunks && chunk_manager) { return chunk_manager->at(x, y); } - return points[y * grid_x + x]; + return points[y * grid_w + x]; } UIGrid::~UIGrid() @@ -387,7 +387,7 @@ PyObjectsEnum UIGrid::derived_type() // #147 - Layer management methods std::shared_ptr UIGrid::addColorLayer(int z_index, const std::string& name) { - auto layer = std::make_shared(z_index, grid_x, grid_y, this); + auto layer = std::make_shared(z_index, grid_w, grid_h, this); layer->name = name; layers.push_back(layer); layers_need_sort = true; @@ -395,7 +395,7 @@ std::shared_ptr UIGrid::addColorLayer(int z_index, const std::string } std::shared_ptr UIGrid::addTileLayer(int z_index, std::shared_ptr texture, const std::string& name) { - auto layer = std::make_shared(z_index, grid_x, grid_y, this, texture); + auto layer = std::make_shared(z_index, grid_w, grid_h, this, texture); layer->name = name; layers.push_back(layer); layers_need_sort = true; @@ -442,8 +442,8 @@ void UIGrid::syncTCODMap() { if (!tcod_map) return; - for (int y = 0; y < grid_y; y++) { - for (int x = 0; x < grid_x; x++) { + for (int y = 0; y < grid_h; y++) { + for (int x = 0; x < grid_w; x++) { const UIGridPoint& point = at(x, y); tcod_map->setProperties(x, y, point.transparent, point.walkable); } @@ -452,15 +452,15 @@ void UIGrid::syncTCODMap() void UIGrid::syncTCODMapCell(int x, int y) { - if (!tcod_map || x < 0 || x >= grid_x || y < 0 || y >= grid_y) return; - + if (!tcod_map || x < 0 || x >= grid_w || y < 0 || y >= grid_h) return; + const UIGridPoint& point = at(x, y); tcod_map->setProperties(x, y, point.transparent, point.walkable); } void UIGrid::computeFOV(int x, int y, int radius, bool light_walls, TCOD_fov_algorithm_t algo) { - if (!tcod_map || x < 0 || x >= grid_x || y < 0 || y >= grid_y) return; + if (!tcod_map || x < 0 || x >= grid_w || y < 0 || y >= grid_h) return; std::lock_guard lock(fov_mutex); tcod_map->computeFov(x, y, radius, light_walls, algo); @@ -468,7 +468,7 @@ void UIGrid::computeFOV(int x, int y, int radius, bool light_walls, TCOD_fov_alg bool UIGrid::isInFOV(int x, int y) const { - if (!tcod_map || x < 0 || x >= grid_x || y < 0 || y >= grid_y) return false; + if (!tcod_map || x < 0 || x >= grid_w || y < 0 || y >= grid_h) return false; std::lock_guard lock(fov_mutex); return tcod_map->isInFov(x, y); @@ -478,8 +478,8 @@ std::vector> UIGrid::findPath(int x1, int y1, int x2, int y2 { std::vector> path; - if (!tcod_map || x1 < 0 || x1 >= grid_x || y1 < 0 || y1 >= grid_y || - x2 < 0 || x2 >= grid_x || y2 < 0 || y2 >= grid_y) { + if (!tcod_map || x1 < 0 || x1 >= grid_w || y1 < 0 || y1 >= grid_h || + x2 < 0 || x2 >= grid_w || y2 < 0 || y2 >= grid_h) { return path; } @@ -497,7 +497,7 @@ std::vector> UIGrid::findPath(int x1, int y1, int x2, int y2 void UIGrid::computeDijkstra(int rootX, int rootY, float diagonalCost) { - if (!tcod_map || !tcod_dijkstra || rootX < 0 || rootX >= grid_x || rootY < 0 || rootY >= grid_y) return; + if (!tcod_map || !tcod_dijkstra || rootX < 0 || rootX >= grid_w || rootY < 0 || rootY >= grid_h) return; // Compute the Dijkstra map from the root position tcod_dijkstra->compute(rootX, rootY); @@ -505,7 +505,7 @@ void UIGrid::computeDijkstra(int rootX, int rootY, float diagonalCost) float UIGrid::getDijkstraDistance(int x, int y) const { - if (!tcod_dijkstra || x < 0 || x >= grid_x || y < 0 || y >= grid_y) { + if (!tcod_dijkstra || x < 0 || x >= grid_w || y < 0 || y >= grid_h) { return -1.0f; // Invalid position } @@ -516,7 +516,7 @@ std::vector> UIGrid::getDijkstraPath(int x, int y) const { std::vector> path; - if (!tcod_dijkstra || x < 0 || x >= grid_x || y < 0 || y >= grid_y) { + if (!tcod_dijkstra || x < 0 || x >= grid_w || y < 0 || y >= grid_h) { return path; // Empty path for invalid position } @@ -538,9 +538,9 @@ std::vector> UIGrid::computeAStarPath(int x1, int y1, int x2 std::vector> path; // Validate inputs - if (!tcod_map || !tcod_path || - x1 < 0 || x1 >= grid_x || y1 < 0 || y1 >= grid_y || - x2 < 0 || x2 >= grid_x || y2 < 0 || y2 >= grid_y) { + if (!tcod_map || !tcod_path || + x1 < 0 || x1 >= grid_w || y1 < 0 || y1 >= grid_h || + x2 < 0 || x2 >= grid_w || y2 < 0 || y2 >= grid_h) { return path; // Return empty path } @@ -686,7 +686,7 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point) int cell_y = static_cast(std::floor(grid_y)); // Only fire if within valid grid bounds - if (cell_x >= 0 && cell_x < this->grid_x && cell_y >= 0 && cell_y < this->grid_y) { + if (cell_x >= 0 && cell_x < this->grid_w && cell_y >= 0 && cell_y < this->grid_h) { // Create Vector object for cell position - must fetch finalized type from module PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); if (vector_type) { @@ -719,7 +719,7 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point) int cell_y = static_cast(std::floor(grid_y)); // Only fire if within valid grid bounds - if (cell_x >= 0 && cell_x < this->grid_x && cell_y >= 0 && cell_y < this->grid_y) { + if (cell_x >= 0 && cell_x < this->grid_w && cell_y >= 0 && cell_y < this->grid_h) { // Create Vector object for cell position - must fetch finalized type from module PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); if (vector_type) { @@ -766,14 +766,14 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { int z_index = 0; const char* name = nullptr; float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f; - int grid_x = 2, grid_y = 2; // Default to 2x2 grid + int grid_w = 2, grid_h = 2; // Default to 2x2 grid // Keywords list matches the new spec: positional args first, then all keyword args static const char* kwlist[] = { "pos", "size", "grid_size", "texture", // Positional args (as per spec) // Keyword-only args "fill_color", "on_click", "center_x", "center_y", "zoom", - "visible", "opacity", "z_index", "name", "x", "y", "w", "h", "grid_x", "grid_y", + "visible", "opacity", "z_index", "name", "x", "y", "w", "h", "grid_w", "grid_h", "layers", // #150 - layers dict parameter nullptr }; @@ -782,7 +782,7 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOOfffifizffffiiO", const_cast(kwlist), &pos_obj, &size_obj, &grid_size_obj, &textureObj, // Positional &fill_color, &click_handler, ¢er_x, ¢er_y, &zoom, - &visible, &opacity, &z_index, &name, &x, &y, &w, &h, &grid_x, &grid_y, + &visible, &opacity, &z_index, &name, &x, &y, &w, &h, &grid_w, &grid_h, &layers_obj)) { return -1; } @@ -833,26 +833,26 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { } } - // Handle grid_size argument (can be tuple or use grid_x/grid_y keywords) + // Handle grid_size argument (can be tuple or use grid_w/grid_h keywords) if (grid_size_obj) { if (PyTuple_Check(grid_size_obj) && PyTuple_Size(grid_size_obj) == 2) { PyObject* gx_val = PyTuple_GetItem(grid_size_obj, 0); PyObject* gy_val = PyTuple_GetItem(grid_size_obj, 1); if (PyLong_Check(gx_val) && PyLong_Check(gy_val)) { - grid_x = PyLong_AsLong(gx_val); - grid_y = PyLong_AsLong(gy_val); + grid_w = PyLong_AsLong(gx_val); + grid_h = PyLong_AsLong(gy_val); } else { PyErr_SetString(PyExc_TypeError, "grid_size tuple must contain integers"); return -1; } } else { - PyErr_SetString(PyExc_TypeError, "grid_size must be a tuple (grid_x, grid_y)"); + PyErr_SetString(PyExc_TypeError, "grid_size must be a tuple (grid_w, grid_h)"); return -1; } } // Validate grid dimensions - if (grid_x <= 0 || grid_y <= 0) { + if (grid_w <= 0 || grid_h <= 0) { PyErr_SetString(PyExc_ValueError, "Grid dimensions must be positive integers"); return -1; } @@ -873,15 +873,15 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { // If size wasn't specified, calculate based on grid dimensions and texture if (!size_obj && texture_ptr) { - w = grid_x * texture_ptr->sprite_width; - h = grid_y * texture_ptr->sprite_height; + w = grid_w * texture_ptr->sprite_width; + h = grid_h * texture_ptr->sprite_height; } else if (!size_obj) { - w = grid_x * 16.0f; // Default tile size - h = grid_y * 16.0f; + w = grid_w * 16.0f; // Default tile size + h = grid_h * 16.0f; } - + // Create the grid - self->data = std::make_shared(grid_x, grid_y, texture_ptr, + self->data = std::make_shared(grid_w, grid_h, texture_ptr, sf::Vector2f(x, y), sf::Vector2f(w, h)); // Set additional properties @@ -993,16 +993,16 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { // #179 - Return grid_size as Vector PyObject* UIGrid::get_grid_size(PyUIGridObject* self, void* closure) { - return PyVector(sf::Vector2f(static_cast(self->data->grid_x), - static_cast(self->data->grid_y))).pyObject(); + return PyVector(sf::Vector2f(static_cast(self->data->grid_w), + static_cast(self->data->grid_h))).pyObject(); } -PyObject* UIGrid::get_grid_x(PyUIGridObject* self, void* closure) { - return PyLong_FromLong(self->data->grid_x); +PyObject* UIGrid::get_grid_w(PyUIGridObject* self, void* closure) { + return PyLong_FromLong(self->data->grid_w); } -PyObject* UIGrid::get_grid_y(PyUIGridObject* self, void* closure) { - return PyLong_FromLong(self->data->grid_y); +PyObject* UIGrid::get_grid_h(PyUIGridObject* self, void* closure) { + return PyLong_FromLong(self->data->grid_h); } PyObject* UIGrid::get_position(PyUIGridObject* self, void* closure) { @@ -1021,16 +1021,26 @@ int UIGrid::set_position(PyUIGridObject* self, PyObject* value, void* closure) { return 0; } +// #181 - Return size as Vector PyObject* UIGrid::get_size(PyUIGridObject* self, void* closure) { auto& box = self->data->box; - return Py_BuildValue("(ff)", box.getSize().x, box.getSize().y); + return PyVector(box.getSize()).pyObject(); } int UIGrid::set_size(PyUIGridObject* self, PyObject* value, void* closure) { float w, h; - if (!PyArg_ParseTuple(value, "ff", &w, &h)) { - PyErr_SetString(PyExc_ValueError, "Size must be a tuple of two floats"); - return -1; + // Accept Vector or tuple + PyVectorObject* vec = PyVector::from_arg(value); + if (vec) { + w = vec->data.x; + h = vec->data.y; + Py_DECREF(vec); + } else { + PyErr_Clear(); + if (!PyArg_ParseTuple(value, "ff", &w, &h)) { + PyErr_SetString(PyExc_TypeError, "size must be a Vector or tuple (w, h)"); + return -1; + } } self->data->box.setSize(sf::Vector2f(w, h)); @@ -1173,12 +1183,12 @@ PyObject* UIGrid::py_at(PyUIGridObject* self, PyObject* args, PyObject* kwds) } // Range validation - if (x < 0 || x >= self->data->grid_x) { - PyErr_Format(PyExc_IndexError, "x index %d is out of range [0, %d)", x, self->data->grid_x); + if (x < 0 || x >= self->data->grid_w) { + PyErr_Format(PyExc_IndexError, "x index %d is out of range [0, %d)", x, self->data->grid_w); return NULL; } - if (y < 0 || y >= self->data->grid_y) { - PyErr_Format(PyExc_IndexError, "y index %d is out of range [0, %d)", y, self->data->grid_y); + if (y < 0 || y >= self->data->grid_h) { + PyErr_Format(PyExc_IndexError, "y index %d is out of range [0, %d)", y, self->data->grid_h); return NULL; } @@ -1777,8 +1787,8 @@ void UIGrid::center_camera() { // Center on grid's middle tile int cell_width = ptex ? ptex->sprite_width : DEFAULT_CELL_WIDTH; int cell_height = ptex ? ptex->sprite_height : DEFAULT_CELL_HEIGHT; - center_x = (grid_x / 2.0f) * cell_width; - center_y = (grid_y / 2.0f) * cell_height; + center_x = (grid_w / 2.0f) * cell_width; + center_y = (grid_h / 2.0f) * cell_height; markDirty(); // #144 - View change affects content } @@ -2043,12 +2053,12 @@ PyMethodDef UIGrid_all_methods[] = { PyGetSetDef UIGrid::getsetters[] = { // TODO - refactor into get_vector_member with field identifier values `(void*)n` - {"grid_size", (getter)UIGrid::get_grid_size, NULL, "Grid dimensions (grid_x, grid_y)", NULL}, - {"grid_x", (getter)UIGrid::get_grid_x, NULL, "Grid x dimension", NULL}, - {"grid_y", (getter)UIGrid::get_grid_y, NULL, "Grid y dimension", NULL}, + {"grid_size", (getter)UIGrid::get_grid_size, NULL, "Grid dimensions (grid_w, grid_h)", NULL}, + {"grid_w", (getter)UIGrid::get_grid_w, NULL, "Grid width in cells", NULL}, + {"grid_h", (getter)UIGrid::get_grid_h, NULL, "Grid height in cells", NULL}, {"position", (getter)UIGrid::get_position, (setter)UIGrid::set_position, "Position of the grid (x, y)", NULL}, {"pos", (getter)UIDrawable::get_pos, (setter)UIDrawable::set_pos, "Position of the grid as Vector", (void*)PyObjectsEnum::UIGRID}, - {"size", (getter)UIGrid::get_size, (setter)UIGrid::set_size, "Size of the grid (width, height)", NULL}, + {"size", (getter)UIGrid::get_size, (setter)UIGrid::set_size, "Size of the grid as Vector (width, height)", NULL}, {"center", (getter)UIGrid::get_center, (setter)UIGrid::set_center, "Grid coordinate at the center of the Grid's view (pan)", NULL}, {"entities", (getter)UIGrid::get_entities, NULL, "EntityCollection of entities on this grid", NULL}, @@ -2119,9 +2129,9 @@ PyObject* UIGrid::get_entities(PyUIGridObject* self, void* closure) PyObject* UIGrid::get_children(PyUIGridObject* self, void* closure) { // Returns UICollection for UIDrawable children (speech bubbles, effects, overlays) - auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollection"); + // Use the type directly from namespace (#189 - type not exported to module) + PyTypeObject* type = &mcrfpydef::PyUICollectionType; auto o = (PyUICollectionObject*)type->tp_alloc(type, 0); - Py_DECREF(type); if (o) { o->data = self->data->children; o->owner = self->data; // #122: Set owner for parent tracking @@ -2245,7 +2255,7 @@ std::optional UIGrid::screenToCell(sf::Vector2f screen_pos) const int cell_y = static_cast(std::floor(grid_space_y / (ptex ? ptex->sprite_height : DEFAULT_CELL_HEIGHT))); // Check if within valid cell range - if (cell_x < 0 || cell_x >= grid_x || cell_y < 0 || cell_y >= grid_y) { + if (cell_x < 0 || cell_x >= grid_w || cell_y < 0 || cell_y >= grid_h) { return std::nullopt; } @@ -2642,7 +2652,7 @@ PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* // Initialize gridstate if not already done if (entity->data->gridstate.size() == 0 && self->grid) { - entity->data->gridstate.resize(self->grid->grid_x * self->grid->grid_y); + entity->data->gridstate.resize(self->grid->grid_w * self->grid->grid_h); // Initialize all cells as not visible/discovered for (auto& state : entity->data->gridstate) { state.visible = false; @@ -2850,7 +2860,7 @@ PyObject* UIEntityCollection::insert(PyUIEntityCollectionObject* self, PyObject* // Initialize gridstate if needed if (entity->data->gridstate.size() == 0 && self->grid) { - entity->data->gridstate.resize(self->grid->grid_x * self->grid->grid_y); + entity->data->gridstate.resize(self->grid->grid_w * self->grid->grid_h); for (auto& state : entity->data->gridstate) { state.visible = false; state.discovered = false; diff --git a/src/UIGrid.h b/src/UIGrid.h index eedd1c2..bb707f2 100644 --- a/src/UIGrid.h +++ b/src/UIGrid.h @@ -69,7 +69,7 @@ public: void resize(float w, float h) override; void onPositionChanged() override; - int grid_x, grid_y; + int grid_w, grid_h; //int grid_size; // grid sizes are implied by IndexTexture now sf::RectangleShape box; float center_x, center_y, zoom; @@ -142,8 +142,8 @@ public: static int init(PyUIGridObject* self, PyObject* args, PyObject* kwds); static PyObject* get_grid_size(PyUIGridObject* self, void* closure); - static PyObject* get_grid_x(PyUIGridObject* self, void* closure); - static PyObject* get_grid_y(PyUIGridObject* self, void* closure); + static PyObject* get_grid_w(PyUIGridObject* self, void* closure); + static PyObject* get_grid_h(PyUIGridObject* self, void* closure); static PyObject* get_position(PyUIGridObject* self, void* closure); static int set_position(PyUIGridObject* self, PyObject* value, void* closure); static PyObject* get_size(PyUIGridObject* self, void* closure); @@ -279,7 +279,7 @@ namespace mcrfpydef { "Args:\n" " pos (tuple, optional): Position as (x, y) tuple. Default: (0, 0)\n" " size (tuple, optional): Size as (width, height) tuple. Default: auto-calculated from grid_size\n" - " grid_size (tuple, optional): Grid dimensions as (grid_x, grid_y) tuple. Default: (2, 2)\n" + " grid_size (tuple, optional): Grid dimensions as (grid_w, grid_h) tuple. Default: (2, 2)\n" " texture (Texture, optional): Texture containing tile sprites. Default: default texture\n\n" "Keyword Args:\n" " fill_color (Color): Background fill color. Default: None\n" @@ -296,18 +296,18 @@ namespace mcrfpydef { " y (float): Y position override. Default: 0\n" " w (float): Width override. Default: auto-calculated\n" " h (float): Height override. Default: auto-calculated\n" - " grid_x (int): Grid width override. Default: 2\n" - " grid_y (int): Grid height override. Default: 2\n\n" + " grid_w (int): Grid width override. Default: 2\n" + " grid_h (int): Grid height override. Default: 2\n\n" "Attributes:\n" " x, y (float): Position in pixels\n" " w, h (float): Size in pixels\n" " pos (Vector): Position as a Vector object\n" - " size (tuple): Size as (width, height) tuple\n" - " center (tuple): Center point as (x, y) tuple\n" + " size (Vector): Size as (width, height) Vector\n" + " center (Vector): Center point as (x, y) Vector\n" " center_x, center_y (float): Center point coordinates\n" " zoom (float): Zoom level for rendering\n" - " grid_size (tuple): Grid dimensions (width, height) in tiles\n" - " grid_x, grid_y (int): Grid dimensions\n" + " grid_size (Vector): Grid dimensions (width, height) in tiles\n" + " grid_w, grid_h (int): Grid dimensions\n" " texture (Texture): Tile texture atlas\n" " fill_color (Color): Background color\n" " entities (EntityCollection): Collection of entities in the grid\n" diff --git a/src/UIGridPoint.cpp b/src/UIGridPoint.cpp index f66f0f6..a636c8c 100644 --- a/src/UIGridPoint.cpp +++ b/src/UIGridPoint.cpp @@ -207,7 +207,7 @@ PyObject* UIGridPointState::get_point(PyUIGridPointStateObject* self, void* clos if (!obj) return NULL; // Get the GridPoint from the grid - int idx = self->y * self->grid->grid_x + self->x; + int idx = self->y * self->grid->grid_w + self->x; if (idx < 0 || idx >= static_cast(self->grid->points.size())) { Py_DECREF(obj); PyErr_SetString(PyExc_IndexError, "GridPointState position out of bounds");